СТАТЬЯ

10.10.01


Предыдущая часть

Профессиональная разработка приложений с помощью Delphi 5

Часть 3. Оформление приложений для Windows95/98/NT/2000 в Delphi

Сергей Трепалин,
УКЦ Interface Ltd.
КомпьютерПресс #3 2001

Статья была опубликована в КомпьютерПресс (www.cpress.ru)

Обработка сообщения WM_GETMINMAXINFO

В Delphi 4 и 5 класс TForm обладает свойством Constraints типа TSizeConstraints. Используя это свойство, программист может задать минимальную и максимальную ширину и высоту формы. Соответственно если пользователь во время выполнения приложения попытается изменить указанную ширину или высоту формы, то приложение не позволит сделать этого. Событие WM_GETMINMAXINFO как раз и предназначено для того, чтобы сообщить системе о возможных диапазонах изменения границ элемента управления. Однако по непонятным соображениям свойство Constraints в Delphi 5 используется не в обработчике события WM_GETMINMAXINFO, а в обработчике события WM_SIZE. Такая «кривая» реализация приводит к некорректному поведению формы при изменении ее размеров: пользователь может сделать ее очень маленькой или очень большой, и только после отпускания кнопки мыши используется информация из свойства Constraints, в соответствии с которой форма вновь изменяет свои размеры. Точно так же некорректно реализовано событие OnConstrainedResize, в котором динамически можно заполнить структуру TSizeConstraints. В связи с этим в Delphi 5, как и в предыдущих версиях Delphi, необходимо создавать обработчик сообщения WM_GETMINMAXINFO.

При обработке этого сообщения необходимо задать минимальные и максимальные размеры формы, а также начальные координаты верхнего левого угла. Как правило, данный обработчик события используют только для задания минимальной ширины и высоты формы. При их определении исходят из того, что все элементы управления на форме должны быть всегда доступны пользователю. Соответственно разработчик никогда не должен создавать формы размером более 640*480 пикселов ( а еще лучше — 600*450). Не следует принимать во внимание возможное увеличение размера формы при увеличении размеров системного шрифта — системные шрифты с большим размером, как правило, используются при высоком графическом разрешении экрана.

Типичный пример обработчика WM_GETMINMAXINFO приведен ниже:

procedure TMainForm.WMGetMinMaxInfo(var Message:TWMGetMinMaxInfo);
begin
   if csLoading in ComponentState then Exit;
   with Message.MinMaxInfo^ do begin
      ptMinTrackSize.X:=
      ScreenToClient(BitExport.ClientToScreen(Point(BitExport.Width,0))).X
      +200;
      ptMinTrackSize.Y:=
      ScreenToClient(BitExport.ClientToScreen(Point(0,BitExport.Height))).Y
      +2*GetSystemMetrics(SM_CYFRAME)
      +GetSystemMetrics(SM_CYCAPTION)+4;
   end;
end;

Обратите внимание на проверку того, загружены ли уже все ресурсы (if csLoading in ComponentState). При отсутствии такой проверки возникает исключительная ситуация при старте приложения.

Обработчик данного события не позволит сделать высоту формы меньше нижнего края элемента управления BitExport, а минимальная ширина формы будет на 200 пикселов больше, чем правый край этого элемента управления (в данном примере на форме размещается элемент, размеры которого изменяются при изменении размера формы).

Нельзя использовать координаты в пикселах при задании значений ptMinTrackSize.X и ptMintrackSize.Y. Во-первых, при изменении положений элементов управления на форме в процессе разработки потребуются и соответствующие исправления в коде. Во-вторых, элементы управления могут изменять размеры и положение при переносе готового приложения на компьютер с другим размером системного шрифта (об этом будет рассказано в следующем разделе).

И наконец, рекомендуется использовать системную метрику для определения величины границ неклиентской области формы. Использование системной метрики и цветов является требованием Microsoft. В вышеприведенном примере обращение к системной метрике осуществляется вызовом функции GetSystemMetrics.Масштабирование формы при изменении размера системного шрифта

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

