уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Как помнят постоянные читатели моего блога, некоторое время назад я активно разбирался с компонентами Ribbon Controls в Delphi. Практически с самого первого их появления в Delphi 2009 и вплоть до нынешней Delphi XE я следил за развитием этих компонентов и освещал кое-какие вопросы по работе Ribbon Controls в своем блоге. Так по-тихоньку по-маленьку набрался материал достаточный для выпуска небольшого справочника, посвященного работе с этими компонентами. Сейчас, когда Вы можете без проблем сохранить любой пост блога в PDF, я не вижу смысла в выпуске второй части справочника в виде отдельной PDF-ки. По-моему, намного эффективнее как для посетителей, так и для блога будет публикация большой статьи, посвященной работе с Ribbon Controls в Delphi. Кому потребуется — сохранит PDF-файлик, другие — смогут получить весь материал в он-лайне. В общем, если Вы ищите в блоге материал по Ribbon Controls в Delphi, то самая полная статья — эта. Читайте, комментируйте, сохраняйте, делитесь с другими ;).

Содержание скрыть

Содержание

  1. Общие сведения по интерфейсу Ribbon
    1. Отличительные особенности лент.
    2. Процесс проектирования Ribbon
    3. Правильная организация лент. Типичные ошибки.
  2. Ribbon Controls в Delphi 2009 — XE Знакомство с новыми компонентами в четыре шага.
    1. Шаг 1 – визуальная разработка интерфейса.
      1. Устройство интерфейса Ribbon
      2. Первый Ribbon интерфейс
        1. Работа с TRibbon
        2. Работа с компонентом TScreenTipsManager
        3. Работа с компонентом TScreenTipsPopup
    2. Шаг 2 – дополнительные элементы интерфейса
      1. Split Buttons. Что это и как сделать?
      2. Галереи в Ribbon
      3. Работа с TRibbonComboBox
      4. Меню: главное меню приложения, PopUp-меню
        1. Главное меню приложения
        2. PopUp-меню приложения
    3. Шаг 3 – работа со ScreenTips.
    4. Шаг 4 – создаем уникальный стиль или как работать в RibbonSkins.
      1. Как руссифицировать меню Costumize Dialog?
      2. Ribbon Skins в Delphi
    5. Вместо заключения. Работа с компонентами Ribbon Controls без мыши.
  3. «Тонкости» работы с Ribbon Controls
    1. Пиктограммы команд, использование стандартных компонентов на ленте Riboon etc.
    2. Продолжение работы без мыши — создаем команды
    3. Работа с RibbonComboBox в run-time
    4. «Живой» интерфейс с Ribbon Controls. Работа с пиктограммами команд
    5. Доработка Ribbon Controls — делаем ScreenTip для кнопки Dialog Action
  4. Заключительное слово

Общие сведения по интерфейсу Ribbon

Отличительные особенности лент


По сравнению с традиционными меню и панелями инструментов, ленты имеют следующие отличительные характеристики:

  • Единый пользовательский интерфейс (UI) для всех команд. Меню являются комплексными и легкими в освоении, панели инструментов эффективны и понятны, но почему бы не использовать немного больше места на экране для создания единообразного пользовательского интерфейса, который выполняет все задачи? Имея единый интерфейс, ленты не требуют от пользователей лишний раз разбираться с тем где искать нужную команду.
  • Видимы и очевидны. Команды в меню понятны по своим названиям, большую часть времени скрыты с глаз. Для сохранения места на экране, кнопки панели инструментов в основном представлены как иконки без надписей (хотя некоторые кнопки панели инструментов используют иконки и надписи одновременно) и понимание того, что выполняет команда зависит либо от подсказки, либо от иконки, когда она не требует лишних пояснений. Однако, пользователи в большинстве случаев знают только значки для наиболее часто используемых команд. Ribbon представляет большинство команд с подписями. Команды на ленте являются видимыми и очевидными и используют подсказки только для предоставления дополнительной информации. Редко когда приходится обращаться к справке, чтобы понять суть команды.
  • Маркированные группы. Хотя в традиционном меню категории обычно имеют подписи, группы в раскрывающемся меню не подписываются и разделены обычным сепаратором. Группы в панели инструментов также обозначены с помощью сепараторов. Организация команд в маркированные группы ленты облегчают поиск команд и определение их назначения.
  • Модальные, но не иерархические. Меню масштабируется путем создания иерархии команд. Меню со множеством элементов может использовать один или несколько уровней подменю, чтобы обеспечить наибольшую вместимость команд. Команды на ленте требуют больше места, чем на пользовательской панели инструментов, поэтому они масштабируются с использованием вкладок. Использование вкладок ленты делает её модальной, иногда требуя от пользователей изменять режимы (переходить по вкладкам, чтобы найти команды). Однако, на вкладке большинство команд либо прямые, либо используют одну Split -кнопку или кнопку-меню, а не иерархию элементов.
  • Прямые и непосредственные. Команда является прямой, если вызывается нажатием одной кнопки (то есть, без навигации по меню) и вступает в силу немедленно (то есть, без использования диалоговых окон, чтобы собрать дополнительные материалы).Команды меню часто косвенные и выполняются не сразу. Как и на панели инструментов, большинство команд ленты предназначены для прямого и непосредственного выполнения, наиболее часто используемые команды вызываются с помощью одного щелчка, без диалогового окна, чтобы собрать дополнительные материалы.
  • Просторные. Меню и панели инструментов в первую очередь предназначены для эффективного использования ограниченного пространства. Для обеспечения такого преимущества, ленты могут потреблять больше вертикального пространства, которое приблизительно эквивалентно меню плюс три ряда панелей. Некоторые существующие программы имеют три или более рядов панелей инструментов, ленты обычно потребляют больше места, чем традиционные интерфейсы для команд.
  • Имеют кнопку приложения и панель быстрого доступа. На ленте всегда представлена кнопка приложения и панель быстрого доступа. Это позволяет пользователям получать доступ к файл-ориентированным и часто используемым командам без изменения вкладки, а также способствует согласованности между программами.
  • Минимальные настройки. Хотя меню имеют фиксированное представление, многие панели инструментов гибко настраиваемые, что позволяет пользователям устанавливать места расположения команд, размеры и содержание. Лента сама по себе не настраивается, но Панель быстрого доступа обеспечивает ограниченные настройки.
  • Улучшенная доступность с клавиатуры. Меню имеют отличную доступность с клавиатуры, поскольку нажатие клавиши Alt дает фокус меню. Однако, не существует такого механизма для панелей инструментов, поскольку они имеют общую навигацию с клавиатуры с содержимым окна. Следовательно, пользователи должны перейти к необходимой панели инструментов с помощью клавишиTab (последняя табуляция), а затем перейти к конкретной команде с помощью клавиш со стрелками. В отличие от этого, ленты обеспечивают расширенную доступность с клавиатуры через keytips, как правило, в три этапа:
  1. Нажмите Alt для входа в режим ввода keytip.
  2. Нажимаете символ для выбора вкладки, кнопки приложения или команды в панели быстрого доступа.
  3. На вкладке, нажмите одну или две буквы, чтобы выбрать команду.

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

Не стоит путать ключи доступа (keytips) с сочетаниями клавиш (ShortCuts). Хотя и ключи доступа и сочетаний клавиш клавиатуры обеспечивает доступ к пользовательским интерфейсам, они имеют различные цели и принципы.

Оригинальный текст: Ribbons

Процесс проектирования Ribbon


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

Когда вы начинаете думать в терминах вкладок и групп (прим— вкладки, группы — элементы Ribbon-интерфейса), то вы обнаружите, что начинают появляться пробелы в функциональности. Вы должны добавить на ленту команды, которые в предыдущих версиях программы могут существовать только в диалоговых окнах. Узнайте, что пользователи чаще всего делают, к каким командам чаще всего обращаются и перенесите эти команды на ленту. По возможности, позвольте пользователям выполнять задачи, не открывая никаких диалоговых окон.

Будьте готовы потратить много времени на тестирование и изменения подписей команд — всех, а не только новых. В нормальном цикле продукта, трудно оправдать изменение названия старой команды. Перемещение команд на ленту дает вам редкую возможность сделать такой тип изменений.

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

Миграция команд из диалоговых окон

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

Например, в Microsoft ® Word, исследование данных показывает, что диалоговое окно настройки шрифта является одним из наиболее часто используемых в программе. Дополнительные данные показывают, что такие функции какsuperscript , subscript и зачеркнутый текст часто вручную добавляются в панель инструментов форматирования. КнопкиУвеличить шрифт и Уменьшить шрифт часто используются в Microsoft ® PowerPoint, где эти команды появились на панели инструментов по умолчанию.

Как результат, в группу Шрифт ленты Word импортированы восемь новых команд, в дополнение к оригинальным командам по умолчанию панели инструментов «Форматирование» в Word. В итоге отпала необходимость часто открывать диалоговое окно «Шрифт». Это также означает, что в большей части пользовательских настроек из предыдущих версий больше нет необходимости. Работа в данном случае оказалась легкой для разработчиков, поскольку все функции, уже были разработаны как кнопки, которые можно было бы добавить на панель инструментов.

Во многих случаях, вы обнаружите, что в версии «пользовательской панели инструментов» функция не существует. Например, в Microsoft Excel 2003, чтобы выделить все ячейки с условным форматированием, пользователи должны были открыть диалоговое окно Перейти…, нажать кнопку Специальный …, (прим. — в русской сборке кнопка Special носит название «Выделить») затем выбрать Условные форматы и, наконец, нажмите кнопку OK, чтобы увидеть результаты. Это диалоговое окно было единственным местом в котором существовала функция. Не существовало эквивалента команды, которая могла бы быть добавлена в меню или панель инструментов пользователя. Чтобы добавить команду для ленты в Office 2007, эта версия панели инструментов должна была быть перестроена.

Часто не сразу понятно, как переместить функцию из диалогового окна на ленту. В типичных диалоговых окнах, пользователь вынужден работать в рамках диалогового окна до нажатия «ОК». В этот момент все выборы сделаны, и пользователь возвращается в окно программы. Когда пользователь нажимает команду на ленте изменения применяются немедленно.

Это означает, что все элементы управления (в ленте) должны работать самостоятельно, и они никогда не могут использовать дополнительные элементы управления. Например, вы никогда не будете выбирать шрифт, а затем размер шрифта, а затем нажимать кнопку ОК, чтобы одновременно подтвердить все изменения. В ленте Ribbon никогда не может быть команд ОК или Отмена. Это не означает, что элементы управления не могут влиять друг на друга. Например, в группеАбзац, выбрав выравнивание по правому краю, снимается выделение с команды «выравнивание по левому краю».

Маркировка команды

Хорошее подписи помогут пользователям достаточно быстро понять суть команды, но большинство команд на старой панели инструментов не имеют подписей. На экранах в 640 (или даже 800) пикселей места совсем не много и желание того, чтобы как можно больше команд уместилось на панели инструментов в качестве возможных средств, как правило приносит в жертву подписи команд. Кроме того, особенно большие переводы подписей могут случайно вытеснить некоторые другие команд за пределы экрана.

Одним из основных преимуществ ленты является то, что она предоставляет достаточно места для всех подписей. Как только вы создадите ваши вкладки и группы, помните, что, по наиболее распространенным правилам, большинство команд на ленте должны быть четко маркированы. У Вас может появится желание отказаться от подписи для того. чтобы втиснуть еще несколько команд на вкладку. Но Вы должны понимать, что большинство пользователей не запомнит все ваши иконки и, в отличие от панели инструментов, на ленте нет отдельной строки меню, где пользователи могут искать подпись.

Только несколько очень известных команд могут быть без подписи по умолчанию. Это будут такие команды, как Bold иCut и они почти наверняка будут на вкладке Главная.

Не используйте многоточие (…) чтобы показать, что команда требует более одного ввода. Непосредственно на ленте многоточия используются только для усечения текста. (Однако, многоточие до сих пор является традиционным значением в раскрывающемся меню.) На лента многоточие автоматически вставляются в конце подписи (прим. — когда название команды не умещается в группе). Когда вы попытаетесь уместить несколько кнопок на вкладке, то увидите, что команды пытаются автоматически занять всё доступное пространство.

Стабилизация пользовательского интерфейса

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

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

Никогда не меняйте подписи динамически. Опять же, изменение ярлыка команды «на лету» вызывает серьезные последствия для общего макета вкладки. Вы должны настроить существующие функциональные возможности и подписи на ленте наиболее подходящим для пользователей способом.

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

Сделайте так, чтобы пользователи полюбили ленту

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

Чтобы пользователи полюбили вашу ленту:

  • Убедитесь, что команды легко найти, как и раньше. С учетом слияния (прим. — ухода от диалоговых окон) и явных маркировок в лентах, большинство команд должно легче находится, чем раньше.
  • Убедитесь, что задачи, также легко выполнять, как и раньше. С помощью ориентированных на результат команд и других видов просмотра в лентах, многие задачи должны стать проще в использовании, чем прежде.
  • Сделайте дополнительные усилия для обеспечения того, чтобы наиболее часто используемые сочетания клавиш и «быстрые» клавиши из вашей старой программы все еще работали как и раньше. Обязательно избегайте присвоения новых значений командам из старых меню. Например, если в прошлой версии программы для доступа к элементу меню «Правка» использовался ключ «Е» , то необходимо стремимся использовать «Е» для доступа к эквивалентной вкладке. Опытные пользователи увлечены эффективным использованием клавиатуры и особенно оценят эту последовательность. И они также будут обращать внимание, когда какой-либо ключ отсутствует.

