Публикации Lazarus

Создание меню и панелей инструментов в Lazarus

25.05.2005
Сергей Смирнов

ActionList как основа пользовательского интерфейса

Каждый, кому приходилось разрабатывать графический пользовательский интерфейс, сталкивался с тем, что существуют некоторые общепринятые принципы его построения. В частности, практически любое окно программы имеет меню и панель инструментов. Как правило, на панели инструментов размещаются кнопки, дублирующие наиболее часто используемые пункты меню. Кроме того, для удобства, некоторые пункты главного меню могут быть продублированы в контекстных меню, которые появляется при нажатии правой кнопки на различных элементах управления формы.

Разумеется, при таком многократном дублировании возникает проблема добавления и изменения команд: одно и то же действие требуется описать несколько раз. Вот тут-то нам и поможет ActionList, точнее, входящие в него элементы Action. Эти элементы аккумулируют все общие свойства пунктов меню и кнопок, а также позволяют определить единые обработчики событий, которые будут активироваться каждый раз при активации любого из элементов, ссылающихся на Action.

Более того, во время выполнения программы мы можем менять свойства любого элемента Action и эти изменения будут автоматически распространяться на все связанные компоненты. Например, если установить свойство Enabled в False у одного из элементов Action, то все связанные компоненты станут недоступными.

Вооружившись столь ценными знаниями, давайте создадим в Lazarus новый проект и разместим на его главной (и единственной) форме компоненты StaticText и ActionList. Первый из них будем использовать для индикации активации различных пунктов меню и кнопок панели инструментов, поэтому увеличим шрифт, чтобы лучше было видно. Второй снабдим пятью элементами Action, создадим для них обработчики событий OnExecute, в которых будем присваивать свойству Caption нашего StaticText нехитрый текст, сообщающий какое из действий было выполнено.

