вторник, 26 февраля 2013 г.

Нюансы использования Ribbon от DevExpress

Версия 1.0.3 

Однако ребята из Редмонда не перестают удивлять нас своими наработками в рамках пользовательского интерфейса. То у них модными были тулбары и плоские кнопки, то тулбары в градиенте и разворачивающиеся меню, лента вот недавно появилась, а в последней версии офиса опять в моду входит концепция "плоских" элементов (я уж не говорю о METRO). Причем каждый раз с новым веянием Microsoft публикует большой манускрипт, описывающий почему именная текущая концепция самая правильная.

А что делать разработчикам стремящимся поддерживать GUI в актуальном состоянии в соответствии с "модой" (как делают большинство коммерческих разработчиков)? По понятным причинам полностью самостоятельно переписывать GUI слишком накладно, нам бы логику программы отладить, какой уж там GUI и прочие финтифлюшки... :)

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

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

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

В статье я буду рассматривать "DevExpress VCL ExpressBars components" версии 12.1.4 и все описанное будет относится именно к данной версии. В более ранних версиях данного пакета некоторые элементы могут отсутствовать или работать не так как описано.

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

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

Комментарий от DevExpress Team:
Тело комментария
Ну что ж, приступим...


1. Форма и dxRibbon


После установки данного пакета в меню New появится два новых пункта, это "DevExpress VCL v12.1 Ribbon 2007 Form" и "DevExpress VCL v12.1 Ribbon 2010 Form". Оба пункта создают новую форму с уже размещенным на ней компонентом dxRiboon в стиле MS Office 2007 и 2010 соответственно.



Данные пункты подойдут скорее только в случае создания проекта с нуля. Если же уже имеется на руках рабочий проект, который требуется перевести на стиль Ribbon, то лучше все сделать самостоятельно. За одно будет более понятно как правильно настроить сам dxRibbon.

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

Итак, создайте новый проект и на главной форме проекта разместите компонент dxRibbon с вкладки ExpressBars. Должно получится вот так:


Это только заготовка будущей ленты. Чтобы заставить ленту заработать, к ней необходимо подключить основной компонент, через который будет происходить работа со всеми элементами DevExpress VCL ExpressBar, называется он dxBarManager и расположен на той же вкладке ExpressBars. Подключается через свойство BarManager компонента dxRibbon.

После подключения лента примет следующий вид:


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

1. В uses главной формы необходимо добавить юнит dxRibbonForm, а саму главную форму приложения унаследовать не от TForm, а от TdxRibbonForm, примерно вот таким образом:

unit Unit1;

interface

uses
  Windows, ..., dxRibbonForm;

type
  TForm1 = class(TdxRibbonForm)
    dxRibbon1Tab1: TdxRibbonTab;
    dxRibbon1: TdxRibbon;
    dxBarManager1: TdxBarManager;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

end.

2. В свойствах dxRibbon параметр SupportNonClientDrawing установить в True.

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



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


... который отвечает как раз за закрытие приложения.

2. Группы, кнопки и меню


Прежде чем разбираться с остальными параметрами dxRibbon необходимо научится работать с основными элементами управления, размещаемыми на ленте.

Все элементы управления, используемые в DevExpress VCL ExpressBar разделяются на три подгруппы:
1. Основные элементы управления: создаются и управляются посредством dxBarManager.
2. Контейнеры для элементов управления: создаются отдельно на форме.
3. Различные компоненты утилитарного назначения (для наведения красоты): наследники от TComponent, размещаются так же на форме.

Элементами управления являются наследники от TdxBarItem.
Их много разных: dxButton, dxBarEdit. dxBarCombo и т.д.
Контейнерами для них являются наследники от TdxBarComponent и TcxControl.
Их так же предостаточно: это и dxRibbon, и dxPopupMenu и прочее элементы.

Комментарий от DevExpress Team:
Кнопка в барах - это TdxBarButton.
TdxBarEdit, TdxBarCombo и иже с ними - это устаревшие версии баровских контролов.
Желательно использовать аналоги на базе TcxBarEditItem с соответствующим значением Properties: TcxComboBoxProperties, TcxTextEditProperties, etc.
Основное и главное отличие элементов управления DevExpress VCL ExpressBar от стандартных элементов управления (тех же кнопок TButton) заключается в том, что они являются виртуальными элементами. Т.е. они присутствуют только в рамках dxBarManager-а и нигде более, а за их визуализацию (отрисовку, реакцию на пользовательский ввод) отвечает класс TdxBarItemLink. По этой причине данные элементы управления нельзя разместить произвольно на форме, только в рамках контейнера. Но именно в их виртуализации и заключается большое удобство работы с данными элементами управления.

Допустим раньше, если у нас была кнопка на тулбаре, которая совершает некое действие (выводит MessageBox, к примеру), то для того чтобы вызвать это же действие из меню необходимо было создавать новый пункт меню, где уже обработчиком цеплять вызов от первой кнопки. В случае с DevExpress VCL ExpressBar создание дополнительных элементов управления с общим обработчиком не потребуется. Из виртуального контрола мы спокойно можем управлять всеми линками на него, отвечающими за визуализацию, и управлять их состоянием (к примеру нажата кнопка или нет, указывать ее Enable/Visible состояния и прочее). Если немного огрубить, данная виртуализация чем-то похожа на механизм работы TAction, только реализованная немного другим способом.

Впрочем рассмотрим это на практике.

Откройте BarManager и на вкладке Commands создайте элемент TdxBarLargeButton.


Далее откройте вкладку событий данного элемента и назначьте новый обработчик OnClick.


В обработчике пропишите следующий код:

procedure TForm1.dxBarLargeButton1Click(Sender: TObject);
begin
  ShowMessage('Мой первый обработчик клика на кнопке TdxBarLargeButton');
end;

Осталось разместить данный элемент в контейнере, но для начала его нужно создать, щелкните на закладке ленты с именем dxRibbonTab1 правой кнопкой и выберите пункт меню "Add Group With ToolBar"


