среда, 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. Больше методов, больше свойств, больше возможностей. Это хорошо. Надо учить. Это тоже хорошо!

суббота, 30 мая 2015 г.

Распространение ПО и другие вопросы

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

понедельник, 6 апреля 2015 г.

Плагины, плагины, кругом одни плагины

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

суббота, 21 марта 2015 г.

Как собрать проект под .Net 2.0 с помощью VS 2013 CE

Чем только не приходилось мне пользоваться в течении карьеры программиста. Начинал я с перевода программы, написанной на Fortran-е для IBM-360, на Pascal для СМ-10. Затем был PL/1 - это на мейнфреймах. После, наступила эра персоналок. Ну как персоналок. В то время персоналка - это не тогда, когда у тебя и дома, и на работе, в твоем личном пользовании, твой, или, по крайней мере, выделенный тебе, компьютер. Это - когда приходишь к определенному времени, тебе освобождают место за компьютером и ты работаешь,  до тех пор, пока не придет следующий работник, или не попросит освободить место кто-нибудь с более высоким статусом. Тогда тоже был Pascal.

Про ошибки и про то, как они обнаруживаются... внезапно

Каждый программист сталкивается в процессе своей трудовой деятельности с целым рядом кошмарных проблем. Округление финансовых данных, работа с датами, кодировки текста - это лишь малый перечень того, с чем приходится бороться поколениям программистов. И, как каждый приличный кошмар, эти программистские кошмары имеют нехорошую привычку возвращаться. Один из таких comeback-ов я наблюдал совсем недавно. Есть такое известное выражение: "Эта штука сильнее, чем "Фауст" Гёте". Так вот, запаслись попкорном? Тогда начнем...

воскресенье, 23 ноября 2014 г.

Использование VirtualBox с образами машин на USB накопителе

Я использую для разработки виртуальные машины, созданные при помощи замечательной программы VirtualBox. Все здорово и замечательно. Получаешь "чистую" систему, есть возможность потестировать под различными версиями операционных систем (не буду приукрашивать, использую только различные версии Windows, разрабатываю под них), можно создавать снимки, возвращаться к предыдущим состояниям (ранее сделанным снимкам). Красота, одним словом.

четверг, 30 октября 2014 г.

Поучительная история про применение знаний.

Завел новый ярлык: "век живи - век учись". Причиной послужил следующий случай. Простая задачка: из смежной системы приходит идентификатор, по которому надо найти данные и добавить их в таблицу. База данных - Oracle. Казалось бы, что может быть проще. Есть, правда, одно отягчающее обстоятельство: приходит только идентификатор, а данные, которые он идентифицирует, могут лежать в одной из нескольких таблиц. Точнее, в одной из двух. То есть, указания, в какой таблице следует осуществлять поиск, нет. К счастью, идентификатор уникален, даже с учетом того факта, что исходных таблиц для поиска несколько - две.