Dynamic Layout¶
- Dynamic Layout
Introduction¶
The OpenEdge GUI implementation is based on code that strongly maps to character units and fixed/absolute positioning. Since sizes are calculated based on characters, the fonts being used define the size and position of widgets/frames/windows. The problem is that there are no facilities for handling layout on a dynamic basis. For example, when a user resizes a window the widgets in that window have no built-in way to deal with the additional space. This means that 4GL GUI screens tend to be quite static.
Some customers have implemented very complex hacks to get some kind of re-layout to occur. It is typically quite fragile, slow and takes a great deal of code.
In FWD we have reworked the UI infrastructure to provide a dynamic layout capability, without requiring a fragile/slow/bespoke 4GL code implementation. Phase 1 of the dynamic layout is available and was implemented in #3269. The core idea is that windows, frames and browse were updated to take advantage of additional space (re-layout) when the window is resized. Since browse is the most common control which needs more space to display data, it was reworked first.
Phase 2 is tracked in #3875, where the remaining widgets will be reworked to support this mode. The 4GL language and runtime options to choose layout strategies for specific frames and windows are tracked in #11351.
Dynamic layout is an optional FWD extension. It is not OpenEdge compatible behavior, it is disabled by default and it requires no 4GL code changes and no reconversion. It is enabled from the directory.
What It Does¶
When dynamic layout is enabled, the extra space of a window is given to the browse widgets on the screen. All other widgets keep their legacy size; they are moved so that they keep their position relative to the browse (widgets to the right of a browse move right, widgets below a browse move down).
Phase 1 capabilities:
- Browse widgets grow and shrink, horizontally and vertically, with the window.
- A frame that contains a browse (at any nesting level) is resized together with the browse; frames without a browse keep their size.
- Multiple browses sharing the same horizontal or vertical band split the space in proportion to their original sizes.
- Static widgets (fill-ins, buttons, labels, images, rectangles, frames without browse, etc.) keep their size and are shifted to make room.
- Browse columns are stretched to use the full browse width, proportionally to each column's data width.
- ADM/ADM2 tabbed page frames are stretched to the available space.
- Optional: the window is enlarged on frame realization so that the browse content fits (optimal size).
- Optional, embedded mode only: the current window follows the size of the embedding page (window maximize).
The layout runs on top of the legacy layout. The legacy 4GL layout rules are applied first, exactly as without this feature, and the dynamic layout only adjusts the result. When there is no visible browse on the screen, nothing is changed.
Samples¶
In the Hotel GUI sample application, add the following node to ./deploy/server/directory.xml:
<node class="container" name="">
<node class="container" name="server">
<node class="container" name="default">
<node class="container" name="runtime">
<node class="container" name="default">
<node class="container" name="dynamic-layout">
<node class="container" name="desktop">
<node class="boolean" name="enabled">
<node-attribute name="value" value="TRUE"/>
</node>
<node class="boolean" name="optimal-size">
<node-attribute name="value" value="FALSE"/>
</node>
</node>
<node class="container" name="embedded">
<node class="boolean" name="enabled">
<node-attribute name="value" value="TRUE"/>
</node>
<node class="boolean" name="window-maximize">
<node-attribute name="value" value="TRUE"/>
</node>
<node class="boolean" name="optimal-size">
<node-attribute name="value" value="FALSE"/>
</node>
</node>
</node>
Hotel GUI Embedded mode + dynamic layout enabled¶

Hotel GUI Virtual Desktop mode + dynamic layout enabled¶

