002.Delphi插件之QPlugins,菜单插件

运行之后的效果如下,

图一

 

图二

 

主界面代码如下

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.

 

posted @ 2019-09-08 23:15  像一棵海草海草海草  阅读(414)  评论(0编辑  收藏  举报