Не думайте, что вы получили право изменять всё. Лучше выполните тестирования в процессе проектирования, для того чтобы убедиться, что пользователи могут не только выполнять свои задачи, но делать это значительно лучше, чем раньше.

Собираем все вместе

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

  1. Сделайте таблицу всех команд в вашей программе. Потратьте время, чтобы получить этот список, вы будете некоторое время зависеть от этого списка.
    • Этот список включает в себя все команды из элементов меню, панели инструментов, контекстных меню, и любые настройки пользовательского интерфейса.
    • Для каждой команды, вы необходимо:
      • Информация по ID команды из панели управления (TCID)
      • Подпись
      • Используемые данные
  2. Отфильтруйте команды, которые относятся к стандартизированным вкладкам программы:
    • Домой
    • Вставить
    • Разметка страницы
    • Аннотация
    • Просмотр
    • Разработчик
    • Стандартизированные Контекстные вкладки
  3. Отфильтруйте команды, которые относятся к контекстным вкладкам.
  4. Отфильтруйте команды, которые относятся к стандартизированным группам:
    • Буфер обмена
    • Шрифт
    • Абзац
    • Редактирование
    • Таблицы
    • Иллюстрации
    • Темы оформления
    • Параметры страницы
    • Упорядочить
    • Правописание
    • Комментарии
    • Просмотр документа
    • Показать/скрыть
    • Масштаб
    • Окно
    • Макросы
  5. Организуйте остальные команды в небольшие группы функций по их функциональности:
    • Группа со специфичными для программы командами.
    • Поговорите с вашим юзабилити-специалистом для выполнения тестов «Сортируйте это» с пользователями.
  6. Организация групп в общей сложности по 5-10 задачам на вкладку.
    • Начните создавать прототип вкладки. Попробуйте использовать ваш прототип пользовательского интерфейса.
    • Тестируйте ваши разработки с пользователями.
    • Чего не хватает? Есть ли функции, которые должны быть перемещены из диалоговых окон?
    • Где можно использовать галереи, чтобы лучше выражать значение и цель функции или набор функций?
    • Могут ли пользователи смотреть на ваши вкладки и знать для чего они предназначены? Есть ли у каждой вкладки очевидная цель?
    • Подумайте об обобщении вкладок. Могут ли пользователи, читая подписи вкладок понимать, что ваша программа делает?
    • Подумайте о будущем вашей программы. Будут ли вкладки, которые вы выбрали иметь смысл для следующих нескольких версий? Соответствуют ли они общей направленности вашей программы?
  7. Создавайте новые возможности и добавляйте их к вашим вкладкам.
    • Пока вводятся новые функции, вы будете иметь дело с большими проблемами в конструкции ваших вкладок.
    • И, что еще хуже, когда что-либо выбрасывается, вы также будите получать те же проблемы постоянно. Подумайте об этом заранее. Новые возможности в программе могут сократить время на дальнейшую разработку, и вы будете иметь дело с ними быстро.
  8. Тестируйте организацию ваших фич.
    • Сделайте это по всему циклу развития, а не только на крупных бета-тестах.
    • Попробуйте использовать ваши прототипы самостоятельно.
    • Дайте другим использовать ваш прототип и сделать отзыв.
    • В максимально возможной степени перенесите «быстрые» клавиши и подписи команд для совместимости с предыдущими версиями программы.
    • Постарайтесь получить отзывы о длительном использовании, а не только о первоначальных реакциях.
  9. Итерация, итерации, итерации.
    • Убедитесь, что все (в том числе разработчики, тестеры, и соответствующие руководители) планируют постоянные итерации по организации команды.
    • Изменения легко вносить, так как в основе макета лежит простой XML (прим. — только не в Delphi).
    • Но, документировать текущие планы трудно. Будьте осторожны и не позволяйте вашим разработчикам бежать впереди своих тестеров.

Оригинальный текст “Ribbon Design Process”.

Правильная организация лент. Типичные ошибки.


Организация и обнаруживаемость

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

Необходимо обеспечить ясное, очевидное и единственное отображение ваших команд, подписей вкладок и групп, где они находятся.

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

Достижение этой цели требует тщательной разработки, тестирования пользователями, и итерации (см. “Процесс проектирования Ribbon”). Не думайте, что это будет легко.

Вот несколько распространенных ошибок, которых следует избегать.

Ошибки при проектировании интерфейса.

Избегайте общих названий вкладок и групп.

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

Избегайте чрезмерно конкретных названий вкладок и групп.

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

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

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

Например, если команда «Настройка полей» для страницы документа расположена на вкладке «Главная» и, одновременно, на вкладке «Настройки страницы», то пользователей такой дубликат команды может привести к заблуждению и единственно верной для них может оказаться команда на вкладке “Настройки страницы”.

Избегайте произвольных размещений команд.

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

Избегайте размещения команд на основе маркетинга.

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

Ribbon Controls в Delphi 2009 — XE.

Шаг 1 – визуальная разработка интерфейса.


Начиная с RAD Studio 2009 в составе VCL Delphi появилась новая вкладка Ribbon Controls. На этой вкладке располагаются компоненты для создания относительно нового интерфейса для приложения, так называемого «ленточного» интерфейса к которому уже давно привыкли пользователи Microsoft Office 2007.

 

1. Устройство интерфейса Ribbon

В основе Ribbon лежат три вида элементов: вкладка, панель и элемент внутри панели (см. рисунок)

Ribbon Elements

Вкладкам и панелям можно изменять порядок расположения в ленте Ribbon, а элементы на панелях могут иметь различную функциональную нагрузку (применяться в качестве кнопок, флажков, списков, галерей и т.д.), а также группироваться по определенным признакам. И, забегая немного вперед, скажу, что все функциональные возможности элементов Ribbon определяются с помощью одно-трех компонентов без применения каких-либо стандартный контролов, наподобие Button, CheckBox  и т.д.

ДоRibbon Componentsполнительно, Ribbon может содержать кнопку главного меню и панель быстрого запуска.

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

Панель быстрого запуска может содержать копии любых элементов Ribbon для удобства их использования.

Теперь, вооружившись элементарными знаниями об устройстве Ribbon, попробуем построить свой собственный ленточный интерфейс.

2. Первый Ribbon интерфейс

Все элементы Ribbon располагаются на странице палитры компонентов Ribbon Controls

Ribbon Controls

На первый взгляд, разработчики по жадничали и оставили нам в распоряжение всего пять элементов:
1. TRibbon — основа всего интерфейса (лента)
2. TRibbonComboBox
3. TRibbonSpinEdit
4. TRibbonScreenTipManager — менеджер всплывающих подсказок
5. TScreenTipsPopup — всплывающая подсказка
Несмотря на то, что компонентов на вкладке всего пять, этих компонентов будет более чем достаточно для разработки полноценного интерфейса.

Ribbon в Delphi работает только совместно с компонентом TActionManager со страницы Additional палитры компонентов. И этот компонент берет на себя львиную долю всей работы, в том числе, и работы с событиями элементов.

Теперь попробуем создать простенький интерфейс, рассмотрев попутно свойства компонентов Ribbon.

2.1. Работа с TRibbon

Откройте Delphi, создайте новое приложение и расположите на форме следующие элементы:

  1. TActionManager (вкладка Additional)
  2. TImageList (вкладка Win32)
  3. TRibbon (вкладка Ribbon Controls)

В итоге Вы должны получить примерно следующую картинку:

Ribbon Application

Теперь выберите TRibbon и в свойстве ActionManager укажите наш ActionManager1.
В свою очередь, у компонента ActionManager1 в свойстве Images укажите ImageList1.
Предварительная настройка закончена — теперь TRibbon будет находится под управлением менеджера действий и каждая новая панель Ribbon будет автоматически добавляться в  список ActionManager1.
Прежде, чем переходить к расстановке вкладок, панелей и элементов на ленте, рассмотрим её константы и основные свойства.

У компонента TRibbon определены следующие константы:

Константа Значение Описание
cRibbonHideWidth 300 Ширина родительского элемента при которой Ribbon
должен автоматически скрываться
cRibbonHideHeight 250 Высота родительского элемента при которой Ribbon
должен автоматически скрываться
cRibbonQuickAccessToolbarLeft 34 Левый отступ панели быстрого запуска
cRibbonHeight 117 Высота ленты
cRibbonQATHeight 26 Высота панели быстрого запуска
cRibbonUnthemedCaptionHeight 30 Высота заголовка ленты
cRibbonFirstTabOffSet 47 Отступ первой вкладки
cRibbonTabSpacing 6 Отступ между вкладками
cRibbonTabHeight 23 Высота вкладки
cRibbonMinimizedHeight 27 Высота вкладки в минимизированном состоянии
cRibbonTabScrollButtonWidth 12 Ширина скрола
cRibbonGroupCaptionHeight 16 Высота заголовка группы
cRibbonGroupHeight 86 Высота группы
cRibbonPageHeight 93 Высота вкладки
cRibbonMinimumCaptionWidth 50 Минимальная ширина заголовка ленты
UM_DISPLAYKEYTIPS WM_USER + 1 Сообщение показа KeyTips
UM_CHECKSIZE WM_USER + 2 Сообщение, посылаемое после WMWindowPosChanged

Свойства компонента TRibbon:

Свойство Тип Описание
ActionManager TCustomActionManager Компонент TActionManager под управлением
которого будет работать лента
ApplicationMenu TApplicationMenu Главное меню приложения
Caption string Заголовок ленты
DocumentName string Необязательное свойство. Имя документа дописывается перед свойством
Caption
HideTabs boolean Определяет будут или нет скрываться вкладки на ленте
QuickAccessToolbar TQuickAccessToolbar Панель быстрого запуска
ScreenTips TCustomScreenTipsManager Менеджер всплывающих подсказок
ShowHelpButton boolean Определяет будет или нет показываться кнопка «Помощь» в правом
верхнем углу ленты
Style TRibbonStyleActionBars Визуальный стиль ленты. Всего по умолчанию доступно три стиля:
Luna, Obsidian и Silver
Tabs TRibbonTabs Коллекция вкладок ленты

Теперь, давайте добавим на нашу ленту первую вкладку. Для этого можно:

    1. Воспользоваться контекстным меню компонента (опция Add Tab)
    2. Воспользоваться опциями компонента в Object Inspector’e:

Ribbon Options

Добавьте на ленту новую вкладку, кликнув на опцию «Add Tab»

Теперь на вкладку можно добавлять новые группы. Сделать это можно либо через контекстное меню вкладки, либо, опять же воспользовавшись опциями в Object Inspector’е. Для примера, добавим на вкладку три группы:

Ribbon Groups

В любой момент вы можете изменить положение групп на вкладке. Для этого необходимо воспользоваться опцией «Reorder Groups…«.
У группы Ribbon появляются следующие дополнительные свойства:
DialogAction : TAction — действие, которое будет выполняться при клике маленькой кнопки в заголовке группы. Если это свойство определено, то вид заголовка группы измениться на следующий:

Ribbon DialogAction

Rows : integer — свойство определяет на сколько строк будут занимать элементы панели. По умолчанию свойство имеет значение 3.

Теперь попробуем добавить обычную кнопку.

    1. В свойствах ImageList устанавливаем значения свойств Height и Width равным 32 и загружаем в него любую картинку.
    2. Двойным кликом по ActionManager открываем редактор и добавляем новое событие:

ActionManager Actions

  1. Редактируем свойства у нового действия (Action)
Свойство Тип Значение Описание
Caption string WebDelphi.ru Заголовок, который будет показываться пользователю при размещении
действия на панели Ribbon
ImageIndex integer 0 Индекс картинки в ImageList, которая будет
ассоциирована с действием
Name string OpenStartURL Внутреннее имя действия. Обязательно его изменяйте, т.к. при большом
количестве действий Вы можете просто запутаться в них
ShortCut TShortCut Ctrl+U Клавиши быстрого вызова действия

Теперь определим обработчик действия. Пусть при вызове действия открывается главная страница нашего блога. Располагаем на форме компонент TWebBrowser (вкладка Interet палитры компонентов), в редакторе Action Manager’а делаем двойной клик по действию и пишем обработчик:

procedure TForm2.OpenStartURLExecute(Sender: TObject);
begin
WebBrowser1.Navigate('http://webdelphi.ru');
end;

Теперь возьмие и перетащите мышкой действие из ActionManager на любую панель Ribbon. В результате должно получиться примерно следующее:

Ribbon with action

По умолчанию новое действие располагается в первой свободной строке (Row) панели. Для того, чтобы новая кнопка выглядела как полагается, т.е. сверху располагалась иконка 32×32 пикселя и снизу подпись, необходимо выбрать действие на панели, перейти в Object Inspector’е выбрать вкладку свойств CommandProperties и указать в свойстве ButtonSize значение bsLarge. При этом ширина панели Ribbon автоматически изменится и вид панели Ribbon станет следующим:

Ribbon Button

Теперь можете запустить приложение и проверить работу.

Ribbon Application

Кроме того, что Вы можете располагать на панели действия в виде кнопок различного размера, Вы также можете, используя ActionManager располагать действия как CheckBox’ы, RadioButton’ы и т.д.
Давайте, например на второй панели разместим 3 действия в виде радиокнопок.

Создаем в Action Manager’е три новых действия с именами RadioOption1, RadioOption2, RadioOption3 соответственно. Перетаскиваем их на панель Ribbon и в свойстве CommandStyle указываем значение csRadioButton. Вид Ribbon должен быть следующим:

Ribbon radio options

Видите, новые опции при запуске приложение оказались недоступными? Все дело в том, что, если Action не имеет обработчика, то в работающем приложении его свойство Enabled автоматически переходит в значение false. Напишем три простых обработчика:

