You will surely ask why you would do that! Granted the Delphi form designer is very easy to use. But in some cases it is not practical because it creates several files and makes more difficult, for example, to hide the form in a component.
This is exactly the case I had: in a component I needed a small helper form. I wanted the code to be in the component source itself. You may already use an "on the fly" form without knowing: InputQuery and InputBox are already such a form. Their code is located in the Dialogs unit.
Actually, there is not much magic to use to create an "on the fly" form. After all a form is just an object as any other object. The only special thing is related to the constructor. The form constructor insist on loading a form from a resource as it had been created with the designer and his DFM file. If there is no DFM and you call Create, you get an exception EResNotFound with a resource name equal to your form class name.
Instead of Create, you have to call CreateNew. This is easy but somewhat misleading. This is why the code I present below override the standard constructor named Create and call CreateNew. This way you can use the standard constructor without worrying.
The sample code I use to demonstrate the "on the fly" form is very easy. The form is a simple form named TComboBoxInputForm with a combobox and a pair of buttons for OK/Cancel. I created an Execute method which calls ShowModal after having initialized the combobox with an array of const values passed as argument.
Here is the code showing the use:
procedure TForm1.Button1Click(Sender: TObject); var Form : TComboBoxInputForm; AResult : String; begin Form := TComboBoxInputForm.Create(Self); try if Form.Execute(['Delphi', 123, 2.3, TRUE], Edit1.Text, AResult, 'Select value') then Edit1.Text := AResult; finally FreeAndNil(Form); end; end;
This code uses TComboBoxInputForm to ask the user to select a value in a list.
The source code is very simple and looks like any other form, except since there is no DFM file, all used components are created and initialized from the constructor.
type TComboBoxInputForm = class(TCustomForm) ComboBox : TComboBox; OKButton : TButton; CancelButton : TButton; protected procedure OKButtonClick(Sender: TObject); procedure CancelButtonClick(Sender: TObject); public constructor Create(AOwner : TComponent); override; function Execute(const Values : array of const; const ADefault : String; out AResult : String; const ACaption : String = '') : Boolean; end; TForm1 = class(TForm) Button1: TButton; Edit1: TEdit; procedure Button1Click(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} { TComboBoxInputForm } constructor TComboBoxInputForm.Create(AOwner: TComponent); begin // We need the following lines to avoid the EResNotFound exception // because we have no DFM resource for our form GlobalNameSpace.BeginWrite; try CreateNew(AOwner); finally GlobalNameSpace.EndWrite; end; BorderStyle := bsToolWindow; ComboBox := TComboBox.Create(Self); ComboBox.Parent := Self; ComboBox.Top := 16; ComboBox.Left := 32; ComboBox.Style := csDropDownList; OKButton := TButton.Create(Self); OKButton.Parent := Self; OKButton.Top := ComboBox.Top + ComboBox.Height + 8; OKButton.Left := ComboBox.Left; OKButton.Width := 50; OKButton.Caption := '&OK'; OKButton.Default := TRUE; OKButton.OnClick := OKButtonClick; CancelButton := TButton.Create(Self); CancelButton.Parent := Self; CancelButton.Top := OKButton.Top; CancelButton.Left := OkButton.Left + OKButton.Width + 16; CancelButton.Width := OKButton.Width; CancelButton.Caption := '&Cancel'; CancelButton.Cancel := TRUE; CancelButton.OnClick := CancelButtonClick; ComboBox.Width := OKButton.Width + CancelButton.Width + 16; ClientWidth := ComboBox.Left + ComboBox.Width + ComboBox.Left; ClientHeight := ComboBox.Top + OKButton.Top + OKButton.Height; end; procedure TComboBoxInputForm.OKButtonClick( Sender : TObject); begin Close; ModalResult := mrOK; end; procedure TComboBoxInputForm.CancelButtonClick( Sender : TObject); begin Close; ModalResult := mrCancel; end; function TComboBoxInputForm.Execute( const Values : array of const; const ADefault : String; out AResult : String; const ACaption : String) : Boolean; var I : Integer; const BoolToStr : array [Boolean] of String = ('FALSE', 'TRUE'); begin Caption := ACaption; ComboBox.Items.Clear; I := Low(Values); while I <= High(Values) do begin case Values[I].VType of vtUnicodeString: ComboBox.Items.Add(Values[I].VPWideChar); vtBoolean: ComboBox.Items.Add(BoolToStr[Values[I].VBoolean]); vtInteger: ComboBox.Items.Add(IntToStr(Values[I].VInteger)); vtExtended: ComboBox.Items.Add(FloatToStr(Values[I].VExtended^)); end; Inc(I); end; // Preselect default item I := ComboBox.Items.IndexOf(ADefault); if I >= 0 then ComboBox.ItemIndex := I; Result := ShowModal = mrOK; if Result then AResult := ComboBox.Text else AResult := ADefault; end;
Follow me on Twitter
Follow me on LinkedIn
Follow me on Google+
Visit my website: http://www.overbyte.be
2 comments:
And for generating the code creating the components there is GExperts Components to Code wizard...
So you basically created custom dialog form by using modal form and modal results.
But why don't you work with modal results in proper way?
Proper way to achieve this would be to assign modal results to each Button. So whenever you click on any of these butons selected modal result is set to its parent (Modal Form in your case).
And as soon as ModalResult of any Modal Form is changed the Form gets automatically closed.
And if you set CancelButton.Cancel to True pressing escape would act the same as would clicking on that Button.
This way you get rid of both OnClick events from your Buttons and make your form to actually behave as it is intendet for modalForms.
Post a Comment