YOUSEFUL / DELPHI
Atlas VCL interface components: FAQ, examples and tutorial
Learning center · Atlas VCL guide
This page covers the visual and layout classes of Atlas VCL. TAtlasWorkspace, TAtlasCommandBar, TAtlasNavigation, TAtlasTileBoard and TAtlasActionTile (unit Atlas.Layout) build an application shell from panes and ordinary TAction objects; TAtlasChart (Atlas.Charts) and TAtlasGauge (Atlas.Gauges) display values; TAtlasThemer (Atlas.Themes) colours a whole control tree with one of eleven palettes; TAtlasWizard (Atlas.Wizard) steps through pages; TAtlasMap (Atlas.Map) shows your own geographic data; and TAtlasGraph with TAtlasDiagram (Atlas.Graph, Atlas.Diagram) model and edit node-and-edge diagrams. Add the units you use to uses. They need only the RTL and the VCL, except that Atlas.Themes links Atlas.Grid, Atlas.Data, Atlas.Diagram, Atlas.Graph and Atlas.Layout so that it can colour those controls. Use them on the VCL main thread. Invalid input raises an exception before anything changes.
FAQ
Which classes make up an application shell? TAtlasWorkspace provides the panes: named panels docked to an edge or the client area, with splitters, floating tool windows and a saved layout. Inside the panes, TAtlasCommandBar gives tabs of grouped buttons, TAtlasNavigation a list of items and TAtlasTileBoard a board of TAtlasActionTile tiles. All three take ordinary actions (any TCustomAction, such as TAction), so one action can be a button, a list item and a tile at once and runs the same OnExecute from each. There is no menu, status bar or ribbon gallery component; use the VCL ones.
Can users drag a pane to another edge? No. There is no drag-to-dock and no docking preview. The application moves panes with MovePane(ID, Edge, Size), floats one with FloatPane(ID, ScreenBounds) and docks it back with DockPane(ID); attach those to commands, as the tutorial does. Users resize docked panes with the splitters (see Troubleshooting for a splitter defect) and move and resize floating tool windows. Closing a tool window docks its pane where it was; the pane and its controls are not destroyed.
How do I save and restore the layout? SaveLayout returns JSON with one entry per pane ID: align (1 top, 2 bottom, 3 left, 4 right, 5 client), size, visible and, for a floating pane, its window bounds. LoadLayout accepts only text that describes exactly the panes that exist now. It checks everything first and raises EAtlasLayoutError without changing anything when the text does not fit. So create every pane before loading, catch EAtlasLayoutError to survive a stale file, and load after the form is shown, because a floating pane opens its window only while the workspace is showing.
Why does a navigation item not follow its action? Command bar buttons and tiles are bound to their action through the VCL action link, so their caption and enabled state follow at once. TAtlasNavigation copies the caption only in AddAction and RefreshActions; call RefreshActions after changing captions. A disabled action's item looks like the others but does nothing when selected. Selecting an item with the mouse or the arrow keys executes its action; setting ItemIndex in code does not.
How do I get a series back from a chart? Keep the reference that AddSeries returns: TAtlasChart has SeriesCount but no indexed series property. The chart owns its series and Clear frees all of them, so drop your references after Clear. Single points cannot be removed or changed either: call the series' Clear and add the points again. Points[I] returns a copy.
When does a bar chart show category names? When Kind is acBar and every point of the first series was added with a caption, as in Series.Add(1, 120, 'Q1'). Otherwise, and always for line charts, the X axis shows five numbers across the padded X range. Both axes divide their range into four equal steps without rounding to round numbers, and the Y axis always includes zero. There are no axis titles, manual ranges, zoom or tooltips.
What does a pie chart accept? One series with values of zero or more. AddSeries raises EAtlasChartError for a second series and Add for a negative value. Setting Kind := acPie fails in the same way when the existing data breaks those rules, and Kind then stays as it was. Slices take their colours from a built-in eight-colour palette, not from the series colour, and zero and missing points are left out.
What happens to gauge values outside the range? TAtlasGauge clamps Value to Minimum..Maximum; only NaN and infinity raise EArgumentException. The bar is green, amber from the warning threshold and red from the critical threshold, and ValueColor returns the current colour. SetRange resets the thresholds to 70% and 90% of the new range, so call SetThresholds after it.
What does TAtlasThemer change? The background and text colour of Root and every control inside it, plus grid header and alternate colours, diagram node colours, and action tiles (primary colour, white text). Chart series, gauge bars, native buttons and tab strips keep their colours, and there are no custom palettes. Ten palettes are light and differ only in their primary colour; index 10, Midnight, is dark. Controls created later, and panes that float in a tool window, need another Apply once they are inside Root.
How do the wizard events fit together? On every page, the last one included, Next first calls OnValidate(Sender, PageIndex, AllowNext); setting AllowNext := False keeps the page. Moving between pages fires OnPageChanged. Accepting the last page fires OnFinish once and disables Back and Next until Restart. Back does not validate, and Cancel only fires OnCancel, so close the form yourself. The buttons have fixed English captions and are not reachable through the API of TAtlasWizard.
Where does the map data come from? From you. TAtlasMap has no base map, tiles, coastlines or geocoding and makes no network requests. It projects your markers and paths with Web Mercator at zoom 0 to 20, pans when the user drags, zooms around the cursor with the mouse wheel and hit-tests markers with MarkerAt. There is no marker click event; handle OnClick and pass the cursor position to MarkerAt.
How are the graph and the diagram divided? TAtlasGraph is the model: nodes with stable IDs, captions and rectangles, directed edges, TopologicalOrder, LayoutHierarchy and JSON persistence. TAtlasDiagram is a view of it in which users select, drag, connect (with ConnectMode), rename (double-click a node), add (double-click empty space) and delete (Delete key) nodes. Every edit goes through the graph and repaints every diagram connected to it. There is no zoom or scrolling; node coordinates are client pixels.
Examples
Render a chart into a PNG file without showing a window. Render needs no parent and no window handle.
uses System.Types, Vcl.Graphics, Vcl.Imaging.pngimage, Atlas.Charts;
procedure ExportQuarterChart(const FileName: string);
var
Chart: TAtlasChart;
Revenue, Cost: TAtlasChartSeries;
Bitmap: TBitmap;
Png: TPngImage;
begin
Chart := TAtlasChart.Create(nil);
Bitmap := TBitmap.Create;
Png := TPngImage.Create;
try
Chart.Kind := acBar;
Chart.Title := 'Revenue and cost';
Revenue := Chart.AddSeries('Revenue', $00876524);
Cost := Chart.AddSeries('Cost', $005986B4);
Revenue.Add(1, 120, 'Q1'); Cost.Add(1, 90);
Revenue.Add(2, 135, 'Q2'); Cost.Add(2, 101);
Revenue.Add(3, 150, 'Q3'); Cost.Add(3, 97);
Bitmap.SetSize(800, 480);
Chart.Render(Bitmap.Canvas, Rect(0, 0, 800, 480));
Png.Assign(Bitmap);
Png.SaveToFile(FileName);
finally
Png.Free;
Bitmap.Free;
Chart.Free;
end;
end;The result is an 800 × 480 image with pairs of bars labelled Q1, Q2 and Q3 (the captions of the first series), a Y axis labelled 0, 37.5, 75, 112.5 and 150, and a legend with Revenue and Cost.
Give a gauge its own range and thresholds. Call SetThresholds after SetRange, which resets them.
uses System.Classes, Vcl.Controls, Atlas.Gauges;
function AddTemperatureGauge(Owner: TComponent; Parent: TWinControl): TAtlasGauge;
begin
Result := TAtlasGauge.Create(Owner);
Result.Parent := Parent;
Result.Align := alTop;
Result.Caption := 'Server room temperature';
Result.SetRange(10, 40);
Result.SetThresholds(27, 32);
Result.Value := 45; // clamped to 40
end;The gauge shows 40 with a full red bar: 45 was clamped to the maximum, which is above the critical threshold of 32.
Offer one command in three places. The button and the tile follow the action's caption and enabled state; the navigation item runs the action but copies its caption only on RefreshActions.
uses System.Classes, Vcl.ActnList, Atlas.Layout;
function AddRefreshCommand(Owner: TComponent; Commands: TAtlasCommandBar; Navigation: TAtlasNavigation;
Tiles: TAtlasTileBoard; OnRefresh: TNotifyEvent): TAction;
begin
Result := TAction.Create(Owner);
Result.Caption := 'Refresh';
Result.OnExecute := OnRefresh;
Commands.AddCommand('Home', 'Data', Result);
Navigation.AddAction(Result);
Tiles.AddTile(Result);
end;You get a Refresh button in the Data group of the Home tab, a Refresh item at the end of the navigation list and a Refresh tile, and all three call OnRefresh. After Result.Enabled := False the button and the tile are disabled; the navigation item still shows but no longer runs the action.
Remember the theme and the pane layout between runs. Restore the theme first, because a pane that is floating is outside the themer's reach, and restore the layout once the form is showing, so that floating panes can open their windows.
uses System.SysUtils, System.IOUtils, Atlas.Layout, Atlas.Themes;
procedure SaveSettings(Workspace: TAtlasWorkspace; Themer: TAtlasThemer; const Folder: string);
begin
TFile.WriteAllText(TPath.Combine(Folder, 'layout.json'), Workspace.SaveLayout, TEncoding.UTF8);
Themer.SaveToFile(TPath.Combine(Folder, 'theme.txt'));
end;
// Call once the form is showing, for example from its first OnActivate.
procedure RestoreSettings(Workspace: TAtlasWorkspace; Themer: TAtlasThemer; const Folder: string);
begin
try
if TFile.Exists(TPath.Combine(Folder, 'theme.txt')) then
Themer.LoadFromFile(TPath.Combine(Folder, 'theme.txt'));
except
on EArgumentException do ; // unknown theme name
end;
try
if TFile.Exists(TPath.Combine(Folder, 'layout.json')) then
Workspace.LoadLayout(TFile.ReadAllText(TPath.Combine(Folder, 'layout.json'), TEncoding.UTF8));
except
on EAtlasLayoutError do ; // the panes changed since the file was written
end;
end;On the next start the panes are where the user left them, floating windows included, in the saved theme. A layout file written by a version with other panes is ignored, and the default layout stays.
Build a small process graph, lay it out and show it in an editable diagram.
uses System.Classes, Vcl.Controls, Atlas.Graph, Atlas.Diagram;
function ShowProcess(Owner: TComponent; Parent: TWinControl): TAtlasDiagram;
var
Graph: TAtlasGraph;
Order, Pick, Pack, Ship: Integer;
begin
Graph := TAtlasGraph.Create(Owner);
Order := Graph.AddNode('Order received', 0, 0);
Pick := Graph.AddNode('Pick items', 0, 0);
Pack := Graph.AddNode('Pack', 0, 0);
Ship := Graph.AddNode('Ship', 0, 0);
Graph.AddEdge(Order, Pick);
Graph.AddEdge(Pick, Pack);
Graph.AddEdge(Pack, Ship);
Graph.LayoutHierarchy;
Result := TAtlasDiagram.Create(Owner);
Result.Parent := Parent;
Result.Align := alClient;
Result.Graph := Graph;
end;Four 160 × 64 boxes stand in a column at x = 40 and y = 32, 162, 292 and 422, joined by downward arrows. Users can drag them, double-click one to rename it and press Delete to remove the selected step.
Show the coordinates and the marker under a click on a map. Assign the handler with Map.OnClick := Status.MapClick.
uses System.SysUtils, System.Types, Vcl.Controls, Vcl.StdCtrls, Atlas.Map;
type
TMapStatus = class
Map: TAtlasMap;
Status: TLabel;
procedure MapClick(Sender: TObject);
end;
procedure TMapStatus.MapClick(Sender: TObject);
var
Position: TPoint;
Geo: TAtlasGeoPoint;
begin
Position := Map.ScreenToClient(Mouse.CursorPos);
Geo := Map.Unproject(PointF(Position.X, Position.Y));
Status.Caption := Format('%.3f, %.3f marker %d', [Geo.Latitude, Geo.Longitude,
Map.MarkerAt(Position.X, Position.Y)]);
end;Each click shows the latitude and longitude under the cursor and the index of the marker there, or -1. A drag that ends inside the map also counts as a click.
Tutorial
Sample project: Application shell.
Goal: build a themed application shell with a navigation pane, a command bar and a chart page, and keep its layout between runs.
Create a folder
ApplicationShellwith the program fileApplicationShell.dpr. It creates the main form in code. A file name on the command line saves a picture of the window instead of running it;Atlas.TestVcl, which does that, is in thesharedfolder of the sample download, and you can leave it out of your own program. Add the Atlas VCLsrcfolder and thesharedfolder to the project's search path.program ApplicationShell; // Atlas VCL application shell: workspace, navigation, command bar, tiles, // chart, gauges and themes. Pass a file name to save a PNG of the window // instead of running it interactively; an optional second argument selects // the page shown in the image (0 Home, 1 Sales, 2 Gauges). uses System.SysUtils, Vcl.Forms, Atlas.TestVcl, Shell.MainForm in 'Shell.MainForm.pas'; var Form: TShellForm; begin Application.Initialize; Application.MainFormOnTaskbar := True; Application.CreateForm(TShellForm, Form); if ParamCount > 0 then begin if ParamCount > 1 then Form.SelectPage(StrToInt(ParamStr(2))); SaveFormImage(Form, ParamStr(1)); end else Application.Run; end.Create
Shell.MainForm.pasand declare the form. The fields keep the components and the two chart series (the chart has no series property, so you keep the referencesAddSeriesreturns). The unit compiles once all the methods from the next steps are in place.unit Shell.MainForm; // Main form of the Atlas VCL application shell: a workspace with a navigation // pane, a command bar and a content pane, themed by TAtlasThemer. interface uses System.Classes, Vcl.Forms, Vcl.Controls, Vcl.ComCtrls, Vcl.ActnList, Atlas.Layout, Atlas.Charts, Atlas.Gauges, Atlas.Themes; type TShellForm = class(TForm) private FWorkspace: TAtlasWorkspace; FCommands: TAtlasCommandBar; FNavigation: TAtlasNavigation; FTiles: TAtlasTileBoard; FPages: TPageControl; FChart: TAtlasChart; FEast, FWest: TAtlasChartSeries; FQueue: TAtlasGauge; FThemer: TAtlasThemer; FAddMonth: TAction; FMonths: Integer; function NewAction(const Caption: string; Handler: TNotifyEvent; Tag: Integer = 0): TAction; function NewPage(const Caption: string): TTabSheet; function AddGauge(Parent: TWinControl; const Caption: string; Top: Integer; Value: Double): TAtlasGauge; procedure ShowPage(Sender: TObject); procedure AddMonth(Sender: TObject); procedure ToggleBars(Sender: TObject); procedure NextTheme(Sender: TObject); procedure FloatNavigation(Sender: TObject); procedure QueueChanged(Sender: TObject); procedure FirstActivate(Sender: TObject); procedure SaveLayout(Sender: TObject; var Action: TCloseAction); public constructor Create(AOwner: TComponent); override; procedure SelectPage(Index: Integer); end;Start the implementation with its
uses, the chart data and the file name used for the saved layout.implementation uses System.SysUtils, System.Types, System.IOUtils, Vcl.Graphics, Vcl.StdCtrls; const MonthNames: array[0..11] of string = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); EastSales: array[0..11] of Double = (18, 22, 21, 29, 35, 38, 41, 39, 44, 47, 52, 58); WestSales: array[0..11] of Double = (24, 29, 27, 32, 38, 42, 40, 45, 49, 51, 55, 61); function LayoutFile: string; begin Result := TPath.Combine(ExtractFilePath(ParamStr(0)), 'shell-layout.json'); end;In the constructor, create the form with
CreateNew(there is no.dfm), then the workspace with three panes: navigation on the left, commands on top and the content in the client area. Because the VCL aligns top panes first, the command pane will span the whole width with the navigation pane below it. The twoMovePanecalls work around a splitter defect described under Troubleshooting; without them the splitters of these two panes cannot be dragged.constructor TShellForm.Create(AOwner: TComponent); var Page: TTabSheet; Slider: TTrackBar; Action: TAction; I: Integer; begin inherited CreateNew(AOwner); Caption := 'Atlas VCL application shell'; Position := poScreenCenter; Width := 1000; Height := 680; Font.Name := 'Segoe UI'; Font.Size := 10; OnActivate := FirstActivate; OnClose := SaveLayout; // 1. A workspace with three panes. The pane caption becomes the title of its floating window. FWorkspace := TAtlasWorkspace.Create(Self); FWorkspace.Parent := Self; FWorkspace.Align := alClient; FWorkspace.AddPane('navigation', 'Navigation', alLeft, 200); FWorkspace.AddPane('commands', '', alTop, 150); FWorkspace.AddPane('content', '', alClient, 400); // AddPane leaves the splitter of a left or top pane on the outer edge, where it // cannot be dragged; MovePane to the same edge puts it between pane and content. FWorkspace.MovePane('navigation', alLeft, 200); FWorkspace.MovePane('commands', alTop, 150);Put the command bar, the navigation list and a page control into the panes.
Pane(ID)returns the pane's panel. The navigation list gets its parent before any action is added, because a list box needs a window for its items.// 2. The command bar, the navigation list and a page control inside the panes. FCommands := TAtlasCommandBar.Create(Self); FCommands.Parent := FWorkspace.Pane('commands'); FCommands.Align := alClient; FNavigation := TAtlasNavigation.Create(Self); FNavigation.Parent := FWorkspace.Pane('navigation'); FNavigation.Align := alClient; FPages := TPageControl.Create(Self); FPages.Parent := FWorkspace.Pane('content'); FPages.Align := alClient;Add three pages with hidden tabs: a tile board, the chart and three gauges. The chart starts with six months (the
AddMonthhandler from step 10 adds one month per call). The first gauge gets its own range;SetThresholdscomes afterSetRange, which resets the thresholds. When run, the Sales page shows two lines and the Gauges page three bars.// 3. Content pages: a tile board, a chart and three gauges. Page := NewPage('Home'); FTiles := TAtlasTileBoard.Create(Self); FTiles.Parent := Page; FTiles.Align := alClient; FTiles.BevelOuter := bvNone; Page := NewPage('Sales'); FChart := TAtlasChart.Create(Self); FChart.Parent := Page; FChart.Align := alClient; FChart.Title := 'Regional sales (thousands)'; FEast := FChart.AddSeries('East', $00876524); FWest := FChart.AddSeries('West', $005986B4); for I := 1 to 6 do AddMonth(nil); Page := NewPage('Gauges'); FQueue := AddGauge(Page, 'Queue depth (0-500, warning 300, critical 450)', 16, 0); FQueue.SetRange(0, 500); FQueue.SetThresholds(300, 450); FQueue.Value := 120; AddGauge(Page, 'CPU % (warning 70, critical 90)', 132, 62); AddGauge(Page, 'Disk % (warning 70, critical 90)', 248, 91); Slider := TTrackBar.Create(Self); Slider.Parent := Page; Slider.SetBounds(16, 364, 560, 40); Slider.Anchors := [akLeft, akTop, akRight]; Slider.Max := 500; Slider.Frequency := 50; Slider.Position := 120; Slider.OnChange := QueueChanged; FPages.ActivePageIndex := 0;Create one action per page and hand the same action to the navigation list, the tile board and the command bar, so that a list item, a tile and a button all switch to that page. Then add the chart and view commands.
AddCommandcreates the Home and View tabs and their groups on first use.// 4. Actions shared by the navigation list, the tile board and the command bar. for I := 0 to FPages.PageCount - 1 do begin Action := NewAction(FPages.Pages[I].Caption, ShowPage, I); FNavigation.AddAction(Action); FTiles.AddTile(Action); FCommands.AddCommand('Home', 'Pages', Action); end; FAddMonth := NewAction('Add month', AddMonth); FCommands.AddCommand('Home', 'Sales', FAddMonth); FCommands.AddCommand('Home', 'Sales', NewAction('Bars / lines', ToggleBars)); FCommands.AddCommand('View', 'Appearance', NewAction('Next theme', NextTheme)); FCommands.AddCommand('View', 'Layout', NewAction('Float navigation', FloatNavigation));Create the themer last and point it at the workspace: setting
Rootapplies the Harbor palette to every control that exists at that moment.// 5. The themer colours every control below its root; assign it after the controls exist. FThemer := TAtlasThemer.Create(Self); FThemer.Root := FWorkspace; end;Add the helper methods.
NewPagehides the tab strip, since the navigation replaces it.SelectPagealso moves the navigation selection; settingItemIndexfrom code does not execute an action, so there is no loop.function TShellForm.NewAction(const Caption: string; Handler: TNotifyEvent; Tag: Integer): TAction; begin Result := TAction.Create(Self); Result.Caption := Caption; Result.Tag := Tag; Result.OnExecute := Handler; end; function TShellForm.NewPage(const Caption: string): TTabSheet; begin Result := TTabSheet.Create(Self); Result.PageControl := FPages; Result.Caption := Caption; Result.TabVisible := False; end; function TShellForm.AddGauge(Parent: TWinControl; const Caption: string; Top: Integer; Value: Double): TAtlasGauge; begin Result := TAtlasGauge.Create(Self); Result.Parent := Parent; Result.SetBounds(16, Top, 560, 100); Result.Anchors := [akLeft, akTop, akRight]; Result.Caption := Caption; Result.Value := Value; end; procedure TShellForm.SelectPage(Index: Integer); begin FPages.ActivePageIndex := Index; FNavigation.ItemIndex := Index; end; procedure TShellForm.ShowPage(Sender: TObject); begin SelectPage(TAction(Sender).Tag); end;Add the chart commands. Months use their number as X and their name as caption, so the bar view is labelled Jan, Feb and so on; April is missing for West, which breaks the West line.
Add monthdisables its own action after December, and its button follows.procedure TShellForm.AddMonth(Sender: TObject); begin if FMonths >= Length(MonthNames) then Exit; // X is the month number. Captions label the bars; line charts use the numeric axis. FEast.Add(FMonths + 1, EastSales[FMonths], MonthNames[FMonths]); if FMonths = 3 then FWest.AddMissing(FMonths + 1) // a gap: the line breaks and no bar is drawn else FWest.Add(FMonths + 1, WestSales[FMonths], MonthNames[FMonths]); Inc(FMonths); if Assigned(FAddMonth) then FAddMonth.Enabled := FMonths < Length(MonthNames); end; procedure TShellForm.ToggleBars(Sender: TObject); begin if FChart.Kind = acLine then FChart.Kind := acBar else FChart.Kind := acLine; end;Add the theme, floating and gauge handlers.
FloatPanetakes screen coordinates, so the position is converted withClientToScreen.procedure TShellForm.NextTheme(Sender: TObject); begin FThemer.ThemeIndex := (FThemer.ThemeIndex + 1) mod AtlasThemeCount; Caption := 'Atlas VCL application shell - ' + AtlasTheme(FThemer.ThemeIndex).Name; end; procedure TShellForm.FloatNavigation(Sender: TObject); var Origin: TPoint; begin if FWorkspace.IsFloating('navigation') then FWorkspace.DockPane('navigation') else begin Origin := ClientToScreen(Point(40, 120)); FWorkspace.FloatPane('navigation', Rect(Origin.X, Origin.Y, Origin.X + 240, Origin.Y + 400)); end; end; procedure TShellForm.QueueChanged(Sender: TObject); begin FQueue.Value := TTrackBar(Sender).Position; end;Save the layout when the window closes and restore it the first time the form is activated. At that point the workspace is showing, so a pane that was floating opens its tool window again. A file that no longer matches the panes is ignored.
procedure TShellForm.FirstActivate(Sender: TObject); begin // Restore the saved layout once the workspace is showing, so a floating pane can open its window. OnActivate := nil; if not FileExists(LayoutFile) then Exit; try FWorkspace.LoadLayout(TFile.ReadAllText(LayoutFile, TEncoding.UTF8)); except on EAtlasLayoutError do ; // a stale or edited file: keep the default layout end; end; procedure TShellForm.SaveLayout(Sender: TObject; var Action: TCloseAction); begin TFile.WriteAllText(LayoutFile, FWorkspace.SaveLayout, TEncoding.UTF8); end; end.Build and run: press F9 in the IDE. Expected output: a 1000 × 680 window with the command bar across the top (Home tab: Pages group with Home, Sales and Gauges, Sales group with Add month and Bars / lines), the navigation list on the left and three dark blue tiles on the Home page. Sales shows "Regional sales (thousands)" with East and West lines over months 1 to 6 and a gap in West at month 4; Bars / lines switches to grouped bars labelled Jan to Jun. Gauges shows 120 (green), 62 (green) and 91 (red); dragging the track bar past 300 turns the first gauge amber. View / Next theme cycles the palettes and shows the name in the title bar; View / Float navigation moves the list into a tool window titled Navigation. Close the program while it floats, start it again, and it opens floating. To adapt it, put a grid or tree of your own on a fourth page: call
NewPage('Customers')before the action loop and parent your control to the page it returns.
Troubleshooting
Dragging a pane's splitter does nothing. In this version the splitter lands on the outer side of its pane, where it has nothing to resize: for left and top panes right after AddPane, and for right and bottom panes after MovePane, DockPane or LoadLayout. For left and top panes, call MovePane with the same edge and size after AddPane, as step 4 does. There is no workaround through the API for a right or bottom pane that has been moved, docked or restored; its size can still be set with MovePane.
A floating pane has disappeared after LoadLayout. The layout was loaded before the form was shown, for example in the constructor. The pane was moved into its tool window, but the window is shown only when the workspace is showing. Load the layout in the form's first OnActivate, as step 12 does, or show the window yourself with FloatingWindow(ID).Show.
AddAction raises "Control '' has no parent window". TAtlasNavigation is a list box and needs a window to add items. Set its Parent before calling AddAction.
Some controls keep their old colours after a theme change. TAtlasThemer colours the controls that are inside Root when Apply runs (setting Root or ThemeIndex calls it). Call Apply again after creating controls, and after docking a pane that was floating when the theme changed. Chart series, gauge bars, native buttons and tab strips are never recoloured.
A gauge ignores the thresholds I set. SetRange was called after SetThresholds and reset them to 70% and 90% of the new range. Call SetRange first.
Setting Kind := acPie raises EAtlasChartError. The chart has more than one series or a negative value; Kind has not changed. Call Chart.Clear, set Kind := acPie and add one series with non-negative values.
Navigation rows are close together although ItemHeight is 32. The constructor sets ItemHeight := 32, but a list box uses it only with an owner-draw style. Set Style := lbOwnerDrawFixed to get 32-pixel rows; the default drawing then still shows the captions.
LayoutHierarchy raises "Graph contains a cycle". The row layout needs an acyclic graph, while the model and the diagram accept cycles. Find the edge that closes the loop, for example with TopologicalOrder inside a try, and remove it with DeleteEdge, or place the nodes yourself with MoveNode.
Connecting two nodes in the diagram shows "Duplicate edge". In ConnectMode the diagram calls AddEdge, which refuses an edge that already exists; the exception comes from the mouse handler, so the application shows it. Check Graph.Edges before switching ConnectMode on, or handle Application.OnException.