procedure TForm2.Action1Execute(Sender: TObject);
begin
ShowMessage('RadioOption1 Executed');
Action1.Checked:=true;
end;
 
procedure TForm2.Action2Execute(Sender: TObject);
begin
ShowMessage('RadioOption2 Executed');
Action2.Checked:=true;
end;
 
procedure TForm2.Action3Execute(Sender: TObject);
begin
ShowMessage('RadioOption3 Executed');
Action3.Checked:=true;
end;

Для того, чтобы все три кнопки действовали как и полагается, чтобы в одно время могла быть нажата только одна кнопка, необходимо в ActionManager’е выставить у всех трех действий одинаковое свойство GroupIndex отличное от нуля.
Теперь снова запустите приложение и можете увидеть, что вторая панель Ribbon действует также как и обычный компонент TRadioGroup.
Все возможные формы представления действий на панелях Ribbon мы рассмотрим в следующей статье, а пока сосредоточимся на основах работы.
Переходим к созданию главного меню. Выбираем компонент TRibbon и в Object Inspector’е жмем опцию «Add Application Menu«. Иконка кнопки главного меню берется из свойств проекта (Application Icon). По умолчанию в главное меню добавляется пустое действие ActionClientItem0, которое Вы можете изменить по своему усмотрению. Добавим в главное меню уже имеющееся у нас действие OpenStartURL. Просто перетащите действие из ActionManager’а на кнопку главного меню. В результате вид главного меню у работающего приложения будет следующим:

Ribbon Application Menu

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

2.2. Работа с компонентом TScreenTipsManager

Ещё одной отличительной чертой интерфейса Ribbon являются красивые всплывающие подсказки. Эти подсказки (ScreenTips) помимо своей красоты и удобства являюся также и более информативными для пользователей нежели те же самые Hints.

За работу и управление подсказками отвечает компонент TScreenTipsManager. Располагаем его на форме и в свойстве  ScreenTips у компонента TRibbon указываем  ScreenTipsManager1. Теперь вы можете создавать подсказки программно или воспользоваться редактором. Так как сегодня мы делаем только  первый шаг в работе с Ribbon Controls, то рассмотрим работу визуального редактора.

Выбираем компонент ScreenTipsManager1, в Object Inspector’e вызываем редактор свойства LinkedActionLists, и добавляем в список уже имеющийся компонент ActionManager1:

Ribbon ScreenTips

Теперь в опциях Object Inspector выбираем действие «Generate Screen Tips«. В итоге, если всё сделано правильно, то Вы должны получить сообщение следующего вида:

ScreenTips

Это сообщение означает, что ScreenTipsManager создал для каждого действия из ActionManager свою подсказку. Для того, чтобы изменить вид подсказок нажмите в Object Inspector’е «Edit Screen Tips» и перед Вами откроется визуальный редактор подсказок:

Ribbon ScreenTips redactor

Работа с редактором достаточно проста и удобна:

  1. Выбираете в списке действие
  2. Редактируете  текст подсказки
  3. При необходимости загружаете картинку, вставляете/удаляете нижнюю подпись (Footer), верхнюю подпись (Header) и т.д.

По умолчанию за текст подсказки принимается значение свойства Hint у действия. Создадим подсказку для одного из наших действий.

Выбираем в редакторе действие OpenStartURL и заполняем текстовое поле:

Ribbon Redactor

Загружаем картинку для подсказки. Для этого делаем двойной клик по соответствующему полю и выбираем рисунок в формате bmp.
Так как у нашего приложения ещё нет своей встроенной справки, то желательно убрать Footer. Для этого снимаем флажок с опции Show Footer. В итоге у меня получилась вот такая подсказка:

Screen Tip

Дополнительно для каждой подсказки вы можете установить радиус скругления для углов (свойство Corner Size), изменить текст Footer’а, отключить показ заголовка, клавиш быстрого вызова (ShortCuts), а также изменить рисунок для Footer (по умолчанию это рисунок в виде вопросительного знака).
А теперь самое важное — для того, чтобы все подсказки появлялись необходимо чтобы у главной формы приложения свойство ShowHint было равно True.
Теперь запустите приложение и наведите курсор мыши на кнопку. Через пару секунд появится всплывающая подсказка:

Application with Screen Tip

Кроме такого вида всплывающих подсказок, вы также можете использовать компонент TScreenTipsPopup для отображения точно таких же всплывающих подсказок в любом месте формы.

2.3. Работа с компонентом TScreenTipsPopup

В отличие от предыдущего компонента TScreenTipsPopup является визуальным компонентом. Смысл работы примерно тот же, что и у ScreenTipsManager, но за одним замечательным различием — TScreenTipsPopup может быть ассоциирован с любым компонентом формы, даже с тем у которого нет действия, описанного в ActionManager’е. В нашем случае таким компонентом является WebBrowser1. Давайте создадим для него свой Screen Tip.
Расположите в любом удобном для Вас месте новый компонент TScreenTipsPopup.
Теперь перейдите в Object Inspector и откройте вкладку свойств Screen Tip. Задайте значения свойств Description и Header. Остальные свойства можно оставить незаполненными.
Ассоциируйте подсказку с компонентом WebBrowser. Для этого в выпадающем списке свойства Associate компонента выберите WebBrowser1, а в свойстве ScreenTipsManager укажите ScreenTipsManager1.
Запустите приложение и наведите указатель мыши на браузер:

WebBrowser ScreenTip

Эта же подсказка появится и при наведении указателя мыши на сам компонент ScreenTipsPopup.

Шаг 2 – дополнительные элементы интерфейса


1. Split Buttons. Что это и как сделать?

split buttonПеред Вами, уже ставший привычным, элемент интерфейса Word 2007 — split-кнопка для вставки текста. Те, кто изучал английский язык, без труда поймут почему кнопка называется именно так (split — расщепленный, разделенный пополам).

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

  • основное — вставка из буфера обмена
  • первое дополнительное — специальная вставка
  • второе дополнительное — вставка гиперссылки

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

  1. Добавляем на форму ещё один ImageList (теперь их будет 2). В первом ImageList’e (назовем его ImageList_32) будут храниться иконки кнопок размером 32х32 пикселя, во втором (ImageList_16) — строго в том же порядке такие же самые иконки, но уже 16х16 пикселей
  2. Изменяем свойства ActionManager’а
    • в Images указываем ImageList_16
    • в LargeImages указываем ImageList_32

Таким образом, по умолчанию все новые действия в ActionManager’e будут, как и полагается, иметь маленькую пиктограмму 16х16 пикселей и при необходимости мы сможем её изменять на большую.

Теперь создаем новое действие в ActionManager’e и сразу изменяем его свойства:

  • Caption = «WebDelphi.ru«
  • Name = «OpenIndex«
  • Hint = «Открыть главную страницу блога«

В обработчике пишем:

procedure TForm3.OpenIndexExecute(Sender: TObject);
begin
WebBrowser1.Navigate('http://webdelphi.ru');
end;

Теперь создаем дополнительные действия для кнопки. Первое дополнительное действие — открывает страницу для подписки на RSS. Свойства действия:

  • Caption = «Open RSS«
  • Name = «RSSIndex«
  • Hint = «Открыть страницу на подписку«

Обработчик действия:

procedure TForm3.RSSIndexExecute(Sender: TObject);
begin
WebBrowser1.Navigate('http://feeds.feedburner.com/myDelphi');
end;

Второе дополнительное действие  загрузит страницу со списком всех статей блога. Его свойства:

  • Caption = «Open List«
  • Name = «OpenList«
  • Hint = «Открыть список статей«

Обработчик действия:

procedure TForm3.OpenListExecute(Sender: TObject);
begin
WebBrowser1.Navigate('http://webdelphi.ru/spisok-statej/');
end;

