Замените контекстное меню TCustomEdit своим собственным

Я хочу заменить все всплывающие меню, отображаемые delphi в компонентах TCustomEdit, таких как TEdit или TMemo, используя мое собственное всплывающее меню (в котором много действий). Пока я заменяю свойство PopUpMenu каждого компонента вручную своим собственным TPopUpMenu. но мне интересно, могу ли я сделать это без изменения этого свойства вручную для каждого компонента во всех моих формах.

Я хочу что-то вроде крюка, чтобы перехватить вызовы в это системное меню и заменить его для моего собственного меню. возможно ли это?

enter image description here

Ответ 1

В вашей основной форме добавьте следующий код. Он должен применяться ко всем пользовательским элементам вашей формы.

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

Ответ 2

Если ваши Формы происходят от общего предка (а не по умолчанию TForm), например TMyBaseForm, что означает TForm1 = class(TMyBaseForm), это можно сделать легко. В событии TMyBaseForm.OnShow вы можете выполнить итерацию через элементы управления, и если вы найдете TEdit или TMemo, вы установите динамическое свойство PopupMenu.

Другим способом является использование Screen.OnActiveFormChange (Screen.OnActiveControlChangeсрабатывает слишком поздно, если вы щелкните правой кнопкой мыши активный элемент управления - EDIT: это правда только с 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 вызвал всплывающее всплывающее окно, обратитесь к 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;

EDIT: Screen.OnActiveControlChange была моей первоначальной мыслью. Я тестировал его в D5. если Edit1 сфокусирован, и я нажимаю правой кнопкой мыши на Edit2, он сначала всплывает по умолчанию, только тогда он становится активным элементом управления. Я, наконец, проверил это с D7 и D2009. оба работают отлично. Это проблема с D5, поэтому ответ Justmade, безусловно, лучшее решение, чем использование Screen.OnActiveFormChange.

Ответ 3

Вы можете назначить один обработчик событий OnContextPopup всем элементам управления редактированием, вызвать метод Popup() TPopupMenu и установить для параметра Handled значение True. Но это не сильно отличается от простого назначения TPopupMenu всем элементам управления редактирования.

Чтобы сделать еще один шаг, вы можете назначить один обработчик события OnContextPopup для своего родителя TForm вместо отдельных элементов управления редактированием. Событие указывает координаты мыши, когда мышь вызывается меню. Вы можете найти дочерний элемент управления под этими координатами, и если это один из ваших править, вызовите Popup() и установите для параметра Handled значение True. Вместо этого пользователь может вызывать меню с помощью клавиатуры, и в этом случае координаты мыши будут {-1, -1}, поэтому используйте свойство TScreen.ActiveControl, чтобы знать, какой элемент управления вызывается.

Ответ 4

Вы можете выполнить назначение Popup непосредственно на установленном крюке в методе TEdit или TMemo Class 'NewInstance. С помощью этого tecnique вам нужно будет включить дополнительный блок с установкой крючка. Код hook присваивает вашему пользовательскому объекту TPopupMenu свойство PopupMenu каждого компонента класса TEdit и TMemo, созданного в вашем приложении.

Сначала оставьте объект TPopupMenu в глобальном TDatamodule или в вашей основной форме. Ключевым моментом здесь является то, что родитель 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.

Ответ 5

Добавьте компонент TApplicationEvents в ваше приложение delphi. Создайте собственное popupmenu (popupmenu1)? В компоненте OnMessage компонента TApplicationEvents добавьте следующий код:

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;

Это будет перехватывать rightclick, и если в это время есть TEdit или TMemo под вашим mousecursor, он свяжет popupmenu с этим компонентом и запустит его.

Ответ 6

Другие возможности:

  • Используйте доступные функции экспертов:

    • Используйте CnPack Property Corrector и определите действие, которое предложит вам указать указанный компонент.
    • Использовать фьючерсы на переименование/замену компонентов GExperts (требуется реализация ваших пользовательских элементов управления)

  • Самое сложное - реализовать TForm descendant witch время перетаскивания дизайна и изменить сброшенные элементы управления Свойство PupupMenu.

  • Ужасно, но гибко и без реализации контроля над потоками - используйте ниже процедуру:

    • CustomizePopupMenu (форма, [TEdit, TMemo], MyPopupMenu)
    • CustomizePopupMenu (AnyForm, [TEdit, TMemo], AnyPopupMenu)

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;