FireMonkey - Мигающая кнопка

Vsevolod Leonov

Постановка задачи

родилась в ходе дискуссий на тему принуждения пользователя "сохранять базу" (делать backup). Дискуссия возникла случайно при разговоре с Дмитрием Кузьменко о "Масштабируемости корпоративных систем на основе СУБД".

Затем эта тема подверглась дальнейшему развитию в плане попыток выяснить возможности реализации "мерцающей кнопки" на компонентной основе FireMonkey.

Эргономика интерфейса

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

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

  • процесс завершён
  • операция закончилась неудачей в течение выполнения
  • просто уведомление о текущем статусе/состоянии

Если первые два пункта характерны для вывода скрытой операции "из тени", то третий пункт часто сводится к отвлечению пользователя от грустных мыслей о зависании приложения. Благое намерение сообщить пользователю о статусе операции (например, в %), чтобы можно было предугадывать срок окончания несколько наивно, ибо интуитивно мы чувствуем нелинейность и нарастание напряжения. Чем ближе к концу, тем выше риск сбоя. Да и процессы немонотонны и нелинейны, поэтому 75% совсем не означает, что через 1/4 прошедшего времени операция закончится. Или что на 75% сбой остался позади.

Императив

Рассмотренное выше является тривиальным. Это и привычно, и легко реализуемо. Здесь мы начинаем от простых техник в виде ProgressBar/StatusBar, "анимашки" (полёт листочков в корзинку), формы и анимации курсора (а-ля "песочные часики") до кульминации в виде комплексной индикации статуса многопоточной задачи (например, закачки в FlashGet).

Следует сказать, что в Windows 7 кнопка на панели задач тоже позволяет показывать степень выполнения текущего процесса приложением (чтобы вспомнить, попробуйте покопировать что-нибудь в проводнике в большом объеме и посмотрите, как будет выглядеть кнопка на панели задача). После выпуска Delphi 2010, которая поддержала разработку под Windows 7, варианты решения данной задачи были опубликованы в исходных кодах на Delphi, но сейчас нет смысла к этому возвращаться. Ввиду возможности мультри-платформенной разработки данный функционал потерял актуальность, да и чисто визуально данное решение нельзя считать удачным. Хотите поспорить? Комментарии внизу.

С TrayIcon ("иконка-в-трее") дела обстоят чуть лучше. Она обладает неким "императивом", т.е. её навязчивость принуждает пользователя сделать действие - кликнуть и узнать, что же там так трепетно бьётся (непрочитанное сообщение, уведомление и т.д.).  И чем более суетливым является движение, тем более навязчивым становится желание от него избавиться. Но здесь опять позволю себе напомнить, что мультри-платформенная разработка и унификация интерфейсов для desktop и mobile (и даже web) перечёркивает планы использования платформенно-зависимых метафор.

Так что идея "мигающей кнопки" вполне себе интересна. Она реально побуждает пользователя нажать на неё. Вот бы еще изготовление оказалось технологичным!

Конструктив

Мне захотелось попробовать сделать "кнопку-мигалку-надоедалку" с использованием стилей. Если вам хочется достичь лёгкости в данной технологии - зацените вебинар с Евгением Крюковым, архитектором FireMonkey, по теме Styles. Сейчас зададим "стилям" в FireMonkey неслабый crash-test. Потянут ли?

Для начала разместите на форме нового проекта FireMonkey HD две кнопки. Первая будет подопытной. Вторая - управляющей.

Чтобы поменять стиль кнопки с "кнопка адекватная" на "кнопка с нервным тиком" выберем пациента (Button1) и прищемим его правой кнопкой. В списке лекарств выберем "Edit Custom Style…". Теперь все остальные кнопки на форме (Button2) будут иметь стиль "by default", а эта - свой собственный. После этого откроется дизайнер стилей.

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

Здесь я рекомендую посмотреть, какой объект за что отвечает. Анимация, кстати, это не когда "что-то где-то движется". В FireMonkey анимация как объект имеет более обобщенный смысл - изменения свойства родительского объекта во времени. Я уже высказывался по данному вопросу, но не касался изменения цвета во времени. Посмотрите TColorAnimation все 2 раза, особенно свойства ParenProperty, Start, Stop и Trigger. Вам нужна справка? Не смешите, здесь всё очевидно.

Когда пользователь наводит мышью на кнопку, то включается анимация. Изменение цвета кнопки не происходит мгновенно, это легко доказать, посмотрев на значение свойства Duration. Что нам остается? Немного перенастроить текущие цветовые анимации (просто зациклив их для постоянного мигания свойством Loop, да и яркости можно добавить).

Позитив

начинается тогда, когда понимаешь, что делаешь. Давайте разберёмся. Первый TColorAnimation управляется триггером "IsMouseOver=true" (свойство Trigger в ObjectInspector). Второй - триггером "IsMouseOver=false". Здесь нужно будет просто сделать так, что когда мышка наводится на кнопку (первая анимация), то кнопка переставала ужасающе мигать, привлекая к себе внимание, но намекала на полезный результат в случае её нажатия. Я ей и Start и Stop поставил в Lawngreen ("зелень на лужайке"). Если мои вкусовые предпочтения вам не нравятся (да и мне самому местами тоже), то функционал не пострадает от выбора другого цвета.