Теперь перетаскиваем действие (TActionOpenIndex на свободную панель Ribbon и задаем новому элементы следующие свойства:

  • CommandProperties —> ButtonSizebsLarge
  • CommandProperties —> ButtonTypebtSplit
  • CommandStylecsButton

Теперь переходим к свойству Items и добавляем дополнительные действия на кнопку. Для этого:

  1. Открываем редактор свойства и жмем кнопку Insert. В список должно добавиться новое действие с названием ActionClientItem0.
  2. Выделяем новое действие и переходим в Object Inspector
  3. Изменяем свойства на следующие:
    • Action = RSSIndex
    • Caption = «Открыть RSS«
  4. Таким же образом добавляем в список второе действие и задаем ему свойства:
    • Action = OpenList
    • Caption = «Открыть список статей«

Запускаем приложение и любуемся результатом:

split_button_2

Как видите, ничего особенно сложного делать не пришлось.

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

Переходим к следующему вопросу — создание галерей.

2. Галереи в Ribbon

Галереи (Galery) — это ещё одна разновидность выпадающего меню, реализованная в интерфейсах Ribbon.

В отличие от простых PopUp-меню и Split-кнопок, в галереях могут содержаться большие графические элементы. Вот, например, содержание галереи «Библиотека нумерации» из Word 2007 (с описанием элементов):

Ribbon GaleryКак видите, галерея имеет очевидные преимущества перед обычными текстовыми меню.

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

Во-вторых, галерея предоставляет пользователю информацию в более удобной и понятной форме. Согласитесь, что если бы вместо крупных рисунков, демонстрирующих различные нумерованные списки, нам бы показали обычное меню с пунктами, типа «Список с латинскими буквами и точкой», то лишний раз заходить в такое меню нам врядли доставило бы удовольствие, а если представить подобное текстовое меню, например, для выбора стилей оформления страницы, то и голову бы сломали думая, что имелось в виду под названиями наподобие «Апекс» или «Солнцестояние».   А галерея дает нам четкое и однозначное представление о том к чему приведет то или иное действие.

Теперь попробуем создать простую галерею. Пусть, например, в галерее будут содержаться картинки, демонстрирующие различные стили оформления для Ribbon.

Смысл создания галерей примерно тот же, что и для split-кнопок, но за одним исключением — все действия (Actions) для галереи должны относиться к одной категории. До этого момента, мы сильно не задумываясь создавали новые действия в ActionManager’e и они автоматически попадали в группу (No Category).

Так как наша галерея будет содержать большие рисунки (100×100 пикселей), то целесообразно для них задействовать ещё один компонент — TActionList (страница Standard палитры компонентов) и новый ImageList.

Итак, укладываем ActionList и новый ImageList на форму и проводим следующие изменения в свойствах:

  1. У ActionListменяем свойства:
    • Name = «StyleList«
    • в Images указываем новый ImageList
  2. Выбираем Action Manager, ищем свойство LinkedActionLists, открываем редактор свойства и добавляем в список StyleList.

Теперь все новые действия из ActionList’а будут автоматически линковаться в ActionManager.

В ImageList загружаем изображения. Если необходимо, то изображения (100х137 пикселей) различных стилей Ribbon Вы можете скачать здесь.

Добавляем новые элементы галереи. Для этого:

  1. Открываем двойным кликом редактор для ActionList (StyleList)
  2. Создаем новое действие
  3. Изменяем его свойства:
    • Caption = «Luna«
    • Name = «galery_Luna«
    • Caterory = «Стили«
    • ImageIndex — указываем индекс изображения стиля в ImageList
  4. Аналогичным образом создаем новые действия для стилей Obsidian и Silver

Теперь в ActionManager’e обычным образом создаем новое действие, например с названием styles. Укладываем новый элемент на панель и изменяем его свойства следующим образом:

  1. CommandProperties —> ButtonTypebtSplit
  2. CommandStylecsGallery
  3. CommandProperties —> ItemsPerRow3
  4. В Itemsдобавляем действия в следующей последовательности:
    • вначале создаем пустой элементи задаем ему свойства:
      • Caption = «&Стили«
      • CommandStyle = csSeparator
    • затем добавляем три действия из StyleList, не меняя их свойств

Пишем обработчики. Пусть по умолчанию основным стилем оформления будет стиль Luna. Тогда обработчик для styles будет следующим:

procedure TForm3.StyleActionExecute(Sender: TObject);
begin
Ribbon1.Style := RibbonLunaStyle;
end;

У событий галереи обработчики будут такими:

procedure TForm3.galery_LunaExecute(Sender: TObject);
begin
Ribbon1.Style := RibbonLunaStyle;
end;

procedure TForm3.galery_ObsidianExecute(Sender: TObject);
begin
Ribbon1.Style := RibbonObsidianStyle;
end;

procedure TForm3.galery_SilverExecute(Sender: TObject);
begin
Ribbon1.Style := RibbonSilverStyle
end;

На всякий случай обращаю внимание — для того, чтобы изменить стиль оформления Ribbon у вас в uses должны быть подключены три модуля: RibbonLunaStyleActnCtrlsRibbonObsidianStyleActnCtrls и RibbonSilverStyleActnCtrls.

Теперь проверим работоспособность нашей галереи. У меня получился вот такой интерфейс:

Ribbon_Galery

Сейчас мы создали простую галерею с одним заголовком и таблицей на 3 столбца.
Если Вам необходимо создать более сложную галерею (например по типу Word 2007 галерея «Фигуры» на вкладке «Вставка»), то каждая новая группа элементов разделяется сепаратором у которого название (Caption) совпадает с названием группы элементов. То есть, если я захочу сделать галерею из двух групп элементов, например «Стили» и «Стили 2«, то дествия будут следующими:

  1. в ActionList’e создаю новые элементы, которые включаю в категория с названием «Стили 2». Неважно какое количество элементов — хоть 100 штук — все они будут расположены в зависимости от свойства ItemsPerRow корневого элемента.
  2. В свойстве Items корневого элемента создаю новый сепаратор (разделитель) со свойством Caption = «&Стили 2»
  3. Добавляю новые элементы из ActionList в Items.

В итоге получаем галерею на два уровня:

Ribbon Galery 2 level

3.Работа с TRibbonComboBox

По своей сути компонент TRibbonComboBox ничем не отличается от своего дальнего родственника TComboBox за одним небольшим исключением — для того, чтобы сохранялся стиль оформления необходимо, чтобы каждый пункт (элемент) TRibbonComboBox представлялся в виде элемента коллекции TActionClients. Как это сделать — см. первый пункт про split-кнопки. В остальном TRibbonComboBox точно такой же как и TComboBox.

RibbonComboBox

Вот, например, на рисунке представлен RibbonComboBox с одним разделителем и тремя элементами для выбора.

4.Меню: главное меню приложения, PopUp-меню

Переходим, наконец, к разработке менюшек в стиле Ribbon.

Главное меню приложения

Вначале, коротко о главном, точнее о составе панели главного меню:

Ribbon MainMenu

Как видно из рисунка, кроме обычного набора элементов главного меню, в Ribbon дополнительно реализована ещё одна панель для размещения дополнительных команд или ссылок на открываемые документы, а также возможность добавления кнопок в нижнюю часть меню. Создадим свое собственное меню.
Для этого необходимо вызвать контекстное меню компонента TRibbon и выбрать пункт «Add Application Menu«. Новое меню по умолчанию содержит один пустой контейнер для действия (TActionClients[0]) и пустую дополнительную панель.
Для того, чтобы изменить заголовок дополнительно панели необходимо:

  • Выбрать компонент TRibbon
  • Переити в Object Inspector и перейти на вкладку свойств ApplicationMenu.

Вкладка свойств  ApplicationMenu содержит следующие свойства:

  1. Caption — заголовок дополнительной панели
  2. CommandType — тип команд для дополнительной панели: ctCommands — в дополнительной панели размещаются элементы команд (действия TAction) ctRecent — в дополнительной панели размещаются ссылки на ранее открытые документы.
  3. Icon — иконка на кнопке главного меню. Её же можно изменить через свойство Application Icon в опциях проекта.
  4. IconSize — размер иконки на кнопке главного меню.
  5. Menu — панель главного меню.

Для того, чтобы изменить состав панели главного меню необходимо выбрать вкладку свойств:

TRibbon —> ApplicationMenu —> Menu

или выбрать в Object Inspector компонент TRibbonApplicationMenuBar.

RibbonApplicationMenu

Здесь:

OptionItems — содержит набор действий для блока дополнительных кнопок меню

RecentItems — набор действий для дополнительной панели главного меню.

Можете потренироваться и создать, например для своего приложения кнопку выхода в главном меню, а также несколько команд в дополнительной панели.

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

Теперь разберемся с основными элементами главного меню. Корневые элементы (те которые видны при открытии меню) создаются также как и любые другие элементы на панелях Ribbon — простым перетаскиванием мышкой действий из ActionManager’а на свободное место панели.

Несколько иначе обстоит дело в случае создания подменю. Для того, чтобы создать для какого-либо корневого элемента список команд с sub-menu необходимо:

  1. выбрать необходимый элемент
  2. добавить необходимые действия в список Items
  3. при этом каждое действие из sub-menu должно иметь свойство CommandStyle = csMenu.

Вот пример под-меню для одного из элементов главного меню:

RibbonApplicationMenu_submenu

Как видно список дополнительных элементов содержит два компонента с типом csSeparator (разделитель)

Кроме того, каждый пункт дополнительного меню содержит поясняющую подпись.

Для того, чтобы сделать подобного рода подменю необходимо, чтобы каждый элемент коллекции TActionClients в списке имел следующие свойства:

  1. CommandStyle csMenu
  2. CommandProperties —> ShowRichContent true
  3. CommandProperties —> Content поясняющий текст для элемента.

Так же при разработке Application Menu следует учитывать следующее обстоятельство — размер панели для под-меню целиком и полностью соответствует размеры основной панели. То есть, если в основном меню у Вас содержится одно действие, то сколько бы элементов вы не вносили в подменю, в работающем приложение подменю будет размером точно в один элемент.

PopUp-меню приложения

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

Единственно, что следует отметить, это то, что при разработке PopUp-меню необходимо использовать компоненты TPopUpActionBar.

Для сохранения общего внешнего вида приложения, не забывайте также, что PopUpActionBar имеет свойство Style.

Шаг 3 – работа со ScreenTips.


Частично про ScreenTips было рассказано в первом шаге изучения Ribbon. В отличие от стандартных Hints (подсказок), ScreenTips дают нам на порядок больше возможностей, в т.ч. вставка в подсказку своих изображений, использование горячих клавишь и т.д.

Рассмотрим простой пример использования ScreenTips.

Открываем Delphi, создаем новый проект и укладываем на главную форму ActionManagerRibbon иScreenTipsManager, как показано на рисунке:

У Ribbon создаем одну страницу RibbonPage1 и на этой странице располагаем одну группу RibbonGroup1.

Теперь производим следующие манипуляции с компонентами:

  1. У компонента ScreenTipsManager1 открываем редактор свойства LinkedActionLists, жмем в редакторе кнопку «Add New…» или Ins. В итоге добавится пустой контейнер.
  2. Выбираем в редакторе «0 — TActionListItem» и в Object Inspector’е указываем ActionManager1:

Собственно теперь наш ScreenTipManager способен создать подсказки для всех действий определенных в ActionManager.

Теперь открываем двойным щелчком редактор ActionMaager’а и добавляем в список новое дейтсвие:

Обработчик действия может быть каким угодно на Ваше усмотрение. Измените только у Action1 свойство Hint (сейчас нам это свойство будет очень важно). Пусть, допустим, оно будет равно «Подсказка для Action1«.

Теперь перетягиваем мышкой Action1 из редактора на RibbonGroup1 и у нас практически всё готово:

 

Всё, что нам теперь остается сделать — это сгенерировать подсказки. Делается это двойным кликом по компоненту ScreenTipsManager или с использованием опции «Generate Screen Tips» в Object Inspector.

При этом, если Вы всё сделали правильно, то получите сообщение вида:

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

Т.е. в данном случае не было определено свойство LinkedActionLists.

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

В редакторе отражены все используемые у ScreenTip свойства:

  • ShowImage: boolean — показывать или нет картинку у подсказки
  • ShowFooter: boolean — показывать или нет «подвал» (нижнюю часть). Обычнов подвале размещается какая-либо дополнительная информация, например, как по умолчанию приглашение нажать F1 для получения справки.
  • ShowHeader: boolean — показывать или нет «шапку», в нашем случае — это название определенное для Action1.
  • Show Short Cut: boolean — показывать или нет горячие клавиши для действия, если они были определены.
  • Border Style — стиль бордюра. Для подсказки ScreenTip возможо применения двух стилей: Normal — при этом форма окна подсказки быдет прямоугольной и Rounded — для придания форме подсказки скругленых углов (используется по умолчанию).
  • Corner Size: integer — определяет радиус скругления углов для подсказки.
  • Color Start и Color End : TColor — определяют градиент заливки формы ScreenTip’а.
  • Offset X и Offset Y: integer — определяют на каком расстоянии от элемента будет располагаться подсказка. Чем больше значение — тем дальше от элемента появится ScreenTip.
  • FooterImage: TBitmap — изображение, которое будет расположено в «подвале». Желательно не выбирать слишком большие изображения.

Дополнительно Вы можее настроить в редакторе вид и текст подсказки при выключенном действии (когда у Actionсвойство Enabled усановлено в False). Для этого необходимо включить свойство «Disabled« в группе «Mode»

Ну, а теперь устанавливаем у главной формы приложения свойство ShowHint в значение True и запускаем приложение. У меня получилась вот такая подсказка для действия Action1:

Красота да и только. Но это ещё не всё. Помните, я сказал, что свойство Hint у Action1 для нас важно? Сказал я это не просто так. Вы, вероятно, заметили, что после того как подсказки были сгенерированы, то текст у подсказок по умолчанию соответствовал тому, что было написано в Hint? Следовательно было бы логично предположить, что ScreenTipManager, а вместе с ним и Ribbon «наследуют» все подсказки из контейнеров. Давайте проверим так ли это на самом деле.

Выделяем RibbonGroup1 и в свойстве DialogAction указываем Action1. По логике вещей мы ничего не поменяли ни в подсказках ни где. Теперь точно такой же ScreenTip должен появиться и у кнопки дополнительного действия у группы. Проверяем:

На деле оказалось, что для DialogAction невозможно установить свой собственный ScreenTip. Это одно из ограничений Ribbon Controls преодолеть которое можно немного переписав компонент группы о чем будет рассказано ниже.

Пока мы можем обойти это ограничение следующим образом. Берем с панели Ribbon Controls компонент ScreenTipsPopup, укладываем его на форму и устанавливаем свойство Visible в false.

Теперь в свойстве Associate выбираем RibbonGroup1. В свойстве ScreenTipsManager указываем ScreenTipManager1 и заполняем все необходимые свойства во вкладке ScreenTip. Запускаем приложение и любуемся результатом:

Конечно этот вариант никак нельзя назвать изящным или панацеей от всех болезней. Скорее всего это что-то наподобие «костыля» для Ribbon Controls, т.к. мы ассоциируем подсказку не конкретно с кнопкой вызова диалога дополнительных действий, а со всей панелькой RibbonGroup1 сразу. Но, тем не менее, вариант вполне работоспособный. А если учесть, что панели Ribbon изменяют свой размер автоматически, то при полном заполнении панели действиями пользователь в принципе и не почувствует «подвоха».

Шаг 4 – создаем уникальный стиль или как работать в RibbonSkins


Как руссифицировать меню Costumize Dialog? .

Самый простой способ решения подобных вопросов – это заглянуть в папку

C:\Program Files\Embarcadero\RAD Studio\7.0\lib\

и посмотреть модуль с названием, наподобие RibbonStr или RibbonConst. Ведь разработчики должны соблюдать все правила написания программного кода, в том числе и избегать использования строковых и, по возможности, других констант в телах процедур и функций. Открываем папку и действительно наблюдаем там файл RibbonConsts.dcu. Одноименный файл, но с расширением *.pas находится в папке

C:\Program Files\Embarcadero\RAD Studio\7.0\source\Win32\vcl\

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

Как видите, всё просто и не требует особых программистских способностей. Только умение пользоваться переводчиком.

Ribbon Skins в Delphi

Начнем изучение темы с того, что наиболее просто – цветовые схемы. В Delphi уже изначально есть три готовых компонента для цветовых схем:

  • XPColorMap
  • StandardColorMap
  • TwilightColorMap

Попробуем применить их на практике. В зависимости от цветовой схемы должны меняться цвета шрифтов и тени компонентов как минимум. Проверим на практике. Размещаем на форме, как обычно: ActionManager и Ribbon. Создаем парочку Action в менеджере, у риббона создаем один Tab и две группы (а можно и одну – как Вам угодно) и размещаем в группах наши Action’ы.
Теперь кидаем на форму простой ComboBox, все три доступные цветовые схемы и кнопку. У меня получилась вот такая неказистая формочка:

Теперь добавляем в ComboBox три строки, каждая из которых будет описывать одну из цветовых схем и в обработчике клика по кнопке пишем следующий код

[]
case ComboBox1.ItemIndex of
  0:Ribbon1.ColorMap:=XPColorMap1;
  1:Ribbon1.ColorMap:=StandardColorMap1;
  2:Ribbon1.ColorMap:=TwilightColorMap1;
end;
Ribbon1.Repaint:
[]

Запускаем приложение и проверяем, что получилось. В итоге главная форма приложения примет следующий вид:

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

Каждый из новых ColorBox будет описывать значение цвета для одного из свойств TcustomRibbonColorMap.
Дописывем обработчик события кнопки:

[]
var Ribbon: TCustomRibbonColorMap;
begin
case case ComboBox1.ItemIndex of
[]
3: begin
      Ribbon:=TCustomRibbonColorMap.Create(self);
      With Ribbon do
        begin
          BevelMinimizedInner:=ColorBox1.Selected;
          BevelMinimizedOuter:=ColorBox2.Selected;
          BtnSelectedFont:=ColorBox3.Selected;
          CaptionFontColor:=ColorBox4.Selected;
          ControlColor:=ColorBox5.Selected;
          ControlInactiveColor:=ColorBox6.Selected;
          ControlFrameColor:=ColorBox7.Selected;
          DisabledFontColor:=ColorBox8.Selected;
          DocumentFontColor:=ColorBox16.Selected;
          FontColor:=ColorBox15.Selected;
          HighlightColor:=ColorBox14.Selected;
          ActiveTabFontColor:=ColorBox13.Selected;
          GroupFontColor:=ColorBox12.Selected;
          InactiveTabFontColor:=ColorBox11.Selected;
          QuickAccessToolbarMoreColor:=ColorBox10.Selected;
          QuickAccessToolbarMoreBorderColor:=ColorBox9.Selected;
  end;
  Ribbon1.ColorMap:=Ribbon;
end;
end;
[]
end;

И теперь можем экспериментировать с цветовой схемой Ribbon сколько нашей душе угодно, подбирать свои цвета шрифтов и т.д. Например, я собрал вот такую «тошнотную» схемку:

Жуть, неправда ли? Но это проcто пример. А у нас остался ещё один открытый вопрос: как сменить не только цвет шрифта, но и тему оформления целиком? Ведь если просто прикинуть, сколько людей купили RAD Studio 2009-XE, сколько используют или пробуют использовать Ribbon Controls? И на всех всего 3 темы. Несерьёзно. Надо как-нибудь выделиться, создать что-то свое.
Благо разработчики из Embarcadero оставили нам возможность немного покреативить в свое удовольствие.
Первое, что следует сделать – понять каким образом и, самое главное, откуда беретя это красивое оформление панелей, менюшек и табов. Я решил оттолкнуться в своих поисках от модуля RibbonLunaStyleActnCtrls и поиски в итоге привели к следующему классу:

TCustomRibbonSkin = class
public
  type  TRibbonSkinLoadType = (ltResource, ltFile);
private
  FBitmap: TAlphaBitmap;
protected
  procedure DoLoadByResource; virtual;
  procedure DoLoadByFile; virtual;
public
  constructor Create; virtual;
  destructor Destroy; override;
  function GetLoadType: TRibbonSkinLoadType; virtual;
  function GetResourceHandle: THandle; virtual;
  function GetLoadTypeValue: string; virtual;
  procedure LoadSkin; virtual;
  property Bitmap: TAlphaBitmap read FBitmap;
end;

Получается с какого стиля оформления мы бы не начинали свои поиски – всё упрётся именно в этот класс. Как видите у этого класса не так уж и много свойств и методов.
С типом TribbonSkinLoadType всё вполне понятно – определяет откуда будет загружаться скин: из ресурсного файла или же из файла на диске.
Далее одно единственное поле: FBitmap: TalphaBitmap – собственно это и есть файл с необходимыми изображениями для скина.
Следом идут два метода, которые срабатывают соответственно в зависимости от того откуда будет загружаться скин.
Но самое замечательное – это открытое для чтения свойство Bitmap.
Возвращаемся к нашему приложению, бросаем на форму ещё одну кнопку, SaveDialog и пишем в свойстве Caption у кнопки «Сохранить скин», а в обработчике такой код:

[]
if SaveDialog1.Execute then
  Ribbon1.Style.RibbonSkin.Bitmap.SaveToFile(SaveDialog1.FileName);
[]

Теперь мы можем взглянуть на битмап скрина Ribbon и создать свой по образу и подобию. Файл весит 1056kb поэтому ради иллюстрации я приведу сжатый в gif и уменьшенный немного по размеру рисунок. Вот он:

Теперь приступаем к фазе №2 –пишем свой модуль для загрузки скина.
Вы обращали внимание, что после первой же компиляции проекта с Ribbon в uses появляется как минимум один дополнительный модуль RibbonLunaStyleActnCtrls? Вот такой же самый модуль мы и будем писать.
Создаем пустой модуль и даем ему какое0нибудь громогласное название, например: RibbonMegaStyleActnCtrls. Сразу в uses подключаем три модуля: ActnMan, RibbonStyleActnCtrls и Ribbon. А также объявляем одну константу:

const LoadType: TCustomRibbonSkin.TRibbonSkinLoadType = ltFile;

То есть договариваемся, что самодельный скин будем грузить из bmp-файла. Теперь обявляем такой класс:

type
TRibbonMegaStyleActionBars = class(TRibbonStyleActionBars)
public
  function GetColorMapClass(ActionBar: TCustomActionBar):   TCustomColorMapClass; override;
  function GetSkinClass: TCustomRibbonSkinClass; override;
  function GetStyleName: string; override;
end;

и расписываем его методы:

implementation
 
uses
ActnMenus, SysUtils;
 
function TRibbonMegaStyleActionBars.GetColorMapClass(
ActionBar: TCustomActionBar): TCustomColorMapClass;
begin
  Result := TRibbonLunaColorMap;
end;
 
function TRibbonMegaStyleActionBars.GetSkinClass: TCustomRibbonSkinClass;
begin
  Result := TRibbonMegaSkin;
end;
 
function TRibbonMegaStyleActionBars.GetStyleName: string;
begin
  Result := 'Ribbon - Mega'; // русскими буквами не писать!
end;

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

Теперь создаем класс для нашего скина, который будем использовать в программе:

TRibbonMegaSkin = class(TCustomRibbonSkin)
public
  function GetLoadTypeValue: string; override;
  function GetLoadType: TCustomRibbonSkin.TRibbonSkinLoadType; override;
end;

Методы опять же просты и неказисты (дли примера):

function TRibbonMegaSkin.GetLoadType: TCustomRibbonSkin.TRibbonSkinLoadType;
begin
  Result := LoadType; //использовали нашу константу
end;

function TRibbonMegaSkin.GetLoadTypeValue: string;
begin
Result := ExtractFilePath(ParamStr(0)) + ‘mega.bmp’; //указали расположение файла
end;
end;

Если не устраивает то, что скин будет лежать прямо рядом с исполняемым файлом – нет проблем, укажите свой путь. А нам остается только зарегистрировать наш новый скин и вовремя его выгрузить. Не будем опять же изобретать велосипед (тем более, что на скорую руку он может получиться далеко не с круглыми колесами), а пишем тоже самое, что и для стандартных скинов в секциях initialization и finalization:

initialization
RibbonMegaStyle := TRibbonMegaStyleActionBars.Create;
RegisterActnBarStyle(RibbonMegaStyle);
 
finalization
UnregisterActnBarStyle(RibbonMegaStyle);
RibbonMegaStyle.Free;

Да и незабудьте перед implementation объявить глобальную переменную:

var
RibbonMegaStyle: TRibbonMegaStyleActionBars;

Теперь подключаем наш модуль в uses и в любом удобном для вас обработчике пишем:

[]
  Ribbon1.Style:=RibbonMegaStyle;
  Ribbon1.Repaint;
[]

Я добавил эти строки в обработчик кнопки для применения цветовой схемы. Вот какой вид приняла моя программа после того как я поиздевался над скином:

Жутко некрасиво, но зато ярко и сразу видно, что кто-то нарочно изуродовал Ribbon.
На сегодня всё. В качестве бонуса к посту прилагается та самая программка, которую я писал по ходу дела работы над статьей. В архиве с программой вы найдёте также её исходники, включая модули RibbonConsts (с одной переведенной строкой) и модулем тестового скина, который, в случае чего, избавит Вас от лишней писанины – просто поменяете путь и название файла и скин готов.

Вместо заключения. Работа с компонентами Ribbon Controls без мыши


Начинающему программисту может показаться, что большинство действий, совершаемых при работе с Ribbon — это движения мышкой по экрану. Взял кнопочку, перетянул на панельку, выбрал мышкой в свойствах значение, ткнул мышкой 2 раза, написал 1 строчку и всё готово. Однако, как это не странно может прозвучать, иногда бывает очень не просто ухватить мышкой тот самый элемент :) Ribbon Controls — это сравнительно новые компоненты в Delphi и, к сожалению, они не лишены некоторых недочётов и ошибок при работе с ними в IDE.  Вероятно Вы уже сталкивались с такой проблемой, когда вы не можете выделить в группе действие? Если да, то не переживайте — не вы первый и не вы последний, кто сталкивается с такой «особенностью» компонентов.  Рассмотрим пример, как можно решить эту проблему без перезапуска проекта.

