Search code examples
delphidelphi-2009propertyeditor

How to modify TComponentProperty to show only particular items on drop down list?


Please consider such scenerio:

I have component called TMenuItemSelector which has two published properties: PopupMenu - allows to pick an instance of TPopupMenu from the form and MenuItem which allows to pick any instance of TMenuItem from the form.

I would like to modify property editor for MenuItem property in a way that when PopupMenu is assigned then only menu items from this PopupMenu are visible in a drop down list.

I know that I need to write my own descendant of TComponentProperty and override GetValues method. The problem is that I do not know how to access the form on which TMenuItemSelector is lying.

Original TComponentProperty is using this method to iterate all available instances:

procedure TComponentProperty.GetValues(Proc: TGetStrProc);
begin
  Designer.GetComponentNames(GetTypeData(GetPropType), Proc);
end;

However, Designer seems to be precompiled so I have no idea how GetComponentNames works.

This is what I have so far, I guess only thing which I am missing is the implementation of GetValues:

unit uMenuItemSelector;

interface

uses
  Classes, Menus, DesignIntf, DesignEditors;

type
  TMenuItemSelector = class(TComponent)
  private
    FPopupMenu: TPopUpMenu;
    FMenuItem: TMenuItem;
    procedure SetPopupMenu(const Value: TPopUpMenu);
    procedure SetMenuItem(const Value: TMenuItem);
  published
    property PopupMenu: TPopUpMenu read FPopupMenu write SetPopupMenu;
    property MenuItem: TMenuItem read FMenuItem write SetMenuItem;
  end;

type
  TMenuItemProp = class(TComponentProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    procedure GetValues(Proc: TGetStrProc); override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(TMenuItem), TMenuItemSelector, 'MenuItem', TMenuItemProp);
  RegisterComponents('Test', [TMenuItemSelector]);
end;

{ TMenuItemSelector }

procedure TMenuItemSelector.SetMenuItem(const Value: TMenuItem);
begin
  FMenuItem := Value;
end;

procedure TMenuItemSelector.SetPopupMenu(const Value: TPopUpMenu);
begin
  FPopupMenu := Value;
end;

{ TMenuItemProperty }

function TMenuItemProp.GetAttributes: TPropertyAttributes;
begin
  Result := inherited GetAttributes + [paValueList, paSortList];
end;

procedure TMenuItemProp.GetValues(Proc: TGetStrProc);
begin
  //How to filter MenuItems from the form in a way that only
  //MenuItems which belong to TMenuItemSelector.PopupMenu are displayed? \
  //And how to get to that form?
  //inherited;

end;

end.

Anyone could help?

Thanks.


Solution

  • When TMenuItemProp.GetValues() is called, you need to look at the TMenuItemSelector object whose MenuItem property is currently being edited, see if that object has a PopupMenu assigned, and if so then loop through its items as neded, eg:

    procedure TMenuItemProp.GetValues(Proc: TGetStrProc); 
    var
      Selector: TMenuItemSelector;
      I: Integer;
    begin 
      Selector := GetComponent(0) as TMenuItemSelector;
      if Selector.PopupMenu <> nil then
      begin
        with Selector.PopupMenu.Items do
        begin
          for I := 0 to Count-1 do
            Proc(Designer.GetComponentName(Items[I]));
        end;
      end else
        inherited GetValues(Proc);
    end; 
    

    BTW, you need to implement TMenuItemSelector and TMenuItemProp in separate packages. With the exception of the RegisterComponents() function, (which is implemented in a runtime package), design-time code is not allowed to be compiled into run-time executables. It is against the EULA, and Embarcadero's design-time pacakges are not allowed to be distributed. You need to implement TMenuItemSelector in a runtime-only package, and then implement TMenuItemProp and Register() in a designtime-only package that Requires the runtime-only package and uses the unit that TMenuItemSelector is declared in, eg:

    unit uMenuItemSelector;
    
    interface
    
    uses
      Classes, Menus;
    
    type
      TMenuItemSelector = class(TComponent)
      private
        FPopupMenu: TPopUpMenu;
        FMenuItem: TMenuItem;
        procedure SetPopupMenu(const Value: TPopUpMenu);
        procedure SetMenuItem(const Value: TMenuItem);
      protected
        procedure Notification(AComponent: TComponent; Operation: TOperation); override;
      published
        property PopupMenu: TPopUpMenu read FPopupMenu write SetPopupMenu;
        property MenuItem: TMenuItem read FMenuItem write SetMenuItem;
      end;
    
    implementation
    
    { TMenuItemSelector }
    
    procedure TMenuItemSelector.Notification(AComponent: TComponent; Operation: TOperation);
    begin
      inherited;
      if Operation = opRemove then
      begin
        if AComponent = FPopupMenu then
        begin
          FPopupMenu := nil;
          FMenuItem := nil;
        end
        else if AComponent = FMenuItem then
        begin
          FMenuItem := nil;
        end;
      end;
    end;
    
    procedure TMenuItemSelector.SetMenuItem(const Value: TMenuItem);
    begin
      if FMenuItem <> Value then
      begin
        if FMenuItem <> nil then FMenuItem.RemoveFreeNotification(Self);
        FMenuItem := Value;
        if FMenuItem <> nil then FMenuItem.FreeNotification(Self);
      end;
    end;
    
    procedure TMenuItemSelector.SetPopupMenu(const Value: TPopUpMenu);
    begin
      if FPopupMenu <> Value then
      begin
        if FPopupMenu <> nil then FPopupMenu.RemoveFreeNotification(Self);
        FPopupMenu := Value;
        if FPopupMenu <> nil then FPopupMenu.FreeNotification(Self);
        SetMenuItem(nil);
      end;
    end;
    
    end.
    

    .

    unit uMenuItemSelectorEditor;
    
    interface
    
    uses
      Classes, DesignIntf, DesignEditors;
    
    type
      TMenuItemSelectorMenuItemProp = class(TComponentProperty)
      public
        function GetAttributes: TPropertyAttributes; override;
        procedure GetValues(Proc: TGetStrProc); override;
      end;       
    
    procedure Register;
    
    implementation
    
    uses
      Menus, uMenuItemSelector;
    
    procedure Register;
    begin
      RegisterComponents('Test', [TMenuItemSelector]);
      RegisterPropertyEditor(TypeInfo(TMenuItem), TMenuItemSelector, 'MenuItem', TMenuItemSelectorMenuItemProp);
    end;
    
    { TMenuItemSelectorMenuItemProp }
    
    function TMenuItemSelectorMenuItemProp.GetAttributes: TPropertyAttributes;
    begin
      Result := inherited GetAttributes + [paValueList, paSortList] - [paMultiSelect];
    end;
    
    procedure TMenuItemSelectorMenuItemProp.GetValues(Proc: TGetStrProc);
    var
      Selector: TMenuItemSelector;
      I: Integer;
    begin
      Selector := GetComponent(0) as TMenuItemSelector;
      if Selector.PopupMenu <> nil then
      begin
        with Selector.PopupMenu.Items do
        begin
          for I := 0 to Count-1 do
            Proc(Designer.GetComponentName(Items[I]));
        end;
      end else
        inherited GetValues(Proc);
    end; 
    
    end.