Recent

Author Topic: Composing multiple standard widgets into a custom widget  (Read 678 times)

colo

  • New Member
  • *
  • Posts: 21
Composing multiple standard widgets into a custom widget
« on: October 09, 2022, 11:43:19 am »
I would like to create a GUI for supervising multiple processes that are executing in the background of my Lazarus application. These processes have a number of properties that can easily be modeled with a class (some informational text, a current state, a progress value, etc.) - now, I would need to graphically represent a variable number of these processes at the same time next to each other (think like Windows' task manager does it for load applied to indididual CPU cores). Each process-related GUI element should have some kind of bounding box that contains labels for that informational text, maybe some graphics that represent state in a visually appealing way, and a also a progress bar associated with the processes' progress value, etc.

What I would ideally like to be able to do is to design this process representation widget visually, using the Lazarus form designer, and then have the result of that process available as some kind of custom widget of my own, so that I can dynamically add new objects instantiated from it to a TForm or another container object like a TGroupBox. Unfortunately, I have a hard time finding the proper documentation that tells me if, or how, I can do that. Also, I'd appreciate a pointer to reading material on programmatically handling/altering/creating forms.

Thanks very much for your input! :)
« Last Edit: October 09, 2022, 12:32:31 pm by colo »

MarkMLl

  • Hero Member
  • *****
  • Posts: 5862
Re: Composing multiple standard widgets into a custom widget
« Reply #1 on: October 09, 2022, 12:22:14 pm »
Use a frame.

