Recent

Author Topic: Custom Component and Radio Group  (Read 492 times)

Aruna

  • Sr. Member
  • ****
  • Posts: 458
Custom Component and Radio Group
« on: August 31, 2024, 03:15:16 pm »
Hi, I have a custom component that runs Linux 'lsblk' command and then displays the output in a Tmemo. This part works. I want to give the user the ability to select any option in lsblk. The source code is below:
Code: Pascal  [Select][+][-]
  1. unit lsblk;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Controls, StdCtrls, Process;
  9.  
  10. type
  11.   { TLSB }
  12.   TLSB = class(TCustomControl)
  13.   private
  14.     FMemo: TMemo;
  15.     procedure DoRunCommand;
  16.   protected
  17.     procedure CreateWnd; override;
  18.   public
  19.     constructor Create(AOwner: TComponent); override;
  20.   published
  21.     property Align;
  22.     property Anchors;
  23.     property Font;
  24.     property ParentFont;
  25.     property ParentShowHint;
  26.     property ShowHint;
  27.     property TabOrder;
  28.     property TabStop;
  29.     property Visible;
  30.   end;
  31.  
  32. procedure Register;
  33.  
  34. implementation
  35.  
  36. constructor TLSB.Create(AOwner: TComponent);
  37. begin
  38.   inherited Create(AOwner);
  39.   with GetControlClassDefaultSize do
  40.         SetInitialBounds(0, 0, 688, 150);
  41.  
  42.   // Initialize the memo
  43.   FMemo        := TMemo.Create(Self);
  44.   FMemo.Parent := Self;
  45.   FMemo.Align  := alClient;
  46. //FMemo.Width  :=600;
  47. //FMemo.Height :=180;
  48.   FMemo.Font.Name:= 'Monospace';
  49.   //FMemo.ScrollBars := ssVertical;
  50.   //Run the command
  51.   DoRunCommand;
  52. end;
  53.  
  54. procedure TLSB.CreateWnd;
  55. begin
  56.   inherited CreateWnd;
  57.   // Additional initialization if necessary
  58. end;
  59.  
  60. procedure TLSB.DoRunCommand;
  61. var
  62.   outstr: string;
  63. begin
  64.   RunCommand('lsblk', ['-S'], outstr); // @TRon's
  65.   FMemo.Lines.Add(outstr);
  66. end;
  67.  
  68. procedure Register;
  69. begin
  70.   RegisterComponents('Linux Sysinfo', [TLSB]);
  71. end;
  72.  
  73. end.
How would one do this, please?

Right now I am using
Code: Pascal  [Select][+][-]
  1. RunCommand('lsblk', ['-S'], outstr);
How would I use a RadioGrop to run the other flags/options/switches?
« Last Edit: August 31, 2024, 03:18:53 pm by Aruna »
Debian GNU/Linux 11 (bullseye)
https://pascal.chat/

bobby100

  • Sr. Member
  • ****
  • Posts: 254
    • Malzilla
Re: Custom Component and Radio Group
« Reply #1 on: August 31, 2024, 06:28:57 pm »
Make one more published property of string type for arguments, and use it instead of '-S' in the DoRunCommand.
Do not forget to initialize it to an empty string in the Create method.

Now, the RadioGroup is a part of your main program. You need to a procedure that will inspect the state(s) of the RadioGroup and according to it create a suitable string to pass to your component.

Aruna

  • Sr. Member
  • ****
  • Posts: 458
Re: Custom Component and Radio Group
« Reply #2 on: September 01, 2024, 03:52:11 am »
Make one more published property of string type for arguments, and use it instead of '-S' in the DoRunCommand.
Do not forget to initialize it to an empty string in the Create method.

Now, the RadioGroup is a part of your main program. You need to a procedure that will inspect the state(s) of the RadioGroup and according to it create a suitable string to pass to your component.
Thank you bobby100  I’m very new to object-oriented programming, I would greatly appreciate it if you could provide some example code to help me better understand the concepts.
If it's not too much trouble, could you please share some examples? Thank you very much for your help!
Debian GNU/Linux 11 (bullseye)
https://pascal.chat/

bobby100

  • Sr. Member
  • ****
  • Posts: 254
    • Malzilla
Re: Custom Component and Radio Group
« Reply #3 on: September 01, 2024, 12:11:57 pm »
No problem to help you, but you need to share some more info with me about what your app should do etc.
2nd question - why did you put the part of the code into a separate component? I am referring to the other thread here on the forum.
I would go for a separate component if I have a GUI widget, or if there is a lot of reusable code to make sense for a separate component, unit or class.
If you are doing it for a learning experience - I can understand that, and I will help you.