Теперь давайте зарядим вторую анимацию. Здесь дела поважнее. Когда пользователь надвигает курсор на кнопку, та замирает и перестает мигать. А вот когда он (нерешительная дрянь и тряпка) убирает мышку, так и не нажав, то кнопка должна начать световое шоу по-новому. Т.е. для второй анимации мы сейчас и оформим эффект "мигания". Я предлагаю свойство Start выставить в #FFEFEFEF (просто скопируйте значение из свойства Stop), а в свойство Stop внесите какое-нибудь радикальное значение. Я поставил Red. Теперь зацикливаем анимацию установкой Loop=true и AutoReverse=true. У кого есть здравый смысл, интуиция и минимальные знания английского языка, тому справка ненужна.

Теперь уже можно запустить программу (некоторые предпочитают предварительно откомпилировать её). Нажимаем Apply and Close в дизайнере стилей, далее действуем согласно многолетней привычке. Подсказок в виде скрин-шотов не будет, но у вас должно получится форма с двумя кнопками. При наведении курсора на первую из них, она заливается радикально зелёным, а при уходе курсора она должна начать тревожно мигать.

Креатив

бывает разный. У программистов (как у любых других инженеров-изобретателей) он выражается в умении приспособить некий инструмент/метод для решения определенной задачи. Задача такова - заставить кнопку мигать саму по себе, без всяких триггеров (т.е. без всякой внешней силы в виде действий пользователя).

Демо-проекты к Delphi XE2 находятся здесь: C:\Users\Public\Documents\RAD Studio\9.0\Samples\FireMonkey. Нас интересует проект "ControlsDemo". Там есть компонент, который анимируется сам по себе, без внешнего воздействия. Вам еще нужна справка? (подсказка, TProgressBar).

Теперь мы знаем, что сам-по-себе-анимирующийся компонент существует в природе. Добавляем еще одну анимацию (TColorAnimation) к нашей кнопке, которую мы опять открыли в дизайнере стилей: правой мышкой Edit Custom Style… Потом притащили за уши найденный в палитре компонентов TColorAnimation и скинули его на нужный TRectangle (показано ниже).

При перетаскивании компонента не обязательно строго следовать красной линии. Вполне можно срезать угол.

После этого ваш конструктор стиля кнопки будет выглядеть так, как показано еще ниже:

Теперь зададим свойства выделенного нового компонента ColorAnimation1 : TColorAnimation так, как  показано ниже нижнего.

Примечание 1. Я уже реально подустал сейвить скрины шотами. Если что-то из живых роликов вам больше нравится, чем чтение экранов, пожалуйста, напишите в комментариях. Меня просто проинструктировали, что есть OldSchool, а есть NewSchool. Типа для "старичков" нужно писать и картинки лепить, а для "молодой поросли" нужны youtube-ные ролики. Отпишитесь внизу, если не лень.

Примечание 2. Именно такая старинная техника буксировки здесь продемонстрирована лишь во благо воспроизводимости примера. Естественно, я давно всё это сделал в 3 раза быстрее, слегка покопипастив в тексте формы (правый клик на форме, View As Text, и такое вот "невизуальное" создание стилей также нужно уметь делать).

Осталось совсем чуть-чуть. Нужно уметь включать мигалку. Еще раз повторим:

  • стилю компонента свойственно использование анимации, которая применима наиболее эффективно к цветовому решению компонента;
  • стиль компонента может содержать много разных объектов-анимаций, применимых к разным графическим примитивам внутри стиля;
  • желательно применять эффекты анимации (цвета) последовательно, разводя их во времени за счет использования различных триггеров (например, MouseOver=true, MouseOver=false);
  • эффект анимации должен включатся каким-либо триггером, поэтому даже "самопроизвольная" анимация должна управляться именно таким механизмом.

Вот последний пункт - некоторое нетривиальное звено в рассуждениях. Для самопроизвольного мигания берем новую анимацию и назначаем ей триггер IsVisible=true. Теперь мы можем, при нажатии на вторую кнопку (событие OnClick) написать следующие строчки:

procedure TForm1.Button2Click(Sender: TObject);
begin
  Button1.StartTriggerAnimation(Button1, 'IsVisible=true');
end;

Примечание 3. Хочу уберечь вас от тривиального, явного и, к сожалению, неправильного решения поработать со свойством Enabled у нашей анимации. Не поможет. Это работает только при использовании анимации в design-time обычным способом. В стилях нужно использовать механизм триггеров.

Завершив

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

Итак, мы поменяли нашей Buton1 стиль. Изначально он "by default". Но мы ему создали "Custom". Он прописался в свойстве StyleLookup кнопки. Если вам нужно сделать кнопку обычной, то достаточно сделать StyleLookup := ", а созданный нами пользовательский стиль Button1Style1 хранится в невизуальном компоненте формы StyleBook1 : TStyleBook.

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


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=28776