Recent

Author Topic: How does the designer work?  (Read 359 times)

kupferstecher

  • Sr. Member
  • ****
  • Posts: 287
How does the designer work?
« on: August 08, 2019, 04:04:05 pm »
Hello all,

I try to understand how the Lazarus designer works internally, especially how the window messages of the controls placed on the form designer are catched and how they are prevented from beeing processed as in a runtime behaviour.
The purpose is to write an own designer for my application. Until now I use another approach by taking "screenshots" of the controls using the "PaintTo"-method (see: https://forum.lazarus.freepascal.org/index.php/topic,31036).
Time by time I'm running into problems (depending on the captured controls) using this method, so I want to use a more robust way.
Lazarus is capable of it, so it should be possible for a custom application as well.

Does anyone know how the designer internally works?

My understanding by now is, that all messages of a window (i.e. of a control) are handled by a procedure TControl.WndProc (and overridden ones, e.g. in TWinControl). This Method searches the Parent(s) for a TCustomPanel, which can have an assigned "Designer". The designers method IsDesignMethod:Boolean is called, which answers if the message is a design or runtime message depending on the message. If its a design method, then it should not be further processed by the control.

I tried to implement such a (very basic) designer, see attached project. The designer's method IsDesignMessage is actually called and I receive e.g. the mouse messages. But the messages are still processed by the control itself (here a TButton), no matter if I return true or false for IsDesignMessage. So the button will still fire a click event and so on, which it shouldn't.

Did I miss something, or is it a completely wrong approach?

Thanks
and
Regards~

--
Designer:
Code: Pascal  [Select]
  1.  TKDesigner = class(TIDesigner)
  2.   function IsDesignMsg(Sender: TControl; var Message: TLMessage): Boolean;override;
  3.   [...]
  4. end;
  5.  
  6.  var
  7.    KDesigner: TKDesigner;
  8.  
  9. implementation
  10.  
  11. { TKDesigner }
  12.  
  13. function TKDesigner.IsDesignMsg(Sender: TControl; var Message: TLMessage): Boolean;
  14. begin
  15.   Result:= false;
  16.   if not (csDesigning in Sender.ComponentState) then EXIT;
  17.  
  18.   if ((Message.Msg>=LM_MOUSEFIRST) and (Message.Msg<=LM_MOUSELAST))
  19.    or ((Message.Msg>=LM_MOUSEFIRST2) and (Message.Msg<=LM_MOUSELAST2)) then begin
  20.     Result:= true;
  21.   end;
  22.  
  23.   writeln('TKDesigner.IsDesignMsg:',booltostr(Result,'true','false'),' ',Inttohex(Message.msg,4));
  24.  
  25. end;

Initialisierung:
Code: Pascal  [Select]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   KDesigner:= TKDesigner.Create;
  4.   Form1.Designer:= KDesigner;
  5.  
  6.   Form1.SetDesigning(true,true);
  7.  
  8. end;

jamie

  • Hero Member
  • *****
  • Posts: 1735
Re: How does the designer work?
« Reply #1 on: August 08, 2019, 04:54:27 pm »
For a designer to work property with a control the "ComponentState" + [ csDesigning ] must be
set just after the Constructor and before anything else.

 From that point on when ever any properties get set and methods are called, sensitive areas where it won't work in a designer must test the componentState to see if it is in cdDesinging and
skip any code that will not work this way and also generate a different default look for the designer itself.

 This code of course also is in your final application but the flag is off by default so it is ignored

 There are other items to worry about, like the controlstate, you need to know if it's in the csloading state in some points because you can not assume all values have been set.

  Basically the designer uses the controls and forms the same way you do in your app, it simply uses some basic items needed common to all "Tcontrol" to move around in the designer.

 Is that what you needed ?


kupferstecher

  • Sr. Member
  • ****
  • Posts: 287
Re: How does the designer work?
« Reply #2 on: August 08, 2019, 09:01:13 pm »
From that point on when ever any properties get set and methods are called, sensitive areas where it won't work in a designer must test the componentState to see if it is in cdDesinging and
skip any code that will not work this way and also generate a different default look for the designer itself.
[...]
Is that what you needed ?
The keypoint for me is, that the controls should not respond to mouse actions when they are in designing state. Forwarding the mouse actions I could do manually, e.g. by overriding the event handlers. But then the "glowing" behaviour when the cursor is over the button still would be there (under Windows). So all controls should look like they are lying under glass, unclickable, no effects on mouse hovering, just like in the Lazarus designer. I'm still not sure how the Lazarus designer accomplishes that.

For a designer to work property with a control the "ComponentState" + [ csDesigning ] must be
set just after the Constructor and before anything else.
I just tried that, created the button on runtime and set the ComponentState. (Thats a bit nasty, because its a protected field).
Still, the result is the same. The button behaves under the mouse like on runtime (see screenshot).

Code: Pascal  [Select]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   KDesigner:= TKDesigner.Create;
  4.   Form1.Designer:= KDesigner;
  5.  
  6.   Form1.SetDesigning(true,true);
  7.   Widgetset.SetDesigning(Form1);
  8.  
  9.   Button2:= TButton.Create(Form1);
  10.   TSetDesigningComp.SetDesigningOfComponent(Button2,true);
  11.   Widgetset.SetDesigning(Button2);
  12.   Button2.Setbounds(20,20,120,30);
  13.   Button2.Parent:= Form1;
  14.  
  15. end;

Dummy class for setting the component state.
Code: Pascal  [Select]
  1. Type TSetDesigningComp = class(TComponent)
  2.    public
  3.      class procedure SetDesigningOfComponent(AComponent: TComponent; Value: Boolean);
  4.   end;
  5.  
  6. class procedure TSetDesigningComp.SetDesigningOfComponent(
  7.   AComponent: TComponent; Value: Boolean);
  8. begin
  9.   TSetDesigningComp(AComponent).SetDesigning(Value);
  10. end;

jamie

  • Hero Member
  • *****
  • Posts: 1735
Re: How does the designer work?
« Reply #3 on: August 09, 2019, 12:35:53 am »
In design mode the designer needs to connect to the Events for mouse action so that it can move it, allow popups to show etc..

 I believe for the most part the default for colors and brushes are the parent form and in this case it would be the designer form.

 I have a GUI drag and drop HMI app for industrial control using these stock controls and basically that is all I do. But in my app most the runtime colors also show in the designer form.

PascalDragon

  • Hero Member
  • *****
  • Posts: 505
  • Compiler Developer
Re: How does the designer work?
« Reply #4 on: August 09, 2019, 09:32:13 am »
@kupferstecher: I suggest you to take a look at designer/designer.pp of Lazarus. One further important tidbit seems to be the check with IsDesignMsg inside TControl.WndProc.
That's just what I found out right away by looking at the code for further infos you'll need to look yourself or hope that someone more knowledgeable with the designer answers. ;)