При этом в ленте создастся пустая группа (о работе с группами чуть позже) а так же тулбар, на котором можно размещать дополнительные элементы управления.
Данный тулбар так же появится в BarManager-е на вкладке "Toolbars".

Для того чтобы разместить нашу кнопку в созданном тулбаре необходимо открыть BarManager и перетащить кнопку на нужное место в dxRibbon.

Запустите приложение и проверьте работоспособность кнопки, должно получится вот так:


А теперь посмотрим что дает нам виртуализация. Откройте опять BarManager и снова перетащите кнопку на ленту так, чтобы она разместилась рядом с первой примерно таким образом:


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

А теперь важный нюанс!
Для удаления элементов управления с ленты не используйте кнопку Delete!!!
Воспользуйтесь любым из следующих двух способов:
1. Просто перетащите требуемый визуальный элемент за пределы контейнера.
2. Щелкните на визуальный элемент правой кнопкой и в меню выберите пункт "Delete Link"



В случае если вы все же воспользуетесь кнопкой "Delete" исчезнут все 4 кнопки (можете поэкспериментировать). Произойдет это по той причине что уничтожились все ссылки TdxBarItemLink, являющиеся "визуальной проекцией" на виртуальный элемент TdxBarLargeButton. Причем разрушены будут ссылки во всех контейнерах, а не только в текущем.

Комментарий от DevExpress Team:
Есть небольшой нюанс. В том случае если открыта Customize форма, будет удален сам элемент TdxBarLargeButton, в результате чего все линки на данный Item также будут удалены. Если Customize форма не отображена, будут удалены сами линки.

2.1 Меню


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

В качестве второго контейнера выступит всплывающее меню TdxRibbonPopupMenu из вкладки ExpressBars. После размещения данного элемента на форме откроется диалог его конфигурации. Выглядит следующим образом:


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

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

procedure TForm1.PopupMenuGatePopup(Sender: TObject);
begin
  dxRibbonPopupMenu1.PopupFromCursorPos;
end;

Ну а само меню, выступающее в качестве шлюза назначается требуемому контролу (в данном случае форме).

Для подключения меню к обычным элементам управления (не форме), используется свойство PopupMenuLinks у BarManager.

3. Стили dxRibbon, цветовая гамма, панель быстрого доступа 


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

Скачайте архив примеров и откройте самый первый, скомпилируйте и запустите приложение.



Начнем сверху вниз.

В самом верху появилась панель быстрого доступа (QuickAccessToolbar).


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

Создается данная панель достаточно тривиально, для этого необходимо открыть BarManager и на закладке Toolbars создать новую панель с произвольным описанием (допустим так и назовем: "Панель быстрого доступа"). Название данной панели будет примерно следующее: dxBarManager1Bar2
После этого на форме появится сама панель снизу ленты:


Сейчас панель не слинкована ни на один из контейнеров, поэтому она размещена непосредственно на главной форме. Для подключения ее в виде панели быстрого доступа необходимо зайти в настройки dxRibbon и у параметра QuickAccessToolbar в качестве Tooolbar указать название только что созданной панели (dxBarManager1Bar2).
После этого панель пропадет с формы, но появится сверху над лентой:


Добавлять в нее элементы можно так же как и при работе с обычной панелью - перетаскиванием из BarManager.
У данной панели есть всего два свойства.

  1. Position - определяющее расположение панели над лентой или под ней.
  2. Visible - отвечающее за видимость данной панели.

Так-же данная панель имеет меню кастомизации, за полноту которого отвечают флаги, хранящиеся в свойстве PopupMenuItems компонента dxRibbon.

  • Флаг rpmiItems отвечает за отображение текущих элементов тулбара в меню, где можно управлять их видимостью.
  • Флаг rpmiMoreCommands отвечает за отображение пункта "More Commands...", при помощи которого можно изменять состав элементов всех тулбаров в рантайме.
  • Флаг rpmiQATPosition отвечает за отображение пункта меню "Show Quick Access Toolbar Below the Ribbon", при помощи которого в рантайм можно управлять свойством Position у QuickAccessToolbar.
  • Флаг rpmiQATAddRemoveItem отвечает за отображение пункта меню "Remove from Quick Access Toolbar" отображаемого при клике правой кнопкой непосредственно на элементе из данного тулбара.
  • Флаг rpmiMinimizeRibbon отвечает за отображение пункта меню "Minimize the Ribbon", позволяющему сворачивать ленту в рантайм.
Если все флаги отключены, глиф справа от тулбара отображаться не будет.

Кстати по поводу rpmiMinimizeRibbon. Манипуляции с данным флагом не повлияют на саму возможность минимизации ленты, данная возможность всегда будет доступна в рантайме и управляется двойным кликом на любой вкладке ленты + в стиле MS Office 2010 будет доступна дополнительная кнопка справа ленты, помогающая свернуть/развернуть ленту в один клик.
Впрочем отключить такое поведение можно посредством параметра MinimizeOnTabDblClick у dxRibbon.

3.1 Стиль ленты


Сама лента  может быть представлена в виде двух различных визуализаций, в стиле MS Office 2007 и MS Office 2010. Отвечает за переключение между данными стилями параметр Style компонента dxRibbon, принимающий значения rs2007 и rs2010 соответственно.

Так же разработчику доступна возможность изменения цветовой схемы ленты через параметр dxRibbon.ColorSchemeName.

Самих цветовых схем достаточно много. Хоть по умолчанию доступны всего три - синяя, черная и серебряная, но можно подключить и расширенные через меню "Modify skin options" в настройках проекта.


Комментарий от DevExpress Team:
Дополнительные схемы - это часть ExpressSkins Library, которая является отдельным продуктом. Прежде чем начать работать с ними, нужно убедиться, что этот продукт установлен.
Со скинами связана одна небольшая неприятность. Каждый новый скин достаточно сильно утяжеляет проект и если размер пустого проекта с лентой находится в районе двух мегабайт, то при всех включенных скинах он вырастает аж до 17 мегабайт, что уже достаточно увесисто :)
Вторая неприятность (и о ней даже специально написано в подсказке диалога настройки скинов) заключается в том, что при отключении скинов они не удаляются автоматом из секции uses, поэтому если вы отключили ненужные по вашему мнению скины - не забудьте почистить и секцию uses, сделав поиск по фразе dxSkin.

