Prologue
So, because of what all actually happened. I write plugins for LedearTask. Also, in 2017, he wrote a vector editor for the Turkish company of machines for the production of stamps MATUYA. With LeaderTask, with MATUYA, we put forward interesting requirements - vertical alignment in multiline editor, indents and TextHint. TextHint is meant - such a gray prompt to enter at least something when the input element is empty.
LeaderTask: Staircase of Goals plugin (input and hint in the center in the multiline editor)
Matuya Stamp Systems (text input aligned right and bottom)As is known, vertical alignment, unlike horizontal, in the standard Windows API for controls
does not exist .
Also, invitations to enter some information in multi-line Windows controls are
not supported .
')
Thus, a dilemma arises - and how? I would like to use standard components, but at the same time have expanded possibilities for entering textual information.
Small retreat
With some, quite remote, then, I do not write components and do not use third-party. In terms of the eternal deadline is much more profitable, faster and more economical to write everything yourself. What will be created in real-time. Those. use the rich features of the designer Delphi, but replace the classes "on the fly" in real-time. How it all looks, I hope to describe in more detail later. What does this mean in the current case, I will describe now.
What is needed
In essence, I need TMemo, in which there is a vertical alignment, say, the
Layout property, the
TextHint property - an “invitation” to enter some text. Also, you need to
indent from top to bottom-left-right to enter and display text so that it does not “stick” to the edges of the control.
Theory
Requires TMemo with additional features. Obviously, you have to write a successor from TMemo. Not from TCustomMemo, and certainly not from TCustomEdit. We need TMemo, because we are not going to write a library of components, but we want to make a project quickly and on time.
Vertical alignment is not available to us. Well, but we can set a text entry rectangle for controls. This is done
as follows .
In our case, it will look like this:
Perform(EM_SETRECT, 0, LPARAM(@ARect))
Where
ARect is the rectangle for entering text. Obviously, its upper bound must depend on the value of the property
Layout . Also, this rectangle can define and indent boundaries from the edges for entering text.
Further, having a little picked the source code, we find the virtual method:
procedure PaintWindow(DC: HDC); virtual;
It is called in any case, that for DoubleBuffered, that without it. His call will be made if there is a
csCustomPaint in the
ControlState of the component, after all the relevant component rendering. We will use it to draw TextHint.
Practice
I will not dwell on some, I think, not very interesting implementation details.
To begin with, we have the following additional properties:
And the main points.
1. The class declaration is certainly:
type TxIPMemo = class (TMemo)
2. In the constructor, write the string:
Constructor TxIPMemo.Create (AOwner : TComponent); begin inherited Create (AOwner);
3. Assignment rectangle input:
function TxIPMemo.SetRect (ARect : TRect) : boolean; begin result := Perform(EM_SETRECT, 0, LPARAM(@ARect))>0; end;
4. Calculate the input rectangle:
function TxIPMemo.CalcRect : TRect; var s : string; h : Integer; rct : TRect; begin rct := Rect(0,0,ClientWidth,ClientHeight);
5. What is TrueWordWrap in the call to the CalcHeight function above. This is the method that returns the true value of the line feed for Tmemo, which actually depends on a number of parameters:
function TxIPMemo.TrueWordWrap : boolean; begin result := not ((Alignment = taLeftJustify) and (ScrollBars in [ssHorizontal, ssBoth])) and (WordWrap or (Alignment <> taLeftJustify)); end;
6. Where the call for recalculation and assignment of the input rectangle occurs:
procedure DoEnter; override; procedure DoExit; override; procedure Change; override; procedure WMSetFont(var Message: TWMSetFont); message WM_SETFONT; procedure CMRecreateWnd(var Message: TMessage); message CM_RECREATEWND;
7. The assignment of the TextHint property is extremely simple, but considering if someone suddenly wants to register a component:
procedure TxIPMemo.SetTextHint (Value : string); begin if FTextHint = Value then exit; FTextHint := Value; if not (csLoading in ComponentState) then Change; end;
8. Draw a TextHint:
procedure TxIPMemo.PaintWindow(DC: HDC); var rct : TRect; str : string; cnv : TCanvas; begin inherited PaintWindow(DC); if Focused then exit; str := Lines.Text; if str <> '' then exit; str := FTextHint; if str = '' then exit; rct := CalcRect; InflateRect (rct, -2,-2); cnv := TCanvas.Create; cnv.Handle := DC; cnv.Font.Assign(Font); cnv.Font.Color := clBtnShadow; DrawTextEx(cnv,rct,str,tlTop,Alignment,TrueWordWrap,false); cnv.Free; end;
9. To facilitate the "substitution" of the standard TMemo on ours, there is the following method. It takes all the main events and properties from the passed pointer to TCustomMemo. When the AWithFree flag is set, destroys the old copy and takes its place. That is, if everything in the code is tied to the “old” TCustomMemo, nothing terrible will happen. Everything will work with the new instance as with the old one, except for those moments when the properties that you already know about are used, and you will still have to contact them as TxIPMemo:
function TxIPMemo.SetMemo (AMemo : PMemoControl; AWithFree : boolean = true) : boolean; var s : string; begin result := AMemo <> nil; if not result then exit; s := Amemo^.Name; BoundsRect := AMemo^.BoundsRect; Parent := AMemo^.Parent; Align := Amemo^.Align; Alignment := TxIPMemo(Amemo^).Alignment; {$IFDEF VER_XE} OnMouseEnter := TxIPMemo(Amemo^).OnMouseEnter; OnMouseLeave := TxIPMemo(Amemo^).OnMouseLeave; {$ENDIF} BorderStyle := TxIPMemo(Amemo^).BorderStyle; Font.Assign(TxIPMemo(Amemo^).Font); Color := TxIPMemo(Amemo^).Color; Visible := Amemo^.Visible; TabOrder := TxIPMemo(Amemo^).TabOrder; ScrollBars := TxIPMemo(Amemo^).ScrollBars; WantTabs := TxIPMemo(Amemo^).WantTabs; WantReturns := TxIPMemo(Amemo^).WantReturns; WordWrap := TxIPMemo(Amemo^).WordWrap; OnKeyPress := TxIPMemo(Amemo^).OnKeyPress; OnKeyDown := TxIPMemo(Amemo^).OnKeyDown; OnKeyUp := TxIPMemo(Amemo^).OnKeyUp; OnChange := TxIPMemo(Amemo^).OnChange; OnClick := TxIPMemo(Amemo^).OnClick; OnMouseDown := TxIPMemo(Amemo^).OnMouseDown; OnMouseMove := TxIPMemo(Amemo^).OnMouseMove; OnMouseUp := TxIPMemo(Amemo^).OnMouseUp; OnEnter := TxIPMemo(Amemo^).OnEnter; OnExit := TxIPMemo(Amemo^).OnExit; if AWithFree then begin Amemo^.Free; Name := s; AMemo^ := self; end; end;
How to use
Suppose that there is already a TMemo component on a form that you either use in dynamics, like the editors described above, or a static component created in a design. This is all not important.
1. We declare somewhere as follows:
FMemo : TxIPMemo;
2. In the OnCreate event of the form, or in its constructor, we write the following:
FMemo := TxIPMemo.Create (self);
We do not worry about the destruction of an instance of a class - it will be guaranteed to be destroyed in the destructor.
3. Perhaps in the same FormCreate handler we write:
FMemo.SetMemo (@Memo1);
Thereby taking properties and events from Memo1, and destroying it, taking its place.
4. Actually, that's all. Assign the necessary properties to our component. And we use TMemo with the possibilities of vertical alignment, indents (which can be negative), invitations for the user with empty Text.
Demo application windowEpilogue
In the source code, the link to which is below, all auxiliary functions used in the text are presented.
The version of the compiler is determined by the {$ I} plugin pro_param.inc.
Because, due to the specifics of the work, I adhere to the concept of a single module - different versions, the presented source codes are compiled in Delphi 7, Delphi XE 7 and Delphi 10.1 Berlin. For the simple reason that Delphi 7 is purchased from me, and Delphi 10.1 Berlin is a free license for commercial products. And the customer recently wants to be completely "white." Therefore, I do not use any paid libraries.
I hope the material will help in situations of varying severity.
Download:
xIPMemoDemo D7, XE 7