unit MainForm;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  Menus, ActnList, Buttons, StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Action1: TAction;
    Action2: TAction;
    Action3: TAction;
    Action4: TAction;
    Action5: TAction;
    ActionList1: TActionList;
    StaticText1: TStaticText;
    procedure Action1Execute(Sender: TObject);
    procedure Action2Execute(Sender: TObject);
    procedure Action3Execute(Sender: TObject);
    procedure Action4Execute(Sender: TObject);
    procedure Action5Execute(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{ TForm1 }

procedure TForm1.Action1Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 1 Executed';
end;

procedure TForm1.Action2Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 2 Executed';
end;

procedure TForm1.Action3Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 3 Executed';
end;

procedure TForm1.Action4Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 4 Executed';
end;

procedure TForm1.Action5Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 5 Executed';
end;

initialization
  {$I mainform1.lrs}

end.
Однако, без связанных с ним компонент ActionList никак себя не проявляет и поэтому бесполезен. Наполнить смыслом его существование помогут меню со своими пунктами и панель инструментов с кнопками.

Создание главного меню

Добавим компонент MainMenu на форму, создадим в нём 3 пункта верхнего уровня и в каждый из них добавим подменю из 1-2 пунктов так, чтобы во всех подменю получилось в общей сложности 5 пунктов. В свойстве Action каждого из этих пяти пунктов выберем соответствующий элемент нашего ActionList. При этом свойства пунктов меню, в частности Caption, будут изменяться согласно выбранному Action. Само меню отобразится вверху формы, как ему и положено.

Создание контекстного меню

Контекстное меню, или, по-другому, всплывающее меню - также очень удобный элемент интерфейса. Его добавление на форму похоже на добавление главного меню с той лишь разницей, что оно имеет вертикальную структуру и не обязано быть единственным: практически каждому компоненту формы можно задать своё контекстное меню. Как и в главное меню, добавим в контекстное меню 5 пунктов, а элементом, для которого мы его сделали объявим саму форму. Для этого свойству формы PopupMenu присвоим только что подготовленное контекстное меню.


Создание панели инструментов

Панель инструментов, несомненно, - самая сложная часть интерфейса, особенно если мы хотим сделать её красивой, удобной и функциональной. За основу возьмём не примитивный TToolBar, а панель TPanel, на которую будем добавлять различные компоненты. Чтобы наша панель была похожа именно на панель инструментов, установим её свойство Align в alTop. Так она распластается вдоль верхней границы доступной области окна. Теперь добавим на панель пять кнопок TSpeedButton и ещё одну панель, на которой разместим выпадающий список и простую кнопку TButton. Все кнопки и панель выровняем по левому краю, установив их свойства Align в alLeft и немного растянем. Должно получиться примерно следующее:

unit MainForm;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  Menus, ActnList, Buttons, StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Action1: TAction;
    Action2: TAction;
    Action3: TAction;
    Action4: TAction;
    Action5: TAction;
    ActionList1: TActionList;
    Button1: TButton;
    ComboBox1: TComboBox;
    MainMenu1: TMainMenu;
    MenuItem1: TMenuItem;
    MenuItem10: TMenuItem;
    MenuItem11: TMenuItem;
    MenuItem12: TMenuItem;
    MenuItem13: TMenuItem;
    MenuItem2: TMenuItem;
    MenuItem3: TMenuItem;
    MenuItem4: TMenuItem;
    MenuItem5: TMenuItem;
    MenuItem6: TMenuItem;
    MenuItem7: TMenuItem;
    MenuItem8: TMenuItem;
    MenuItem9: TMenuItem;
    Panel1: TPanel;
    Panel2: TPanel;
    PopupMenu1: TPopupMenu;
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    SpeedButton3: TSpeedButton;
    SpeedButton4: TSpeedButton;
    SpeedButton5: TSpeedButton;
    StaticText1: TStaticText;
    procedure Action1Execute(Sender: TObject);
    procedure Action2Execute(Sender: TObject);
    procedure Action3Execute(Sender: TObject);
    procedure Action4Execute(Sender: TObject);
    procedure Action5Execute(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{ TForm1 }

procedure TForm1.Action1Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 1 Executed';
end;

procedure TForm1.Action2Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 2 Executed';
end;

procedure TForm1.Action3Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 3 Executed';
end;

procedure TForm1.Action4Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 4 Executed';
end;

procedure TForm1.Action5Execute(Sender: TObject);
begin
  StaticText1.Caption := 'Action 5 Executed';
end;

procedure TForm1.Button1Click(Sender: TObject);
var
	i: integer;
begin
  for i := 0 to ActionList1.ActionCount - 1 do
  begin
  	(ActionList1.Actions[i] as TAction).Visible := True;
  	(ActionList1.Actions[i] as TAction).Enabled := True;
  end;
end;

procedure TForm1.ComboBox1Change(Sender: TObject);
begin
  case ComboBox1.Itemindex of
    0..4: (ActionList1.Actions[ComboBox1.Itemindex] as TAction).Visible := False;
    5..9: (ActionList1.Actions[ComboBox1.Itemindex - 5] as TAction).Enabled := False;
  end;
end;

initialization
  {$I mainform.lrs}

end.
Вы спросите, откуда взялись пикторгаммы на кнопках? Я разместил их там вручную. На самом деле, для управления пиктограммами кнопок и пунктов меню можно использовать компонент TImageList. Однако, с одной стороны, это не совсем тривиальная задача, т.к. для кнопок и меню используются изображения разного размера, а с другой - реализация TImageList в Lazarus, к сожалению, пока не завершена.

Давайте теперь рассмотрим более подробно сам компонент TPanel. В данном случае нас более всего интересует, как наиболее эстетично и рационально разместить на панели другие компоненты. TPanel предоставляет для этого массу возможностей. Во-первых, разместить на ней можно не только кнопки, но и другие компоненты, в частности дополнительные панели, которые могут содержать элементы, не нуждающиеся в масштабировании. Помните, что мы выровняли все дочерние компоненты влево и теперь они расположены впритык друг к другу и меняют высоту, повторяя высоту панели? Так вот: выпадающий список и кнопка отмены сделанных с его помощью изменений не должны менять ни размер, ни взаимное положение. Именно поэтому они размещены внутри дополнительной панели.

Что касается собственного внешнего вида и размещения встроенных компонент, панель обладает набором свойств, позволяющих изменять всё это в довольно широких пределах. Для начала, попробуйте поэкспериментировать со свойствами BevelOuter BevelInner, которые определяют вид обрамления панели. Выбирая различные сочетания этих свойств, можно добиться довольно причудливых результатов. Другими важными свойствами являются различные отступы и границы. Вот как влияют некоторые из них на расположение компонентов в составе панели:

Но и это ещё не всё. Давайте заполним выпадающий список следующими строками:

Make Action 1 invisible
Make Action 2 invisible
Make Action 3 invisible
Make Action 4 invisible
Make Action 5 invisible
Make Action 1 disabled
Make Action 2 disabled
Make Action 3 disabled
Make Action 4 disabled
Make Action 5 disabled
После этого запрограммируем процедуры обработки событий списка и кнопки как сделано в листинге выше. Запустим получившуюся программу и выбирая разные строки в списке, убедимся, что соответствующие пункты главного и контекстного меню, а также кнопки на панели исчезают либо становятся недоступными. Такое поведение интерфейса довольно полезно, например, при реализации системы разделения прав доступа.

Актуальные версии
FPC3.2.2release
Lazarus3.2release
MSE5.10.0release
fpGUI1.4.1release
links