Комментарий от DevExpress Team:
Можно избежать утяжеления путем динамической загрузки скинов.
В составе инсталятора идут бинарные файлы скинов (...\DevExpress.VCL\ExpressSkins Library\Binary Skin Files\). Вы можете просто загрузить скины из этих бинарных файлов, используя технику, показанную вот в этой статье: How to load skins dynamically
В случае стиля MS Office 2010 для стандартных цветовых схем так же доступна возможность изменять цвет кнопки, отображающую BackStage панель (о ней чуть позже).


Отвечает за это свойство dxRibbon.ColorSchemeAccent. Доступно правда всего пять цветов, но хоть и мелочь - а приятно :)

3.2 TdxBarSubItem, TdxBarListItem


Для изменения настроек ленты в рантайм я выбрал два элемента управления.
Это TdxBarSubitem, позволяющий отображать во всплывающем списке дополнительные элементы управления (грубо говоря он предоставляет контейнер в виде выпадающего меню). 


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

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

procedure TForm1.dxBarButton1Click(Sender: TObject);
begin
  // включаем стиль MS Office 2007
  dxRibbon.Style := rs2007;
  // отключаем кнопку отвечающую за переключения цвета BackStage Button
  btnColorSchemeAccent.Visible := ivNever;
end;

procedure TForm1.dxBarButton2Click(Sender: TObject);
begin
  // включаем стиль MS Office 2010
  dxRibbon.Style := rs2010;
  // включаем кнопку отвечающую за переключения цвета BackStage Button
  btnColorSchemeAccent.Visible := ivAlways;
end;

Пока не обращайте внимание на код отключения BackStage Button - до него еще успеем дойти.

Для того чтобы данные две кнопки автоматически переключались и разработчику не нужно было ручками выставлять их состояние, свойство ButtonStyle обоих кнопок было установлено в bsChecked, а GroupIndex установлен в значение 10 (в принципе можно использовать любое другое, я обычно использую кратное десятке).
Таким образом они стали работать в режиме RadioButton-ов.

А для переключения  цвета ленты я использовал еще более простой в использовании элемент TdxBarListItem.


Визуально он практически ничем не отличается от используемого ранее TdxBarSubitem, за исключением пары моментов.
  1. Он также отображает всплывающее меню, но оно не является контейнером для сторонних элементов управления и настраивается через свойство Items.
  2. Так как меню не кастомизируемо, его элементам нельзя назначить иконку.
  3. Для переключение элементов меню в режим RadioButton используется свойство ShowCheck
Обработчик данного элемента выглядит следующим образом:

procedure TForm1.btnColorSchemeNameClick(Sender: TObject);
begin
  // Устанавливаем цветовую схему ленты
  case btnColorSchemeName.ItemIndex of
    0: dxRibbon.ColorSchemeName := 'Blue';
    1: dxRibbon.ColorSchemeName := 'Black';
    2: dxRibbon.ColorSchemeName := 'Silver';
  end;
end;

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

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

3.3. TcxImageList


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

Самым важным его свойством является то, что он позволяет работать с 32-битными иконками с альфа-каналом. Раньше приходилось возится с ImageList ручками переводя его в режим ILC_COLOR32 и руками добавлять в него иконки из ресурсов, дабы не было потери альфаканала при конвертации. С появлением TcxImageList данная проблема отпала.

Второй интересной возможностью является параметр CompressData, позволяющий уменьшить размер информации об изображениях, размещаемой в DFM. В одном из моих проектов один из ImageList-ов содержит более 500 иконок (проект чем-то похож на MS Excel и такое количество изображений обусловлено значительным количеством функциональных элементов). Будь они помещены в обычный TImageList их объем занял-бы в районе 1.2 мегабайта. В случае использования сжатия размер уменьшается примерно до 700кб (мелочь, а приятно).

Обычно используются два листа, подключаемые через свойство ImageOptions элемента BarManager. Первый ImageList содержит иконки размером 16х16 и подключается через параметр Images, второй 32х32 через параметр LargeImages. Есть еще несколько листов, таких как DisabledImages, DisabledLargeImages и HotImages, но я их обычно не использую ибо нет такой необходимости.

После подключения листов к BarManager у каждого элемента можно назначить требуемую ему иконку, используя свойства ImageIndex/LargeImageIndex и т.п.

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

Для примера посмотрите вот на эту форму:


Обратите внимание на кнопку "Цвет стиля", она сейчас отображает большую иконку, но вот маленькая ей не назначена и при уменьшении ширины формы произойдет следующее:


Иконка у данной кнопки пропадет.
Конечно не критично, но как-то неаккуратненько :)

Ну и кстати обратите внимание на зеленый шарик в панели быстрого доступа. Таким шариком отображаются элементы, у которых не назначен ImageIndex, а в данном случае этим элементом как раз и является кнопка "Цвет стиля".

