我想用我自己的popup菜单(它有很多更多的动作)replaceTCditom或TMemo等TCustomEdit组件中的delphi显示的所有popup式菜单。 到目前为止,我用我自己的TPopUpMenu手动replace每个组件的PopUpMenu属性。 但我想知道如果我可以做到这一点,而不需要手动修改我的所有表单中的每个组件的这个属性。
我想像一个钩子拦截到这个系统菜单的调用,并取代我自己的菜单。 这可能吗?
在您的主窗体中添加以下代码。 它应该适用于所有表单的自定义控件。
TForm2 = class(TForm) procedure FormCreate(Sender: TObject); private procedure ActiveControlChanged(Sender: TObject); end; implementation type TCustomEditAccess = class(TCustomEdit); TCustomGridAccess = class(TCustomGrid); procedure TForm2.ActiveControlChanged(Sender: TObject); begin if (Screen.ActiveControl is TCustomEdit) and not Assigned(TCustomEditAccess(Screen.ActiveControl).PopupMenu) then TCustomEditAccess(Screen.ActiveControl).PopupMenu := MyPopupMenu else if (Screen.ActiveControl is TCustomGrid) then begin TCustomGridAccess(Screen.ActiveControl).ShowEditor; if Assigned(TCustomGridAccess(Screen.ActiveControl).InplaceEditor) then TCustomEditAccess(TCustomGridAccess(Screen.ActiveControl).InplaceEditor).PopupMenu := MyPopupMenu; end; end; procedure TForm2.FormCreate(Sender: TObject); begin Screen.OnActiveControlChange := ActiveControlChanged; end;
这只是kobik答案的一个简化版本(从编码的角度来看),也将解决由代码或其他复杂控件创建的任何TCustomEdit,这些控件不使用表单作为所有者。
他的指导如何确定哪个CustomEdit弹出应用。
编辑:添加网格InplaceEditor支持
如果你的表单来自一个共同的祖先(而不是默认的TForm
),例如TMyBaseForm
,这意味着TForm1 = class(TMyBaseForm)
这可以很容易地完成。 在TMyBaseForm.OnShow
事件中,您可以遍历它的控件,如果您发现TEdit
或TMemo
可以动态设置其PopupMenu
属性。
另一种方法是使用Screen.OnActiveFormChange
( Screen.OnActiveControlChange
触发太晚,如果您右键单击活动控件 – 编辑:这只是与D5真 ),在您的主窗体事件处理程序捕获活动窗体并迭代通过Screen.ActiveForm
控制并将TEdit
或TMemo
属性PopupMenu
为您的自定义MyPopupMenu
:
procedure TForm1.FormCreate(Sender: TObject); begin Screen.OnActiveFormChange := ActiveFormChange; end; procedure TForm1.ActiveFormChange(Sender: TObject); begin CustomEditControlsNormalize(Screen.ActiveForm); end; type TCustomEditAccess = class(TCustomEdit); procedure TForm1.CustomEditControlsNormalize(F: TForm); var I: Integer; begin if not Assigned(F) then Exit; for I := 0 to F.ComponentCount - 1 do if F.Components[I] is TCustomEdit then TCustomEditAccess(F.Components[I]).Popupmenu := MyPopupMenu; end;
要确定哪个TCustomEdit控件导致Popupmenu弹出,请参阅MyPopupMenu.PopupComponent
(在MyPopupMenu.OnPopup
事件中):
procedure TForm1.MyPopupMenuPopup(Sender: TObject); begin if MyPopupMenu.PopupComponent is TCustomEdit then begin FEditPopupControl := TCustomEdit(MyPopupMenu.PopupComponent); Caption := FEditPopupControl.Name; // debug :-P end; end;
编辑: Screen.OnActiveControlChange
是我最初的想法。 我已经在D5中测试过了。 如果Edit1被关注,我右键单击Edit2,它将首先弹出默认菜单,只有这样它才成为主动控件。 我终于用D7和D2009测试了这个。 两者都很好。 这只是一个D5的问题,所以Justmade的答案肯定比使用Screen.OnActiveFormChange
更好的解决方案。
您可以将一个OnContextPopup
事件处理程序分配给所有编辑控件,让它调用TPopupMenu
的Popup()
方法,并将事件的Handled
参数设置为True
。 但是,与直接将TPopupMenu
分配给所有编辑控件并没有什么不同。
为了进一步,您可以将一个OnContextPopup
事件处理程序分配给您的父TForm
而不是单独的编辑控件。 当鼠标调用菜单时,事件告诉你鼠标的坐标。 您可以找到这些坐标下的子控件,如果它是您的编辑控件之一,则调用Popup()
并将Handled
设置为True。 用户可以通过键盘来调用菜单,在这种情况下,鼠标坐标将是{-1, -1}
,所以使用TScreen.ActiveControl
属性来知道正在调用哪个控件。
您可以直接在TEdit或TMemo类的NewInstance
方法的已安装挂钩上执行Popup分配。 有了这个技术,你只需要增加一个单元来安装钩子。 该钩子的代码会将您的自定义TPopupMenu对象分配给在您的应用程序中创建的类TEdit
和TMemo
的每个组件的PopupMenu
属性。
首先,在全局TDatamodule或主表单中放置一个TPopupMenu对象。 这里的关键点是,PopupMenu父级应始终可用,并且是在应用程序初始化时创建的第一个,或者至少在安装钩子之前创建的。
然后,创建一个空的新单位。 无论你想要什么来调用它。 在我的情况下popup_assignment.pas
。 来源是这样的:
unit popup_assignment; interface uses Windows, StdCtrls; implementation uses globaldatamodule; // Unit of global TPopupMenu parent {------------------------------------------------------------------------------} function TEditNewInstance(AClass: TClass): TObject; begin Result := TEdit.NewInstance; TEdit(Result).PopupMenu := global_datamodule.customedit_popupmenu; // <- your global TPopupMenu component !!! end; function TMemoNewInstance(AClass: TClass): TObject; begin Result := TMemo.NewInstance; TMemo(Result).PopupMenu := global_datamodule.customedit_popupmenu; // <- your global TPopupMenu component !!! end; function GetVirtualMethod(AClass: TClass; const VmtOffset: Integer): Pointer; begin Result := PPointer(Integer(AClass) + VmtOffset)^; end; procedure SetVirtualMethod(AClass: TClass; const VmtOffset: Integer; const Method: Pointer); var WrittenBytes: DWORD; PatchAddress: PPointer; begin PatchAddress := Pointer(Integer(AClass) + VmtOffset); WriteProcessMemory(GetCurrentProcess, PatchAddress, @Method, SizeOf(Method), WrittenBytes); end; {$IFOPT W+}{$DEFINE WARN}{$ENDIF}{$WARNINGS OFF} // no compiler warning const vmtNewInstance = System.vmtNewInstance; {$IFDEF WARN}{$WARNINGS ON}{$ENDIF} var orgTEditNewInstance: Pointer; orgTMemoNewInstance: Pointer; initialization orgTEditNewInstance := GetVirtualMethod(TEdit, vmtNewInstance); orgTMemoNewInstance := GetVirtualMethod(TMemo, vmtNewInstance); SetVirtualMethod(TEdit, vmtNewInstance, @TEditNewInstance); SetVirtualMethod(TMemo, vmtNewInstance, @TMemoNewInstance); finalization SetVirtualMethod(TEdit, vmtNewInstance, OrgTEditNewInstance); SetVirtualMethod(TMemo, vmtNewInstance, orgTMemoNewInstance); end.
将一个TApplicationEvents组件添加到您的delphi应用程序中。 做你自己的popupmenu(popupmenu1)? 在TApplicationEvents组件的OnMessage中,添加以下代码:
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean); var ctrl: TWincontrol; begin if (msg.Message = WM_RBUTTONUP) or (msg.Message = WM_KEYUP ) then begin ctrl := FindControl(Msg.hwnd); if ctrl <> nil then begin if ((ctrl is TEdit)) then begin (ctrl as TEdit).PopupMenu := Popupmenu1; end; if ((ctrl is TMemo)) then begin (ctrl as TMemo).PopupMenu := Popupmenu1; end; end; end; end;
这将拦截右键,如果当时在你的mousecursor下有一个TEdit或TMemo,它会将popup菜单链接到该组件并启动它。
其他可能性:
使用可用的专家功能:
最复杂 – 实现TForm后代女巫设计时拖放和修改掉落控件的PupupMenu属性。
丑陋但灵活,没有任何后裔控制执行 – 使用下面的程序:
procedure CustomizePopupMenu( const aCtrl: TWinControl; const aClasses: array of TControlClass; const aPopUp: TPopupMenu); procedure Process(const aCtrl: TWinControl; const aClasses: array of TControlClass; const aPopUp: TPopupMenu); procedure Match(const aCtrl: TControl; const aClasses: array of TControlClass; const aPopUp: TPopupMenu); var Ix: Integer; begin for Ix := Low(aClasses) to High(aClasses) do begin if aCtrl.InheritsFrom(aClasses[Ix]) then aCtrl.PopupMenu:= aPopUp; end; end; var Ix: Integer; Ctrl: TControl; begin for Ix := 0 to Pred(aCtrl.ControlCount) do begin if aCtrl.Controls[Ix] is TWinControl then Process(TWinControl(aCtrl.Controls[Ix]), aClasses, aPopUp); Match(aCtrl.Controls[Ix], aClasses, aPopUp) end; end; begin if (aCtrl <> nil) and (Length(aClasses) > 0) and (aPopUp <> nil) then Process(aCtrl, aClasses, aPopUp) end;