有一种解决方法,但它非常非常肮脏:使用破解程序类来获取对TPopupMenu.Items菜单项属性的FHandle私有成员的访问权限。
破解程序类涉及复制目标类的私有存储布局,直到并包括感兴趣的私有成员,并使用类型转换将该类型“覆盖”到上下文中的目标类型实例上,然后允许您访问目标的内部存储。
在这种情况下,目标对象是TPopupMenu的Items属性,它是TMenuItem的一个实例。 TMenuItem派生自TComponent ,因此为TMenuItem提供对FHandle的访问的破解程序类是:
type
// Here be dragons...
TMenuItemCracker = class(TComponent)
private
FCaption: string;
FChecked: Boolean;
FEnabled: Boolean;
FDefault: Boolean;
FAutoHotkeys: TMenuItemAutoFlag;
FAutoLineReduction: TMenuItemAutoFlag;
FRadioItem: Boolean;
FVisible: Boolean;
FGroupIndex: Byte;
FImageIndex: TImageIndex;
FActionLink: TMenuActionLink;
FBreak: TMenuBreak;
FBitmap: TBitmap;
FCommand: Word;
FHelpContext: THelpContext;
FHint: string;
FItems: TList;
FShortCut: TShortCut;
FParent: TMenuItem;
FMerged: TMenuItem;
FMergedWith: TMenuItem;
FMenu: TMenu;
FStreamedRebuild: Boolean;
FImageChangeLink: TChangeLink;
FSubMenuImages: TCustomImageList;
FOnChange: TMenuChangeEvent;
FOnClick: TNotifyEvent;
FOnDrawItem: TMenuDrawItemEvent;
FOnAdvancedDrawItem: TAdvancedMenuDrawItemEvent;
FOnMeasureItem: TMenuMeasureItemEvent;
FAutoCheck: Boolean;
FHandle: TMenuHandle;
end;
注意:由于这种技术依赖于目标类内部存储布局的精确复制,因此破解程序声明可能需要包含$IFDEF变体,以适应不同 Delphi 版本之间内部布局的变化。上面的声明对于Delphi XE4是正确的,应该对照 TMenuItem 源检查其他Delphi 版本的正确性。
有了这个破解器类,我们就可以提供一个实用程序 proc 来包装我们将使用它提供的访问权限执行的讨厌的技巧。在这种情况下,我们可以像往常一样清除菜单项,但也可以自己调用DestroyMenu()使用cracker cast 用 0 覆盖FHandle成员变量,因为它现在无效并且需要为 0 才能强制TPopupMenu重新创建菜单下次需要时:
procedure ResetPopupMenu(const aMenu: TPopupMenu);
begin
aMenu.Items.Clear;
// Here be dragons...
DestroyMenu(aMenu.Items.Handle);
TMenuItemCracker(aMenu.Items).FHandle := 0;
end;
在您的示例代码中,只需将您的 Button2Click 处理程序中对 PopupMenu1.Items.Clear 的调用替换为对ResetPopupMenu ( PopupMenu1 )的调用。
不用说,这是极端危险的。例如,除了在类的私有存储中到处乱搞的纯粹疯狂之外,在这种特定情况下,没有考虑取消合并的菜单。
但是您问是否有解决方法,这里至少有一个。:)
与简单地销毁和重新创建TPopupMenu相比,您是否认为这更实用或更可取取决于您。类破解是一种可以帮助您摆脱困境的技术,否则可能无法解决,但绝对应该被视为“最后的手段”!