Внимание ошибка!
Ну а теперь к основной и главной проблеме использования двух ImageList.
Дело в том что увы, но иногда значения параметров LargeImageIndex слетают, ну точнее не то чтобы слетают, но при загрузке DFM на позициях больших иконок отображаются совершенно не те изображения, которые были указаны изначально.
В более ранних версиях данных контролов это было огромной головной болью, сейчас же стало немного полегче, особенно если вы используете параметр SyncImageIndex выставленный у каждого элемента в True, но все равно периодически бывают сбои.
Спасает от данной ошибки SVN, используя который можно в любой момент откатится на правильный DFM формы, впрочем иногда достаточно обычного переоткрытия проекта не сохраняя при этом никаких изменений в диалогах закрытия, которые вывалит Delphi.
Но самый правильный способ - это использование полностью синхронизированных картинок в обоих ImageList.
Конечно количество маленьких иконок 16х16 всегда будет больше чем 32х32, поэтому я обычно пользуюсь таким решением:
При разработке GUI приложения я стараюсь заложиться на максимальное количество элементов управления, которые будут представлены в виде больших икон, размещаю их изображения в обоих списках самыми первыми и синхронизированными (т.е. добиваюсь того чтобы свойства ImageIndex и LargeImageIndex имели одинаковые значения) после чего делаю буффер из 30 иконок в маленьком ImageList про запас, которые забиваю пустой иконкой. Если потребуется добавить еще какой нибудь элемент в большой ImageList, в маленьком я его буду добавлять не в конец, а на соответствующую позицию зарезервированного буфера.
Обычно помогает. Размеры буфера, если вы будете использовать мой подход думайте сами, если планируется кардинальное изменение GUI в перспективе, лучше сделать его побольше. В противном случае придется перелопачивать все индексы иконок по всему проекту, ну либо иметь проблемы при рассинхронизации. Особенно больно бывает когда эти проблемы вылезают при релизной компиляции на билдсервере.

Важное уточнение:
Такое поведение проявляется только в случаях если TcxImageList расположен не на главной форме (в моем случае он размещен на отдельном TDataModule). Вот что по этому поводу сообщают разработчики...

Комментарий от DevExpress Team:
Скорее всего, это известная проблема с TPersistent с референсами на компоненты, находящихся в DataModule. У нас достаточно много тикетов по этой теме. Например, посмотрите на объяснения в следующих тикетах:
1. FakeComponentLink
2. cxGridDBTableView (and cxTreeList) lose properties referencing objects on another form.
Во втором тикете предложен workaround пользователем Mike F, который может помочь в решении данной проблемы.

3.4. параметры Application/BackStage button и QuickAccessBar


Перейдем к следующим трем кнопкам демопримера.


Они расположены на отдельном тулбаре, который я создал через меню "Add Group With ToolBar". Самая первая кнопка является обычной TdxBarLargeButton у которой выставлен стиль bsChecked, позволяющий ей находиться в двух режимах - вжатом и отжатом. За управление данным режимом отвечает свойство кнопки Down.
Обработчик кнопки выглядит следующим образом:

procedure TForm1.btnShowAppButtonClick(Sender: TObject);
begin
  dxRibbon.ApplicationButton.Visible := btnShowAppButton.Down;
end;

Грубо говоря в зависимости от состояния данной кнопки происходит переключение видимости ApplicationButton приложения.

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


Слева вариант для стиля MS Office 2007, справа для 2010-го.
Во втором случае у данной кнопки появляется возможность изменения цвета (как я и говорил выше в главе 3.1). Для изменения цвета данной кнопки я опять использую TdxBarListItem, содержащий список цветов в порядке, объявленном в типе TdxRibbonColorSchemeAccent и обработчик кнопки изменения цвета выглядит следующим образом:

procedure TForm1.btnColorSchemeAccentClick(Sender: TObject);
begin
  dxRibbon.ColorSchemeAccent :=
    TdxRibbonColorSchemeAccent(btnColorSchemeAccent.ItemIndex);
end;

Единственно, дабы кнопка изменения цвета была доступна только в случае включенного стиля rs2010 приходится управлять ее видимостью через параметр Visible, который не является булевым типом (как обычно) а представляет из себя перечисление TdxBarItemVisible и принимает следующие значения:
  • ivNever - элемент управления не видим
  • ivInCustomizing - элемент управления виден только в режиме настройки ленты
  • ivAlways - элемент управления виден всегда
Третья кнопка "Отображать QuickAccessBar" отвечает за управление параметрами Position и Visible у компонента QuickAccessToolbar.
Представляет она из себя составной элемент из TdxBarLargeButton со стилем bsCheckedDropDown, всплывающего меню, закрепленного через параметр DropDownMenu данной кнопки и двух кнопок TdxBarButton


Переключение кнопки в режим bsCheckedDropDown позволяет выполнить сразу несколько действий. Во первых верхняя часть кнопки (там где иконка с изображением "глаза") работает в режиме bsChecked, описаном чуть выше. Это позволяет выполнить следующий обработчик:

procedure TForm1.btnShowQABClick(Sender: TObject);
begin
  dxRibbon.QuickAccessToolbar.Visible := btnShowQAB.Down;
end;

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

С оставшимися двумя кнопками, размещенными в выпадающем меню не связано никаких нюансов, за исключением одного момента. Если обратите внимания на их свойства, то увидите что у них не выставлены свойства ImageIndex и LargeImageIndex, однако изображения у обоих кнопок присутствуют. Эти изображения (представляющее собой стрелки вверх и вниз) добавлены посредством параметра Glyph, который принимает изображения только в виде BMP файлов.

Последнее на чем остановлюсь в данном демопримере, это то, что самая правая кнопка отделена внутри своего тулбара от других вертикальным разделителем. Делается такой эффект достаточно просто, в DesignTime выделите требуемый элемент управления и перетащите его немного правее на пару пикселей, после чего слева от него автоматически появится разделитель группы, либо установите признак начала группы через меню элемента, включив флаг "Begin a Group":


4. TdxBarApplicationMenu


Пришло время разобраться с настройкой Application Button в стиле MS Office 2007.
Откройте второй демопример и посмотрите его исходный код. Как видите там объявлен всего один обработчик, все остальное полностью настроено в DesignTime и выглядит при запуске следующим образом:


Реализовано такое выпадающее меню при помощи компонента TdxBarApplicationMenu.
Подключается данный компонент в dxRibbon через свойство ApplicationButton.Menu.

Визуально оно разделено на три части.

1. Меню слева, в котором представлены кнопки "Новый документ", "Открыть" и т.д.
Настраивается как обычное меню перетаскиванием необходимых элементов из BarManager:


Если не нравится что кнопки большие, то их можно уменьшить, указав в параметре TdxBarApplicationMenu.ItemOptions.Size значение misNormal.