Для решения проблемы достаточно приучить себя использовать структуру проекта (окно Structure, вызываемое нажатием клавиш Shift+Alt+F11). В принципе, если сильно захотеть, то привычка вырабатывается  за несколько часов.

Рассмотрим небольшой пример использования окна «Structure»  при работе с Ribbon Controls. Расположите на форме TRibbon и создайте два или больше Action’ов как показано на рисунке:

Теперь предположим, что у нас опять пропала возможность выбора необходимого элемента на форме, а нам надо добраться до свойств элемента ‘Info‘ и изменить размер кнопки на bsSmall.

Добраться до необходимого элемента через структуру проекта можно как показано на рисунке ниже:

Выбираем элемент в Structure и получаем необходимые нам свойства в Object Inspector, правда при этом Action все равно не выделяется в дизайнере :). Но лучше уж так, чем никак.

Здесь же в Structure можно создавать новые элементы интерфейса. Для этого необходимо выбрать корневой элемент Items и в его контекстном меню выбрать опцию на добавление нового Item’а:

А дальше уже можно спокойно работать в Инспекторе, назначать изображение, обработчик действия и т.д.

Более «продвинутый» способ работы с компонентами Ribbon Controls — это создание всех необходимых элементов управления только в коде программы — никаких кликов мышкой и перетаскиваний, только чистый Delphi-код. Конечно, подобный подходи намного более сложен, но в качестве примера продемонстрируем как быстро создавать вкладки и группы на ленте Ribbon.

Как Вы уже знаете, в основе Ribbon лежат три вида компонентов: вкладка, группа и элемент, отвечающий за выполнение какого-либо действия (Кнопки, ComboBox  и т.д.). При этом элементы располагаются в группах, а те, в свою очередь на вкладках и умещается всё это хозяйство на родительском компоненте — ленте TRibbon.

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

Открываем Delphi, создаем новое приложение и укладываем на форму стандартный для Ribbon набор компонентов: ActionManager, ImageList и TRibbon. Создавать все элементы интерфейса будем по клику на кнопке TButton. Вид нашего будущего приложения должен быть примерно таким:

Первое, что нам необходимо — это создать новую вкладку Ribbon. Вся совокупность вкладок (Tabs) является коллекцией TRibbonTabs, которая имеет следующее описание:

TRibbonTabs = class(TOwnedCollection)
private
  FDesignerAdd: Boolean;
  function GetItem(Index: Integer): TRibbonTabItem;
  procedure SetItem(Index: Integer; const Value: TRibbonTabItem);
  function GetRibbon: TCustomRibbon;
  procedure CreatePageForItem(AItem: TRibbonTabItem);
protected
  procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
public
  constructor Create(AOwner: TPersistent; ItemClass: TCollectionItemClass);
  function Add: TRibbonTabItem;
  function FindGroup(const GroupName: string): TControl;
  function GetPageFromPageCaption(const Caption: string): TCustomRibbonPage;
  function IndexOfCaption(const Caption: string): Integer;
  function TabExists(const Caption: string): Boolean;
  function SelectNext: Boolean;
  function SelectPrior: Boolean;
  property DesignerAdd: Boolean read FDesignerAdd write FDesignerAdd;
  property Ribbon: TCustomRibbon read GetRibbon;
  property Items[Index: Integer]: TRibbonTabItem read GetItem write SetItem; default;
end;

Как можно видеть из приведенного листинга, отдельная вкладка — это объект TRibbonTabItem. Попробуем создать новую вкладку. Создаем событие onClick  у Button1 и пишем :

procedure TForm5.Button1Click(Sender: TObject);
var RibbonTabItem:TRibbonTabItem;
begin
  RibbonTabItem:=TRibbonTabItem.Create(ribbon1.Tabs);
end;

Теперь можете запустить приложение и убедиться, что новая вкладка создается и содержит стандартное название типа «RibbonTab1», «RibbonTab2» и т.д. Но в рабочем приложении необходимо давать вкладкам другие, более осмысленные названия. Поэтому давайте немного разберемся с объектом TRibbonTabItem.

В числе прочих, TRibbonTabItem имеет следующие свойства:

BestTabWidth: Integer — оптимальная ширина вкладки, т.е. ширина вкладки не может быть больше этого значения. Свойство только для чтения.
MinTabwidth: Integer — минимальная ширина вкладки . Свойство только для чтения
Caption: string — заголовок вкладки.
Page: TCustomRibbonPage — объект, содержащий все необходимые свойства и методы для управления дочерними элементами вкладки (группами).

Таким образом, нам пригодятся сегодня два свойства: Caption и Page. Вначале изменим заголовок новой вкладки, например, воспользовавшись свойством Count у коллекции Ribbon1.Tabs :

[...]
  RibbonTabItem.Caption:='Tab #'+IntToStr(Ribbon1.Tabs.Count);
[...]

В итоге при работе с приложением у Вас должно получиться примерно следующее:

Двигаемся дальше. Следующий шаг — добавление новой группы (Ribbon Group). Здесь в принципе тоже нет никаких сложностей — каждая группа — это дочерний элемент вкладки. Здесь при работе, нам как раз и пригодиться свойство Page: TCustomRibbonPage.

Объект TCustomRibbonPage содержит следующие полезные для нас публичные свойства и методы:

procedure AlignGroups — автоматическое выравнивание групп на вкладке
procedure AddGroup(const Group: TCustomRibbonGroup) — добавление новой группы на вкладку
function FindGroup(const GroupCaption: string): TControl — поиск группы по её заголовку
function FindGroupAt(const Pt: TPoint): TCustomRibbonGroup — поиск группы по координатам точки.
property GroupCount: Integer — количество групп на вкладке
property Groups[Index: Integer]: TCustomRibbonGroup — отдельная группа вкладки
property Index: Integer — порядковый номер вкладки на Ribbon
property Ribbon: TCustomRibbon — родительский элемент вкладки.

Для того, чтобы добавить новую группу нам необходимо воспользоваться методом AddGroup где в качестве входного параметра выступает добавляемая нами группа. Что ж, приступим. Добавляем новую переменную типа TCustomRibbonGroup в обработчик события и пишем:

[...]
  Group:=TRibbonGroup.Create(RibbonTabItem.page);//группа размещается на вкладке
  RibbonTabItem.Page.AddGroup(Group); //добавляем группу в коллекцию
  Group.Parent:=RibbonTabItem.Page; //указываем родительский элемент для группы
  Group.Caption:='Group #'+IntToStr(RibbonTabItem.Page.GroupCount);//указываем заголовок
[...]

Теперь при каждом клике по кнопке в real-time создается новая вкладка и на каждой вкладке одна группа.  Следует отметить, что каждая новая группа по умолчанию имеет следующие свойства:

Width := 200;
  Height := TCustomRibbon.cRibbonGroupHeight;//86 px
  Align := alNone;
  GroupAlign := gaVertical;
  Rows := 3; //количество строк