BrunoK

  • Full Member
  • ***
  • Posts: 158
  • Retired programmer
Re: How does the designer work?
« Reply #5 on: August 09, 2019, 10:03:37 am »
Take a look at ($LAZARUSDIR)\examples\objectinspector.

From there you may start to try/understand how the design mode works.
Lazarus trunk r. 59978/03.01.2019 (+/- patches regarding enabled, TScrollBar, TCursorImage). FPC 3.0.4 32 bits. (+heaptrc with leaked ClassName+Revisited TList) , Windows 10 Pro x64 (v. 1803)

kupferstecher

  • Sr. Member
  • ****
  • Posts: 287
Re: How does the designer work?
« Reply #6 on: August 09, 2019, 10:05:21 am »
I have a GUI drag and drop HMI app for industrial control using these stock controls and basically that is all I do. But in my app most the runtime colors also show in the designer form.
What technique do you use to catch the events in your designer?
I guess you didn't implement a TIDesigner derived designer?

@kupferstecher: I suggest you to take a look at designer/designer.pp of Lazarus. One further important tidbit seems to be the check with IsDesignMsg inside TControl.WndProc.
Yes, I already digged into that, thats how I came up with the posted code. Unfortunately it doesn't work. As said I can receive the message in the designer via IsDesignMsg, but it gets further processed, even if I return "true". The procedure TControl.WndProc is immediately exited at that point. But TWinControl.WndProc was called before (inheritance), I'm not sure what was already happening there.

When I click the button, the sequence is as follows (displayed with writeln, maybe the sequence got distorted?):
When lowering the mouse button:
  - TKDesigner.IsDesignMsg is called with message Button Down
After releasing the mouse button
  - TForm1.Button1Click
  - TKDesigner.IsDesignMsg is called with message Button Up
Seems that IsDesignMsg was called after the message was processed.

Could it be that I have to do something with the message passed via IsDesignMsg? Like marking it as handled or anything like that.


Quote
or hope that someone more knowledgeable with the designer answers. ;)
Yes, I really hope that. At least one guy should know how the Lazarus designer really works  :)

EDIT: @BrunoK: I'll have a look, thanks.