среда, 10 июня 2015 г.

Delphi: как показать окно сообщения над всеми окнами приложения

Все меняется. И это грустно. Долгое время моим наиболее используемым инструментом программирования являлся Delphi. Я специально не говорю "языком", потому что Delphi - это не просто язык программирования. IDE (интегрированная среда разработки) Delphi долгое время оставалась для меня эталоном того, что можно сделать для разработчика. А чего стоила инфраструктура компонентов, Wizard-ов, плагинов?

Но, в последнее время, я все больше сижу на Java, и к Delphi-разработкам возвращаюсь все реже и реже. К тому же, мое активное программирование на этом замечательном средстве застряло на уровне версий Delphi 5, Delphi 6, Delphi 7. Нет, я пробовал что-то писать на новых версиях, например, Delphi XE 3, даже пробовал FireMonkey, но опыт этот был весьма поверхностным и непродолжительным.

Именно поэтому я решил, что буду писать про то, что делаю в Delphi. Вполне возможно, многое из того, что я опишу, покажется современным гуру этого средства детским лепетом, но это меня не останавливает.

Сегодня расскажу вот о чем. Есть у меня программка, которая что-то там делает :) Хотя нет, для полноты картины, напишу о ней несколько слов.

Программа написана с использованием Delphi XE3 (старье, да?), но используемые методы программирования и технологии более подошли бы еще более ранним версиям среды разработки. Например, используется VCL, а не FireMonkey.

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

Я думаю, многие сталкивались с такой проблемой, когда окошко показывается не поверх остальных окон, открытых в Windows, а предательски прячется под каким-нибудь из них. Особенно часто это происходит, когда открываемое окно не является главной формой приложения, а является так называемым toolWindow, да и само приложение представлено только иконкой в трее.

Наверное, я смалодушничал - взял и просто присвоил этой форме стиль fsStayOnTop (ну не форме, конечно, а ее свойству FormStyle). После этого форма стала стабильно появляться поверх всех открытых окон, что не могло не радовать. Но, как часто бывает, такое решение возымело не только положительный, но и отрицательный эффект.

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


А ведь такая безобидная строчка кода:

MessageDlg(Format(rsCoversionError, [Id]), mtError, [mbClose], 0);

Конечно, такой результат не мог меня удовлетворить и я отправился курить мануалы. И уже первые затяжки стали приносить результат!!! В общем, справка Delphi вывела на метод MessageBox. На самом деле, методов два: один объявлен в Vcl.Forms.TApplication, другой - в Winapi.Windows. Нам нужен тот, который описан в Winapi.Windows (то есть, второй), потому что первый (который объявлен в Vcl.Forms.TApplication) является оберткой и как-то сам там все пытается организовать. А нам нужен полный контроль.

Итак, нужный нам MessageBox - обычный метод Win32 API. Он примечателен тем, что первым параметром у него идет дескриптор родительского окна, и, установив его в дескриптор моей формы:

MessageBox(StayOnTopForm.Handle, PWideChar(Format(rsCoversionError, [Id])), 'Error', MB_OK);

я получил желаемый результат:



Однако, этот результат меня тоже не вполне удовлетворил. Дело в том, что я не зря написал, что предпочитаю для выдачи сообщений использовать метод MessageDlg. Ну нравится мне больше внешний вид этого окна, чем того, которое появляется в результате вызова MessageBox. И хотя их и можно сделать абсолютно похожими, но, в случае с MessageBox, придется немного потанцевать с бубном. MessageDlg все-таки более user friendly, на мой взгляд.

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

И тут руководство (не в смысле "начальство", а в смысле "документ") не подкачало. Вернее, на высоте оказался Delphi, ну и online справка, конечно. У формы обнаружилась пара свойств: PopupMode и PopupParent. Изучение документации по обоим свойствам привело меня к следующим выводам: свойство PopupMode можно вообще не трогать, потому что сообщение все равно показывать при помощи метода ShowModal сконструированной формы, а этот метод принудительно выставит значение свойства в pmAuto. А вот в PopupParent, пожалуй, стоит записать мою формочку, чтобы сообщение появилось поверх нее. Я так и сделал:

  dlg := CreateMessageDialog(Format(rsCoversionError, [Id]), 
              mtError, [mbClose]);
  dlg.PopupParent := StayOnTopForm;
  dlg.ShowModal;
  FreeAndNil(dlg);


И результат не заставил себя долго ждать:



Ну и напоследок немного дегтя в бочку... В какой-то момент я подумал, что существует еще один вариант решения моей задачки. Пока я лазил по документации, я набрел на такие методы класса TApplication, как NormalizeAllTopMosts, NormalizeTopMosts и RestoreTopMosts. С их помощью можно написать примерно такой код:

  Application.NormalizeAllTopMosts;
  MessageDlg(Format(rsCoversionError, [Id]), mtError, [mbClose], 0);
  Application.RestoreTopMosts;


после чего, судя по документации, сообщение об ошибке должно появиться над формой. Но не тут-то было. Практика - упрямая вещь, о которую разбиваются многие красивые теории. Запустив приложение и создав предпосылки для появления ошибки, я увидел сообщение о ней под формой. Дальнейшее ковыряние выявило, что для формочек со стилем границ (свойство BorderStyle) bsToolWindow этот метод не работает. Но, например для стиля bsSizeable - все пучком.

Какой же урок я вынес из этого случая? Delphi изменился. Даже в части так хорошо знакомого мне раньше VCL. Больше методов, больше свойств, больше возможностей. Это хорошо. Надо учить. Это тоже хорошо!

Комментариев нет:

Отправить комментарий