В принципе, вы всегда можете изменить некоторые из этих свойств как Вам угодно. В представленном выше примере мы создали новую вкладку Ribbon и научились размещать на этой вкладке необходимые группы. Аналогичным образом можно продолжить рассмотрение вопроса и сделать, например, наполнение группы элементами из ActionManager и присваивание этим элементам необходимых свойств. Однако, стоит отметить, что чрезмерное использование таких приемов (создание элементов управления Ribbon по ходу работы программы) может очень сильно нарушить один из основных принципов построения лент, о которых говорилось выше (см. Отличительные особенности лент). В принципе, невидимыми на ленте могут оставаться только контекстные вкладки, которые, кстати, пока не поддерживаются компонентами Ribbon Controls, или, если угодно, то «на лету», можно наполнять содержимое списка выбора (о них речь пойдет ниже), менять внешний вид кнопок (пиктограммы), но никак не создавать новые вкладки и группы. Поэтому прежде, чем создавать элементы управления «на лету» — подумайте, может есть более простой способ предоставить пользователю необходимые команды? А представленный выше пример удобно использовать, например, в случае, когда сравнительно простую по содержанию ленту необходимо создавать в момент запуска программы до показа главной формы пользователю. Если Вам нужен именно такой вариант работы, то ниже будет представлен дополнительный пример — создание собственных элементов управления на ленте Ribbon на примере кнопки.

 «Тонкости» работы с Ribbon Controls

Пиктограммы команд, использование стандартных компонентов на ленте Riboon etc.


 Графика в Ribbon Controls.

Наверное, этот вопрос самый «больной» для всех кто впервые сталкивается с Ribbon Controls. То «зазубринки» какие-то проявляются, то картинка вместо прозрачного имеет чёрный, или ещё хуже, белый фон и т.д. Давайте разберемся «глюк» ли это компонентов или невнимательность?
Во-первых, заходим на msdn.com и задаем простой запрос «ribbon images». Открываем первую же найденную статью и видим небольшую табличку в которой чёрным по белому показано то, сколько DPI должна иметь картинка в зависимости от её линейных размеров. Например, для картинок 32х32 pixels картинка должна быть не менее 96 dpi. Далее под таблицей первый абзац говорит примерно следующее:

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

Вывод из всего вышесказанного:

  1. Следуйте тем рекомендациям, которые даются на msdn.com и снизите вероятность того, что получите «зазубринку» или что-то ещё.
  2. Тщательно подбирайте размеры и dpi картинок.

Теперь, когда информация с MSDN получена проверим как эти рекомендации работают в Delphi 2009-XE. Создаем новый проект с Ribbon Controls. Создаем главное меню приложения (как это делать — см. предыдущую часть), кидаем на форму ImageList для картинок. В ImageList будем держать картинки 32х32 пикселя.
Теперь пробуем загрузить в ImageList картинку в формате png с параметрами, которые нам рекомендует masdn, т.е. 32×32 96 dpi. У меня получилось следующее:


Может по этому поводу возникают споры о том, что Ribbon не умеет работать с прозрачными png?  Так Ribbon Controls здесь не при чем! Мы его даже не касались. Следовательно «проблема» в ImageList. Смотрим в его настройки и видим свойство «Color Depth«:

Меняем глубину цвета на 32 бита и снова загружаем ту же картику в ImageList. Смотрим результат:

Разница на лицо. Теперь создаем в Action Manager’e новый Action, присваиваем ему вновь загруженное изображение, перетаскиваем Action в главное меню и смотрим результат в запущенном Delphi-приложении:

Где хоть одна зазабрина на картинке или непрозрачный фон? Как видно из примера, достаточно следовать рекомендациям MSDN, чтобы избежать проблем с изображениями при работе с Ribbon Controls.

Компонент Ribbon ComboBox — «лишняя приблуда». Можно обойтись стандартным

Тоже достаточно спорный момент. Было дело, когда я и сам так считал, пока не прочитал туже докуменацию на msdn и help delphi. На самом деле этот компонент далеко не лишний в более-менее серьезных проектах.
Во-первых, стоит отметить, что контролов Ribbon не так уж и много в Delphi 2009-XE, чтобы что-то было лишним.
Во-вторых, этот компонент был создан не только для «массовки», но и для того, чтобы обеспечить максимальное соответствие технологии Fluent UI. Хотя бы даже в плане внешнего вида.
Рассмотрим такой пример. Создадам две группы Ribbon на ленте. На первую поместим обычный ComboBox, а на вторую RibbonComboBox.
Получим, на первый взгляд, практически идентичный вид панелей. Разве что «родной» Ribbon ComboBox 100% вписывается в оформление, а стандартный «торчит»:

Вот различие номер один — совместимость с темой оформления. RibbonComboBox полностью вписывается в оформление ленты.
Двигаемся дальше. Добавим в каждый из комбобоксов, скажем..по 20 элементов. Запустите приложение и Вы убедитесь, что есть ещё одно отличие.
Стандартный ComboBox покажет в списке ровно столько элементов, сколько определено в его свойстве DropDownCount, а риббоновский — все, так как у него такого свойства просто нет.
Однако это совсем не значит, что в RibbonComboBox размер его списка определяется только количеством элементов в нем. Дело в том, что при установке нового контрола на ленту Delphi устанавливает его в контейнер (TActionClients[x]) . Так как выделить контейнер мышкой подчас яавляется делом ювелирной точности и недюжего терпения (см. раздел «Вместо заключения«), то показываю как выглядит TActionClients в окне Structure:

Выбираем контейнер, содержащий RibbonComboBox и внимательно смотрим в его свойства:

Самый простой спосооб узнать, что за свойства выделены на рисунке — это попробовать их изменить. Например, изменив свойсво width вы измените размер контейнера целиком, а изменив свойство LabelWidth — измените только размер подписи и т.д. Но главное для нас сейчас — это свойство AllowResize, которое, применительно к RibbonComboBox, указывает какие линейные размеры списка мы позволяем изменить в run-time. Установим это свойство в grBoth. Запускаем приложение, раскрываем наш RibbonComboBox и видим у списка новую особенность по сравнению со стандартным компонентом TComboBox:


Как видите TRibbonComboBox — это не лишний компонент на палитре, а необходимый без которого подчас невозможно организовать даже более менее развитый интерфейc приложения, использующего Ribbon Controls.

Продолжение работы без мыши — создаем команды


В предыдущей части нашего знакомства с Ribbon Controls мы остановились на том, что создали вкладку и разместили на ней одну группу. В целом на всё ушло не так уж и много времени. На этот раз задачка будет немного по сложнее, а именно научиться создавать кнопки и присваивать им выполнение определенных действий.

Как Вы уже знаете (см «Шаг 2 – дополнительные элементы интерфейса«) кнопки у Ribbon могут быть трёх типов: маленькие (bsSmall) с иконкой 16х16, большие (bsLarge), занимающие по высоте всю группу (Ribbon Group) и большие split-кнопки выполняющие несколько действий — верхняя часть кнопки выполняет основное действие и нижняя содержит список дополнительных действие (Actions). Давайте вначале попробуем создать простую маленькую кнопку. Что нам для этого необходимо?
Во-первых, ActionManager и ImageList с хотя бы одной иконкой 16х16.
Во-вторых, нам необходимо, чтобы наша кнопка выполняла какое-либо действие. Следовательно нужен обработчик.
Т.к. ActionManager и ImageList у Вас уже должны остаться на форме от прошлой части нашей работы, то сразу перейдем к написанию обработчика, чтоб потом к нему не возвращаться. Само по себе действие Action:TBasicAction — это объект наследник от (TCompoent) у которого определено следующее свойство:

property OnExecute: TNotifyEvent read FOnExecute write SetOnExecute;

Тип TNotifyEvent это ни что иное как стандартное событие:

TNotifyEvent = procedure(Sender: TObject) of object;

Следовательно, создаем, например такую процедуру у главной формы приложения:

[...]
private
  procedure MyCustomAction(Sender: TObject);
[...]

и тело процедуры. Пусть это будет что-нибудь простое и одновременно информативное, например:

procedure TForm5.MyCustomAction(Sender: TObject);
begin
  ShowMessage('It is my Action!!!');
end;

Обработчик есть. Создаем наш первый элемент управления Ribbon без использования мыши. Для этого в обработчике onClick у единственной кнопки нашего приложения пишем:

[...]
Ribbon1.ActionManager:=ActionManager1;//ассоциировали мэнеджер c Ribbon
ActionManager1.ActionBars.Add;//добавили новую коллекцию
ActionManager1.ActionBars[0].ActionBar:=Group;//добавили в коллекцию новую панель
with ActionManager1.ActionBars[0].Items.Add do
  begin
    action:=TAction.Create(self); //создали новое действие
    action.OnExecute:=MyCustomAction;//присвоили ему обработчик
    caption:='Caption'; //надпись
    visible:=True; //сделали видимым
end;
[...]

Теперь запускаем приложение и любуемся результатом:

Можете проверить — по клику на кнопке «Caption» сработает наш самописный обработчик. Думаю, для Вас не составит никакого труда присвоить такой кнопке картинку из ImageList. А теперь попробуем создать большую кнопку. Следует вспомнить следующее положение при работе с Ribbon:

Для того, чтобы новая кнопка выглядела как полагается, т.е. сверху располагалась иконка 32х32 пикселя и снизу подпись, необходимо выбрать действие на панели, перейти в Object Inspector, выбрать вкладку свойств CommandProperties и указать в свойстве ButtonSize значение bsLarge. При этом ширина панели Ribbon автоматически изменится

Логично предположить, что у нашего Action как минимум есть свойство ActionClients, а у него в свою очередь CommandProperties и т.д. и т.п.  Логично, что следующий код:

ActionClients[0].CommandProperties.ButtonSize:=bsLarge;

сработает?  На первый взгляд логично, а на деле оказывается, что CommandProperties вообще не имеет ничего похожего на ButtonSize.

Вот всё, что есть у этого типа данных (модуль ActnMan):

TCommandProperties = class(TPersistent)
public
type
  TChangeState = (csIgnore, csRecreate, csRebuild);
protected
type
  TCommandPropertiesChangeEvent = procedure (Sender: TObject; State: TChangeState) of object;
private
  FActionClientItem: TActionClientItem;
  FOnChange: TCommandPropertiesChangeEvent;
  FChangeState: TChangeState;
  function GetControl: TCustomActionControl;
protected
  procedure InvalidateControl(const ResetBounds: Boolean); inline;
  procedure DoChanged; virtual;
public
  constructor Create(AItem: TActionClientItem); virtual;
  procedure Assign(Source: TPersistent); override;
  property Control: TCustomActionControl read GetControl;
  property ActionClientItem: TActionClientItem read FActionClientItem write FActionClientItem;
  property OnChange: TCommandPropertiesChangeEvent read FOnChange write FOnChange;
end;

Секрет работы с изображениями довольно прост. Если вы откроете модуль  ActnMan и посмотрите его содержимое то достаточно быстро обнаружите такой тип данных:

TButtonProperties = class(TCommandProperties)
public
type
  TButtonSize = (bsSmall, bsLarge);
  TButtonType = (btNone, btDropDown, btSplit, btGallery);
  TGroupPosition = (gpNone, gpStart, gpMiddle, gpEnd, gpSingle);
  TTextAssociation = (taImage, taDropdown);
private
  FButtonSize: TButtonSize;
  FButtonType: TButtonType;
  FGroupPosition: TGroupPosition;
  FTextAssociation: TTextAssociation;
  procedure SetButtonType(const Value: TButtonType);
  procedure SetButtonSize(const Value: TButtonSize);
  procedure SetGroupPosition(const Value: TGroupPosition);
  procedure SetTextAssociation(const Value: TTextAssociation);
public
  constructor Create(AItem: TActionClientItem); override;
  procedure Assign(Source: TPersistent); override;
published
  property ButtonSize: TButtonSize read FButtonSize write SetButtonSize default bsSmall;
  property ButtonType: TButtonType read FButtonType write SetButtonType default btNone;
  property GroupPosition: TGroupPosition read FGroupPosition write SetGroupPosition default gpNone; 
  property TextAssociation: TTextAssociation read FTextAssociation write SetTextAssociation default taImage;
end;

Вот собственно, тот самый тип данных, который нам нужен для работы со всевозможными «конфигурациями» кнопок. Дописываем наш обработчик onClick:

[...]
with ActionManager1.ActionBars[0].Items.Add do
  begin
[...]
    (ActionClients[0].CommandProperties as TButtonProperties).ButtonSize:=bsLarge;
    ImageIndex:=0;
  end;
[...]

Снова запускаем приложение и на этот раз видим нормальную большую и красивую кнопку Ribbon:

Надеюсь, дальнейшая работа с простыми кнопками не составит для Вас особого труда.

Работа с RibbonComboBox в run-time


В целом, задача достаточно простая, но, думаю, что тем, кто только знакомится с Ribbon Controls будет полезно узнать как добавить новый элемент в RibbonCombobox в runtime.

Как работать со списком RibbonCombobox в design-time, используя мышь и встроенные редакторы, Вы всегда можете узнать, посмотрев соответствующую главу справочника по компонентам Ribbon в Delphi. В принципе, прочитав её станет понятно, чем новый визуальный компонент RibbonComboBox выгодно отличается от стандартного при размещении на ленте.

В runtime задачка несколько усложняется тем, что нам необходимо добраться до коллекции Items у RibbonCombobox“вслепую” да ещё и добавить обработчик на OnExecute.

Давайте создадим простенькое приложение как показано на рисунке:

