В современных приложениях часто требуется создавать динамические интерфейсы, элементы которых загружаются из базы данных. В этой статье мы рассмотрим, как правильно реализовать динамическое меню с использованием компонента TRibbon в Delphi 12, а также разберем решение распространенной проблемы, когда в группах отображается только один элемент.
Проблема динамического создания меню Ribbon
Как видно из исходного сообщения пользователя steelha, основная сложность заключалась в том, что при попытке динамического создания элементов меню в группах Ribbon отображался только один элемент, несмотря на то, что в базе данных их было несколько.
Анализ исходного кода
В первоначальной версии кода использовался следующий подход:
1. Очистка существующих вкладок Ribbon
2. Загрузка данных о главных вкладках меню из БД
3. Для каждой вкладки загрузка подгрупп
4. Для каждой подгруппы загрузка элементов
Основная ошибка заключалась в неправильной работе с ActionManager и ActionBarItem, что приводило к перезаписи элементов вместо их добавления.
Решение проблемы
Рассмотрим исправленную версию кода, которая решает проблему с отображением всех элементов меню.
Ключевые изменения в исправленном коде
Очистка ActionBars перед началом процесса:
amMenu.ActionBars.Clear;
Это важно для предотвращения конфликтов с существующими элементами.
Правильное создание и связывание ActionBarItem с TRibbonGroup:
Создание различных типов кнопок в зависимости от настроек:
if QryItems.FieldByName('tipo_control').AsString = 'LARGEBUTTON' then
begin
Bg := TRibbonLargeButtonControl.Create(Group);
Bg.ActionClient := BarAction;
end
else if QryItems.FieldByName('tipo_control').AsString = 'SMALLBUTTON' then
begin
Bs := TRibbonSmallButtonControl.Create(Group);
Bs.ActionClient := BarAction;
end;
Полный рабочий пример
Вот полный код процедуры для динамического создания меню Ribbon:
procedure TfrmPrincipal.Button2Click(Sender: TObject);
var
TabItem: TRibbonTabItem;
Group: TRibbonGroup;
BarAction: TActionClientItem;
PadreIDTab, PadreIDGrupo: Integer;
Action: TAction;
i: Integer;
GroupCaption: string;
CurrentActionBarItem: TActionBarItem;
Bg: TRibbonLargeButtonControl;
Bs: TRibbonSmallButtonControl;
begin
ribMenu.Tabs.Clear;
amMenu.ActionBars.Clear;
// Загрузка главных вкладок меню
QryMenu.Close;
QryMenu.SQL.Text := 'SELECT * FROM Menu WHERE (padre_id IS NULL OR padre_id = 0) AND activo = 1 ORDER BY orden ASC';
QryMenu.Open;
while not QryMenu.Eof do
begin
// Создание вкладки
TabItem := ribMenu.Tabs.Add;
TabItem.Caption := Trim(QryMenu.FieldByName('titulo').Value);
PadreIDTab := QryMenu.FieldByName('menu_item_id').Value;
// Загрузка групп для текущей вкладки
QrySubMenu.Close;
QrySubMenu.SQL.Text := 'SELECT * FROM Menu WHERE padre_id = :PadreIDTab AND activo = 1 ORDER BY orden ASC';
QrySubMenu.ParamByName('PadreIDTab').Value := PadreIDTab;
QrySubMenu.Open;
while not QrySubMenu.Eof do
begin
if UpperCase(QrySubMenu.FieldByName('tipo').Value) = 'GRUPO' then
begin
GroupCaption := Trim(QrySubMenu.FieldByName('titulo').AsString);
// Поиск существующей группы
Group := nil;
for i := 0 to TabItem.Page.ControlCount - 1 do
if (TabItem.Page.Controls[i] is TRibbonGroup) and
(TRibbonGroup(TabItem.Page.Controls[i]).Caption = GroupCaption) then
begin
Group := TRibbonGroup(TabItem.Page.Controls[i]);
Break;
end;
// Создание новой группы при необходимости
if not Assigned(Group) then
begin
Group := TRibbonGroup.Create(ribMenu);
Group.Parent := TabItem.Page;
Group.Caption := GroupCaption;
end;
PadreIDGrupo := QrySubMenu.FieldByName('menu_item_id').Value;
// Поиск или создание ActionBarItem для группы
CurrentActionBarItem := nil;
for i := 0 to amMenu.ActionBars.Count - 1 do
if amMenu.ActionBars[i].ActionBar = Group then
begin
CurrentActionBarItem := amMenu.ActionBars[i];
Break;
end;
if not Assigned(CurrentActionBarItem) then
begin
CurrentActionBarItem := amMenu.ActionBars.Add;
CurrentActionBarItem.ActionBar := Group;
CurrentActionBarItem.Tag := PadreIDGrupo;
end;
// Загрузка элементов для текущей группы
QryItems.Close;
QryItems.SQL.Text := 'SELECT * FROM Menu WHERE padre_id = :PadreIDGrupo AND activo = 1 ORDER BY orden';
QryItems.ParamByName('PadreIDGrupo').Value := PadreIDGrupo;
QryItems.Open;
while not QryItems.Eof do
begin
if UpperCase(QryItems.FieldByName('tipo').Value) = 'ITEM' then
begin
// Создание действия
Action := TAction.Create(Self);
Action.Caption := Trim(QryItems.FieldByName('titulo').Value);
Action.OnExecute := ItemClick;
Action.Tag := QryItems.FieldByName('menu_item_id').AsInteger;
// Добавление элемента в группу
BarAction := CurrentActionBarItem.Items.Add as TActionClientItem;
BarAction.Action := Action;
BarAction.Caption := Action.Caption;
// Создание соответствующего типа кнопки
if QryItems.FieldByName('tipo_control').AsString = 'LARGEBUTTON' then
begin
Bg := TRibbonLargeButtonControl.Create(Group);
Bg.ActionClient := BarAction;
end
else if QryItems.FieldByName('tipo_control').AsString = 'SMALLBUTTON' then
begin
Bs := TRibbonSmallButtonControl.Create(Group);
Bs.ActionClient := BarAction;
end;
end;
QryItems.Next;
end;
QryItems.Close;
end;
QrySubMenu.Next;
end;
QrySubMenu.Close;
QryMenu.Next;
end;
// Обновление отображения Ribbon
if ribMenu.Tabs.Count >= 2 then
begin
ribMenu.TabIndex := 1;
ribMenu.TabIndex := 0;
end;
ribMenu.Refresh;
QryMenu.Close;
end;
Обработка кликов по элементам меню
Для обработки действий при нажатии на элементы меню используется общий обработчик:
procedure TfrmPrincipal.ItemClick(Sender: TObject);
var
Action: TAction;
MenuItemID: Integer;
begin
if Sender is TAction then
begin
Action := TAction(Sender);
MenuItemID := Action.Tag;
// Здесь можно реализовать логику обработки нажатия
ShowMessage('Выбрано меню с ID: ' + IntToStr(MenuItemID) +
', заголовок: ' + Action.Caption);
end;
end;
Альтернативное решение
Вместо использования ActionManager можно реализовать более легковесное решение, создавая кнопки напрямую:
procedure TfrmPrincipal.CreateRibbonMenuDirectly;
var
TabItem: TRibbonTabItem;
Group: TRibbonGroup;
Button: TRibbonLargeButton;
PadreIDTab, PadreIDGrupo: Integer;
begin
ribMenu.Tabs.Clear;
QryMenu.Close;
QryMenu.SQL.Text := 'SELECT * FROM Menu WHERE (padre_id IS NULL OR padre_id = 0) AND activo = 1 ORDER BY orden ASC';
QryMenu.Open;
while not QryMenu.Eof do
begin
TabItem := ribMenu.Tabs.Add;
TabItem.Caption := Trim(QryMenu.FieldByName('titulo').Value);
PadreIDTab := QryMenu.FieldByName('menu_item_id').Value;
QrySubMenu.Close;
QrySubMenu.SQL.Text := 'SELECT * FROM Menu WHERE padre_id = :PadreIDTab AND activo = 1 ORDER BY orden ASC';
QrySubMenu.ParamByName('PadreIDTab').Value := PadreIDTab;
QrySubMenu.Open;
while not QrySubMenu.Eof do
begin
if UpperCase(QrySubMenu.FieldByName('tipo').Value) = 'GRUPO' then
begin
Group := TRibbonGroup.Create(ribMenu);
Group.Parent := TabItem.Page;
Group.Caption := Trim(QrySubMenu.FieldByName('titulo').AsString);
PadreIDGrupo := QrySubMenu.FieldByName('menu_item_id').Value;
QryItems.Close;
QryItems.SQL.Text := 'SELECT * FROM Menu WHERE padre_id = :PadreIDGrupo AND activo = 1 ORDER BY orden';
QryItems.ParamByName('PadreIDGrupo').Value := PadreIDGrupo;
QryItems.Open;
while not QryItems.Eof do
begin
if UpperCase(QryItems.FieldByName('tipo').Value) = 'ITEM' then
begin
Button := TRibbonLargeButton.Create(Group);
Button.Parent := Group;
Button.Caption := Trim(QryItems.FieldByName('titulo').Value);
Button.Tag := QryItems.FieldByName('menu_item_id').AsInteger;
Button.OnClick := DirectButtonClick;
end;
QryItems.Next;
end;
QryItems.Close;
end;
QrySubMenu.Next;
end;
QrySubMenu.Close;
QryMenu.Next;
end;
QryMenu.Close;
end;
procedure TfrmPrincipal.DirectButtonClick(Sender: TObject);
begin
if Sender is TRibbonLargeButton then
ShowMessage('Нажата кнопка: ' + TRibbonLargeButton(Sender).Caption +
', ID: ' + IntToStr(TRibbonLargeButton(Sender).Tag));
end;
Советы по улучшению кода
Вынесение SQL-запросов в отдельный модуль: Для больших проектов лучше вынести все запросы к базе данных в отдельный модуль или класс.
Кэширование данных меню: Если меню редко меняется, можно загружать его один раз при старте приложения и кэшировать.
Использование стилей: Для единообразия интерфейса применяйте стили к элементам Ribbon.
Ленивая загрузка: Для больших меню можно реализовать ленивую загрузку элементов при активации вкладки.
Логирование ошибок: Добавьте обработку ошибок при загрузке меню из базы данных.
Заключение
В этой статье мы рассмотрели решение проблемы динамического создания меню с использованием компонента TRibbon в Delphi 12. Основные ключевые моменты:
- Правильная очистка существующих элементов перед созданием новых
- Корректная работа с ActionManager и ActionBarItem
- Создание отдельных действий для каждого элемента меню
- Альтернативный подход без использования ActionManager
Представленные решения помогут вам создавать гибкие и динамические интерфейсы, элементы которых загружаются из базы данных, что особенно важно для корпоративных приложений с возможностью настройки меню под конкретные нужды пользователей.
Описание процесса создания динамического меню с использованием компонента Ribbon в Delphi 12, включая решение проблемы отображения всех элементов в группах.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.