运行之后的效果如下,
图一
图二
主界面代码如下
unit Frm_Main; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, CommCtrl, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, QPlugins, qplugins_params, qplugins_base, Serv_Menu, System.ImageList, Vcl.ImgList, Vcl.ExtCtrls, Vcl.StdCtrls; type // 定义一个菜单服务 TQMenuService = class(TQService, IQMenuService) protected // 注册, 在接口IQMenuService中定义,子类来实现它 function RegisterMenu(const APath: PWideChar; AOnEvent: IQNotify; ADelimitor: WideChar) : IQMenuItem; // 注销,在接口IQMenuService中定义,子类来实现它 procedure UnregisterMenu(const APath: PWideChar; AOnEvent: IQNotify; ADelimitor: WideChar); public constructor Create; overload; destructor Destroy; override; end; // 菜单服务的部分接口 TQMenuItem = class(TInterfacedObject, IQMenuItem) private protected FMenuItem: TMenuItem; FOnClick: IQNotify; FName: string; // 菜单接口 FParams: IQParams; function GetCaption: PWideChar; procedure SetCaption(const S: PWideChar); function GetHint: PWideChar; procedure SetHint(const S: PWideChar); function SetImage(AHandle: HBITMAP): Boolean; function getParams: IQParams; procedure setParams(AParams: IQParams); function GetParentMenu: IQMenuItem; procedure DoClick(ASender: TObject); public constructor Create(AMenuItem: TMenuItem; AOnClick: IQNotify); overload; property Name: string read FName write FName; property Params: IQParams read getParams write setParams; end; TForm_Main = class(TForm) MainMenu1: TMainMenu; ilMenus: TImageList; Label1: TLabel; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private {Private declarations} public {Public declarations} end; var Form_Main: TForm_Main; implementation uses qstring; var MainMenu: TMainMenu; // 全局菜单变量 {$R *.dfm} {TMenuService} // 创建菜单服务 constructor TQMenuService.Create; begin inherited Create(IQMenuService, 'QMenuService'); end; // 释放 destructor TQMenuService.Destroy; begin inherited; end; // 注册菜单 function TQMenuService.RegisterMenu(const APath: PWideChar; AOnEvent: IQNotify; ADelimitor: WideChar): IQMenuItem; var p: PWideChar; AName: QStringW; AMenu, ANewMenu: TMenuItem; AItem: IQMenuItem; AChildMenu: TQMenuItem; AIdx: Integer; // 识别是哪个菜单 function IndexOfMenuName: Integer; var I: Integer; AIntf: IQMenuItem; begin Result := -1; // for I := 0 to AMenu.Count - 1 do begin // 将菜单保存的Tag转成菜单条目 AIntf := IQMenuItem(Pointer(AMenu.Items[I].Tag)); if (InstanceOf(AIntf) as TQMenuItem).Name = AName then begin Result := I; Break; end; end; end; begin // 菜单 AMenu := Form_Main.MainMenu1.Items; // 如果菜单文本存在 p := PWideChar(APath); // '/文件/ShowForm','/文件/Exit' while p^ <> #0 do begin // 字符串分解 AName := DecodeTokenW(p, [ADelimitor], #0, True); // 文件,ShowForm // 存在 if Length(AName) > 0 then begin // 菜单名 AIdx := IndexOfMenuName; if AIdx = -1 then begin // 创建菜单 ANewMenu := TMenuItem.Create(MainMenu); if p^ = #0 then AChildMenu := TQMenuItem.Create(ANewMenu, AOnEvent) else begin AChildMenu := TQMenuItem.Create(ANewMenu, nil); end; AChildMenu.Name := AName; // 返回添加的子菜单 Result := AChildMenu; Result._AddRef; // 创建并添加子条目 ANewMenu.Tag := IntPtr(Pointer(Result)); ANewMenu.Caption := AName; AMenu.Add(ANewMenu); AMenu := ANewMenu; end else begin // 返回 Result := IQMenuItem(Pointer(AMenu.Items[AIdx].Tag)); AMenu := AMenu.Items[AIdx]; end; end; end; end; procedure TQMenuService.UnregisterMenu(const APath: PWideChar; AOnEvent: IQNotify; ADelimitor: WideChar); begin end; // 创建 procedure TForm_Main.FormCreate(Sender: TObject); begin // 定义的菜单赋值 MainMenu := MainMenu1; // 注册一个菜单服务,TQMenuService有多个功能,但IQMenuService只有注册和注销 RegisterServices('/Services/Menus', [TQMenuService.Create(IQMenuService, 'MenuService')]); // 通过服务接口ID获取服务接口实例 PluginsManager.ById(IQMenuService); end; {TQMenuItem} // 创建菜单 constructor TQMenuItem.Create(AMenuItem: TMenuItem; AOnClick: IQNotify); var ATemp: Pointer; begin inherited Create; // 关联菜单的单击事件 FMenuItem := AMenuItem; // 关联菜单单击事件 FMenuItem.OnClick := DoClick; FOnClick := AOnClick; end; // 菜单点击事件 procedure TQMenuItem.DoClick(ASender: TObject); var AFireNext: Boolean; begin AFireNext := True; // if Assigned(FOnClick) then begin FOnClick.Notify(MN_CLICK, Params, AFireNext); // 演示版本,不传递参数 end; end; // 菜单接口_读取标题 function TQMenuItem.GetCaption: PWideChar; begin Result := PWideChar(FMenuItem.Caption); end; // 菜单接口_读取Hint function TQMenuItem.GetHint: PWideChar; begin Result := PWideChar(FMenuItem.Hint); end; // 菜单接口_读取参数 function TQMenuItem.getParams: IQParams; begin Result := FParams; end; // 菜单接口_读取父菜单 function TQMenuItem.GetParentMenu: IQMenuItem; begin if Assigned(FMenuItem.Parent) then Result := IQMenuItem(FMenuItem.Parent.Tag) else Result := nil; end; // 菜单接口_设置标题 procedure TQMenuItem.SetCaption(const S: PWideChar); begin FMenuItem.Caption := S; end; // 菜单接口_设置Hint procedure TQMenuItem.SetHint(const S: PWideChar); begin FMenuItem.Hint := S; end; // 菜单接口_设置图片 function TQMenuItem.SetImage(AHandle: HBITMAP): Boolean; var ABitmap: TBitmap; AIcon: TBitmap; AImages: TCustomImageList; begin AImages := (FMenuItem.Owner as TMenu).Images; AIcon := nil; ABitmap := TBitmap.Create; try ABitmap.Handle := AHandle; // 图标尺寸如果不对,则生成临时的位图,否则ImageList会添加失败 if (ABitmap.Width <> AImages.Width) or (ABitmap.Height <> AImages.Height) then begin // 图标 AIcon := TBitmap.Create; AIcon.SetSize(AImages.Width, AImages.Height); AIcon.Canvas.Brush.Color := ABitmap.TransparentColor; AIcon.Canvas.FillRect(Rect(0, 0, AImages.Width, AImages.Height)); AIcon.Canvas.Draw((AImages.Width - ABitmap.Width) shr 1, (AImages.Height - ABitmap.Height) shr 1, ABitmap); AIcon.Transparent := True; FMenuItem.ImageIndex := AImages.AddMasked(AIcon, ABitmap.TransparentColor); end else FMenuItem.ImageIndex := AImages.AddMasked(ABitmap, ABitmap.TransparentColor); finally FreeAndNil(AIcon); FreeAndNil(ABitmap); end; Result := FMenuItem.ImageIndex <> -1; end; // 菜单接口_设置参数 procedure TQMenuItem.setParams(AParams: IQParams); begin FParams := AParams; end; // 关闭窗口 procedure TForm_Main.FormDestroy(Sender: TObject); // 销毁菜单 procedure DestoryMenus(AParent: TMenuItem); var I: Integer; AMenu: TMenuItem; begin for I := 0 to AParent.Count - 1 do begin AMenu := AParent.Items[I]; // 只要菜单Tag存在,这释放销毁 if AMenu.Tag <> 0 then begin IQMenuItem(Pointer(AMenu.Tag))._Release; end; DestoryMenus(AMenu); end; end; begin // 销毁菜单 DestoryMenus(MainMenu.Items); end; end.
Serv_Menu代码如下
unit Serv_Menu; interface uses windows, menus, QPlugins, qplugins_base, qplugins_params; const // 传递的参数 MN_CLICK = 0; type // 这里只实现了菜单服务的部分接口,如果要实现更多的接口,请自己扩展实现 IQMenuItem = interface ['{83323919-93DE-4D40-87FB-7266AE804D6C}'] function GetCaption: PWideChar; procedure SetCaption(const S: PWideChar); function GetHint: PWideChar; procedure SetHint(const S: PWideChar); function GetParams: IQParams; procedure SetParams(AParams: IQParams); function SetImage(AHandle: HBITMAP): Boolean; function GetParentMenu: IQMenuItem; // 菜单的四个属性,标题/Hint/父菜单/参数 property Caption: PWideChar read GetCaption write SetCaption; property Hint: PWideChar read GetHint write SetHint; property ParentMenu: IQMenuItem read GetParentMenu; property Params: IQParams read GetParams write SetParams; end; IQMenuService = interface ['{667BD198-2F9A-445C-8A7D-B85C4B222DFC}'] // 注册, 在接口中定义,子类来实现它 function RegisterMenu(const APath: PWideChar; AOnEvent: IQNotify; ADelimitor: WideChar = '/'): IQMenuItem; // 注销, 在接口中定义,子类来实现它 procedure UnregisterMenu(const APath: PWideChar; AOnEvent: IQNotify; ADelimitor: WideChar = '/'); end; implementation end.
菜单窗口代码如下
unit Frm_Menu; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, qplugins, qplugins_params, qplugins_base, Serv_Menu, Vcl.StdCtrls, Vcl.ExtCtrls; type TForm_Menu = class(TForm) Label1: TLabel; Image1: TImage; Memo1: TMemo; private {Private declarations} public {Public declarations} end; var Form_Menu: TForm_Menu; implementation {$R *.dfm} type // 通知响应接口 TShowFormAction = class(TQInterfacedObject, IQNotify) protected procedure Notify(const AId: Cardinal; AParams: IQParams; var AFireNext: Boolean); stdcall; end; {TShowFormAction} // 通知响应接口 procedure TShowFormAction.Notify(const AId: Cardinal; AParams: IQParams; var AFireNext: Boolean); var F: TForm_Menu; I: Integer; AName: string; begin // 如果有传过来参数,参数名字为'Exit'者退出程序 if Assigned(AParams) and (ParamAsString(AParams.ByName('Name')) = 'Exit') then Application.Terminate else begin // 创建窗口 F := TForm_Menu.Create(Application); // Memo输出 with F.Memo1.Lines do begin BeginUpdate; try // 输出传过来的参数 for I := 0 to AParams.Count - 1 do begin // 窗口输出参数 Add(IntToStr(I) + ': ' + AParams[I].Name + '=' + ParamAsString(AParams[I])); end; finally EndUpdate; end; end; // 显示窗口 F.ShowModal; F.Free; end; end; var // 通知响应接口,关注某个通知时,应实现IQNotify接口,以便接收相关的通知 AShowForm: IQNotify; // 添加菜单相关内容 procedure DoMenuServiceReady(const AService: IQService); stdcall; var F: TForm_Menu; begin // 菜单回调函数 with AService as IQMenuService do begin // 通知响应接口 AShowForm := TShowFormAction.Create; // 注册菜单 with RegisterMenu('/文件/ShowForm', AShowForm) do begin // 窗体信息 Caption := '显示窗体(&S)'; F := TForm_Menu.Create(nil); // 设置图标 SetImage(TBitmap(F.Image1.Picture.Graphic).Handle); Params := NewParams([1, 'Hello,world']); F.Free; end; // 注册第二个菜单 with RegisterMenu('/文件/Exit', AShowForm) do begin Caption := '退出(&X)'; Params := NewParams([]); // 参数名字为Exit Params.Add('Name', ptUnicodeString).AsString := NewString('Exit'); end; end; end; // initialization在单元中放在文件结尾前,包含用来初始化单元的代码,它在主程序运行前运行并且只运行一次。 initialization // 通知响应接口 AShowForm := nil; // 等待指定的服务注册,DoMenuServiceReady为服务注册完成时的通知回调 PluginsManager.WaitService(IQMenuService, DoMenuServiceReady); // 在单元中放在 initialization 和 end. 之间,包含了单元退出时的代码。在程序退出时运行并且只运行一次。 finalization // 检查菜单接口是否存在,存在则释放菜单功能 if Assigned(AShowForm) then begin // 释放菜单功能 with PluginsManager as IQMenuService do begin UnregisterMenu('/File/ShowForm', AShowForm); end; AShowForm := nil; end; end.