Ribbon

Ничего лишнего – только наш “подопытный” компонент и ActionManager без которого, как известно, при работе с лентами – никуда.

Теперь обратим наше внимание на то, как располагается компонент RibbonComboBox в окне Structure:

Ribbon_2

Как видите, всё опять же завязано на ActionManager’e:

  • ActionBars содержит коллекцию вкладок ленты Ribbon (ActionBars)
  • Каждый элемент вкладки (ActionBar) содержит произвольное количество элементов (Items)
  • Каждый элемент в группе может также иметь произвольное количество элементов уже в своей коллекции Items.

Всё, что нам надо – добраться до последней коллекции и создать там новый элемент.

Напишем такой обработчик OnCreate у главной формы:

procedure TForm1.FormCreate(Sender: TObject);
begin
   with ActionManager1.ActionBars[0].Items[0].ActionClients[0].Items.Add do
    begin
      Action:=TAction.Create(self);
      TAction(Action).Caption:='Заголовок действия';
      Action.Category:='Категория';
      Caption:='Наш первый элемент'
    end;
end;

Можете запустить приложение и убедиться, что в RibbonCombobox содержится неактивный элемент с текстом «Наш первый элемент». Рассмотрим подробнее, что мы сделали:

ActionManager1.ActionBars[0] – добрались до группы RibbonGroup1 на которой лежит список.

ActionManager1.ActionBars[0].Items[0] – получили доступ к коллекции контролов, расположенных в группе и, в частности, доступ к контейнеру с RibbonComboBox

И, наконец, получили доступ к коллекции Items у RibbonComboBox и воспользовались методом Add для добавления в коллекцию нового элемента.

Далее мы создали для нового элемента действие (Action), которое поместили в категорию “Категория” (в принципе, могли бы обойтись спокойно и без этого – Action был бы доступен в списке всех действие у ActionManager’а).

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

procedure TForm1.MySuperAction(Sender: TObject);
const S = 'Выбран элемент из группы %d и свойством Caption "%s"';
begin
  ShowMessage(Format(S,[TAction(Sender).GroupIndex,
                       TAction(Sender).Caption]));
end;

Теперь добавляем в обработчик OnCreate всего одну строку:

procedure TForm1.FormCreate(Sender: TObject);
begin
   with ActionManager1.ActionBars[0].Items[0].ActionClients[0].Items.Add do
    begin
      ...
      Action.OnExecute:=MySuperAction;
      ...
    end;
end;

Запускаем программу и видим, что при выборе элемента в списке выдается сообщение, содержащее индекс группы в которой содержится Action и заголовок Action’a.

«Живой» интерфейс с Ribbon Controls. Работа с пиктограммами команд


Прежде всего определимся с задачей. Все видели Paint в Windows 7? Кто не видел — вот скрин той самой части ленты, про которую сегодня пойдет речь:

Суть работы такова: когда пользователь кликает по маленькому квадратику с цветом, то, в зависимости от того, какая кнопка нажата «Цвет 1» или «Цвет 2» та и принимает цвет нажатого квадрата.
Получается такая иллюзия «живости» интерфейса от которой неискушенный пользователь скажет «Вах!». А нам, как разработчиком восхищаться особенно не чем — нам такой механизм взаимодействия надо соорудить из подручных средств в Delphi 2009-XE.
Начнем, как полагается в случае с Ribbon Controls — с мышевозного программирования.

Работаем мышкой.

Для этого запускаем Delphi, создаем новый проект, и на главную форму укладываем следующие компоненты:

  1. TRibbon (1 штука)
  2. TActionManager (1 штука)
  3. TActionList (1 штука)
  4. TImageList (2 штуки)

Я буду работать с произвольными иконками, например, обозначающими типы файлов.

У Вас уже сейчас должно быть подготовлено 2 набора картинок:

  1. Картинки 16х16
  2. Точно такие же картинки, но только размером 32х32.

Теперь у первого ImageList (назовем его il1) выставляем свойства:

  1. ColorDepth = cd32Bit
  2. Height = 32
  3. Width =32

Грузим в него картинки 32х32. У второго ImageList (il2) выставляем свойства:

  1. ColorDepth = cd32Bit
  2. Height = 16
  3. Width =16

Грузим в него картинки в том же порядке, что и в первом ImageList. Если порядок будет нарушен — получим лишнюю головную боль с определением индекса картинки — мелочь, но неприятная.

Теперь открываем свойства ActionList и в свойсве Images указываем il2. Соответственно у ActionManager в Imagesуказываем il1.

Также у ActionManager‘а в свойства LinkedActionLists добавляем наш ActionList.

Следующий шаг — настройка Action’ов. Вначале настраиваем действия в ActionList. Для этого открываем его, добавляем действие и сразу определяем новую группу для Action. У меня — это группа «Файлы». В зависимости от того скалько картинок у вас в il1 и il2 (должно быть в обоих одно и то же количесво) добавляем столько же Action’ов и назначаем каждому свое изображение. В итоге у меня получился следующий набор действий:

Теперь переходим в AсtionManager и добавляем в него два действие, назначив им изображение из il1. Должна получиться примерно такая картинка:

Теперь остается только аккуратно разложить все Action’ы на панельках и первый этап можно считать законченным. У меня получилась лента следующего вида (в запущенном приложении):

Так как мы ещё не назначили ни одного обработчика Action’ам, то, соответственно, в запущенном приложении все элменты неактивны. Также я специально не стал менять свойство Caption у элементов на ленте и Вам менять их пока не советую — проще будет понять, что мы сейчас начнем делать.

Ищем способы смены изображения элемента «на лету»

Прежде всего нам надо определиться с тем как можно сменить изображение на любом элементе управления на ленте Ribbon. Логично предположить, что надо как-то докапаться до конкретного Action’а и поменять у него свойство ImageIndex. Ведь именно так мы и действовали при работе в ActionList? Но на самом деле всё немного проще. Смотрите. У ActionManager’а есть два замечательных метода:

function FindItemByCaption(ACaption: string): TActionClientItem;
function FindItemByAction(Action: TCustomAction): TActionClientItem;

Как уже понятно, с помощью них мы можем получить доступ к TActionClientItem у которого, в свою очередь имеется свойство ImageIndex — его-то мы и будем изменять. Остается тольео определиться с тем, какой из методов мы будем использовать. Я буду в качестве примера искать элемент по Caption. И выбор мой не случаен. Посмотрите на рисунок выше и попробуйте, не заглядывая в свои исходники, ответить на вопрос «Какой Caption у первого слева TActionClientItem’a?» :). Будет не верно, если Вы решили, что это actMainAction1. На самом деле для TActionClientItemсвойство Caption будет равно &actMainAction1. По умолчанию свойство Caption берется таким же как и у Action, который назначается Вами для элемента, НО обязательно где-то вставляется амперсанд. Говорю «где-то», потому что встречал его и в начале и в середине и в конце строки. Вот такая небольшая особенность поиска по Caption.

Можете теперь немного потренироваться в поисках элементов на ленте по их заголовкам. А я тем временем приведу пример как можно сменить изображение на элементе:

ActionManager1.FindItemByCaption('&actMainAction1').ImageIndex:=2

или так

(ActionManager1.FindItemByCaption('&actMainAction1').Action as TAction).ImageIndex:=2

Вот так запросто мы сменили изображение на первой слева кнопке. Думаю, что на этом способе и остановимся.

Организуем «живой» интерфейс Ribbon

Остается только поработать немного со свойствами элементов на ленте и составить обработчики действий. Вначале настраиваем большие кнопки. Выделяем первую кнопку на ленте, в Object Inspector’е выбираем вкладку свойств Aсtion и вытсавояем свойства:

GroupIndex = 1
AutoCheck = true

Точно такие же свойства выставляем и у второй кнопки. Теперь определим для них действия.  У меня по клику на люой из кнопок в ListBox додавлятся строка, содержащая тип файла. Обработчик получился немного корявнький, но работоспособный:

procedure TForm5.BigButtonAction(Sender: TObject);
begin
  case (Sender as TAction).ImageIndex of
    0:lst1.Items.Add('AI');
    1:lst1.Items.Add('ACC');
    2:lst1.Items.Add('ACE');
    3:lst1.Items.Add('AIFF');
    4:lst1.Items.Add('ASP');
    5:lst1.Items.Add('AVI');
  end;
end;

Второй обработчик будем использовать для всех мелких кнопок. Здесь нам надо будет определить какая из больших кнопок нажата в данный момент и назначить ей «свою» картинку:

procedure TForm5.SmallButtonAction(Sender: TObject);
begin
 if (actmgr1.FindItemByCaption('&actMainAction1').Action as TAction).Checked then
   (actmgr1.FindItemByCaption('&actMainAction1').Action as TAction).ImageIndex:=
   (Sender as TAction).ImageIndex
 else
   (actmgr1.FindItemByCaption('a&ctMainAction2').Action as TAction).ImageIndex:=
   (Sender as TAction).ImageIndex
end;

Теперь назначаем этот обработчик каждой маленькой кнопке (событие OnExcute) и остается только «причесать» наше приложение, а точнее, убрать все подписи для маленьких кнопок. И здесь банальным стиранием свойства Caption не обойтись — надо обязательно выставить у каждого ActionClientItem’a свойство ShowCaption в False:

Теперь можно запустить приложение и убедиться в его работоспособности — в зависимости от того какая большая кнопка нажата у той и меняется иконка при нажатии маленькой кнопки, а также по своему выполнеятся Action. Практически также и работает Paint — какой цвет выбрали — тем и рисуем.

Доработка Ribbon Controls — делаем ScreenTip для кнопки Dialog Action


В начале рассмотрим суть проблемы.

Если вы запустите MS Word 2007 и наведете курсор мыши на кнопку DialogAction первой же группы (буфер обмена), то увидите вот такую красивую подсказку:

Причём, обратите внимание, что подсказка появляет только когда курсор мыши находится над кнопкой. Когда курсор перемещается по заголовку группы — подсказки нет.
В Delphi же дела обстоят несколько иначе — подсказка (TScreenTipsPopup) ассоциируется не с кнопкой DialogAction, а сразу со всей группой. И не важно где находится курсор мыши — подсказка упорно вываливается каждый раз как курсор попадает в область группы. А ведь хочется так как у всех. Так как надо.
Вот в этом и состоит наша сегодняшняя задача — отобразить подсказку именно для DialogAction.
Для решения поставленной задачи нам понадобятся:

  1. 1 TActionManager
  2. 1 TRibbon
  3. 1 TScreenTipsManager
  4. 1 TScreenTipsPopup

На момент чтения этой части Вы уже должны быть в курсе как работать с компонентами Ribbon Controls и представлять себе (хотя бы примерно) их «внутренности». Если нет, то советую сначала ознакомиться с первой частью справочника.

Размещаем компоненты на форме, указываем необходимые свойства, а также создаем одно действие в Action Manager, назовем его «act_dialog» — оно и будет у нас находится в DialogAction группы Ribbon.

Теперь немного разберемся с тем, почему нельзя «повесить» новую подсказку на кнопку DialogAction группы? Говоря кратко, действия разворачиваются следующим образом: по умолчанию текст подсказки выбирается из свойства Hint для TAction, затем, в момент наведения курсора мыши на объект показывается подсказка и срабатывает событие OnHint все у того же TAction.

Казалось бы все должно быть логично — при определении свойства DialogAction мы в Object Inspector явно указываем TAction, следовательно есть и Hint и должно быть событие OnHint. Оказывается нет. Откройте модуль Ribbon.pas и найдите в нем описание класса TCustomRibbonGroup — предка TRibbonGroup:

TCustomRibbonGroup = class(TCustomActionControlBar, IRibbonGroup,
    IRibbonBarKeyTip, IRibbonKeyTip)
  public
    [...]
  private
    [...]
    FDialogAction: TContainedAction;
    [...]
  protected
    [...]
    procedure DrawShowDialogButton; virtual;
    [...]
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    [...]
    property DialogAction: TContainedAction read FDialogAction write SetDialogAction;
    [...]
end;

свойство DialogAction представляет собой объект TContainedAction. Если Вы посмотрите, что это за объект и какие он имеет свойства, то убедитесь, что у этого объекта нет ни свойства Hint, ни соответственно события OnHint, а сама кнопка, которую мы видим в программе никак не контейнер для объекта (как в самой группе), а просто скин, натянутый поверх заголовка.

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

Следовательно, мы можем поступить следующим образом — создать собственного наследника от TCustomRibbonGroup и сделать у него свойство DialogAction таким, какое нам необходимо, а именно TCustomAction.

type
  TMyRibbonGroup = class(TCustomRibbonGroup)
  private
    FDialogAction: TCustomAction;
    procedure SetDialogAction(const Value: TCustomAction);
  public
    constructor Create(AOwner:TComponent);override;
  published
    property DialogAction: TCustomAction read FDialogAction write SetDialogAction;
end;
 
procedure TMyRibbonGroup.SetDialogAction(const Value: TCustomAction);
begin
 inherited DialogAction:=Value;
 if FDialogAction <> Value then
  begin
    if FDialogAction <> nil then
      FDialogAction.RemoveFreeNotification(Self);
    FDialogAction := Value;
    FDialogAction.FreeNotification(Self);
    Invalidate;
  end;
end;

Теперь нам необходимо решить вопрос: как мы узнаем, что курсор находится именно над кнопкой DialogAction, а не на заголовке или ещё где-либо? Определим следующие методы в нашем новом классе:

function TMyRibbonGroup.GetCaptionRect: TRect;
begin
  Result := Rect(1, Height - GetCaptionHeight - 1, Width - 2, Height - 2);
end;
 