2. Список открытых ранее документов. Настраивается через параметр ExtraPane, в котором параметр Header содержит текст, отображаемый в заголовке, AllowPin разрешает отрисовку маркеров закрепления справа от каждого элемента и самый главный параметр Items, который и содержит в себе список отображаемых элементов. Каждый элемент данного списка имеет следующие параметры:
  • DisplayText - непосредственно отображаемый текст
  • ImageIndex - иконка документа
  • Pin - состояние маркера закрепления
  • Text - неотображаемый текст элемента
Первый и последний параметр можно комбинировать следующим образом, к примеру параметр Text содержит полный путь к файлу, а параметр DisplayText только наименование файла.

Остальные параметры не столь существенны.

Так же ExtraPane содержит свойство WidthRatio по умолчанию выставленное в 2.5
Данное свойство отвечает за процентное увеличение ширины меню от его оригинального размера. В принципе просто как украшательство. Выставьте данному параметру значение 1.5 - этого будет за глаза.

3. Остались дополнительные кнопки снизу. В данном случае это кнопки "Настройки" и "Выход". Данные элементы являются двумя стандартными кнопками TdxBarButton подключенными через свойство TdxBarApplicationMenu.Buttons. Данное свойство так же является списком в котором у каждого элемента есть всего два параметра, это Item, в котором необходимо указать какой именно элемент управления будет отображен из присутствующих в BarManager-е и свойство Width, отвечающее за желаемую ширину данного элемента (если установлен ноль - используется ширина элемента по умолчанию).


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

procedure TForm2.dxBarApplicationMenu1ExtraPaneItemClick(Sender: TObject;
  AIndex: Integer);
begin
  ShowMessage('Требуется открыть файл: ' +
    dxBarApplicationMenu1.ExtraPane.Items[AIndex].Text);
end;

5. TdxRibbonBackstageView


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

После этого откройте сам пример. Как вы сможете увидеть, компоненты TdxBarApplicationMenu нет, вместо нее на форме расположен компонент TdxRibbonBackstageView, в котором и происходит вся настройка.

Подключается данный контрол так же в dxRibbon через свойство ApplicationButton.Menu.
Он представляет из себя некий аналог TPageControl, т.е. для работы с ним нужно создать табы, размещаемые слева панели, а так-же туда могут помещаться дополнительные элементы управления из BarManager.

Настройка его в принципе вообще минимальная. Он предоставляет всего одно настраиваемое свойство Buttons. Работает данное свойство по аналогии с TdxBarApplicationMenu.Buttons, т.е. это список в который подключаются элементы управления из BarManager, у каждого из которых есть всего одно дополнительное свойство Position, управляющее расположением элемента, над или под табами BackStage.


Сами же табы являются скажем так неким аналогом панелей из TPageControl, т.е. все что мы можем, это создать необходимое количество табов и присвоить им название. Это практически все доступные настройки, влияющие на отображение. Добавляются табы через меню "Add Tab", удаляются там же.


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


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

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

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

1. Умеет работать в качестве пина и многострочной кнопки с иконкой слева:


2. Умеет работать как обычная кнопка с иконкой сверху и кнопка с отрисовкой угла для эмуляции подзакладок (автопереключение не реализовано, необходимо делать руками):


3. Умеет работать как DropDown кнопка (это только эмуляция, отображение меню необходимо делать самостоятельно)


4. Так же умеет работать в режиме Checked, поддерживает 3 ImageList и автоматом изменяет иконку при изменении размеров и прочее.

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

На первых порах этого в принципе достаточно.

Да и, забыл нюанс, когда будете смотреть пример с закладкой принтеров, обратите внимание на компонент mnuPrinters. Он содержит в себе три TdxBarLargeButton, но отображаются они слегка не так как я показывал раньше. Это результат изменения настроек самого меню в параметре ItemOptions, где я включил флаг ShowDescriptions и Size указал misLarge.

6. TdxRibbonGalleryItem.


Галерея - один из самых интересных элементов управления DevExpress VCL ExpressBars Components.


Правда имеет один минус - ее нельзя полностью настроить в DesignTime, поэтому работа с ней всегда требует написания кода.

Update: к сожалению здесь сказалась моя не внимательность. Невозможность настройки  можно было наблюдать в ранних версиях пакетов. В текущей версии 12.1.4 полная настройка из DesignTime присутствует.

Логически галерея разделена на несколько элементов.
1. Группы - любой элемент в галерею добавляется только в группу. Если группа всего одна, ее заголовок можно не отображать.
2. Элементы групп - непосредственное наполнение галереи.
3. Фильтры - расположены в самом верху галереи. При помощи них можно сделать удобную фильтрацию, создав набор фильтров и к каждому из них подключить одну или несколько групп, за видимость которой он будет отвечать.
4. Дополнительные элементы - располагаются внизу (как на скриншоте) и являются стандартными элементами управления перетаскиваемыми из BarManager.

Откройте четвертый демопример и запустите его.
Вы увидите две абсолютно одинаковых галереи, одна из которых отображается в свернутом режиме (левая), вторая в развернутом.
Так как часть галереи можно настроить из DesignTime, а часть все равно придется прописывать в RunTime, я не стал данные галереи вообще настраивать в DesignTime, просто создал в BarManager-е и поместил на ленту, а код инициализации вынес в конструктор формы.
Выглядит он следующим образом:

procedure TForm1.InitGallery(Value: TdxRibbonGalleryItem; Collapsed: Boolean);
var
  Group: TdxRibbonGalleryGroup;
  GroupItem: TdxRibbonGalleryGroupItem;
  FilterCategory: TdxRibbonGalleryFilterCategory;
