Saturday, April 24, 2021

Delphi Tip of the Day - Toggle Visibility On/Off

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:

  1. On the main menu as an option.
  2. 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;

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;

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

2 comments:

  1. Are you sure this is correct:

    > 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);

    ReplyDelete
    Replies
    1. I guess it depends on your perspective. I was looking at it as though the statements had yet to be acted upon.

      Delete