function TMyRibbonGroup.GetDialogButtonRect: TRect;
begin
  Result := GetCaptionRect;
  Result.Left := Result.Right - 15;
  Result.Bottom := Result.Top + 14;
end;

Исходя из названий методов уже должно быть понятно, что метод GetCaptionRect возвращает нам TRect для всего заголовка группы, а GetDialogButtonRectTRect только для кнопки.

Теперь нам остается создать у нашего класса два события OnMouseMove — чтобы определить, что курсор движется по группе и ещё одно, например OnHotDialogButton — чтобы поймать момент, когда курсор находится в области кнопки.
Определим в классе следующие поля:

type
  TOnHotDialogButton = TNotifyEvent;
 
TMyRibbonGroup = class(TCustomRibbonGroup)
  private
    FDialogAction: TCustomAction;
    FOnMouseMove: TMouseMoveEvent;//мышь движется по области группы
    FDialogButtonHot: boolean; //указывает на то, что кнопка выделена
    FOnHotDialogButton:TOnHotDialogButton; //мышь находится на кнопке
[..]
end;

В TCustomRibbonGroup есть следующий метод:

  procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer);override;

Воспользуемся им для решения нашей задачи:

type
  TMyRibbonGroup = class(TCustomRibbonGroup)
  private
    [...]
     procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override;
  protected
    [...]
  end;
 
procedure TMyRibbonGroup.MouseMove(Shift: TShiftState; X, Y: Integer);
var  LPt: TPoint;
begin
  inherited;
  if Assigned(FOnMouseMove) then
    FOnMouseMove(Self,Shift, X, Y);
  if (FDialogAction <> nil) then
   begin
     LPt := Point(X, Y);
     FDialogButtonHot:= not DesignMode and PtInRect(GetDialogButtonRect, LPt);
   end;
  if FDialogButtonHot then
    if Assigned(FOnHotDialogButton) then
      begin
        FOnHotDialogButton(Self);
      end;
end;

Теперь остается только воспользоваться нашими наработками. Во первых, создадим обработчики события для нашего компонента:

type
  TForm6 = class(TForm)
    [..]
    procedure act_dialogExecute(Sender: TObject);
  private
    procedure HintDialog(Sender: TObject);
    procedure MouseMove(Sender: TObject;Shift: TShiftState; X, Y: Integer);
  public
    { Public declarations }
  end;
 
var
  Form6: TForm6;
  MyGroup: TMyRibbonGroup;
 
implementation
 
procedure TForm6.act_dialogExecute(Sender: TObject);
begin
  ShowMessage('Its MyActionDialog!')
end;
 
procedure TForm6.HintDialog(Sender: TObject);
begin
  ScreenTipsPopup1.Action:=act_dialog; //определили попапу действие
  ScreenTipsPopup1.Associate:=MyGroup //ассоциировали попап с группой
end;
 
procedure TForm6.MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  Ribbon1.DocumentName:=IntToStr(X)+' '+IntToStr(Y);
end;

Теперь у ScreenTipManager’а определяем обработчик события OnShowScreenTip:

procedure TForm6.ScreenTipsManager1ShowScreenTip(Manager: TObject;
  Action: TBasicAction; var ShowScreenTip: Boolean);
begin
  ShowScreenTip:=MyGroup.DialogButtonHot; //показываем подсказку если кнопка выделена
end;

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

Теперь нам остается только создать новую группу и определить у неё DialogAction. Сделать это можно, например так (по клику на кнопке Button1):

procedure TForm6.Button1Click(Sender: TObject);
var
    RibbonTabItem:TRibbonTabItem;
    a:TActionClientItem;
begin
  RibbonTabItem:=TRibbonTabItem.Create(Ribbon1.Tabs);//создали вкладку
  MyGroup:=TMyRibbonGroup.Create(RibbonTabItem.page);//создали группу
  RibbonTabItem.Page.AddGroup(MyGroup);//поместили группу на вкладку
  MyGroup.Parent:=RibbonTabItem.Page;//определили родителя
 
  MyGroup.Caption:='MyGroup';//дали название группе
 
  MyGroup.DialogAction:=act_dialog;//назначили действие кнопке
  MyGroup.OnHotDialogButton:=HintDialog;//определили событие
  MyGroup.OnMOuseMove:=MouseMove;//определили событие
end;

Теперь запускаем приложение и любуемся результатом — когда курсор попадает на кнопку DialogAction появляется подсказка, в остальных случаях её нет. Что и требовалось сделать.

 

Представленный выше пример был собран в виде отдельного компонента, скачать который вы можете по ссылке ниже:

[download id=»102″ format=»1″]

Заключительное слово

Вот, пожалуй, и собран воедино весь тот материал по Ribbon Controls в Delphi, который копился в блоге практически два года. Представленные выше разделы, как могли заметить постоянные читатели моего блога, практически ничем не отличаются от тех статей, которые я публиковал и в которых делился с Вами своими идеями, работами по части использования лент в своих приложениях. Как видите, эта статья оказалась не совсем тем, что можно смело назвать словом «справочник», да я и, признаться, не ставил перед собой такую цель — написать online-книгу содержащую абсолютно все моменты и вопросы по работе с компонентами Ribbon Controls в Delphi. Да я мог бы переписать сюда весь хэлп Delphi и разбавить его примерами из статей, но был бы в этом случае какой-либо смысл? Хэлп Вы всегда сможете прочитать пусть и на английском языке. Представленная же выше статья, надеюсь, сможет показать любому начинающему использовать Ribbon программисту пути решения наиболее часто возникающих вопросов и проблем, а заодно и наметить некоторые пути доработки компонентов в тех случаях, когда их функциональных возможностей становится недостаточно, например, как в части работы с DialogAction.

Теперь, когда рассмотрение Ribbon Controls в Delphi 2009-XE подошло к концу остается только ждать, что же принесет нам новая версия Delphi XE2 о которой уже так много сказано в Сети. Как говориться, поживем — увидим.

До следующих встреч в блоге WebDelphi.ru.

Если эта статья Вам понравилась, то отметьте это нажатием кнопки +1 :)

5 2 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
22 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Леха
29/08/2011 10:40

Влад, спасибо! Очень своевременная статья. Я как раз начал прогу писать с использованием ленты =)

Keeper
31/08/2011 15:04

Сборка замечательная, как надумаю использовать риббоны, обязательно обращусь. Спасибо!

Павел
Павел
02/09/2011 21:00

Ещё раз спасибо за актуальную сборку, перечитал ещё раз с удовольствием. Читаю твои обзоры Ribbon с самого начала, что и подвигло меня в корне изменить интерфейс моей программы, и надо сказать, за это я благодарен. Так вот, к сути дела. Применяя на практике «ручное» создание TActionClient для ActionManager обнаружил небольшой недочёт. В тексте ты пишешь with ActionManager1.ActionBars[0].Items.Add do   begin     action:=TAction.Create(self); //создали новое действие     action.OnExecute:=MyCustomAction;//присвоили ему обработчик     caption:=’Caption’; //надпись     visible:=True; //сделали видимым end; и т.д. Однако, если указывать просто Caption, то это свойство становится недоступным в событии Execute, которое мы для него… Подробнее »

Павел
Павел
03/09/2011 01:21

Дык у меня тоже так — работает, да и работает. Только когда случаются неполадки при вроде правильном на первый взгляд коде, вот тогда и приходится разбираться.

Виталик
Виталик
10/05/2012 23:19

добрый вечер)) у меня такой вот вопрос пытаюсь заткнуть картинку на кнопку, всё делаю по инструкции (подключаю imageList-ы, картинки 16*16 и 32*32), в процессе разработки картинки отображаются, а при запуске готовой программы нет :( картинки находятся в папке с программой, так же в папке Debug, но всё равно не хотят. Подскажите что можно или нужно сделать для их отображения?

Виталик
Виталик
11/05/2012 11:59

вот ссылка на пример, на кнопки выход работает замечательно, показать сообщение нет
http://narod.ru/disk/48746807001.0c2a62a226a68ac59a586e30ad402436/ribbon%2B%D0%BA%D0%B0%D1%80%D1%82%D0%B8%D0%BD%D0%BA%D0%B8.zip.html

Павел
Павел
12/05/2012 19:56

Скорее всего, ваша ошибка заключается в том, что вы подключаете ImageList прямо к Ribbon. ImageIndex необходимо указывать непосредственно в Action у ActionList, а вашем ActionList у всех Action свойство ImageList стоит в -1, потому иконки и нету. Установите свойcтво ImageList, удалите кнопки с Ribbon и заново перетащите их из ActionList. Будет работать.

Alexander Nagumanov
Alexander Nagumanov
08/08/2012 17:35

Для меня работа через окно структуры не совсем удобна, поскольку, почему-то, она все-время «скачет» после выбора какого-нибудь компонента. Продуктивнее работать через списки, например:

1. Выбираем наш ActionManager.
2. В инспекторе объектов дважды щелкаем на его список ActionBars.
3. Выбираем нужную группу.
4. Далее её Items.

Так можно добраться до любого ActionClient’а, причем окошки списков всегда перед глазами :-).

Alexander Nagumanov
Alexander Nagumanov
08/08/2012 18:06

Совет для тех, кто создает ActionClientItems «автоматом» через ActionManager (как я :-)). Иногда необходимо управлять элементами именно через ActionManager, а не через конечные ActionClient’ы. Например создание динамического радио-списка для RibbonComboBox’а. В дизайнтайме помещаем в него все постоянные элементы — сепараторы и т.п. Далее, добавим одно действие (AnchorAction). Его можно сделать невидимым (если оно не нужно). Далее в runtime создаем действия: … var … Action:TAction; i:integer; begin … i:= 0; //Начало какого-нибудь цикла … Action:= TAction.Create(Self); Action.Category:= ‘Пример’; Action.Name:= ‘MyAction’ + IntToStr(i); //Может пригодиться при поиске через ActionManager.FindItemByAction(FindComponent(‘Имя компонента’) as TAction) Action.Caption:= ‘My caption ‘+ IntToStr(i); //Для примера Action.OnExecute:= MyActionExecute; //Назначаем… Подробнее »

Alexander Nagumanov
Alexander Nagumanov
08/08/2012 18:25

Костыль для выравнивания кнопок в группе. Надо было сделать в одной группе следующее:
——————

Бк | Бк Комбобокс
МкМкМк
——————
Бк — большая кнопка, Мк — маленькая кнопка, | — сепаратор.

Выстраиваться это все автоматом не могло.
Сделал выравнивание группы по горизонтали, затем добавил пару ActionClientItem в группу, со стилем csCustom. Caption составил из необходимого количества пробелов. Не забывайте, также, про свойства NewCol и NewRow.

Alexander Nagumanov
Alexander Nagumanov
08/08/2012 18:26

Даже здесь форматирование все покоробило :-) Маленькие кнопки должны быть под комбобоксом.

Леонид
Леонид
08/08/2013 17:42

День добрый!
Спасибо этой статье, с Ribbon вроде все понятно… Но не могу динамически создать главное меню! В ObjectInspector ему соответствует TActionClients(0) и нет имени, по которому можно обратиться к списку пунктов меню. TRibbonApplicationMenuBar содержит только дополнительные параметры.
Просьба подсказать!

mmu
mmu
29/04/2014 14:28

Добрый день, уважаемые читатели блога и, если повезет, автор :) Столкнулся с нерешаемой для меня задачей. Решил попробовать Ribbon interface из набора Delphi по опубликованным материалам. Пока работал на WinXP все было нормально. Но как перешел на Win 7 и 8.1, то столкнулся с проблемой: кнопка с атрибутом ButtonSize=bsLarge перестала разворачиваться по высоте во все отведенное для нее место в группе и, поэтому, перестала отображаться вторая строка заголовка кнопки. Особенно это раздражает когда в экранных настройках выставляется средний и крупный размер шрифта. И кроме того перестал прорисовываться заголовок программы. Вместо верхней цветной полосы с нужными буковками — черная полоса. При… Подробнее »

Павел
Павел
30/04/2014 00:43

Попробуйте откомпилировать программу в Delphi 2010, а затем — в более поздних, начиная с XE. Возможно, там проблема исчезнет.

mmu
mmu
10/05/2014 18:49

Доброго времени суток Павел. Большое спасибо за совет.
Старый проект зашел достаточно далеко и перевести его в 2010 не представляется возможным. Поэто му, создал пробный с одной кнопкой. Радует, что ущла черная полоса заголовка, но проблема с основной кнопкой, благополучно, осталась. Причем она видна еще в дизайне. Странно, неужели это только у меня и больше никто не видит???!!!
Перекомпилировать могу только в XE4. Промежуточных версий нет.

Павел
Павел
12/05/2014 09:29

Я таких проблем раньше не замечал… Пришлите, пожалуйста, тестовый проект, попробую у себя запустить. Также проверьте на виртуальной машине с Win7 + Sp1. Иногда, кстати, помогает передобавление Ribbon в проект — то есть полностью его удалить и заново натыкать кнопок. Проблемы со времён 2009-й версии не исправлены и бывает, IDE валится при работе с Ribbon, я для себя уяснил, что в этот момент проект лучше не сохранять, а перезапустить IDE и ошибка исчезнет. Именно по этой причине в новых проектах я создаю Ribbon не в дизайн-тайм, а в ран-тайм — при таком способе точно не бывает ошибок типа «снежный ком»… Подробнее »

mmu
mmu
12/05/2014 13:22
Ответить на  Павел

Каким образом я могу переслать вам проект? Да заодно и скрин шоты

Павел
Павел
13/05/2014 17:15
Ответить на  mmu

Nightmare terrible @ g mail.com — удалите пробелы