begin
  // Устанавливаем иконку по умолчанию
  Value.ImageIndex := 0;

  // Указываем как будет выглядеть галерея, в виде кнопки или в развернутом виде
  Value.GalleryInRibbonOptions.Collapsed := Collapsed;

  // Указываем что отображать элементы нужно с использованием заголовка и описания
  Value.GalleryInMenuOptions.ItemTextKind := itkCaptionAndDescription;

  // Указываем количество колонок в которых будут размещатся элементы
  Value.GalleryOptions.ColumnCount := 2;

  // Инициализируем фильтры
  Value.GalleryFilter.Caption := 'Список фильтров';
  Value.GalleryFilter.Visible := True;

  // Добавляем первую группу
  Group := Value.GalleryCategories.Add;
  Group.Header.Caption := 'Страны';
  Group.Header.Visible := True;

  // Добавдяем ее в фильтры
  FilterCategory := Value.GalleryFilter.Categories.Add;
  FilterCategory.Caption := Group.Header.Caption;
  FilterCategory.Groups.Add(Group); // добавлять можно неколько груп для каждого фильтра

  // Добавляем элементы группы
  GroupItem := Group.Items.Add;
  GroupItem.Caption := 'Россия';
  GroupItem.Description :=
    'Россия - официально Российская Федерация или Россия,' +
    ' на практике используется также сокращение РФ';
  GroupItem.ImageIndex := 1;

  GroupItem := Group.Items.Add;
  GroupItem.Caption := 'США';
  GroupItem.Description := 'Соединённые Штаты Америки были' +
    ' образованы в 1776 году при объединении тринадцати ' +
    'британских колоний, объявивших о своей независимости.';
  GroupItem.ImageIndex := 2;

  GroupItem := Group.Items.Add;
  GroupItem.Caption := 'Норвегия';
  GroupItem.Description := 'Королевство Норвегия, Норвегия — ' +
    'государство в Северной Европе, располагающееся в западной' +
    ' части Скандинавского полуострова';
  GroupItem.ImageIndex := 3;

  // Добавляем вторую группу
  Group := Value.GalleryCategories.Add;
  Group.Header.Caption := 'Разное';
  Group.Header.Visible := True;

  // Добавдяем ее в фильтры
  FilterCategory := Value.GalleryFilter.Categories.Add;
  FilterCategory.Caption := Group.Header.Caption;
  FilterCategory.Groups.Add(Group);

  // Добавляем элементы группы
  GroupItem := Group.Items.Add;
  GroupItem.Caption := 'Почтовый конверт';
  GroupItem.Description := 'Конверт — оболочка для вкладывания, ' +
    'хранения и пересылки бумаг или плоских предметов';
  GroupItem.ImageIndex := 4;

  GroupItem := Group.Items.Add;
  GroupItem.Caption := 'Патрон';
  GroupItem.Description := 'Патрон (унитарный патрон) — боеприпас ' +
    'стрелкового оружия и малокалиберных (до 76 мм) пушек, который ' +
    'заряжается в один (лат. unitas — «единство») приём';
  GroupItem.ImageIndex := 5;

  GroupItem := Group.Items.Add;
  GroupItem.Caption := 'Калькулятор';
  GroupItem.Description := 'Калькулятор (лат. calculator «счётчик») — ' +
    'электронное вычислительное устройство для выполнения операций ' +
    'над числами или алгебраическими формулами';
  GroupItem.ImageIndex := 6;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  InitGallery(dxRibbonGalleryItem1, True);
  InitGallery(dxRibbonGalleryItem2, False);
end;

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

Обработчик галерей так же достаточно тривиальный:

procedure TForm1.GalleryGroupItemClick(Sender: TdxRibbonGalleryItem;
  AItem: TdxRibbonGalleryGroupItem);
begin
  // в качестве демо назначаем иконку выбранного элемента галерее
  Sender.ImageIndex := AItem.ImageIndex;
  // показываем что выбрали
  ShowMessage(AItem.Caption

7. TcxBarEditItem


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

Обычно происходит недопонимание с тем, где именно менять значения данного контрола? Допустим когда он находится в режиме чекбокса, у него отсутствует значение Checked или хотя бы Down. Когда он отображает картинку, нет свойства Picture или Bitmap.

На самом деле все достаточно просто, все значения задаются через одно единственное свойство - EditValue имеющее тип Variant.

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



Изначально TcxBarEditItem находится в режиме DateEdit.
Обработчик первой кнопки переключает его в режим CheckBox-а

procedure TForm1.Button1Click(Sender: TObject);
// uses cxCheckBox
begin
  // Устанавливаем заголовок
  cxBarEditItem1.Caption := 'CheckBoxItem';
  // переключаем режим на CheckBox
  cxBarEditItem1.PropertiesClass := TcxCheckBoxProperties;
  // Указываем контролу что в случае неопределенного значения оставаться в отключенном состоянии
  (cxBarEditItem1.Properties as TcxCheckBoxProperties).NullStyle := nssUnchecked;
  // Устанавливаем изначальное значние
  cxBarEditItem1.EditValue := True;

  // далее работаем непосредственно с линком
  // так как линк у нас единственный, то сразу напрямую обращаемся к его свойствам

  // Устанавливаем ширину (в данном случае ширину пространства на которой будет расположен сам чек)
  cxBarEditItem1.Links[0].UserWidth := 0;

  // указываем что Caption контрола должен быть всегда справа
  cxBarEditItem1.Links[0].ViewLayout := ivlGlyphControlCaption;
end;

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

Кстати вот картинка что будет если не производить дополнительную конфигурацию через линк.


Так получилось из-за того, что ранее данный линк отображал DateEdit и все настройки остались от него, вместо кучи пустого места где отображается чек - был EDIT, от него осталась ширина, ну а то что чек расположен справа - это тоже наследство от предыдущего состояния.

Комментарий от DevExpress Team:
На самом деле, это поведение некорректно. Спасибо, что указали на проблему. На данный момент ведется работа по ее исправлению.
Если хотите поиграться с настройками линка в режиме DesignTime щелкните правой кнопкой на требуемом элементе и выберите пункт "Select Link", после чего откроются параметры уже самого линка (по умолчанию отображаются параметры виртуального элемента).



А вот так выглядит переключение в режим Image и загрузка изображения:

procedure TForm1.Button2Click(Sender: TObject);
// uses cxImage
var
  M: TMemoryStream;
  S: AnsiString;
begin
  cxBarEditItem1.Caption := 'ImageItem';
  cxBarEditItem1.PropertiesClass := TcxImageProperties;
  (cxBarEditItem1.Properties as TcxImageProperties).GraphicClass := TBitmap;
  M := TMemoryStream.Create;
  try
    M.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'tmp.bmp');
    SetLength(S, M.Size);
    M.ReadBuffer(S[1], M.Size);
    cxBarEditItem1.EditValue := S;
  finally
    M.Free;
  end;
  cxBarEditItem1.Links[0].UserWidth := 100;
  cxBarEditItem1.Links[0].ViewLayout := ivlGlyphCaptionControl;
