2

I have a TMemo on the form and I've set an OnChange event for it. I hope the OnChange event not to be triggered when the user presses Ctrl+X in the memo. But Ctrl+X just cuts the text selection, which will for sure trigger the OnChange event. How can I prevent that?

I've tried to detect Ctrl+X in the KeyUp event, and if the user pressed Ctrl+X I unbind the memo's OnChange event and programmatically cut the text again. But this doesn't work, and I don't how to programmatically send Ctrl+X.

procedure TForm1.Memo1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key = Ord('X')) and (Shift = [ssCtrl]) then
  begin
    Memo1.OnChange := nil;
    // programmatically cut the text here, which I don't know how to do
    Memo1.OnChange := Memo1Change;
  end;
end;
AmigoJack
  • 5,234
  • 1
  • 15
  • 31
Fajela Tajkiya
  • 629
  • 2
  • 10
  • Pressing [SHIFT+DEL also cuts to the clipboard](https://en.wikipedia.org/wiki/Cut,_copy,_and_paste#Common_keyboard_shortcuts) - that's the legacy shortcut which existed before CTRL+V was hyped and is still supported/must be expected. – AmigoJack Jan 19 '22 at 16:39
  • @AmigoJack Will the WM_CUT message cover it? – Fajela Tajkiya Jan 19 '22 at 22:38
  • Why not trying it out yourself? It should, just like right clicking and selecting "cut" from the context menu. – AmigoJack Jan 20 '22 at 08:41

1 Answers1

4

Don't rely on keyboard events (They are not executed for example when you cut something by using the popupmenu), rely on windows messages instead.

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    procedure Memo1Change(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FPrevMemoWindowProc : TWndMethod;
    procedure MemoWindowProc(var AMessage: TMessage);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  Clipbrd;

procedure TForm1.MemoWindowProc(var AMessage: TMessage);
begin
  if(AMessage.Msg = WM_CUT) then
  begin
    if(Memo1.SelLength > 0) then
    begin
      Memo1.OnChange := nil;
      try
        Clipboard.AsText := Memo1.SelText;
        Memo1.ClearSelection();
        Exit;
      finally
        Memo1.OnChange := Memo1Change;
      end;
    end;
  end;

  FPrevMemoWindowProc(AMessage);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FPrevMemoWindowProc := Memo1.WindowProc;
  Memo1.WindowProc := MemoWindowProc;
end;

procedure TForm1.Memo1Change(Sender: TObject);
begin
  ShowMessage('Change');
end;
Fabrizio
  • 7,603
  • 6
  • 44
  • 104
  • Thank you for your answer! I've tried it and ctrl x doesn't trigger OnChange event, which is cool. But by my testing, it seems the cut text isn't copied to clipboard. See this image: https://i.imgur.com/7dRf7XK.gif – Fajela Tajkiya Jan 19 '22 at 10:10
  • 1
    Because this code's logic is flawed: assigning `Clipboard.AsText` must come before assigning `Memo1.Text` - move it 2 lines up. As it stands now you'll always set the clipboard to an empty text, which can be pasted, too. No idea why 4 people upvote without seeing this mistake. – AmigoJack Jan 19 '22 at 10:20
  • That's why you should always test your code before posting an answer at SO! In any case, why make it so complicated? Why not simply do `Clipboard.AsText := Memo1.SelText; Memo1.ClearSelection;`? – Andreas Rejbrand Jan 19 '22 at 11:12
  • Thanks guys for the explanation. 95% percent of the answer is correct and I've learned a lot from it! – Fajela Tajkiya Jan 19 '22 at 11:43
  • I've updated the answer by applying the suggested fix and simplification – Fabrizio Jan 19 '22 at 13:26