Activation and Configuration¶
The feature is configured separately for the two GUI client modes:
dynamic-layout/desktop- Virtual Desktop mode of the web client and the Swing GUI client.dynamic-layout/embedded- Embedded mode of the web client.
Add the following under /server/default/runtime/default (or other supported nodes, see below) to enable the feature:
<node class="container" name="dynamic-layout">
<node class="container" name="desktop">
<node class="boolean" name="enabled">
<node-attribute name="value" value="TRUE"/>
</node>
</node>
<node class="container" name="embedded">
<node class="boolean" name="enabled">
<node-attribute name="value" value="TRUE"/>
</node>
</node>
</node>
The layout options are read from the following directory nodes, in the order given (the first match wins):
/server/<serverID>/runtime/<account_or_group>/dynamic-layout/server/<serverID>/runtime/default/dynamic-layout/server/default/runtime/<account_or_group>/dynamic-layout/server/default/runtime/default/dynamic-layout
This means the feature can be enabled for a specific user or group only, for example to pilot it with a few users.
Available values, under the desktop or embedded container:
| Value Name | Type | Default | Meaning |
|---|---|---|---|
| enabled | boolean | false | Enables the dynamic layout feature for the client mode. Ignored in ChUI; dynamic layout is only supported in GUI. |
| optimal-size | boolean | false | When a frame is realized in a non-modal window, the window is enlarged so that the browse content fits. See Optimal Size. |
| window-maximize | boolean | false | Embedded mode only. When the embedding page (iframe) is resized, the current window is resized to the new iframe size, which then triggers the dynamic layout. |
Notes:
- The values are read once, when the client session starts. A change requires a new client session.
- Enabling dynamic layout does not change the
<window>:RESIZEattribute. In Virtual Desktop mode the user can only resize a window whenRESIZEisTRUE.
Virtual Desktop Mode vs Embedded Mode¶
The layout algorithm is the same in both modes. What differs is what causes the window size to change.
| Aspect | Virtual Desktop (and Swing) | Embedded |
|---|---|---|
| Configuration node | dynamic-layout/desktop |
dynamic-layout/embedded |
| Who changes the window size | The user (dragging the border when RESIZE is TRUE, maximize/restore) or 4GL code assigning the window size. |
The host page. With window-maximize the current window is moved to the iframe origin and resized to the iframe size every time the iframe is resized. 4GL code can also assign the window size. |
optimal-size |
Useful: the window is enlarged to show the browse content, up to the desktop work area. | Read, but window-maximize is the usual choice since the iframe defines the available space. |
window-maximize |
Ignored. | Makes the 4GL window follow the iframe. |
| ADM/ADM2 tab folders | The page frame (folder-frm) is stretched and its 3D border rectangles are hidden. |
The tabs are usually rendered by the host page; the page content is laid out the same way. |
| Typical setup (Hotel GUI) | enabled as needed, optimal-size=FALSE |
enabled=TRUE, window-maximize=TRUE, optimal-size=FALSE |
How It Works¶
Architecture¶
The layout runs on the client side (in the FWD client JVM). It is not a LayoutManager: the dynamic layout assumes the widgets are already laid out by the legacy rules and it is invoked at different points than the legacy layout. The algorithm is also not called directly by the widgets. An integration layer formalizes the few places where the widget tree and the algorithm meet.
DynamicLayoutConfigreads the directory values once per client context.ThinClient.isDynamicLayoutEnabled()is the global switch checked by the widgets.- When the feature is enabled, each
WindowGuiImplandDialogBoxWindowcreates aWindowDynamicLayoutIntegrationfor its content pane, and eachFrameGuiImplcreates aDynamicLayoutIntegrationfor its scroll container. These classes implementHasDynamicLayout. - The integration owns the algorithm instances:
DynamicLayout(re-layout) and, for windows,OptimalSizeLayout(window sizing). Both extendAbstractDynamicLayout, which holds the shared tree and row/column parsing logic. - The algorithm changes widget locations and sizes through the normal widget API (
setLocation,setSize,Frame.setOuterSize). A resized browse lays itself out again, which also stretches its columns.

When the Layout Runs¶
A layout is requested when:
- a frame is realized (
FrameGuiImpl.realizeFramenotifies the integration of the parent window); - a window is resized (
WindowGuiImplresize logic callsonWindowResized); this covers user resizing, maximize/restore, 4GL assignments of the window size and the embedded iframe resize whenwindow-maximizeis on; DISABLE-REDRAWis set back toFALSEon the window or any widget inside it while a layout request is pending.
The request is not executed immediately:
- If redraw is disabled (session-wide or for the container), the request is only marked as pending and is executed when redraw is enabled again.
- Otherwise the layout is posted to the client event queue (
invokeLaterOS) and runs inside aneventDrawingBracket, so it is synchronized with the other UI events and drawn in one go. - Multiple requests made during the same UI change are merged; if a parent container already has a pending layout, the child is laid out as part of the parent's layout.

From the code, a DialogBoxWindow creates an integration, but none of the triggers reaches it: frame realization only notifies a parent WindowGuiImpl, and DialogBoxWindow.resizeTo() does not notify the integration. Dialog boxes are not resizable by the user anyway, so in practice only the browse column stretching applies there.
The Algorithm¶
DynamicLayout.doLayout() executes these steps:
buildTree- build the layout tree of the visible widgets.parseRows- group the widgets of each container into horizontal bands (rows) and cells.inflateWidth- distribute the available width.parseColumns- group the widgets into vertical bands (columns) and cells.inflateHeight- distribute the available height.layoutAdmPageFrame- stretch the ADM page frames, when an ADM UI was detected.