Можно отметить три способа решения этой проблемы:

  1. Приложение устанавливает новый системный шрифт. От приложений такого рода следует по возможности избавляться, поскольку непонятно его поведение при запуске второго аналогичного приложения, которое снова захочет изменить системный шрифт для себя.
  2. Приложение не изменяет позиций и размеров элементов управления при изменении величины системного шрифта, однако размеры самих элементов управления достаточно велики, чтобы разместить на них надписи при увеличении размеров системного шрифта. Формы в таких приложениях часто производят плохое впечатление при малых размерах системного шрифта. Кроме того, если пользователь устанавливает системный шрифт больше стандартного Large Font Windows высокого графического разрешения, надписи зачастую все равно не умещаются на элементах управления.
  3. Наконец, в ряде приложений величина и размеры элементов управления меняются пропорционально размеру системного шрифта. Это, на мой взгляд, наиболее корректный способ масштабирования, именно о нем и пойдет речь ниже.

Форма имеет два свойства, которые регулируют масштабирование: Scaled и PixelsPerInch. Если установить свойство Scaled равным False, то при изменении системного шрифта форма масштабироваться не будет. Если при этом заранее сделать элементы управления большими, получится тот самый результат, что в пункте 2. Следовательно, значение свойства Scaled должно быть равно True.

Непонятно, зачем свойство PixelsPerInch доступно в инспекторе объектов. Перед показом формы данное свойство пересчитывается в соответствии с реальным размером системного шрифта, а его первоначальное значение, установленное в инспекторе объектов, заменяется результатом этого расчета. Но для корректного масштабирования формы важно анализировать именно пересчитанное значение.

Если форма имеет «толстые» границы (ее размер в этом случае может изменяться пользователем во время выполнения приложения) или если форме разрешено иметь полосы прокрутки, то размер клиентской области формы останется таким же, что был на этапе разработки. При этом часть элементов управления окажется за пределами клиентской области и пользователь вынужден будет использовать полосы прокрутки или же перемещать границы формы, а это приводит к лишним операциям.

Поэтому перед показом формы иногда необходимо писать код для пересчета ее ширины и высоты. Это делается в обработчике события OnShow. Использование OnShow гарантирует, что все ресурсы (в том числе и размеры элементов управления) уже загружены.

procedure TDialogForm.FormShow(Sender: TObject);
var
   I,XMax,YMax:integer;
   PT:TPoint;
begin
   if not FFirstRun then Exit;
   {Resizing of form}
   if (Screen.PixelsPerInch<>96) and (ComponentCount>0) then begin
      XMax:=0;
      YMax:=0;
      for I:=0 to ComponentCount-1 do 
      if Components[I] is TControl 
      then with Components[I] as TControl do begin
         PT:=Self.ScreenToClient(ClientToScreen(Point(Width,Height)));
         if PT.X>XMax then XMax:=PT.X;
         if PT.Y>YMax then YMax:=PT.Y;
      end;
      XMax:=XMax+2*GetSystemMetrics(SM_CXDLGFRAME)+4;
      YMax:=YMax+2*GetSystemMetrics(SM_CYDLGFRAME)
      +GetSystemMetrics(SM_CYCAPTION)+4;
      Width:=XMax;
      Height:=YMax;
   end;
   FFirstRun:=False;
end;

Данный код можно использовать в большинстве форм. Исключением является компонент TScrollBox — его «детей» не надо учитывать для определения размеров формы. Классовая переменная FFirstRun используется для однократного запуска данного кода.

