有一种解决方法,但它非常非常肮脏:使用破解程序类来获取对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相比,您是否认为这更实用或更可取取决于您。类破解是一种可以帮助您摆脱困境的技术,否则可能无法解决,但绝对应该被视为“最后的手段”!