Search code examples
delphipastedelphi-2007

Why dataset's state changes to dsEdit after suppressing WM_PASTE?


I'm intercepting and suppressing the WM_PASTE message for a TDBEdit by assigning its WindowProc property, as described in this answer.

After pressing Ctrl+V, despite the WM_PASTE is intercepted, the dataset's state changes from dsBrowse to dsEdit.

Why is this happening and how could I avoid that?

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, DBCtrls, StdCtrls, Mask, DB, DBClient;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FPrevWindowProc : TWndMethod;
    procedure   MyWindowProc(var AMessage: TMessage);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  Dst : TClientDataSet;
  Dsc : TDataSource;
  Fld : TField;
  Nav : TDBNavigator;
  Edt : TDBEdit;
begin
  //dataset
  Dst := TClientDataSet.Create(Self);
  Dst.FieldDefs.Add('TEST', ftString, 20);
  Dst.CreateDataSet();
  Dst.Active := True;
  Fld := Dst.Fields[0];
  Dst.Append();
  Fld.AsString := 'test';
  Dst.Post();

  //datasource
  Dsc := TDataSource.Create(Self);
  Dsc.DataSet := Dst;

  //navigator
  Nav := TDBNavigator.Create(Self);
  Nav.DataSource := Dsc;
  Nav.Top := 3;  
  Nav.Left := 3;
  Nav.Parent := Self;

  //editor
  Edt := TDBEdit.Create(Self);
  Edt.DataSource := Dsc;
  Edt.DataField := Fld.FieldName;
  Edt.Top := 31;
  Edt.Left := 3;
  Edt.Parent := Self;
  FPrevWindowProc := Edt.WindowProc;
  Edt.WindowProc := MyWindowProc;
end;

procedure   TForm1.MyWindowProc(var AMessage: TMessage);
begin
  if(AMessage.Msg = WM_PASTE) then
  begin
    ShowMessage('WM_PASTE, exit!');
    Exit;
  end;

  FPrevWindowProc(AMessage);
end;

end.

Solution

  • Using the interposer class solution in Remy's answer to your linked question, if you create a BeforeEdit handler for your DataSet and put a breakpoint in it, you will find that the breakpoint trips before the interposer's WMPaste() method is entered.

    If you then trace out of the BeforeEdit handler, you will eventually arrive in TDBEdit.KeyPress() which (in D7) contains the code below:

    procedure TDBEdit.KeyPress(var Key: Char);
    begin
      inherited KeyPress(Key);
      if (Key in [#32..#255]) and (FDataLink.Field <> nil) and
        not FDataLink.Field.IsValidChar(Key) then
      begin
        MessageBeep(0);
        Key := #0;
      end;
      case Key of
        ^H, ^V, ^X, #32..#255:
          FDataLink.Edit;
        #27:
          begin
            FDataLink.Reset;
            SelectAll;
            Key := #0;
          end;
      end;
    end;
    

    So, the DataSet is put into dsEdit state by the call to FDataLink.Edit() as a result of KeyPress() seeing the ^V character.

    You could achieve the behavior you want by also overriding KeyPress() in the interposer class. The following will prevent pressing ^V from having any effect:

    type  // This can be in your Form's unit but must go before your Form's type declaration
      TDBEdit = class(DBCtrls.TDBEdit)
        procedure WMPaste(var Message: TMessage); message WM_PASTE;
        procedure KeyPress(var Key: Char); override;
      end;
    [...]
    
    procedure TDBEdit.WMPaste(var Message: TMessage);
    begin
      if not (Message.Msg = WM_PASTE) then
        inherited;
    end;
    
    procedure TDBEdit.KeyPress(var Key: Char);
    begin
      case Key of
        ^V : Key := #0;
      end;  { case }
      inherited;
    end;