Если элементы управления создаются динамически (во время выполнения, а не на этапе разработки), то при установке границ элементов управления нельзя пользоваться абсолютными координатами — только относительными! Например, если элемент управления MyControl должен располагаться под кнопкой BitOK, иметь такую же ширину, а по высоте на 4 пиксела не доходить до края формы, то следует писать код: MyControl.SetBounds(BitOK.Left, BitOK.Top+BitOK.Height+4, BitOK.Width, ClientWidth-BitOK.Top-BitOK.Height-8); и ни в коем случае не писать, например, так: MyControl.SetBounds(10,60,70,180);

Все сказанное выше о масштабировании работает только при совпадении шрифтов формы и установленных на ней элементов управления. Поэтому не рекомендуется определять для элементов управления отдельные шрифты. Если же это необходимо, размеры таких элементов управления должны быть пересчитаны в явном виде перед показом формы. При этом для расчета масштабного коэффициента нельзя пользоваться свойством Height (или Size) шрифта. Вместо этого нужно вызвать функцию Windows API GetTextMetrics и использовать поле tmHeight структуры TTextMetric.

Создание приложений с невидимой главной формой и использование Tray Icon

В некоторых специальных типах приложений (например, MIDAS-серверов) требуется, чтобы при его старте главная форма не показывалась на экране. Попытка изменить свойство Visible главной формы ни к чему хорошему не приводит. Для скрытия главной формы в момент старта приложения необходимо установить свойство Application.ShowMainForm в False. Это обычно достигается добавлением одной строки кода в *.dpr-файл:

begin
   Application.Initialize;
   Application.ShowMainForm:=False;
   Application.CreateForm(TForm1, Form1);
   Application.Run;
FNID:TNotifyIconData;
end.

В приложениях такого типа не только не показывается форма, но и на панели задач (taskbar) отсутствует кнопка для показа или скрытия главной формы приложения. Поэтому, если все же по требованию пользователя необходимо показывать главную форму, это достигается помещением небольшой пиктограммы (tray icon) в правом углу панели задач (taskbar). Подобным образом это реализовано, например, в Microsoft Volume Control и ряде других приложений.

Создание tray-пиктограммы необходимо описывать в конструкторе класса формы. Для этого сошлемся на модуль ShellAPI и секции private класса TForm1 объявим переменную:

Далее, воспользовавшись прилагаемым к Delphi графическим редактором Image Editor, создадим новый ресурсный файл с расширением *.res, а в него поместим пиктограмму, размеры которой должны составлять 16*16 (пиктограммы других размеров не могут быть помещены на панель задач!). Назовем эту пиктограмму MICON и сохраним файл ресурсов под именем Unit1.res. Дальнейшие изменения производим в файле Form1.pas. Прежде всего считаем ресурсы из файла Unit1.res. Для этого добавим команду:

{$R *.RES}

Затем объявим в секции Private переменные FHI:Ticon и FNID:TNotifyIconData. Конструктор класса TForm1 перепишем следующим образом:

constructor TForm1.Create(AOwner:TComponent);
begin
   inherited Create(AOwner);
   FHI:=TIcon.Create;
   FHI.Handle:=LoadIcon(HInstance,'MICON');
   FNID.cbSize:=sizeof(FNID);
   FNID.Wnd:=Handle;
   FNID.uID:=1;
   FNID.uCallbackMessage:=WM_ICONNOTIFY;
   FNID.HIcon:=FHI.Handle;
   FNID.szTip:='Hidden application';
   FNID.uFlags:=nif_Message or nif_Icon or nif_Tip;
   Shell_NotifyIcon(NIM_ADD,@FNID);
end;

Первоначально создается объект TIcon и из ресурсов загружается пиктограмма. После этого заполняется структура TNotifyIconData (она определена в модуле ShellAPI), при этом сразу же осуществляется создание кода для всплывающего меню. Указывается дескриптор окна, которому следует посылать сообщения, — в данном случае это главная форма приложения. Поле FNID.uID содержит порядковый номер пиктограммы в ресурсах. FNID.uCallbackMessage содержит сообщение, которое будет посылаться окну FNID.Wnd при движении или нажатии кнопки мыши над tray-пиктограммой. Оно определено как константа:

