2
votes

I would like to have a TMemo which will always begin with the string 'SELECT c_name FROM ' and I want to lock it so users cannot remove or replace this string in the TMemo, they will have to write their text AFTER this string. Can someone help me with that ? I tried something with the onChange event but the problem is that users can click at the begining of the TMemo and edit it by the begining.

I'm using Delphi 6.

2
This is not possible with TMemo.David Heffernan
Why is it impossible with TMemo? Can't we use SelStart or something else?Moussamoa
Don't make life difficult for yourself. Just put two TMemos below each other (touching) with border bsNone, and make the top one read-only.Jan Doggen
It just is not possible. The control doesn't support partial read only. Yes you can change the text back every time the user changes it, but it will look quite revolting and I don't believe you will make it work robustly. Why don't you ask how to solve your problem rather than asking how to implement your solution?David Heffernan
What @jandoggen said: Don't put the "select ..." in the memo in the first place. And how are you ever going to protect the result from Sql Injection, never mind the support headaches you'll be making for yourself with users' home-rolled Sql? Better to get a 3rd party library that allows a Sql query to be built graphically - see e.g. devtools.korzh.com/easyqueryMartynA

2 Answers

10
votes

What you are asking for is not possible with TMemo. It is just a thin wrapper around a standard Win32 EDIT control, which does not support this kind of functionality.

You need to use TRichEdit instead. It supports protecting text like you have described. After adding the desired text, select it using the TRichEdit.SelStart and TRichEdit.SelLength properties and then set the TRichEdit.SelAttributes.Protected property to true. If the user tries to modify the protected text in any way, TRichEdit will reject the modification by default (you can override that decision by using the TRichEdit.OnProtectChange event to set the AllowChange parameter to true). For example:

RichEdit1.Text := 'SELECT c_name FROM ';
RichEdit1.SelStart := 0;
RichEdit1.SelLength := 19;
RichEdit1.SelAttributes.Protected := True;

Update: by default, protecting the text also prevents the user from copying it. If you want the user to be able to copy the protected text, you have to explicitly enable that. Although TRichEdit has a OnProtectChange event to allow access to protected text, it does not tell you WHY the text is requesting access. However, the underlying EN_PROTECTED notification does. So, you can subclass the TRichEdit to intercept EN_PROTECTED directly, and then return 0 (allow access) only when the user is copying the protected text.

interface

uses
  ...;

type
  TMyForm = class(TForm)
    RichEdit1: TRichEdit;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    DefRichEditWndProc: TWndMethod;
    procedure RichEditWndProc(var Message: TMessage);
  public
    { Public declarations }
  end;

...

implementation

uses
  Richedit;

procedure TMyForm.FormCreate(Sender: TObject);
begin
  DefRichEditWndProc := RichEdit1.WindowProc;
  RichEdit1.WindowProc := RichEditWndProc;

  RichEdit1.Text := 'SELECT c_name FROM ';
  RichEdit1.SelStart := 0;
  RichEdit1.SelLength := 19;
  RichEdit1.SelAttributes.Protected := True;
end;

procedure TMyForm.RichEditWndProc(var Message: TMessage);
begin
  DefRichEditWndProc(Message);
  if Message.Msg = CN_NOTIFY then
  begin
    if TWMNotify(Message).NMHdr.code = EN_PROTECTED then
    begin
      if PENProtected(Message.lParam).Msg = WM_COPY then
        Message.Result := 0;
    end;
  end;
end;

Alternatively:

interface

uses
  ...;

type
  TRichEdit = class(ComCtrls.TRichEdit)
  private
    procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
  end;

  TMyForm = class(TForm)
    RichEdit1: TRichEdit;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

...

implementation

uses
  Richedit;

procedure TMyForm.FormCreate(Sender: TObject);
begin
  RichEdit1.Text := 'SELECT c_name FROM ';
  RichEdit1.SelStart := 0;
  RichEdit1.SelLength := 19;
  RichEdit1.SelAttributes.Protected := True;
end;

procedure TRichEdit.CNNotify(var Message: TWMNotify);
begin
  inherited;
  if Message.NMHdr.code = EN_PROTECTED then
  begin
    if PENProtected(Message.NMHdr).Msg = WM_COPY then
      Message.Result := 0;
  end;
end;
2
votes

Well it is possible, but I don't know, if you like the solution.

As already said TMemo has not that that kind of behaviour implemented. So you have to program this behaviour.

Use the application OnIdle event and a memento for the content. On each Idle message validate memo content. If the memo content did not start with 'SELECT c_name FROM ' then assign the memento value, otherwise store the memo content to memento.

Here is an example for that validation and cursor protection

type
  TMainForm = class( TForm )
    Memo1: TMemo;
    ApplicationEvents1: TApplicationEvents; // OnIdle = ApplicationEvents1Idle
    procedure ApplicationEvents1Idle( Sender: TObject; var Done: Boolean );
  private
    FMemo1StartWith: string;
    FMemo1Memento  : string;
    procedure ValidateMemo( AMemo: TMemo; const AStartWith: string; var AMemento: string );
  public
    procedure AfterConstruction; override;

  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

procedure TMainForm.AfterConstruction;
begin
  inherited;
  FMemo1StartWith := 'SELECT c_name FROM ';
end;

procedure TMainForm.ApplicationEvents1Idle( Sender: TObject; var Done: Boolean );
begin
  ValidateMemo( Memo1, FMemo1StartWith, FMemo1Memento );
end;

procedure TMainForm.ValidateMemo( AMemo: TMemo; const AStartWith: string; var AMemento: string );
var
  lCurrentContent: string;
begin
  // protect content

  lCurrentContent := AMemo.Text;
  if Pos( AStartWith, lCurrentContent ) = 1
  then
    AMemento := lCurrentContent
  else if Pos( AStartWith, AMemento ) = 1
  then
    AMemo.Text := AMemento
  else
    AMemo.Text := AStartWith;

  // protect cursor position

  if ( AMemo.SelLength = 0 ) and ( AMemo.SelStart < Length( AStartWith ) )
  then
    AMemo.SelStart := Length( AStartWith );
end;