I would begin with redesigning your component TLSB:
- chose a better name, LSB normally stays for Less Significant Bit. It can lead to some confusion.
- make it more universal, with published properties like Command and Arguments. This way you expand the usability of your component - run any terminal command with arguments and catch the output.

This is how I would do it:
Code: Pascal  [Select][+][-]
  1. unit TerminalRun;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Controls, StdCtrls, Process;
  9.  
  10. type
  11.   TTerminalRun = class(TCustomControl)
  12.   private
  13.     FCommand: string;
  14.     FArguments: string;
  15.     FMemo: TMemo;
  16.     function DoRunCommand: boolean;
  17.   protected
  18.     procedure CreateWnd; override;
  19.   public
  20.     constructor Create(AOwner: TComponent); override;
  21.     function Run: boolean;
  22.   published
  23.     property Command: string read FCommand write FCommand;
  24.     property Arguments: string read FArguments write FArguments;
  25.     property Align;
  26.     property Anchors;
  27.     property Font;
  28.     property ParentFont;
  29.     property ParentShowHint;
  30.     property ShowHint;
  31.     property TabOrder;
  32.     property TabStop;
  33.     property Visible;
  34.   end;
  35.  
  36. procedure Register;
  37.  
  38. implementation
  39.  
  40. constructor TTerminalRun.Create(AOwner: TComponent);
  41. begin
  42.   inherited Create(AOwner);
  43.   with GetControlClassDefaultSize do
  44.     SetInitialBounds(0, 0, 688, 150);
  45.  
  46.   // Initialize the memo
  47.   FMemo := TMemo.Create(Self);
  48.   FMemo.Parent := Self;
  49.   FMemo.Align := alClient;
  50.   FMemo.Font.Name := 'Monospace';
  51.  
  52.   // Initialize the variables
  53.   FCommand := '';
  54.   FArguments := '';
  55. end;
  56.  
  57. procedure TTerminalRun.CreateWnd;
  58. begin
  59.   // Do we really need this?
  60.   inherited CreateWnd;
  61.   // Additional initialization if necessary
  62. end;
  63.  
  64. function TTerminalRun.Run: boolean;
  65. begin
  66.   Result := False;
  67.   if FCommand <> '' then
  68.     Result := DoRunCommand;
  69. end;
  70.  
  71. function TTerminalRun.DoRunCommand: boolean;
  72. var
  73.   outstr: string;
  74. begin
  75.   Result := RunCommand(FCommand, [FArguments], outstr); // @TRon's
  76.   if Result then
  77.     FMemo.Lines.Add(outstr)
  78.   else
  79.     FMemo.Lines.Add('Error executing command ' + FCommand + ' ' + FArguments);
  80. end;
  81.  
  82. procedure Register;
  83. begin
  84.   RegisterComponents('Linux Sysinfo', [TTerminalRun]);
  85. end;
  86.  
  87. end.

So, I've added two properties - Command and Arguments. I've added a function Run with feedback as boolean. I've converted your DoRunCommand to a function to get feedback. See, the RunCommand is also a function, so you can get a feedback if successfully run or not. We add some checks in Run function and also include this in final feedback. There are better ways to get the error info if the function did not succeed, but let leave it at this simple method for now.
Don't put DoRunCommand in Create method - this is a bad idea because it is also executed in the IDE, at design time. If your DoRunCommand triggers an exception, you will lock your IDE. With my new organization of the code, where you need to set the Command and the Arguments before Run, it won't work anyway. You need to take care about the code that it is executed also in the IDE at the design time, to not lock your IDE.
To use the component, one must set the command and the arguments, and execute the Run function after that. You do this from your main unit/form.
« Last Edit: September 01, 2024, 01:06:22 pm by bobby100 »

bobby100

  • Sr. Member
  • ****
  • Posts: 254
    • Malzilla
Re: Custom Component and Radio Group
« Reply #4 on: September 01, 2024, 01:32:51 pm »
I just saw a mistake in my code. The FArguments is not a single string but an array. The way I've written it, it will take just one argument.

Aruna

  • Sr. Member
  • ****
  • Posts: 458