end;

Особенно обратите внимание на загрузку картинки в EditValue - очень не привычно :)

8. TdxBarItemLink


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

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


Если у линка элемента 4 выставить параметр BeginGroup то элементы с четвертого по шестой будут отделены от первых трех:


Допустим мы хотим изменить расположение элементов, чтобы с первого по третий шли в линию друг за другом, а ниже них оставшиеся элементы 4-6, для этого свойство Position линков на элементы 2, 3, 5 и 6 должно быть установлено в ipContinuesRow, а у элементов 1 и 4 должно остаться в изначальном ipBeginsNewRow.


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


Здесь изображены два TcxBarEditItem в режиме DateEdit, только у нижнего ViewLayout выставлен в ivlGlyphControlCaption, в результате чего получилась такая вот неприятность :)

9. Работа с TdxRibbonBarPainter


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

Работа с пайнером достаточно проста и выглядит примерно так:

var
  Painter: TdxRibbonBarPainter;
begin
  Painter := TdxRibbonBarPainter.Create(TdxNativeUInt(dxRibbon1));
  try
    Painter.Skin.DrawBackground(Canvas.Handle, Rect, Part, State);
  finally
    Painter.Free;
  end;

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

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

Константы элементов и их состояний объявлены в модуле dxBarSkinConsts.

Для облегчения поиска подходящего бэкграунда я написал небольшой примерчик (шестой демопример).


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


10. Ошибки


К сожалению ошибки всегда присутствуют в коде, как бы разработчик ни старался. Правда здесь можно выразить большой респект разработчикам DevExpress - ошибки они правят очень оперативно и за последние шесть лет пользования их продуктом на моей памяти поменялось в этом плане очень много. Думаю я не ошибусь если поставлю их в один ряд по оперативности правки глюков с разработчиками Wine (линуксовый эмулятор Win) и ребят из компании EurekaLog, с которыми я постоянно работаю на уровне фидбэков, работающих на принципе - нет выпуску новой версии без правки известных ошибок (практически не утрирую :).

Впрочем хватит петь дифирамбы, вернемся обратно к ошибкам:
Об одной ошибке, с которой вы можете столкнуться, я уже рассказал чуть выше (рассинхронизация ImageIndex) а вот и вторая.

В наличии есть некорректная работа с TAction, а именно с состоянием Checked (мы говорим только о версии 12.1.4 и более ранних).
Воспроизводится данная ошибка достаточно банально, откройте пример из папки Bug и посмотрите на исходный код.

Смысл данного примера заключается в следующем:
Созданы три TAction у каждого из которых параметр "Tag" имеет некое значение (для простоты 1, 2 и 3) и имеется параметр "Foo", который содержит в себе номер элемента, который должен быть в состоянии Checked.

Логика работы всех трех TAction простая, в Execute берем значение параметра "Tag" у активного акшена и назначаем его параметру "Foo", а в Update возвращаем состояние Checked, ориентируясь на значение параметра "Foo" и "Tag" акшена.

Грубо говоря этим мы эмулируем поведение данных трех элементов в режиме RadioButton.

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

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

Хоть параметр "Foo" сейчас принял значение 1 и "Tag" акшена, на который слинкован первый пункт меню также равен единице и, даже более того, метод Action1Update тоже был вызван выставив правильное состояние Checked, первый пункт меню сейчас не выделен.

А вот это уже ошибка.

Ну либо второй вариант воспроизведения: запустите приложение и в меню выберите второй или третий пункт, после чего щелкните на кнопку внизу "Выбрать первый пункт" и снова вызовите меню. Хоть мы и попытались через TAction реализовать логику TRadioButton, этого у нас не получилось и вы увидите два (!!!) выделенных пункта меню.

Почему это происходит и как это поправить:

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

Поправить данную ошибку достаточно просто:

procedure TdxBarButtonControl.ControlUnclick(ByMouse: Boolean);
begin
  if not ByMouse or (HotPartIndex = bcpButton) then
  begin
    if (ButtonItem <> nil) and (bstChecked in ButtonItem.FInternalStates) then
      // Исправление ошибки работы с TAction.Checked
      if (ItemLink <> nil) and (ItemLink.Item <> nil) and (ItemLink.Item.Action <> nil) then
      begin
       // управление параметром Down отдаем на откуп Action-у
      end
      else
        // если Action не слинкован - воспроизводим стандартное поведение
        ButtonItem.Down := not ButtonItem.Down;
    ControlInactivate(True);
    inherited;
  end;
end;

Правда для этого придется править уже модуль dxBar.

Комментарий от DevExpress Team:
Первая ситуация известна давно и подробно описана в следующем тикете:
How to deal with TdxBarButton ButtonStyle = bsChecked По поводу второго варианта открыт новый тикет:
TdxBarButtons behave incorrectly on changing the TAction.Checked property if their style is set to bsChecked
Как я и говорил выше, реакция практически моментальная - респект парням :)

11. В качестве заключения


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

Но на всякий случай, дабы подсластить пилюлю, я решил в качестве бонуса добавить в папку с компонентами еще один юнит, в котором реализован класс TDragDropManager (пример работы показан в седьмом демопримере). Данный класс предназначен для работы с TcxPageControl.

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

Чтобы исправить данную несправедливость и был разработан класс TDragDropManager который слегка помогает экземплярам TcxPageControl обмениваться табами между собой.

