Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace TCustomEdit context menu with my own

Tags:

windows

delphi

I want to replace all the popup menus displayed by delphi in the TCustomEdit components like TEdit or TMemo using my own popup menu (which has a lot of more actions). So far I replace the PopUpMenu property of each component manually with my own TPopUpMenu. but I wondering if I can do this without modify this property manually for each component in all my forms.

I want something like a hook to intercept the calls to this system menu and replace for my own menu. is this possible?

enter image description here

like image 663
Salvador Avatar asked Mar 09 '12 00:03

Salvador


3 Answers

If your Forms derives from a common ancestor (rather than the default TForm) for example TMyBaseForm, meaning TForm1 = class(TMyBaseForm) this could be done easy. In the TMyBaseForm.OnShow event you could iterate through it's controls, and if you find a TEdit or TMemo you set their PopupMenu property dynamically.

Another way is to use Screen.OnActiveFormChange (Screen.OnActiveControlChange fires too late if you right-click on the active control - EDIT: This is True only with D5) in your main Form event handler to capture the active form and iterate through the Screen.ActiveForm controls and set TEdit or TMemo property PopupMenu to your custom 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;    

To determine which TCustomEdit control caused the Popupmenu to pop-up refer to the MyPopupMenu.PopupComponent (in the MyPopupMenu.OnPopup event):

procedure TForm1.MyPopupMenuPopup(Sender: TObject);
begin
  if MyPopupMenu.PopupComponent is TCustomEdit then
  begin
    FEditPopupControl := TCustomEdit(MyPopupMenu.PopupComponent);
    Caption := FEditPopupControl.Name; // debug :-P
  end;
end;

EDIT: Screen.OnActiveControlChange was my initial thought. I have tested it in D5. if Edit1 is focused and I right-click on Edit2, it will first pop-up the default menu, only then it becomes the active control. I finally tested this with D7 and D2009. both works just fine. This is a D5 issue only so Justmade's answer is surely a better solution than using Screen.OnActiveFormChange.

like image 175
kobik Avatar answered Nov 09 '22 12:11

kobik


In your main form, add the following code. It should apply to all your form's custom control.

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;

It is just a simplified version (in the point of view of coding) of kobik's answer and will also address any TCustomEdit that are created by code or other complex Controls which do not use the Form as Owner.

His instruction on how to determine which CustomEdit popup apply.

Edit : Add Grid InplaceEditor Support

like image 42
Justmade Avatar answered Nov 09 '22 14:11

Justmade


You could assign a single OnContextPopup event handler to all of the edit controls, have it call the Popup() method of the TPopupMenu, and set the event's Handled parameter to True. But that is not much different than just assigning the TPopupMenu to all of the edit controls directly.

To take it a step further, you could assign a single OnContextPopup event handler to your parent TForm instead of the individual edit controls. The event tells you the mouse coordinates when the menu is being invoked by mouse. You can locate the child control underneath those coordinates, and if it is one of your edit conrols then call Popup() and set Handled to True. The user can invoke menus by keyboard instead, in which case the mouse coordinates will be {-1, -1}, so use the TScreen.ActiveControl property to know which control is being invoked on.

like image 23
Remy Lebeau Avatar answered Nov 09 '22 13:11

Remy Lebeau