Re: Custom Component and Radio Group
« Reply #5 on: September 01, 2024, 03:21:36 pm »
No problem to help you, but you need to share some more info with me about what your app should do etc.
I am trying to learn and teach myself by experimenting. I was trying to see if I could build a component that the end-user could use to see the output of all block devices in a linux system. I also wanted to give the end-user the ability to choose which option to run when using lsblk.
2nd question - why did you put the part of the code into a separate component? I am referring to the other thread here on the forum.
Like I said I am very new and trying to learn so that was me experimenting trying to get this working :-)
I would go for a separate component if I have a GUI widget, or if there is a lot of reusable code to make sense for a separate component, unit or class.
If you are doing it for a learning experience - I can understand that, and I will help you.
I was simply trying to teach myself how to create a component that uses lsblk.
I would begin with redesigning your component TLSB:
- chose a better name, LSB normally stays for Less Significant Bit. It can lead to some confusion.
- make it more universal, with published properties like Command and Arguments. This way you expand the usability of your component - run any terminal command with arguments and catch the output.
When initially trying to compile the compiler complained so I used LSB no specific reason or meaning to that. It worked so I used it. It was the first thing that came to my mind. I will choose a better name now. I have near-zero experience with published properties I am trying to learn this.

This is how I would do it:
Code: Pascal  [Select][+][-]
  1. unit TerminalRun;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Controls, StdCtrls, Process;
  9.  
  10. type
  11.   TTerminalRun = class(TCustomControl)
  12.   private
  13.     FCommand: string;
  14.     FArguments: string;
  15.     FMemo: TMemo;
  16.     function DoRunCommand: boolean;
  17.   protected
  18.     procedure CreateWnd; override;
  19.   public
  20.     constructor Create(AOwner: TComponent); override;
  21.     function Run: boolean;
  22.   published
  23.     property Command: string read FCommand write FCommand;
  24.     property Arguments: string read FArguments write FArguments;
  25.     property Align;
  26.     property Anchors;
  27.     property Font;
  28.     property ParentFont;
  29.     property ParentShowHint;
  30.     property ShowHint;
  31.     property TabOrder;
  32.     property TabStop;
  33.     property Visible;
  34.   end;
  35.  
  36. procedure Register;
  37.  
  38. implementation
  39.  
  40. constructor TTerminalRun.Create(AOwner: TComponent);
  41. begin
  42.   inherited Create(AOwner);
  43.   with GetControlClassDefaultSize do
  44.     SetInitialBounds(0, 0, 688, 150);
  45.  
  46.   // Initialize the memo
  47.   FMemo := TMemo.Create(Self);
  48.   FMemo.Parent := Self;
  49.   FMemo.Align := alClient;
  50.   FMemo.Font.Name := 'Monospace';
  51.  
  52.   // Initialize the variables
  53.   FCommand := '';
  54.   FArguments := '';
  55. end;
  56.  
  57. procedure TTerminalRun.CreateWnd;
  58. begin
  59.   // Do we really need this?
  60.   inherited CreateWnd;
  61.   // Additional initialization if necessary
  62. end;
  63.  
  64. function TTerminalRun.Run: boolean;
  65. begin
  66.   Result := False;
  67.   if FCommand <> '' then
  68.     Result := DoRunCommand;
  69. end;
  70.  
  71. function TTerminalRun.DoRunCommand: boolean;
  72. var
  73.   outstr: string;
  74. begin
  75.   Result := RunCommand(FCommand, [FArguments], outstr); // @TRon's
  76.   if Result then
  77.     FMemo.Lines.Add(outstr)
  78.   else
  79.     FMemo.Lines.Add('Error executing command ' + FCommand + ' ' + FArguments);
  80. end;
  81.  
  82. procedure Register;
  83. begin
  84.   RegisterComponents('Linux Sysinfo', [TTerminalRun]);
  85. end;
  86.  
  87. end.

So, I've added two properties - Command and Arguments. I've added a function Run with feedback as boolean. I've converted your DoRunCommand to a function to get feedback. See, the RunCommand is also a function, so you can get a feedback if successfully run or not. We add some checks in Run function and also include this in final feedback. There are better ways to get the error info if the function did not succeed, but let leave it at this simple method for now.
Thank you for the explanation and really appreciate your help.

Don't put DoRunCommand in Create method - this is a bad idea because it is also executed in the IDE, at design time. If your DoRunCommand triggers an exception, you will lock your IDE. With my new organization of the code, where you need to set the Command and the Arguments before Run, it won't work anyway. You need to take care about the code that it is executed also in the IDE at the design time, to not lock your IDE.
To use the component, one must set the command and the arguments, and execute the Run function after that. You do this from your main unit/form.
Oh dear, no wonder @wp also said do not run commands in the create method. I have much to learn. Thank you for your help and time @bobby100
Debian GNU/Linux 11 (bullseye)
https://pascal.chat/

 

TinyPortal © 2005-2018