I'm porting over one of my Delphi 5 VCL, desktop applications to FMX. The target audience are Windows and macOS users. I'm not focused on mobile at this juncture. I'm spending quite a bit of time on the User Experience (UX). I guess you would call this approach "Desktop UX First".
I want to give the user the option of showing or hiding panels. I've decided to place this functionality in two places:
- On the main menu as an option.
- On the bottom tool bar as a button.
The first thing that came to mind was testing the "IsChecked" state of the main menu option. I slept on it and came back to the drawing board the next day. Instead of thinking about trapping clicks, I thought about what the intent of the click really was. And the answer is to show or hide the panel.
If the user clicks either the main menu option or the toolbar button I want the panel to show or hide. Both of these options need to work together behind the scenes. The intent of either click focuses on the "visibility" of the panel. And wouldn't you know, TPanel has a Visible property.
Visible is a boolean value, meaning it has only two states. True/False. And in Delphi there is a very cleaver way to toggle the state of a boolean value. This can be a little tricky to wrap your had around at first. However, once you understand it, you'll love it.
Boolean Value becomes not Boolean Value;
Boolean Value (True) becomes not Boolean Value (True);
Boolean Value (False) becomes not Boolean Value (False);
Boolean Value := not Boolean Value;
Boolean Value := not Boolean Value;
Panel.Visibile becomes not Panel.Visibile;
Panel.Visibile (True) becomes not Panel.Visibile (True);
Panel.Visibile (False) becomes not Panel.Visibile (False);
Panel.Visibile := not Panel.Visibile;
Panel.Visibile (True) becomes not Panel.Visibile (True);
Panel.Visibile (False) becomes not Panel.Visibile (False);
Panel.Visibile := not Panel.Visibile;
The statement Panel.Visibile := not Panel.Visibile; toggles the value, back and forth between True and False.
Let's see how to put this to use in the real world. In Delphi create a new FMX application.
Click File > New > Multi-Device Application - Delphi and choose Blank Application.
Add the following components:
TMainMenu
TPanel x2
TToolbar
TButton
TSplitter
TStatusBar
Structure the components so they are parented like this:
Your form should look similar to this:
The goal is to toggle the Left Panel visibility. When you first run the application the form opens with the default settings. We want both the MenuItem (Show Left Panel) and the ToolBar Button to work together.
Hiding the Left Panel the form changes accordingly.
Regardless of which method was used to Show/Hide the left panel both items stay in synch by calling on procedure ToggleLeftPanelOnOff.
I'm testing the Sender to make sure it was invoked by either a TMenuItem or a TButton control before I toggle the panel visibility. Although it's not displayed here this allows for calling ToggleLeftPanelOnOff from inside the form's OnShow method in essence setting the correct values without toggling the panels visibility.
Here is the program code:
unit Unit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, FMX.Controls.Presentation, FMX.Menus; type TForm1 = class(TForm) MainMenu1: TMainMenu; StatusBar1: TStatusBar; pnlLeft: TPanel; pnlMain: TPanel; splLeft: TSplitter; tbarMain: TToolBar; btnTBarLeft: TButton; mnuFile: TMenuItem; menuOptions: TMenuItem; mnuShowLeftPanel: TMenuItem; mnuClose: TMenuItem; procedure mnuCloseClick(Sender: TObject); procedure mnuShowLeftPanelClick(Sender: TObject); procedure btnTBarLeftClick(Sender: TObject); private { Private declarations } public { Public declarations } procedure ToggleLeftPanelOnOff(Sender: TObject); end; var Form1: TForm1; implementation {$R *.fmx} procedure TForm1.btnTBarLeftClick(Sender: TObject); begin ToggleLeftPanelOnOff(Sender); end; procedure TForm1.mnuCloseClick(Sender: TObject); begin Close; end; procedure TForm1.mnuShowLeftPanelClick(Sender: TObject); begin ToggleLeftPanelOnOff(Sender); end; procedure TForm1.ToggleLeftPanelOnOff(Sender: TObject); begin // ================================================================== // Only toggle visibility if invoked by a menuitem or button click. // ================================================================== if (Sender is TMenuItem) or (Sender is TButton) then pnlLeft.Visible := not pnlLeft.visible; // ================================================================== // Set menuitem and button properties // ================================================================== if pnlLeft.Visible then begin mnuShowLeftPanel.IsChecked := True; btnTBarLeft.StyleLookup := 'arrowlefttoolbutton'; end else begin mnuShowLeftPanel.IsChecked := False; btnTBarLeft.StyleLookup := 'arrowrighttoolbutton'; end; end; end.
Here is the text version of the Form:
object Form1: TForm1 Left = 0 Top = 0 Caption = 'Toggle Demo' ClientHeight = 480 ClientWidth = 640 FormFactor.Width = 320 FormFactor.Height = 480 FormFactor.Devices = [Desktop] DesignerMasterStyle = 0 object StatusBar1: TStatusBar Position.Y = 458.000000000000000000 ShowSizeGrip = True Size.Width = 640.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False TabOrder = 1 end object pnlLeft: TPanel Align = Left Size.Width = 161.000000000000000000 Size.Height = 458.000000000000000000 Size.PlatformDefault = False TabOrder = 2 end object pnlMain: TPanel Align = Client Size.Width = 475.000000000000000000 Size.Height = 458.000000000000000000 Size.PlatformDefault = False TabOrder = 3 object tbarMain: TToolBar Align = Bottom Position.Y = 418.000000000000000000 Size.Width = 475.000000000000000000 Size.Height = 40.000000000000000000 Size.PlatformDefault = False TabOrder = 0 object btnTBarLeft: TButton Align = Left Size.Width = 40.000000000000000000 Size.Height = 40.000000000000000000 Size.PlatformDefault = False StyleLookup = 'arrowlefttoolbutton' TabOrder = 0 Text = 'btnTBarLeft' OnClick = btnTBarLeftClick end end end object splLeft: TSplitter Align = Left Cursor = crHSplit MinSize = 20.000000000000000000 Position.X = 161.000000000000000000 Size.Width = 4.000000000000000000 Size.Height = 458.000000000000000000 Size.PlatformDefault = False end object MainMenu1: TMainMenu Left = 304 Top = 224 object mnuFile: TMenuItem Text = 'File' object mnuClose: TMenuItem Locked = True Text = 'Close' OnClick = mnuCloseClick end end object menuOptions: TMenuItem Text = 'Options' object mnuShowLeftPanel: TMenuItem Locked = True IsChecked = True Text = 'Show Left Panel' OnClick = mnuShowLeftPanelClick end end end end
Enjoy,
Gunny Mike
Are you sure this is correct:
ReplyDelete> Boolean Value becomes not Boolean Value;
> Boolean Value (True) becomes not Boolean Value (True);
> Boolean Value (False) becomes not Boolean Value (False);
> Boolean Value := not Boolean Value;
I would have expected:
Boolean Value (True) becomes not Boolean Value (False);
Boolean Value (False) becomes not Boolean Value (True);
I guess it depends on your perspective. I was looking at it as though the statements had yet to be acted upon.
Delete