@@ -4,9 +4,11 @@ interface
44
55uses
66 System.Classes,
7+ Vcl.Menus,
78 ActnList,
89 ActnMan,
9- UIRibbonCommands;
10+ UIRibbonCommands,
11+ System.Generics.Collections;
1012
1113type
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>)
151201implementation
152202
153203uses
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);
342392begin
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 ;
347399end ;
348400
349401{ TUICommandDecimalActionLink }
@@ -463,4 +515,152 @@ procedure TUICommandRecentItemsActionLink.SetAction(Value: TBasicAction);
463515 (Client as TUICommandRecentItems).OnSelect := CommandSelect;
464516end ;
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+
466666end .
0 commit comments