CnMenuHook.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. {******************************************************************************}
  2. { CnPack For Delphi/C++Builder }
  3. { 中国人自己的开放源码第三方开发包 }
  4. { (C)Copyright 2001-2018 CnPack 开发组 }
  5. { ------------------------------------ }
  6. { }
  7. { 本开发包是开源的自由软件,您可以遵照 CnPack 的发布协议来修 }
  8. { 改和重新发布这一程序。 }
  9. { }
  10. { 发布这一开发包的目的是希望它有用,但没有任何担保。甚至没有 }
  11. { 适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。 }
  12. { }
  13. { 您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果 }
  14. { 还没有,可访问我们的网站: }
  15. { }
  16. { 网站地址:http://www.cnpack.org }
  17. { 电子邮件:master@cnpack.org }
  18. { }
  19. {******************************************************************************}
  20. unit CnMenuHook;
  21. { |<PRE>
  22. ================================================================================
  23. * 软件名称:CnWizards IDE 专家工具包
  24. * 单元名称:菜单挂接服务单元
  25. * 单元作者:周劲羽 (zjy@cnpack.org)
  26. * 备 注:该单元用来实现对 IDE 内部 PopupMenu 的挂接操作,通过修改菜单的
  27. * OnPopup 事件,在弹出前先删除自定义的菜单,执行原来的 OnPopup 后再重
  28. * 新增加定义的菜单,以实现自定义菜单的功能。
  29. * 之所以采用该方法,是因为直接修改 PopupMenu 在 IDE 中可能会导致出错。
  30. * 单元提供了以下类:
  31. * - TCnAbstractMenuItemDef
  32. * 抽象的用户菜单项基类,如果需要特别定制的菜单处理服务,可以自己
  33. * 从该类中派生。
  34. * - TCnMenuItemDef
  35. * 普通的用户菜单项类,可以满足绝大部分需要,使用时直接创建该类实
  36. * 例并注册到管理器中即可。
  37. * - TCnSepMenuItemDef
  38. * 用来生成一个分隔菜单项。
  39. * - TCnMenuHook
  40. * 菜单管理器,用于管理一组相同功能的菜单,如代码编辑器可能会有多
  41. * 个实例,每个实例都有一个 PopupMenu,这样就可以用一个管理器来管
  42. * 理。管理器提供了挂接 PopupMenu 方法、注册自定义菜单项以及其它
  43. * 服务。
  44. * 开发平台:PWin2000Pro + Delphi 5.01
  45. * 兼容测试:PWin9X/2000/XP + Delphi 5/6/7 + C++Builder 5/6
  46. * 本 地 化:该单元中的字符串支持本地化处理方式
  47. * 单元标识:$Id$
  48. * 修改记录:2003.10.11
  49. * 修改部分标识符,使之更容易理解,增加注释
  50. * 2003.05.01
  51. * 创建单元
  52. ================================================================================
  53. |</PRE>}
  54. interface
  55. {$I CnPack.inc}
  56. uses
  57. Windows, Messages, SysUtils, Classes, Forms, ActnList, Menus, Contnrs,
  58. CnConsts, CnClasses, CnCompConsts;
  59. type
  60. //==============================================================================
  61. // 抽象的用户菜单项基类
  62. //==============================================================================
  63. { TCnAbstractMenuItemDef }
  64. TCnMenuItemInsertPos = (ipFirst, ipLast, ipAfter, ipBefore);
  65. TCnMenuItemStatus = set of (msVisible, msEnabled, msChecked);
  66. TCnAbstractMenuItemDef = class(TObject)
  67. private
  68. FActive: Boolean;
  69. protected
  70. function GetName: string; virtual; abstract;
  71. function GetInsertPos: TCnMenuItemInsertPos; virtual; abstract;
  72. function GetRelItemName: string; virtual; abstract;
  73. function GetCaption: string; virtual; abstract;
  74. function GetHint: string; virtual; abstract;
  75. function GetStatus : TCnMenuItemStatus; virtual; abstract;
  76. function GetAction: TCustomAction; virtual; abstract;
  77. procedure MenuItemCreated(MenuItem: TMenuItem); virtual; abstract;
  78. {* 当用户菜单项被创建后调用该方法}
  79. public
  80. procedure Execute(Sender: TObject); virtual; abstract;
  81. {* 菜单项执行方法}
  82. property Active: Boolean read FActive write FActive;
  83. {* 菜单项定义是否有效,如果无效,则菜单不会自动创建}
  84. property Name: string read GetName;
  85. {* 菜单项的组件名}
  86. property InsertPos: TCnMenuItemInsertPos read GetInsertPos;
  87. {* 用户菜单项的插入位置}
  88. property RelItemName: string read GetRelItemName;
  89. {* 当 InsertPos 为 ipAfter, ipBefore 时,相对的原菜单名}
  90. property Caption: string read GetCaption;
  91. {* 菜单项的标题}
  92. property Hint: string read GetHint;
  93. {* 菜单项的提示信息}
  94. property Status: TCnMenuItemStatus read GetStatus;
  95. {* 菜单项的状态}
  96. property Action: TCustomAction read GetAction;
  97. {* 菜单项对应的 Action}
  98. end;
  99. //==============================================================================
  100. // 普通的用户菜单项类
  101. //==============================================================================
  102. { TCnMenuItemDef }
  103. TMenuItemCreatedEvent = procedure (Sender: TObject; MenuItem: TMenuItem) of object;
  104. TCnMenuItemDef = class(TCnAbstractMenuItemDef)
  105. private
  106. FName: string;
  107. FInsertPos: TCnMenuItemInsertPos;
  108. FRelItemName: string;
  109. FCaption: string;
  110. FHint: string;
  111. FAction: TCustomAction;
  112. FStatus: TCnMenuItemStatus;
  113. FOnClick: TNotifyEvent;
  114. FOnCreated: TMenuItemCreatedEvent;
  115. protected
  116. function GetName: string; override;
  117. function GetInsertPos: TCnMenuItemInsertPos; override;
  118. function GetRelItemName: string; override;
  119. function GetCaption: string; override;
  120. function GetHint: string; override;
  121. function GetStatus: TCnMenuItemStatus; override;
  122. function GetAction: TCustomAction; override;
  123. procedure MenuItemCreated(MenuItem: TMenuItem); override;
  124. public
  125. constructor Create(const AName, ACaption: string; AOnClick: TNotifyEvent;
  126. AInsertPos: TCnMenuItemInsertPos; const ARelItemName: string = '';
  127. const AHint: string = ''; AAction: TCustomAction = nil);
  128. destructor Destroy; override;
  129. procedure Execute(Sender: TObject); override;
  130. procedure SetCaption(const Value: string);
  131. {* 设置菜单标题}
  132. procedure SetHint(const Value: string);
  133. {* 设置菜单提示信息}
  134. property OnClick: TNotifyEvent read FOnClick write FOnClick;
  135. {* 菜单点击事件}
  136. property OnCreated: TMenuItemCreatedEvent read FOnCreated write FOnCreated;
  137. {* 当菜单项被动态创建之后调用,用户可以在该事件中修改菜单属性}
  138. end;
  139. //==============================================================================
  140. // 分隔菜单项类
  141. //==============================================================================
  142. { TCnSepMenuItemDef }
  143. TCnSepMenuItemDef = class(TCnMenuItemDef)
  144. public
  145. constructor Create(AInsertPos: TCnMenuItemInsertPos; const ARelItemName: string);
  146. end;
  147. //==============================================================================
  148. // 被挂接的 TPopupMenu 菜单对象数据类
  149. //==============================================================================
  150. { TMenuObj }
  151. TMenuObj = class(TObject)
  152. private
  153. FOldOnPopup: TNotifyEvent;
  154. FMenu: TPopupMenu;
  155. public
  156. constructor Create(AMenu: TPopupMenu; NewOnPopup: TNotifyEvent);
  157. destructor Destroy; override;
  158. property Menu: TPopupMenu read FMenu;
  159. property OldOnPopup: TNotifyEvent read FOldOnPopup;
  160. end;
  161. //==============================================================================
  162. // 菜单挂接管理器
  163. //==============================================================================
  164. { TCnMenuHook }
  165. TMenuPopupEvent = procedure (Sender: TObject; Menu: TPopupMenu) of object;
  166. TCnMenuHook = class(TCnComponent)
  167. private
  168. FMenuList: TObjectList;
  169. FMenuItemDefList: TObjectList;
  170. FActive: Boolean;
  171. FOnAfterPopup: TMenuPopupEvent;
  172. FOnBeforePopup: TMenuPopupEvent;
  173. procedure SetActive(const Value: Boolean);
  174. function GetMenuItemDef(Index: Integer): TCnAbstractMenuItemDef;
  175. function GetMenuItemDefCount: Integer;
  176. protected
  177. function GetMenuObj(Menu: TPopupMenu): TMenuObj;
  178. procedure OnMenuPopup(Sender: TObject); virtual;
  179. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  180. function FindMenuItem(AMenu: TPopupMenu; const AName: string): TMenuItem;
  181. procedure DoRemoveMenuItem(AMenu: TPopupMenu; const AName: string);
  182. procedure DoAddMenuItem(AMenu: TPopupMenu; Item: TCnAbstractMenuItemDef);
  183. procedure GetComponentInfo(var AName, Author, Email, Comment: string); override;
  184. public
  185. constructor Create(AOwner: TComponent); override;
  186. destructor Destroy; override;
  187. procedure HookMenu(AMenu: TPopupMenu);
  188. {* 挂接一个 PopupMenu 菜单}
  189. procedure UnHookMenu(AMenu: TPopupMenu);
  190. {* 取消对 PopupMenu 菜单的挂接}
  191. function IsHooked(AMenu: TPopupMenu): Boolean;
  192. {* 判断 PopupMenu 菜单是否已经挂接}
  193. function AddMenuItemDef(Item: TCnAbstractMenuItemDef): Integer;
  194. {* 增加一个用户菜单项定义,返回列表索引号}
  195. procedure RemoveMenuItemDef(Item: TCnAbstractMenuItemDef);
  196. {* 移去一个用户菜单项定义}
  197. function IndexOfMenuItemDef(const AName: string): Integer;
  198. {* 查找指定菜单在列表中的索引号}
  199. property Active: Boolean read FActive write SetActive;
  200. {* 菜单挂接活跃属性}
  201. property MenuItemDefCount: Integer read GetMenuItemDefCount;
  202. {* 用户菜单项定义计数}
  203. property MenuItemDefs[Index: Integer]: TCnAbstractMenuItemDef read GetMenuItemDef;
  204. {* 用户菜单项定义数组}
  205. property OnBeforePopup: TMenuPopupEvent read FOnBeforePopup write FOnBeforePopup;
  206. {* 被挂接的菜单弹出前事件,此时用户菜单项已经释放,用户可在此进行特别的处理}
  207. property OnAfterPopup: TMenuPopupEvent read FOnAfterPopup write FOnAfterPopup;
  208. {* 被挂接的菜单弹出后事件,此时用户菜单项已经创建,用户可在此进行特别的处理}
  209. end;
  210. implementation
  211. const
  212. csMenuItemTag = $8080;
  213. //==============================================================================
  214. // 普通的用户菜单项类
  215. //==============================================================================
  216. { TCnMenuItemDef }
  217. constructor TCnMenuItemDef.Create(const AName, ACaption: string;
  218. AOnClick: TNotifyEvent; AInsertPos: TCnMenuItemInsertPos; const ARelItemName,
  219. AHint: string; AAction: TCustomAction);
  220. begin
  221. inherited Create;
  222. FActive := True;
  223. FStatus := [msVisible, msEnabled];
  224. FName := AName;
  225. FCaption := ACaption;
  226. FOnClick := AOnClick;
  227. FInsertPos := AInsertPos;
  228. FRelItemName := ARelItemName;
  229. FHint := AHint;
  230. FAction := AAction;
  231. FOnCreated := nil;
  232. end;
  233. destructor TCnMenuItemDef.Destroy;
  234. begin
  235. inherited;
  236. end;
  237. procedure TCnMenuItemDef.Execute(Sender: TObject);
  238. begin
  239. if Assigned(FOnClick) then
  240. FOnClick(Sender);
  241. end;
  242. function TCnMenuItemDef.GetAction: TCustomAction;
  243. begin
  244. Result := FAction;
  245. end;
  246. function TCnMenuItemDef.GetCaption: string;
  247. begin
  248. Result := FCaption;
  249. end;
  250. function TCnMenuItemDef.GetHint: string;
  251. begin
  252. Result := FHint;
  253. end;
  254. function TCnMenuItemDef.GetInsertPos: TCnMenuItemInsertPos;
  255. begin
  256. Result := FInsertPos;
  257. end;
  258. function TCnMenuItemDef.GetName: string;
  259. begin
  260. Result := FName;
  261. end;
  262. function TCnMenuItemDef.GetRelItemName: string;
  263. begin
  264. Result := FRelItemName;
  265. end;
  266. function TCnMenuItemDef.GetStatus: TCnMenuItemStatus;
  267. begin
  268. Result := FStatus;
  269. end;
  270. procedure TCnMenuItemDef.SetCaption(const Value: string);
  271. begin
  272. FCaption := Value;
  273. end;
  274. procedure TCnMenuItemDef.SetHint(const Value: string);
  275. begin
  276. FHint := Value;
  277. end;
  278. procedure TCnMenuItemDef.MenuItemCreated(MenuItem: TMenuItem);
  279. begin
  280. if Assigned(FOnCreated) then
  281. FOnCreated(Self, MenuItem);
  282. end;
  283. //==============================================================================
  284. // 分隔菜单项类
  285. //==============================================================================
  286. { TCnSepMenuItemDef }
  287. constructor TCnSepMenuItemDef.Create(AInsertPos: TCnMenuItemInsertPos;
  288. const ARelItemName: string);
  289. begin
  290. inherited Create('', '-', nil, AInsertPos, ARelItemName, '', nil);
  291. end;
  292. //==============================================================================
  293. // 被挂接的 TPopupMenu 菜单对象数据类
  294. //==============================================================================
  295. { TMenuObj }
  296. constructor TMenuObj.Create(AMenu: TPopupMenu; NewOnPopup: TNotifyEvent);
  297. begin
  298. inherited Create;
  299. FMenu := AMenu;
  300. FOldOnPopup := FMenu.OnPopup;
  301. FMenu.OnPopup := NewOnPopup;
  302. end;
  303. destructor TMenuObj.Destroy;
  304. begin
  305. FMenu.OnPopup := FOldOnPopup;
  306. inherited;
  307. end;
  308. //==============================================================================
  309. // 菜单挂接管理器
  310. //==============================================================================
  311. { TCnMenuHook }
  312. constructor TCnMenuHook.Create(AOwner: TComponent);
  313. begin
  314. inherited;
  315. FMenuList := TObjectList.Create;
  316. FMenuItemDefList := TObjectList.Create;
  317. FActive := True;
  318. FOnAfterPopup := nil;
  319. FOnBeforePopup := nil;
  320. end;
  321. destructor TCnMenuHook.Destroy;
  322. begin
  323. FMenuItemDefList.Free;
  324. FMenuList.Free;
  325. inherited;
  326. end;
  327. //------------------------------------------------------------------------------
  328. // 菜单项处理
  329. //------------------------------------------------------------------------------
  330. function TCnMenuHook.FindMenuItem(AMenu: TPopupMenu;
  331. const AName: string): TMenuItem;
  332. var
  333. i: Integer;
  334. begin
  335. Result := nil;
  336. if (AMenu = nil) or (AName = '') then Exit;
  337. for i := 0 to AMenu.Items.Count - 1 do
  338. if SameText(AMenu.Items[i].Name, AName) then
  339. begin
  340. Result := AMenu.Items[i];
  341. Exit;
  342. end;
  343. end;
  344. procedure TCnMenuHook.DoAddMenuItem(AMenu: TPopupMenu;
  345. Item: TCnAbstractMenuItemDef);
  346. var
  347. MenuItem, RelItem: TMenuItem;
  348. Idx: Integer;
  349. begin
  350. Assert(Assigned(AMenu));
  351. Assert(Assigned(Item));
  352. if FActive and Item.Active then
  353. begin
  354. MenuItem := FindMenuItem(AMenu, Item.Name);
  355. if not Assigned(MenuItem) then
  356. begin
  357. MenuItem := TMenuItem.Create(AMenu);
  358. MenuItem.Name := Item.Name;
  359. RelItem := FindMenuItem(AMenu, Item.RelItemName);
  360. Idx := 0;
  361. case Item.InsertPos of
  362. ipFirst: Idx := 0;
  363. ipLast: Idx := AMenu.Items.Count;
  364. ipAfter:
  365. if Assigned(RelItem) then
  366. Idx := RelItem.MenuIndex + 1
  367. else
  368. Idx := AMenu.Items.Count;
  369. ipBefore:
  370. if Assigned(RelItem) then
  371. Idx := RelItem.MenuIndex
  372. else
  373. Idx := 0;
  374. end;
  375. AMenu.Items.Insert(Idx, MenuItem);
  376. end;
  377. // 定义一个 Tag,以标志没有 Name 的自定义菜单
  378. MenuItem.Tag := csMenuItemTag;
  379. MenuItem.Caption := Item.Caption;
  380. MenuItem.Hint := Item.Hint;
  381. MenuItem.Enabled := msEnabled in Item.Status;
  382. MenuItem.Visible := msVisible in Item.Status;
  383. MenuItem.Checked := msChecked in Item.Status;
  384. MenuItem.OnClick := Item.Execute;
  385. MenuItem.Action := Item.Action;
  386. Item.MenuItemCreated(MenuItem);
  387. end
  388. end;
  389. procedure TCnMenuHook.DoRemoveMenuItem(AMenu: TPopupMenu;
  390. const AName: string);
  391. var
  392. Item: TMenuItem;
  393. begin
  394. Item := FindMenuItem(AMenu, AName);
  395. if Assigned(Item) then
  396. Item.Free;
  397. end;
  398. //------------------------------------------------------------------------------
  399. // 菜单挂接处理
  400. //------------------------------------------------------------------------------
  401. function TCnMenuHook.GetMenuObj(Menu: TPopupMenu): TMenuObj;
  402. var
  403. i: Integer;
  404. begin
  405. for i := 0 to FMenuList.Count - 1 do
  406. if TMenuObj(FMenuList[i]).Menu = Menu then
  407. begin
  408. Result := TMenuObj(FMenuList[i]);
  409. Exit;
  410. end;
  411. Result := nil;
  412. end;
  413. procedure TCnMenuHook.HookMenu(AMenu: TPopupMenu);
  414. begin
  415. if not IsHooked(AMenu) then
  416. begin
  417. FMenuList.Add(TMenuObj.Create(AMenu, OnMenuPopup));
  418. AMenu.FreeNotification(Self);
  419. end;
  420. end;
  421. procedure TCnMenuHook.UnHookMenu(AMenu: TPopupMenu);
  422. var
  423. Obj: TMenuObj;
  424. begin
  425. Obj := GetMenuObj(AMenu);
  426. if Assigned(Obj) then
  427. begin
  428. Obj.Menu.RemoveFreeNotification(Self);
  429. FMenuList.Remove(Obj);
  430. end;
  431. end;
  432. function TCnMenuHook.IsHooked(AMenu: TPopupMenu): Boolean;
  433. begin
  434. Result := Assigned(GetMenuObj(AMenu));
  435. end;
  436. procedure TCnMenuHook.Notification(AComponent: TComponent;
  437. Operation: TOperation);
  438. begin
  439. inherited;
  440. if (Operation = opRemove) and (AComponent is TPopupMenu) then
  441. UnHookMenu(AComponent as TPopupMenu)
  442. end;
  443. //------------------------------------------------------------------------------
  444. // 新增菜单挂接项处理
  445. //------------------------------------------------------------------------------
  446. function TCnMenuHook.AddMenuItemDef(
  447. Item: TCnAbstractMenuItemDef): Integer;
  448. begin
  449. Result := FMenuItemDefList.IndexOf(Item);
  450. if Result < 0 then
  451. Result := FMenuItemDefList.Add(Item);
  452. end;
  453. procedure TCnMenuHook.RemoveMenuItemDef(Item: TCnAbstractMenuItemDef);
  454. begin
  455. FMenuItemDefList.Remove(Item);
  456. end;
  457. function TCnMenuHook.IndexOfMenuItemDef(
  458. const AName: string): Integer;
  459. var
  460. i: Integer;
  461. begin
  462. for i := 0 to MenuItemDefCount - 1 do
  463. if SameText(MenuItemDefs[i].Name, AName) then
  464. begin
  465. Result := i;
  466. Exit;
  467. end;
  468. Result := -1;
  469. end;
  470. function TCnMenuHook.GetMenuItemDefCount: Integer;
  471. begin
  472. Result := FMenuItemDefList.Count;
  473. end;
  474. function TCnMenuHook.GetMenuItemDef(
  475. Index: Integer): TCnAbstractMenuItemDef;
  476. begin
  477. Result := TCnAbstractMenuItemDef(FMenuItemDefList[Index]);
  478. end;
  479. procedure TCnMenuHook.OnMenuPopup(Sender: TObject);
  480. var
  481. Menu: TPopupMenu;
  482. MenuObj: TMenuObj;
  483. i: Integer;
  484. begin
  485. if not (Sender is TPopupMenu) then
  486. Exit;
  487. Menu := Sender as TPopupMenu;
  488. // 必须先把以前注册的菜单清掉,否则会出错
  489. for i := 0 to MenuItemDefCount - 1 do
  490. DoRemoveMenuItem(Menu, MenuItemDefs[i].Name);
  491. // 根据 Tag 移去没有名字的菜单项
  492. for i := Menu.Items.Count - 1 downto 0 do
  493. if Menu.Items[i].Tag = csMenuItemTag then
  494. Menu.Items[i].Free;
  495. if Assigned(FOnBeforePopup) then
  496. FOnBeforePopup(Self, Menu);
  497. // 调用原来的事件
  498. MenuObj := GetMenuObj(Menu);
  499. if Assigned(MenuObj) then
  500. if Assigned(MenuObj.OldOnPopup) then
  501. MenuObj.OldOnPopup(Sender);
  502. // 如果菜单项本身没有内容,则说明不会弹出,此处也不添加内容,避免强行弹出
  503. if Menu.Items.Count = 0 then
  504. Exit;
  505. if Active then
  506. begin
  507. // 重新更新自定义菜单项
  508. for i := 0 to MenuItemDefCount - 1 do
  509. if MenuItemDefs[i].Active then
  510. DoAddMenuItem(Menu, MenuItemDefs[i]);
  511. if Assigned(FOnAfterPopup) then
  512. FOnAfterPopup(Self, Menu);
  513. end;
  514. end;
  515. procedure TCnMenuHook.SetActive(const Value: Boolean);
  516. begin
  517. FActive := Value;
  518. end;
  519. procedure TCnMenuHook.GetComponentInfo(var AName, Author, Email,
  520. Comment: string);
  521. begin
  522. AName := SCnMenuHookName;
  523. Author := SCnPack_Zjy;
  524. Email := SCnPack_ZjyEmail;
  525. Comment := SCnMenuHookComment;
  526. end;
  527. end.