Skip to content

Commit dcb182f

Browse files
- Added functionality to TRibbonCollectionAction that allows to add items to a ribbon collection dynamically, at runtime (issue #49).
- Introduced new subclass of TRibbonCollectionAction: TRibbonPopupMenuAction. Assigning the "Menu" property of this class will allow us to connect the target ribbon collection to a TPopupMenu. The framework will automatically update the ribbon collection, if a change to the popup menu occurs.
1 parent f4496f4 commit dcb182f

File tree

4 files changed

+226
-7
lines changed

4 files changed

+226
-7
lines changed

Lib/UIRibbon.Register.pas

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ procedure Register;
2828
lDesignerPath: string;
2929
begin
3030
RegisterComponents('Windows Ribbon Framework for Delphi', [TUIRibbon]);
31-
RegisterActions(cPackageTitle, [TRibbonCollectionAction, TRibbonFontAction, TRibbonColorAction], nil);
31+
RegisterActions(cPackageTitle, [TRibbonCollectionAction, TRibbonPopupMenuAction, TRibbonFontAction, TRibbonColorAction], nil);
3232

3333
lDesignerPath := TMyRibbonFrameworkEditor.GetDesignerPath();
3434
if lDesignerPath.IsEmpty and (getActiveProject <> nil) then begin

Lib/UIRibbon.pas

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ TUIRibbon = class(TWinControl, IUIApplication)
147147
fRecentItems: TUICommandRecentItems;
148148
/// Ribbon configuration options.
149149
fOptions: TUIRibbonOptions;
150+
/// The highest command Id that was used until now. Used for the generation of a unique command id.
151+
fMaxCommandId: Cardinal;
150152

151153
/// <summary>
152154
/// Sets the application modes for this Ribbon form.
@@ -241,7 +243,12 @@ TUIRibbon = class(TWinControl, IUIApplication)
241243
/// Localize the given Ribbon command using the resource identifiers of the given markup item.
242244
/// </summary>
243245
procedure LocalizeRibbonElement(const pCommand: TUICommand; const pMarkupItem: TRibbonMarkupElement);
244-
246+
/// <summary>
247+
/// Returns a command id that hasn't been used yet. We keep track of the previously used IDs by increasing the value of member fMaxCommandId.
248+
/// </summary>
249+
/// <returns>string A command id that has not been used yet.</returns>
250+
/// <author>[email protected]</author>
251+
function CreateUnusedCommandId(): Cardinal;
245252
/// <summary>
246253
/// Gets or sets the mapping dictionary, which is automatically created by
247254
/// the "Ribbon Designer" of the "Windows Ribbon Framework".
@@ -500,6 +507,7 @@ implementation
500507
Forms,
501508
Menus,
502509
GraphUtil,
510+
Math,
503511
UITypes,
504512
UIRibbonActions,
505513
UIRibbonUtils;
@@ -572,6 +580,11 @@ procedure TUIRibbon.ActivateContextTab(const pCommandId: Integer);
572580

573581
procedure TUIRibbon.AddCommand(const Command: TUICommand);
574582
begin
583+
// If 0 was used as command id, we generate a unique command id and use it instead.
584+
if Command.CommandId = 0 then
585+
Command.CommandId := CreateUnusedCommandId
586+
else
587+
fMaxCommandId := Max(fMaxCommandId, Command.CommandId); // Keep track of the highest command id that was used until now.
575588
FCommands.Add(Command.CommandId, Command);
576589
end;
577590

@@ -1074,6 +1087,12 @@ procedure TUIRibbon.LocalizeRibbonElement(const pCommand: TUICommand; const pMar
10741087
pCommand.Keytip := Trim(pCommand.Caption)[1];
10751088
end;
10761089

1090+
function TUIRibbon.CreateUnusedCommandId(): Cardinal;
1091+
begin
1092+
Inc(fMaxCommandId);
1093+
Result := fMaxCommandId;
1094+
end;
1095+
10771096
procedure TUIRibbon.InvalidateUICommand(const Command: TUICommand;
10781097
const Aspects: TUICommandInvalidations);
10791098
var

Lib/UIRibbonActions.pas

Lines changed: 204 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ interface
44

55
uses
66
System.Classes,
7+
Vcl.Menus,
78
ActnList,
89
ActnMan,
9-
UIRibbonCommands;
10+
UIRibbonCommands,
11+
System.Generics.Collections;
1012

1113
type
1214
TUICommandActionLink = class abstract (TActionLink)
@@ -131,6 +133,54 @@ TRibbonAction<T:TUICommand> = class(TCustomAction)
131133
end;
132134

133135
TRibbonCollectionAction = class(TRibbonAction<TUICommandCollection>)
136+
strict private
137+
fActionList: TList<TPair<TCustomAction, string>>;
138+
function GetItem(pIndex: Integer): TPair<TCustomAction, string>;
139+
public
140+
constructor Create(AOwner: TComponent); override;
141+
destructor Destroy; override;
142+
/// <summary>
143+
/// Adds an action to the internal list and populates it to the command
144+
/// </summary>
145+
/// <param name="pAction">The action that will be added to the collection.</param>
146+
/// <param name="pCategory">(Optional) The category index that will be used for the collection.
147+
/// An empty category name will be ignored and the default category will be used instead</param>
148+
/// <returns>None</returns>collection
149+
procedure Add(pAction: TCustomAction; const pCategory: string = '');
150+
/// Clears the internal list and the command collection.
151+
procedure Clear;
152+
/// Allows to add multiple actions add once.
153+
procedure AddRange(pSource: TList<TPair<TCustomAction, string>>);
154+
/// Returns the amount of actions that have been added.
155+
function ItemCount: Integer;
156+
property Items[Index: Integer]: TPair<TCustomAction, string> read GetItem; default;
157+
/// <summary>
158+
/// This method uses the action items that are stored in the internal list fActionList,
159+
/// and dynamically creates commands that will be added to the collection.
160+
/// </summary>
161+
procedure RefreshCommandCollection();
162+
end;
163+
164+
/// <summary>
165+
/// This type of action can be used to link a TPopupMenu to a ribbon collection. The menu items, or rather their actions,
166+
/// will automatically be populated to the collection.
167+
/// </summary>
168+
TRibbonPopupMenuAction = class(TRibbonCollectionAction)
169+
strict private
170+
fPopupMenu: TPopupMenu;
171+
fOriginalOnMenuChange: TMenuChangeEvent;
172+
strict protected
173+
/// <summary> Setter for property Menu </summary>
174+
procedure SetPopupMenu(pValue: TPopupMenu);
175+
/// <summary> Event handler for the menu's OnChange-event. We use this to update the collection. </summary>
176+
procedure MenuChange(Sender: TObject; Source: TMenuItem; Rebuild: Boolean);
177+
published
178+
/// <summary>
179+
/// Property that can be used to dynamically fill the collection items:
180+
/// If this type of action is assigned to a collection type ribbon, such as a Drop Down Gallery,
181+
/// the collection is automatically filled using the menu items of this property.
182+
/// </summary>
183+
property Menu: TPopupMenu read fPopupMenu write SetPopupMenu;
134184
end;
135185

136186
TRibbonColorAction = class(TRibbonAction<TUICommandColorAnchor>)
@@ -151,13 +201,13 @@ TRibbonFontAction = class(TRibbonAction<TUICommandFont>)
151201
implementation
152202

153203
uses
154-
Menus,
155204
Controls,
156205
UIRibbon,
157206
{$if CompilerVersion >= 24}
158207
System.Actions,
159208
{$endif}
160-
System.SysUtils;
209+
System.SysUtils,
210+
System.Math;
161211

162212
{ TUICommandActionLink }
163213

@@ -342,8 +392,10 @@ procedure TUICommandCollectionActionLink.SetAction(Value: TBasicAction);
342392
begin
343393
inherited;
344394
(Client as TUICommandCollection).OnSelect := CommandSelect;
345-
if (Action is TRibbonCollectionAction) then
395+
if (Action is TRibbonCollectionAction) then begin
346396
TRibbonCollectionAction(Action).UICommand := (Client as TUICommandCollection);
397+
TRibbonCollectionAction(Action).RefreshCommandCollection;
398+
end;
347399
end;
348400

349401
{ TUICommandDecimalActionLink }
@@ -463,4 +515,152 @@ procedure TUICommandRecentItemsActionLink.SetAction(Value: TBasicAction);
463515
(Client as TUICommandRecentItems).OnSelect := CommandSelect;
464516
end;
465517

518+
{ TRibbonCollectionAction }
519+
520+
procedure TRibbonCollectionAction.Add(pAction: TCustomAction; const pCategory: string = '');
521+
begin
522+
fActionList.Add(TPair<TCustomAction, string>.Create(pAction, pCategory));
523+
RefreshCommandCollection;
524+
end;
525+
526+
procedure TRibbonCollectionAction.RefreshCommandCollection();
527+
var
528+
lAction: TCustomAction;
529+
lCategory: string;
530+
lItem: TUIGalleryCollectionItem;
531+
lCommandAction: TUICommandAction;
532+
lCommandCollection: TUICommandCollection;
533+
I, lTargetCategoryId: Integer;
534+
535+
function FindOrCreateCategory(pCategoryCaption: string): Integer;
536+
var
537+
lCurrentCollectionItem: IUICollectionItem;
538+
lGalleryCollectionItem: TUIGalleryCollectionItem;
539+
begin
540+
Result := -1;
541+
542+
for lCurrentCollectionItem in lCommandCollection.Categories do begin
543+
lGalleryCollectionItem := lCurrentCollectionItem as TUIGalleryCollectionItem;
544+
if SameText(lGalleryCollectionItem.LabelText, pCategoryCaption) then
545+
// Category with given caption found -> return the correct id.
546+
Exit(lGalleryCollectionItem.CategoryId)
547+
else
548+
Result := Max(Result, lGalleryCollectionItem.CategoryId); // Keep track of the highest used id. We may need it later to create a new category.
549+
end;
550+
// No category with given caption found -> create it
551+
lGalleryCollectionItem := TUIGalleryCollectionItem.Create;
552+
// Use highest seen category id, increased by one, for this new category
553+
Inc(Result);
554+
lGalleryCollectionItem.CategoryId := Result;
555+
lGalleryCollectionItem.LabelText := pCategoryCaption;
556+
lCommandCollection.Categories.Add(lGalleryCollectionItem);
557+
end;
558+
559+
begin
560+
// Command link is not (yet) created -> exit.
561+
if not Assigned(UICommand) then exit;
562+
563+
lCommandCollection := UICommand as TUICommandCollection;
564+
// Clear the ribbon collection
565+
lCommandCollection.Items.Clear;
566+
// Iterate the internal list of actions and fill the ribbon collection
567+
for I := 0 to fActionList.Count - 1 do begin
568+
lAction := fActionList[I].Key;
569+
lCategory := fActionList[I].Value;
570+
if lCategory.IsEmpty then
571+
lTargetCategoryId := -1
572+
else
573+
lTargetCategoryId := FindOrCreateCategory(lCategory);
574+
575+
// Create a new command item and assign the target action
576+
lCommandAction := TUICommandAction.Create((lCommandCollection.Owner as TUIRibbon), 0);
577+
lCommandAction.Assign(lAction);
578+
// Create a collection item, that holds the action and can be added to the collection.
579+
lItem := TUIGalleryCollectionItem.Create;
580+
lItem.Command := lCommandAction;
581+
lItem.CategoryId := lTargetCategoryId;
582+
lCommandCollection.Items.Add(lItem);
583+
end;
584+
end;
585+
586+
procedure TRibbonCollectionAction.AddRange(pSource: TList<TPair<TCustomAction, string>>);
587+
begin
588+
fActionList.AddRange(pSource);
589+
RefreshCommandCollection;
590+
end;
591+
592+
procedure TRibbonCollectionAction.Clear;
593+
begin
594+
fActionList.Clear;
595+
RefreshCommandCollection;
596+
end;
597+
598+
constructor TRibbonCollectionAction.Create(AOwner: TComponent);
599+
begin
600+
inherited;
601+
fActionList := TList<TPair<TCustomAction, string>>.Create;
602+
end;
603+
604+
destructor TRibbonCollectionAction.Destroy;
605+
begin
606+
FreeAndNil(fActionList);
607+
inherited;
608+
end;
609+
610+
611+
function TRibbonCollectionAction.GetItem(pIndex: Integer): TPair<TCustomAction, string>;
612+
begin
613+
Exit(fActionList[pIndex]);
614+
end;
615+
616+
function TRibbonCollectionAction.ItemCount: Integer;
617+
begin
618+
Result := fActionList.Count;
619+
end;
620+
621+
{ TRibbonPopupMenuAction }
622+
623+
procedure TRibbonPopupMenuAction.MenuChange(Sender: TObject; Source: TMenuItem; Rebuild: Boolean);
624+
var
625+
I, J: Integer;
626+
lCategory: string;
627+
lCategoryActionList: TList<TPair<TCustomAction, string>>;
628+
begin
629+
if Assigned(fOriginalOnMenuChange) then
630+
fOriginalOnMenuChange(Sender, Source, Rebuild);
631+
632+
Clear; // Clear the collection and refill it.
633+
lCategory := '';
634+
// We use this list to collect actions category wise. We map Menu separators to ribbon categories.
635+
lCategoryActionList := TList<TPair<TCustomAction, string>>.Create;
636+
for I := 0 to Menu.Items.Count - 1 do begin
637+
if not Assigned(Menu.Items[I].Action) and SameText(Menu.Items[I].Caption, cLineCaption) then begin
638+
// This menu item is a separator -> use a category with empty caption
639+
lCategory := lCategory + ' ';
640+
// The first separator was found -> update the existing items
641+
if lCategory = ' ' then begin
642+
for J := 0 to lCategoryActionList.Count - 1 do begin
643+
lCategoryActionList[J] := TPair<TCustomAction, string>.Create(lCategoryActionList[J].Key, lCategory);
644+
end;
645+
end;
646+
// Submit this list and clear it for the next category
647+
AddRange(lCategoryActionList);
648+
lCategoryActionList.Clear;
649+
// Change the category key, so that following items will use a different category.
650+
lCategory := lCategory + ' ';
651+
end else
652+
lCategoryActionList.Add(TPair<TCustomAction, string>.Create(Menu.Items[I].Action as TCustomAction, lCategory));
653+
end;
654+
// Submit the last category
655+
AddRange(lCategoryActionList);
656+
end;
657+
658+
procedure TRibbonPopupMenuAction.SetPopupMenu(pValue: TPopupMenu);
659+
begin
660+
fPopupMenu := pValue;
661+
// Keep track of pre-existing event handlers. We call them together with our custom event handler.
662+
fOriginalOnMenuChange := fPopupMenu.OnChange;
663+
fPopupMenu.OnChange := MenuChange;
664+
end;
665+
466666
end.

Lib/UIRibbonCommands.pas

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ TUICommand = class abstract(TComponent, IUICommandHandler)
251251
property Tag: Integer read FTag write FTag;
252252

253253
{ The Id of the command as specified in the Ribbon markup }
254-
property CommandId: Cardinal read FCommandId;
254+
property CommandId: Cardinal read FCommandId write FCommandId;
255255

256256
(************************************************************************
257257
* The properties below are available for most (but not all) command

0 commit comments

Comments
 (0)