const
   WM_ICONNOTIFY=WM_USER+1234;

Поле FNID.szTip содержит подсказку, которая будет всплывать при прохождении курсора мыши над пиктограммой. Поле uFlags определяет тип tray-пиктограммы — в данном примере при двойном щелчке на пиктограмме должны быть инициированы посылка сообщений окну FNID.Wnd и показ пиктограммы плюс показ подсказки. И наконец, вызов метода Shell_NotifyIcon использует заполненную структуру для показа пиктограммы.

Для работы всплывающего меню поместим компонент TPopupMenu на форму Form1. Определим два метода: Show (показ формы) и Close (прекращение работы приложения). В обработчике события OnClick для строки меню Show поместим код остановки сервиса:

procedure TForm1.Show1Click(Sender: TObject);
begin
   Show;
   BringWindowToTop(Handle);
end;

и в обработчике события OnClick строки меню Properties осуществим показ формы:

procedure TForm1.Close1Click(Sender: TObject);
begin
   Application.Terminate;
end;

Но этого еще недостаточно — осталось вызвать всплывающее меню при нажатии правой кнопки мыши над tray-пиктограммой. Сообщение WM_ICONNOTIFY будет посылаться главной форме. Создадим обработчик событий WM_ICONNOTIFY следующим образом:

procedure TForm1.WMIconNotify(var Message:TMessage);
var
   PT:TPoint;
begin
   if Message.lParam=WM_LBUTTONDOWN then begin
      Show;
      BringWindowToTop(Handle);
   end else if Message.lParam=WM_RBUTTONDOWN then begin
      GetCursorPos(PT);
      PopupMenu1.Popup(PT.X,PT.Y);
   end;
end;

При нажатии левой кнопки мыши будет показываться главная форма приложения, а с помощью правой кнопки мыши над пиктограммой будет вызываться всплывающее меню. Соответственно при выборе меню Show происходит показ формы, а при выборе меню Close — закрытие приложения.

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

destructor TForm1.Destroy;
begin
   FNID.uFlags:=0;
   Shell_NotifyIcon(NIM_DELETE,@FNID);
   FHI.Free;
   inherited Destroy;
end;

В этом фрагменте кода, помимо прекращения показа TrayIcon, происходит освобождение ресурсов.

И наконец, последняя проблема, которую необходимо решить при создании данного приложения, — это его поведение при нажатии кнопки Close на форме. По умолчанию при нажатии этой кнопки приложение закрывается. В данном случае это нежелательно — закрытие приложения разумно осуществлять в команде всплывающего меню, как это было описано выше. Форма имеет обработчик события OnClose, где можно изменить переменную Action и тем самым изменить поведение формы при нажатии кнопки Close. Однако это не относится к главной форме приложения — любое значение переменной Action вызовет деструктор главной формы и, как следствие, закрытие приложения. Для того, чтобы спрятать главную форму, не вызывая ее деструктора, необходимо переписать событие WM_CLOSE:

procedure TForm1.WMClose(var Message:TMessage);
begin
   Message.Result:=0;
   Hide;
end;

Обратите внимание на отсутствие оператора Inherited в данном коде. Обработчик WM_CLOSE вызывает деструктор по умолчанию, поэтому Inherited нельзя вызывать. Это один из немногих случаев, когда не требуется вызывать обработчик события по умолчанию.

Продолжение статьи

Дополнительную информацию Вы можете получить в компании Interface Ltd.

Отправить ссылку на страницу по e-mail
Обсудить на форуме Inprise/Borland


Interface Ltd.
Тel/Fax: +7(095) 105-0049 (многоканальный)
Отправить E-Mail
http://www.interface.ru
Ваши замечания и предложения отправляйте автору
По техническим вопросам обращайтесь к вебмастеру
Документ опубликован: 10.10.01