Recent

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

kupferstecher

  • Sr. Member
  • ****
  • Posts: 342
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: 2310
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 ?

Number 1 at blue screen app creations!

kupferstecher

  • Sr. Member
  • ****
  • Posts: 342
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: 2310
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.
Number 1 at blue screen app creations!

PascalDragon

  • Hero Member
  • *****
  • Posts: 958
  • 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: 208
  • 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. 62137/27.10.2019 (+/- patches regarding TScrollBar, IntitalSetupDialog, Options.Environment options, SearchResults).  Lazarus 3.0.6 raw from svn.
FPC 3.0.4 32 bits. (+heaptrc with leaked ClassName+Revisited TList) , Windows 10 Pro x64 (v. 1903 / 18362.418)

kupferstecher

  • Sr. Member
  • ****
  • Posts: 342
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.

Slawek

  • New member
  • *
  • Posts: 5
Re: How does the designer work?
« Reply #7 on: February 12, 2020, 10:18:37 pm »
Hello,
I wanted to refresh the topic.
I wanted to do exactly the same in my application and I can't handle the designer.
Does anyone know how to solve this?

Thanks for the help

jamie

  • Hero Member
  • *****
  • Posts: 2310
Re: How does the designer work?
« Reply #8 on: February 12, 2020, 10:43:01 pm »
the controls have flags in them so they will property draw on the screen and disable key features that won't work at design time.

 As you look on the prior post above you can see what needs to be done..

 If you are using existing controls then these controls already behave property if it detects the DesignMode flag set..

 You dynamically create the forms in the designer using the *.lfm resource file that gets generated. This file dictates all the property settings etc.. but these settings are only setters for the fields in the control because if the control detect it's in design Mode much of the control will do nothing other than just show itself on the screen..

 Take a look at the source code of a few controls and you'll see what I mean.
Number 1 at blue screen app creations!

kupferstecher

  • Sr. Member
  • ****
  • Posts: 342
Re: How does the designer work?
« Reply #9 on: February 13, 2020, 10:40:03 am »
Hello jamie,

for me that topic is also not solved, yet. I tried te ComponentState stuff, the IsDesignMessage and also the hack into TControl.WndProc. But I had no success.
Catching the click events is no problem, but the animation under Windows when hovering with the mouse over the button is.

Note: Thats not about creating controls for usage in the Lazarus desiger, but it's about creating an own simple designer that can handle standard controls like TButton and so on.

Can you give further advise?

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 5956
    • wiki
Re: How does the designer work?
« Reply #10 on: February 13, 2020, 10:54:21 am »
OK, I don't know the answer.

But following some ideas, I guess you need to intercept NCHitTest.

Following that (search)
B:\lazarus_latest_svn_2\lcl\interfaces\win32\win32callback.inc
line 2744

        if TWSWinControlClass(TWinControl(Control).WidgetSetClass).GetDesignInteractive(TWinControl(Control), P) then
          Result := HTTRANSPARENT

There may be more....

There is a lot more code with references to "Designer"....

kupferstecher

  • Sr. Member
  • ****
  • Posts: 342
Re: How does the designer work?
« Reply #11 on: February 13, 2020, 11:51:43 am »
Hello Martin,

thanks for the post. I'll need some time to dig in.

avra

  • Hero Member
  • *****
  • Posts: 1772
    • Additional info
Re: How does the designer work?
« Reply #12 on: February 13, 2020, 12:24:19 pm »
Maybe you don't need a cannon to kill a fly? If all you need is a quick runtime form designer out of the box then you might want to take a look at jvcllaz or pl_ExDesign packages in OPM. More info about JVCL here: https://wiki.freepascal.org/JVCL_Components#JvRuntimeDesign.
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

kupferstecher

  • Sr. Member
  • ****
  • Posts: 342
Re: How does the designer work?
« Reply #13 on: February 13, 2020, 06:34:45 pm »
Maybe you don't need a cannon to kill a fly?
Seems to be a quite huge fly, though~

The package JvRuntimeDesign I testet before (and now once again), it has the same problem: the mouse hover animation is not blocked (blue shading). See attached screenshot. The example of the pl_ExDesign package is identical with JvRuntimeDesign, package and function names differ, but the problem is the same...

Lazarus proofs that it is possible, but yet nobody could tell me how.