На сем откланиваюсь.

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

Удачи.

---

Александр (Rouse_) Багель
Февраль, 2013

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

  1. В MS Office'е (и других MS продуктах), когда жамкаешь кнопку Alt на Ribbon-панели появляются подсказки в виде буковки (или циферки) в квадратике (или кружочке). Т.е. я могу нажать последовательно к примеру Alt, В, Т - по первой кнопке произойдёт переход к вкладке [В]ставка, по второй - [Т]аблица. (MS всё же придерживается идеи, что любое действие можно выполнить с клавиатуры без мышки).

    В стандартном делфовом риббоне я такое не увидел (именно подсветку хоткеев). Есть ли тут что-то подобное?

    ОтветитьУдалить
  2. Хороший материал. То, что нужно, чтоб дать человеку только начинающему работать с риббоном от DevExpress. Ну а за описанные подводные камни и нюансы отдельное спасибо :)

    ОтветитьУдалить
  3. Небольшой апдейт, добавлены комментарии на статью от DevExpress Team

    ОтветитьУдалить
  4. Большое спасибо за FWBackStageButton, с ней приложение выглядит более закончено.

    ОтветитьУдалить
    Ответы
    1. Незачто, правда в статье немного устаревший вариант данного контрола.
      Я его постоянно расширяю и когда решу, что его функционал меня полностью устраивает - выложу на паблик :)
      Так что следите за новостями на основном сайте: http://rouse.drkb.ru/

      Удалить
  5. Полезная статья. С ней реально легче и быстрее вникнуть в разработку совместно с dxRibbon. Сейчас решил внедрить в свой проект.

    Извините за скорее всего глупый вопрос, но как мне панель Quick Access toolbar разместить в самой верхней части формы, т.е. там же, где эта панель размещена в Excel или Word. У вас на скриншотах именно так, как я хотел бы видеть у себя, но ни слова не сказано, как эту панель разместить в заголовке формы. У меня она между заголовком формы и самим dxRibbon.

    ОтветитьУдалить
    Ответы
    1. Нужно выполнить вот эти условия: " В uses главной формы необходимо добавить юнит dxRibbonForm, а саму главную форму приложения унаследовать не от TForm, а от TdxRibbonForm, примерно вот таким образом:"

      Удалить
    2. Спасибо! То, что нужно

      Удалить
    3. Добрый день!
      Спасибо за статью!
      Но есть один момент. Это все красиво, пока форма одна. Если же их несколько и "ленте" дочерней формы сделать Merge в главную, то вся красота, увы, пропадает. Хотя, возможно, я не до конца разобрался.

      Удалить
    4. Идеология Ribbon разрешает размещение ленты только на одной из форм :)

      Удалить
    5. Поэтому и делается Merge. И в результате внешний вид основной и дочерних форм значительно различается.

      Удалить
    6. Таки да, не до конца разобравшись был. Если после Merge поставить ленте Visible := true; и ShowTabHeaders := false; дочерняя форма снова выглядит аналогично главной.

      Удалить
  6. Извините за еще один глупый вопрос. Как расположить подсказки (hint) рядом с элементов ленты? Просто у меня при наведении на любой элемент ленты подсказка всплывает снизу под всей лентой. HintAllign или HintPosition не нашел ((

    ОтветитьУдалить
    Ответы
    1. Ну или на край поуправлять. Сделать многострочный hint с выделением заглавия hint'а, как это сделано в MS Office

      Удалить
    2. Хинты внизу ленты так и должны быть (такова идеология рибона), а по поводу украшений и многострочности, стоит смотреть на TdxScreenTipRepository, как хранилище хинтов и свойство ScreenTip, где элементу выставляется соответствующий хинт.

      Удалить
  7. Здравствуйте!

    Подскажите, пожалуйста, как избавиться от одного странного на мой взгляд поведения контрола. Имею ленту dxRibbon, нанем есть контрол cxBarEditItem. Я его использую как ButtonEdit. Так вот, одна из кнопок этого контрола очищает свойство EditValue, присваивая его к Null. Но изменение отражается только при смене фокуса с этого контрола на другой. Не пойму, как добиться того, чтобы изменение сразу же отобрадалось? Как только не пытался, но не получается....

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

      Удалить
    2. dxBarManager.AlwaysSaveText := True;

      Удалить
  8. Что-то не могу установить компонент.
    Билд без проблем, инсталл - [DCC Fatal Error] F2092 Program or unit '.FWBackStageButton' recursively uses itself

    и еще Undeclared identifier: 'DXBAR_MENUITEM' и тоже самое с DXBAR_MENUEXTRAPANE_PINBUTTON

    версия 6.55, делфи - ХЕ

    ОтветитьУдалить
    Ответы
    1. По всей видимости из-за того что используется устаревшая версия DevExpress.
      Компонент разрабатывался на базе версии 12.1.4.
      Декларация констант DXBAR_MENUITEM и DXBAR_MENUEXTRAPANE_PINBUTTON выполнена в модуле dxBarSkinConsts

      Удалить
    2. в dxBarSkinConsts этих констант нет, проверял. Видимо из-за версии DevExpress.
      спасибо за статью. сэкономил себе кучу времени

      Удалить
  9. Проверил на последней версии (13,1,2) таке поведение не воспроизвелось. Может имеет смысл обновить версию DevExpress на более свежую?

    ОтветитьУдалить
  10. При использовании тем на формах и PageControl появляется красный Х на всю форму.
    Компоненты, которые сверху, его перекрывают. Но остатки его все равно видно очень хорошо.
    Особенно, если использовать AlignWithMargins. Как это исправить?
    Или может в исходниках где-то поменять. Заранее спасибо.

    ОтветитьУдалить
    Ответы
    1. А скриншот можно, или хотя бы шаги для воспроизведения, а то я с таким не сталкивался...

      Удалить
  11. Добрый день! Хотелось бы узнать можно ли как-то заголовок формы передвинуть с центра налево?

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