Building the Layout Tree¶
The tree (TNode) contains the visible frame items of the window: data widgets (fill-ins, editors, combo-boxes, toggle-boxes, radio-sets, etc.), browses, labels, images, buttons, rectangles and frames. Child frames are processed recursively; ADM page frames are marked and handled separately.
A node is dynamic if it is a browse, or a frame that contains a dynamic node at any depth. Everything else is static.
The tree is kept between runs. The original location and size of each node are captured the first time it is laid out and all later layouts are computed from these values, so repeated resizing does not accumulate rounding errors.
Rows, Columns and Cells¶
For each container, the widgets are sorted by their top coordinate. A widget starts a new row when its top is below the bottom of the current row; otherwise it joins the row and may extend it. Inside a row, the widgets are sorted by their left coordinate and grouped into cells the same way: a widget starts a new cell when it starts to the right of the current cell. A cell is dynamic when it contains at least one dynamic widget. Neighboring static cells are merged.

For each row the algorithm records:
totalStaticWidth- the sum of the static cell widths and of the gaps between cells;totalInitialWidth- the sum of the original widths of the dynamic cells.
The columns pass is the same logic rotated by 90 degrees: vertical bands by left coordinate, cells by top coordinate.
Distributing the Space¶
For each row:
available = container width - totalStaticWidth- each dynamic cell gets
available * (cell initial width / totalInitialWidth) - the last dynamic cell gets the remainder, which absorbs the rounding
- a dynamic cell is never smaller than 0.5 characters wide (4 characters high in the columns pass)
The cells are then placed from left to right, keeping the original gaps. Dynamic widgets are resized to the new cell width (keeping their offsets inside the cell). Static widgets keep their size and their offset from the cell's left edge, so they move together with the cell.
Example, a row with two browses and a column of buttons, in a 78 characters wide container:

