11 марта 2010 г.

Task Dialogs от Vista/Windows 7 в Windows XP: читаем спецификацию с другой стороны

Начиная с Windows Vista у нас появились так называемые Task Dialogs - замена старичку MessageBox, предоставляющие гораздо больше возможностей по кастомизации. В Delphi 2010 (а может и раньше, я не помню) у нас появилась обёртка: новый компонент TTaskDialog. Было бы очень удобно использовать его в своих программах, но... этим вы убираете возможность работы на Windows XP и ниже. Что же делать?

Ну, если мы посмотрим на компонент TTaskDialog, то увидим, что он является обёрткой над единственной функцией - TaskDialogIndirect. Более того, весь API новых диалогов состоит всего из двух функций, одна из которых является просто облегчённой версией второй.

Итого, мы имеем такую цепочку: ваш код -> TTaskDialog -> TaskDialogIndirect -> WinAPI.

Поскольку на старых системах (Windows XP и младше) третий пункт отсутствует, мы получаем вылет при попытке вызова TTaskDialog на старых системах: ваш код -> TTaskDialog -> ???? (вылет) -> WinAPI.

Что можно сделать? Конечно, вы всегда можете определить, под какой системой вы запущены и либо использовать новые диалоги (TTaskDialog), либо использовать старый вариант (своя форма). Как показывает история, если вы заставите людей писать два варианта кода, один из которых работает на всех системах, а второй - только на новой, но выглядит чуточку лучше, то они просто не станут писать второй вариант.

Окей, тогда вы можете написать свой компонент с нуля, встроив в него авто-выбор варианта реализации. Тогда прикладной код будет всего в одном варианте. Однако это означает, что вы выкидываете на свалку уже написанный хороший компонент (TTaskDialog). Более того, если вы уже писали программы с его применением, то вам придётся производить замену компонентов в этих проектах.

Более лучшим решением было бы - не трогать компонент и прикладной код вообще. В цепочке выше у нас остаются тогда два пункта: TaskDialogIndirect и WinAPI. Ну, мы не можем изменить ОС, поэтому WinAPI отпадает. Но что мы тогда можем сделать? Мы можем прочитать спецификацию с другой стороны и реализовать свой TaskDialogIndirect.

Действительно, документация MSDN говорит о том, как и что вы должны передавать в функцию, чтобы вызвать диалог. Но если вы посмотрите на это с другой стороны, то увидите, что эта документация говорит о том, что вам нужно сделать, чтобы реализовать Task Dialogs!

Иными словами, я предлагаю написать эмулирующую прослойку, которая будет подтыкаться на старых системах. В новых системах - будет использоваться родной вариант:
Win7/Vista: ваш код -> TTaskDialog -> TaskDialogIndirect -> WinAPI.
WinXP/2000: ваш код -> TTaskDialog -> эмуляция TaskDialogIndirect -> WinAPI.

В итоге вам придётся просто вписать в uses модуль, как вы автоматически получите возможность использовать Task Dialogs на старых системах.

Причём это не сверх-сложная задача: я потратил на неё всего три дня (и это при моей вечной нехватке времени). Представляю вам свой вариант решения. В архиве модули и демка. Демка демонстрирует многие возможности Task Dialogs, но далеко не все. Более того, число всевозможных комбинаций параметров чрезвычайно велико, так что не представляется возможным показать их все.

Windows 7:
Windows XP:
Особенности:

- Эмуляция сосредоточена в файле TaskDialogs.pas. Вы можете подключить его в uses, если вы вызываете Task Dialogs руками, минуя компоненты. В этом случае включайте этот модуль последним. Функции из этого модуля автоматически выбирают либо эмуляцию, либо родную поддержку (подключать надо в каждый модуль, где вы вызываете функции Task Dialogs).

- Используется подмена TTaskDialog (модуль TaskDialogsComponent.pas) с помощью метода Geo для модификации поведения: вызова эмуляции вместо возбуждения исключения на старых системах. Подключайте этот модуль последним, если вы используете диалоги через компоненты (подключать надо в каждый модуль, где вы используете компоненты).