However note carefully that at design time (i.e. when you're using the Lazarus IDE) you should always edit the frame's content using the original frame: if you change the properties of an instance of a frame (i.e. a frame that's been placed on a form) you'll give yourself problems.

The properties of an instance of a frame may, however, be edited at runtime in the usual way.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

colo

  • New Member
  • *
  • Posts: 21
Re: Composing multiple standard widgets into a custom widget
« Reply #2 on: October 09, 2022, 01:22:55 pm »
Use a frame.

Thanks a lot, that looks exactly like the thing/construct I'd been been looking for - I just did not notice it from its name or icon in the IDE (the plethora of available widgets sometimes makes my head spin, and this is a very good thing :D)!

I'll see if I can follow https://wiki.lazarus.freepascal.org/Frames to implement a toy "frame" within a toy program to understand the concept's implementation better.

MarkMLl

  • Hero Member
  • *****
  • Posts: 5862
Re: Composing multiple standard widgets into a custom widget
« Reply #3 on: October 09, 2022, 02:03:54 pm »
They're easy enough to use, I've done quite a lot with them in the context of tabbed pages etc. created on-the-fly.

I've just checked and with one particular program I had a notebook with dynamically-created pages selectable by a tab at the top. Each page was populated using a TFrame, which itself contained another notebook with tabs down the side. I think that illustrates the flexibility.

The weakness is that the content of the frame isn't locked once it's been put on a form, and it's very easy to get into a mess where you have to edit the form's .lfm file at text to get rid of stuff that shouldn't be in there.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

colo

  • New Member
  • *
  • Posts: 21
Re: Composing multiple standard widgets into a custom widget
« Reply #4 on: October 09, 2022, 02:18:19 pm »
The weakness is that the content of the frame isn't locked once it's been put on a form, and it's very easy to get into a mess where you have to edit the form's .lfm file at text to get rid of stuff that shouldn't be in there.

I take it you are referring to when in the design phase, using the form designer in the IDE, here?

MarkMLl

  • Hero Member
  • *****
  • Posts: 5862
Re: Composing multiple standard widgets into a custom widget
« Reply #5 on: October 09, 2022, 03:52:45 pm »
Yes.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

colo

  • New Member
  • *
  • Posts: 21
Re: Composing multiple standard widgets into a custom widget
« Reply #6 on: October 09, 2022, 10:01:01 pm »
I think I already experienced what you warned me of when playing around with my first custom Frame ;) I can manage - thanks for the heads-up!


Meanwhile I have encountered a problem that has left me a bit stumped right now... In my program, fefore I show a modal form, I would like to spawn a few instances of my custom Frame within that form, and as soon as I create the second Frame instance, the runtime catches an exception telling me that:

Quote
Duplicate name: A component named 'MyCustomFrame' already exists.

That did not happen when I added a series of pre-existing components, like multiple TMemo, instead of my Frame when trying out dynamic component creation. What does my own Frame unit need to learn to do to (I assume) be able to assign itself a name dynamically, so that this problem is avoided?

MarkMLl

  • Hero Member
  • *****
  • Posts: 5862
Re: Composing multiple standard widgets into a custom widget
« Reply #7 on: October 10, 2022, 09:21:15 am »
You'd do best putting a little demo app together and posting it here so that people can scribble all over it :-)

At this point I think I'd also make a couple more management suggestions: once you start talking about concrete code you definitely need to say what OS and version of Lazarus/FPC you're using. I'd also suggest that you post that info in the body of a message rather than "being clever" and putting it in your sig, since as soon as you change your sig /all/ messages will be updated when redefined which thoroughly screws the context when somebody gets here via Google or the forum's own search facility.

Having said that, here's the code that was used to create a new rule on the screenshot I showed you yesterday. I don't know to what extent the fact that I'm suffixing the name matters, or if the critical thing is that I'm instantiating to a fresh tabbed page which is acting as the container.

Code: Pascal  [Select][+][-]
  1. PROCEDURE relabel;
  2.  
  3. VAR     i: INTEGER;
  4.  
  5. BEGIN
  6.   WITH TCustomTabControl(DisplayForm.Notebook1) DO BEGIN
  7.     FOR i:= 1 TO PageCount - 1 DO BEGIN
  8.       Pages.Strings[i]:= 'Rule ' + IntToStr(i);
  9.       TFrameDisplayFilter(CustomPage(i).Controls[0]).Name:= 'FrameDisplayFilter_' + IntToStr(i);
  10.       TFrameDisplayFilter(CustomPage(i).Controls[0]).ButtonDelete.Enabled:= PageCount > 2
  11.     END;
  12.  
  13. (* Note two-phase rename here so that we don't have two components named the    *)
  14. (* same when we do an "Insert Before".                                          *)
  15.  
  16.     FOR i:= 1 TO PageCount - 1 DO
  17.       TFrameDisplayFilter(CustomPage(i).Controls[0]).Name:= 'FrameDisplayFilter' + IntToStr(i)
  18.   END
  19. END { relabel } ;
  20.  
  21.  
  22. PROCEDURE insertCommon(insertAt: INTEGER; copyFrom: TTabSheet);
  23.  
  24. (* Insert the indicated page. Don't try to do anything clever with the catch-   *)
  25. (* all state since the act of inserting a page won't risk discarding data.      *)
  26.  
  27. VAR     cf, ct: TFrameDisplayFilter;
  28.  
  29. BEGIN
  30.   WITH TCustomTabControl(DisplayForm.Notebook1) DO BEGIN
  31.     Pages.Insert(insertAt, 'XX' + IntToStr(insertAt) + 'XX');
  32.     Assert(Pages.Objects[insertAt] IS TTabSheet);
  33.     TTabSheet(Pages.Objects[insertAt]).InsertControl(TFrameDisplayFilter.Create(DisplayForm), 0)
  34.   END;
  35.  
  36. (* Replicate the current page's settings to the new one. Note that this doesn't *)
  37. (* attempt any Assign() operations since I want to make sure that all objects   *)
  38. (* remain distinct.                                                             *)
  39.  
  40.   Assert(copyFrom.Controls[0] IS TFrameDisplayFilter);
  41.   cf:= TFrameDisplayFilter(copyFrom.Controls[0]);
  42.   ct:= TFrameDisplayFilter(DisplayForm.Notebook1.CustomPage(insertAt).Controls[0]);
  43.   ct.RadioGroupDirection.ItemIndex:= cf.RadioGroupDirection.ItemIndex;
  44.   ct.RadioGroupPause.ItemIndex:= cf.RadioGroupPause.ItemIndex;
  45.   ct.RadioGroupRts.ItemIndex:= cf.RadioGroupRts.ItemIndex;
  46.   ct.RadioGroupCts.ItemIndex:= cf.RadioGroupCts.ItemIndex;
  47.   ct.RadioGroupDsr.ItemIndex:= cf.RadioGroupDsr.ItemIndex;
  48.   ct.RadioGroupDtr.ItemIndex:= cf.RadioGroupDtr.ItemIndex;
  49.   ct.RadioGroupCd.ItemIndex:= cf.RadioGroupCd.ItemIndex;
  50.   ct.RadioGroupRing.ItemIndex:= cf.RadioGroupRing.ItemIndex;
  51.   ct.RadioGroupError.ItemIndex:= cf.RadioGroupError.ItemIndex;
  52.   ct.CheckGroupErrors.Checked[0]:= cf.CheckGroupErrors.Checked[0];
  53.   ct.CheckGroupErrors.Checked[1]:= cf.CheckGroupErrors.Checked[1];
  54.   ct.CheckGroupErrors.Checked[2]:= cf.CheckGroupErrors.Checked[2];
  55.   ct.RadioGroupCharMatch.ItemIndex:= cf.RadioGroupCharMatch.ItemIndex;
  56.   ct.LabeledEditCharMatchMin.Text:= cf.LabeledEditCharMatchMin.Text;
  57.   ct.LabeledEditCharMatchMax.Text:= cf.LabeledEditCharMatchMax.Text;
  58.   ct.RadioGroupCharMatch2.ItemIndex:= cf.RadioGroupCharMatch2.ItemIndex;
  59.   ct.ComboBoxCharset.Text:= cf.ComboBoxCharset.Text;
  60.   ct.ColorBoxForeground.Selected:= cf.ColorBoxForeground.Selected;
  61.   ct.ColorBoxBackground.Selected:= cf.ColorBoxBackground.Selected;
  62.   ct.CheckBoxCatchall.Checked:= cf.CheckBoxCatchall.Checked;
  63.   relabel                               (* ...and rebuild internal structures   *)
  64. END { insertCommon } ;
  65.  

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

colo

  • New Member
  • *
  • Posts: 21
Re: Composing multiple standard widgets into a custom widget
« Reply #8 on: October 10, 2022, 12:50:58 pm »
Ha, I feel like such a moron!! I tried setting the .Name attribute of the created component, but in doing so I had a subtle error in the RHS of the assignment, mistook the compiler's somewhat bewildering error message as its way of telling me that .Name cannot be re-assigned at runtime, and stopped engaging with the problem yesterday... Your example code showed me that this is very much possible to do so, made me discard my assumption and re-investigate. I spotted the error, and now I can spawn any number of custom frame copies on the form - so thanks very much once again :D

FTR, I am doing this using Lazarus 2.2.4 on GNU/Linux (Arch Linux) with the gtk2 UI components. I will heed your advice in that regard when seeking help next time around - I kinda know it's bound to happen eventually...  O:-)

 

TinyPortal © 2005-2018