| Cell | Original x | Original width | After resize to 108 chars |
|---|---|---|---|
| browse A | 2 | 40 | x=2, width 60 |
| browse B | 44 | 20 | x=64, width 30 |
| buttons (static) | 66 | 12 | x=96, width 12 |
The static width is 2 + 2 + 2 + 12 = 18 (the three gaps and the buttons), so available = 108 - 18 = 90. Browse A gets 90 * 40 / 60 = 60 and browse B gets the remainder, 30.
Frames¶
A dynamic frame is resized with setOuterSize, its auto-placement is reset and its inner size is updated. For each container the algorithm also computes a minimum size: the sum of the static widths plus the minimum cell size for each dynamic cell. If the new frame size is smaller than this minimum, the frame gets a virtual size equal to the minimum, so it becomes scrollable instead of overlapping its content.
Browse Columns¶
When dynamic layout is enabled, BrowseGuiImpl.doLayout() always calls stretchColumns(), even if the window was never resized:
- only the visible, unlocked columns are stretched; locked columns keep their width;
- the available viewport width is distributed in proportion to each column's data width;
- a column is never narrower than its data width; when there is not enough room, the browse scrolls horizontally;
- the last column gets the remainder.
As a consequence, column widths defined in the 4GL code are replaced by the proportional widths.
ADM Page Frames¶
ADM/ADM2 tabbed windows use a page frame named folder-frm that draws the tab background with several rectangles. This structure cannot be handled by the generic algorithm (overlapping rectangles have no clear layout relation), so it is detected by name and handled explicitly:
- the page frame is excluded from the row/column analysis;
- it is resized to fill its parent;
- the largest rectangle is kept and stretched, the other rectangles (3D border effect) are hidden;
- the page content inside the folder is centered horizontally.
Optimal Size¶
With optimal-size enabled, OptimalSizeLayout runs before DynamicLayout when a frame is realized in a regular (non-modal) window, and not while the user is resizing:
- the rows are parsed using each browse's optimal width (sum of the column data widths plus borders and the vertical scrollbar);
- the window width is set to the widest row, limited to the desktop work area;
- the window height is set to the full height of the work area;
- the window is never made smaller than its current size.
Since a frame realization triggers it, the window can grow again on every ADM page switch. This is why it is disabled in most configurations.
Interaction with 4GL Code¶
- No 4GL changes are needed and there is no 4GL syntax to control the feature. Whether it is active is decided only by the directory.
- The new sizes are stored in the client-side widget configuration and are sent to the server with the next client state, so 4GL code reading for example
BROWSE:WIDTH-PIXELSafter a resize should see the stretched value. - The layout runs after the legacy layout. If the application has its own resize logic (for example in a
WINDOW-RESIZEDtrigger) both will move the widgets. Such screens must be tested and the legacy logic is best disabled for them. DISABLE-REDRAWdelays the layout until redraw is enabled again. A screen that leavesDISABLE-REDRAWset toTRUEwill not be re-laid out.
Limitations¶
- Only browse is dynamic. Editors, selection lists, fill-ins and other widgets keep their legacy size (see #3875).
- The configuration is global per client mode (with per account/group overrides); there is no per-window, per-frame or per-widget option.
- The row/column structure is inferred from absolute positions. Overlapping widgets, or widgets that partially overlap the browse band, can produce unexpected results. There are no anchors or other layout hints.
- Static widgets placed in the same cell as a browse keep their offset from the cell's left/top edge; they are not anchored to the right or bottom edge.
- Browse column widths are always recalculated, even without resizing.
- Optimal size works only for non-modal windows, uses the full work-area height and never shrinks the window.
- Dialog boxes are not re-laid out (only their browse columns are stretched).
- ADM page frames are recognized only by the
folder-frmname; custom tab folder implementations are treated as regular frames. - GUI only (web and Swing); the configuration is ignored in ChUI.
Leveraging Dynamic Layout¶
Enabling:
- Enable it per client mode, and if needed only for a pilot account or group first.
- Virtual Desktop: set
RESIZE = TRUEon the windows that should be resizable; consideroptimal-sizeonly for screens without ADM paging. - Embedded: enable
window-maximizeso the 4GL window follows the iframe size.
Screens that work best:
- The browse occupies its own horizontal and vertical band; the static widgets are clearly above, below, left or right of it.
- No overlapping widgets around the browse.
- No legacy resize logic in
WINDOW-RESIZEDtriggers, or that logic is disabled.
When a screen does not re-layout as expected, check:
- Is there a visible browse in the window? Without a browse nothing changes.
- Is the window resizable (
RESIZE) or, in embedded mode, iswindow-maximizeon? - Is the screen a dialog box?
- Is
DISABLE-REDRAWleftTRUE? - Do widgets overlap the browse band, or does a
WINDOW-RESIZEDtrigger move widgets as well?
For FWD developers, AbstractDynamicLayout.TNode.formatTree() prints the layout tree, and DynamicLayout has commented-out keepLocations()/checkLocations() calls that report the frames whose location or size changed between runs.
Roadmap¶
- #3875 - dynamic layout for the remaining 4GL widgets and OCX controls. For example an editor may take space in both directions, while fill-ins mostly need horizontal space. Minimum and maximum width/height attributes are being considered so that the layout does not rely on assumptions about the screen design.
- #11351 - 4GL language and runtime enhancements for layout strategies (responsive design): named layouts (single column, double column, etc.) chosen per form factor, media query support and smart defaults.
Code Map¶
All classes are in com.goldencode.p2j.ui.client.
| Class | Role |
|---|---|
layout.DynamicLayoutConfig |
Reads the dynamic-layout/desktop or dynamic-layout/embedded directory values, once per client context. |
layout.HasDynamicLayout |
Implemented by WindowGuiImpl, DialogBoxWindow and FrameGuiImpl to expose their integration. |
layout.DynamicLayoutIntegration |
Receives the layout requests of a container, handles DISABLE-REDRAW, merges requests and runs the layout on the event queue. |
layout.WindowDynamicLayoutIntegration |
Window integration: window resize hook and optimal size. |
layout.AbstractDynamicLayout |
Layout tree (TNode), dynamic widget detection, row/column/cell parsing, ADM page frame detection. |
layout.DynamicLayout |
Space distribution (inflateWidth, inflateHeight) and ADM page frame layout. |
layout.OptimalSizeLayout |
Calculates and applies the optimal window size. |
layout.HasOptimalSize |
Implemented by BrowseGuiImpl to report its optimal width. |
gui.BrowseGuiImpl |
stretchColumns() and getOptimalWidth(). |
gui.FrameGuiImpl, gui.WindowGuiImpl |
Trigger points: frame realization and window resize. |
gui.driver.web.GuiWebDriver |
desktopResized() applies window-maximize in embedded mode. |
© 2017-2026 Golden Code Development Corporation. ALL RIGHTS RESERVED.