- К сожалению, в системе нет документированного способа получить заголовки стандартных кнопок. Поэтому, в режиме эмуляции - только английские resourcestring. Используйте локализацию приложения. Не забываем, что в D2010 у нас есть локализованные версии RTL/VCL.

- Shield-значки в любых местах на старых системах просто игнорируются.

- Проверял только на Windows XP. Хотя не вижу причин, почему не станет работать на Win2k или другой старой системе, но специально я не тестировал.

- Delphi 2010 и выше. Если вдруг TTaskDialog появился в Delphi ранее, то, скорей всего, будет работать и на более старой Delphi. На Delphi 7 - однозначно нет, т.к. там нет компонента (впрочем, никто не мешает вам поставить туда компонент от Delphi 2010, но учтите, что придётся пилить напильником).

- Реализованы все возможности стандартных Task Dialogs, кроме TDIF_SIZE_TO_CONTENT и навигации (TDM_NAVIGATE_PAGE/TDN_NAVIGATED). Причина: имхо, визарды проще делать на чём-нибудь другом, так что обойдёмся без страничек. До-реализовывать не планирую. А TDIF_SIZE_TO_CONTENT реализовывать больно сложно, да и документирован флаг лишь частично. Лучше его не использовать, а задавать ширину диалога явно. Все прочие возможности реализованы.

- Поскольку реализовал я это в спешке за три дня - могут быть (и наверняка есть) БАГИ. Время от времени я буду выкладывать обновлённую версию. Также большая просьба сообщать о найденных недочётах/проблемах.

Читать далее: Дело о неработающем ShowMessage.

6 комментариев :

  1. Отличная идея!
    1) Общее замечание: У меня нет XP, но по скриншоту - огромные кнопки в ней выглядят не очень. Имхо, было бы красивее - если б они были более похожи на Вистовский вариант.
    2) А ещё у меня есть давняя идея - сделать аналог, который сможет работать в Delphi 6 и позволит показывать для Висты вистовский диалог а для XP - XP-шный. Только возиться с написанием - лень, всегда находятся дела поважнее. =)

    А ты сам используешь этот контрол?

    ОтветитьУдалить
  2. >>> Имхо, было бы красивее - если б они были более похожи на Вистовский вариант.
    Контрол CommandLink (вернее, такой стиль PUSHBUTTON), который используется в Vista-е, отсутствует в XP.
    Так что - только обычные кнопки. Ну, если у кого чешется - может свой контрол написать.

    >>> А ты сам используешь этот контрол?
    Если вопрос про TTaskDialog, то я его не использовал из-за отсутствия поддержки XP. Теперь буду.
    Если вопрос про мою работу - то я её только-только написал.

    ОтветитьУдалить
  3. Полезная информация, спасибо!

    ОтветитьУдалить
  4. Спасибо за статью.
    Остаётся только один риторический вопрос: "Почему этим вопросом не озаботились разработчики?", учитывая неподъёмные трудозатраты в три человекодня.

    ОтветитьУдалить
  5. А реально сделать подобную фишку на уровне системы? Ну то есть, например, создать программу (твик системы), которая добавляла бы в XP совместимость с диалогами Vista в программах, в том числе и с диалогами открытия/сохранения файлов (TFileOpenDialog). Это бы решило сразу кучу проблем и позволило разработчикам свободно писать программы с применением новых диалогов - в случае необходимости запуска на XP пользователю всего лишь нужно было б установить эту вспомогательную софтину.

    ОтветитьУдалить
  6. Реально, но это будет криво.

    Должно быть примерно так - программа внедряется во все программы, ждёт пока та запросит функции новых диалогов, и возвращает свои функции вместо ошибки.

    Это будет работать при условии что программы проверяют наличие новых диалогов по импорту функции, а не по версии ОС (к примеру, именно последнее справедливо для Delphi). А также при условии, что динамический импорт вообще используется. Т.е. если статический - то выход только подмена системных DLL, что есть совсем плохо.

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

    И чего ради? Чтобы программы, которые и так уже как-то работают на XP, предлагая свой аналог новым диалогам, использовали бы вместо своего обходного пути ваш?

    По-моему, это не стоит того.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.