Crear componentes en tiempo de ejecución - Delphi
-
05-07-2019 - |
Pregunta
¿Cómo puedo crear un componente en tiempo de ejecución y luego trabajar con él (cambiar propiedades, etc.)?
Solución
Depende de si es un componente visual o no visual. El principio es el mismo, pero hay algunas consideraciones adicionales para cada tipo de componente.
Para componentes no visuales
var
C: TMyComponent;
begin
C := TMyComponent.Create(nil);
try
C.MyProperty := MyValue;
//...
finally
C.Free;
end;
end;
Para componentes visuales:
En esencia, los componentes visuales se crean de la misma manera que los componentes no visuales. Pero debe establecer algunas propiedades adicionales para que sean visibles.
var
C: TMyVisualComponent;
begin
C := TMyVisualComponent.Create(Self);
C.Left := 100;
C.Top := 100;
C.Width := 400;
C.Height := 300;
C.Visible := True;
C.Parent := Self; //Any container: form, panel, ...
C.MyProperty := MyValue,
//...
end;
Algunas explicaciones del código anterior:
- Al establecer el propietario del componente (el parámetro del constructor), el componente se destruye cuando se destruye el formulario propietario.
- Establecer la propiedad
Parent
hace que el componente sea visible. Si lo olvida, su componente no se mostrará. (Es fácil perderse esa :))
Si desea muchos componentes , puede hacer lo mismo que anteriormente pero en un bucle:
var
B: TButton;
i: Integer;
begin
for i := 0 to 9 do
begin
B := TButton.Create(Self);
B.Caption := Format('Button %d', [i]);
B.Parent := Self;
B.Height := 23;
B.Width := 100;
B.Left := 10;
B.Top := 10 + i * 25;
end;
end;
Esto agregará 10 botones en el borde izquierdo del formulario. Si desea modificar los botones más adelante, puede almacenarlos en una lista. ( TComponentList es el más adecuado, pero también eche un vistazo a propuestas de los comentarios a esta respuesta)
Cómo asignar controladores de eventos:
Debe crear un método de controlador de eventos y asignarlo a la propiedad del evento.
procedure TForm1.MyButtonClick(Sender: TObject);
var
Button: TButton;
begin
Button := Sender as TButton;
ShowMessage(Button.Caption + ' clicked');
end;
B := TButton.Create;
//...
B.OnClick := MyButtonClick;
Otros consejos
Para simplificar el proceso de creación de componentes en tiempo de ejecución, puede usar GExperts .
- Cree un componente (o más componentes) visualmente y establezca sus propiedades.
- Seleccione uno o más componentes y ejecute GExperts, Componentes para codificar.
- Pegue el código generado en su aplicación.
- Eliminar los componentes del diseñador de formularios visuales.
Ejemplo (código de creación de TButton generado de esta manera):
var
btnTest: TButton;
btnTest := TButton.Create(Self);
with btnTest do
begin
Name := 'btnTest';
Parent := Self;
Left := 272;
Top := 120;
Width := 161;
Height := 41;
Caption := 'Component creation test';
Default := True;
ParentFont := False;
TabOrder := 0;
end;
Me gustaría agregar eso al agregar controles dinámicamente ... es una buena idea agregarlos a una lista de objetos (TObjectList) como se sugiere en < 1 > por @Despatcher.
procedure Tform1.AnyButtonClick(Sender: TObject);
begin
If Sender is TButton then
begin
Case Tbutton(Sender).Tag of
.
.
.
// Or You can use the index in the list or some other property
// you have to decide what to do
// Or similar :)
end;
end;
procedure TForm1.BtnAddComponent(Sender: TObJect)
var
AButton: TButton;
begin
AButton := TButton.Create(self);
Abutton. Parent := [Self], [Panel1] [AnOther Visual Control];
AButton.OnClick := AnyButtonClick;
// Set Height and width and caption ect.
.
.
.
AButton.Tag := MyList.Add(AButton);
end;
Debe agregar la Unidad 'Contnrs' a su lista de Usos. Es decir, System.Contnrs.pas la Unidad de Contenedores base Y puedes tener muchas listas de objetos. Sugiero usar una TObjectList para cada tipo de control que use por ejemplo,
Interface
Uses Contnrs;
Type
TMyForm = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
Var
MyForm: TMyForm;
checkBoxCntrlsList: TObjectList; //a list for the checkBoxes I will createin a TPanel
comboboxCntrlsList: TObjectList; //a list of comboBoxes that I will create in some Form Container
esto le permite manipular / administrar fácilmente cada control, ya que sabrá qué tipo de control es, por ejemplo,
Var comboBox: TComboBox;
I: Integer;
begin
For I = 0 to comboboxCntrlsList.Count -1 do // or however you like to identify the control you are accessing such as using the tag property as @Despatcher said
Begin
comboBox := comboboxCntrlsList.Items[I] as TComboBox;
...... your code here
End;
end;
Esto le permite usar los métodos y propiedades de ese control No olvides crear las TObjectLists, tal vez en el formulario create event ...
checkBoxCntrlsList := TObjectList.Create;
comboboxCntrlsList := TObjectList.Create;
Pero si no sé cuántos componentes quiero crear, p. ej. si depende de la decisión del usuario Entonces, ¿cómo puedo declarar componentes dinámicamente?
Se ha sugerido la respuesta: la forma más fácil es una Lista de objetos (componentes). TObjectList es el más simple de usar (en los controles de unidades). ¡Las listas son geniales!
In Form1 Public
MyList: TObjectList;
procedure AnyButtonClick(Sender: TObject);
// Puedes ser más sofisticado y declarar // TNotifyevents y asignarlos, pero vamos a hacerlo simple :) . . .
procedure Tform1.AnyButtonClick(Sender: TObject);
begin
If Sender is TButton then
begin
Case Tbutton(Sender).Tag of
.
.
.
// Or You can use the index in the list or some other property
// you have to decide what to do
// Or similar :)
end;
end;
procedure TForm1.BtnAddComponent(Sender: TObJect)
var
AButton: TButton;
begin
AButton := TButton.Create(self);
Abutton. Parent := [Self], [Panel1] [AnOther Visual Control];
AButton.OnClick := AnyButtonClick;
// Set Height and width and caption ect.
.
.
.
AButton.Tag := MyList.Add(AButton);
end;
Una lista de Objetos puede contener cualquier objeto visual o no, pero eso le da una sobrecarga adicional de clasificar qué elementos son cuáles, mejor tener listas relacionadas si desea múltiples controles dinámicos en paneles similares, por ejemplo.
Nota: al igual que otros comentaristas, es posible que haya simplificado demasiado por brevedad, pero espero que tenga la idea. Necesita un mecanismo para administrar los objetos una vez que se crean y las listas son excelentes para estas cosas.
Durante una investigación sobre " crear un formulario delphi usando la plantilla basada en xml " ;, encuentro algo útil señalando RTTI y usando herramientas abiertas api (ToolsApi.pas, creo). Eche un vistazo a las interfaces en la unidad.
Muy fácil. Llamada Crear. Ejemplo:
procedure test
var
b : TButton;
begin
b:=TButton.Create(nil);
b.visible:=false;
end;
Esto crea un componente (TButton es un componente) en tiempo de ejecución y establece la propiedad visible.
Para el constructor: pase nil si desea administrar la memoria usted mismo. Pase un puntero a otro componente si desea que se destruya cuando se destruya el otro componente.
Algunos componentes anulan el método 'Cargado'. Este método no se llamará automáticamente si crea una instancia en tiempo de ejecución. Delphi lo llamará cuando se complete la carga desde el archivo de formulario (DFM).
Si el método contiene código de inicialización, su aplicación puede mostrar un comportamiento inesperado cuando se crea en tiempo de ejecución. En este caso, verifique si el escritor de componentes ha utilizado este método.
Si anida controles ganadores en Cuadros de grupo / Controles de página / Etc ..., creo que es beneficioso que el cuadro de grupo principal también sea el propietario. He notado una fuerte disminución en los tiempos de cierre de las ventanas al hacer esto, en lugar de que el propietario sea siempre el formulario principal.
Este es un ejemplo de cómo emular una etiqueta de botón en Evernote
unit Unit7;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, CHButton, Vcl.ExtCtrls, RzPanel, CHPanel, RzCommon,RzBmpBtn, Vcl.StdCtrls;
type
// This is panel Button
TButtonClose = class (TRzPanel)
CloseButton : TRzBmpButton;
procedure CloseButtonClick(Sender: TObject);
procedure CloseButtonMouseEnter(Sender: TObject);
procedure MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
TForm7 = class(TForm)
CHButton1: TCHButton;
RzPanel1: TRzPanel;
RzBmpButton1: TRzBmpButton;
procedure CHButton1Click(Sender: TObject);
procedure RzBmpButton1Click(Sender: TObject);
procedure RzPanel1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure RzPanel1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure RzPanel1MouseEnter(Sender: TObject);
procedure RzBmpButton1MouseEnter(Sender: TObject);
procedure FormMouseEnter(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form7: TForm7;
MyCloseButton : TButtonClose;
implementation
{$R *.dfm}
// constructor for on the fly component created
constructor TButtonClose.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
// Set Events for the component
Self.OnMouseEnter := Self.CloseButtonMouseEnter;
Self.OnMouseDown := Self.MouseDown;
Self.OnMouseUp := Self.MouseUp;
Self.Height := 25;
// Close button on top panel Button
// Inherited from Raize Bitmap Button
CloseButton := TRzBmpButton.Create(self);
// Set On Click Event for Close Button
CloseButton.OnClick := Self.CloseButtonClick;
// Place Close Button on Panel Button
CloseButton.Parent := self;
CloseButton.Left := 10;
CloseButton.Top := 5;
CloseButton.Visible := False;
// Setting the image for the button
CloseButton.Bitmaps.Up.LoadFromFile(ExtractFilePath(Application.ExeName)+'\close.bmp');
end;
procedure TButtonClose.CloseButtonClick(Sender: TObject);
begin
// Free the parent (Panel Button)
TControl(Sender).Parent.Free;
end;
procedure TButtonClose.CloseButtonMouseEnter(Sender: TObject);
begin
// Show the Close button
CloseButton.Visible := True;
end;
procedure TButtonClose.MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
// Emulate Button down state, since it is panel
TRzPanel(Sender).BorderOuter := fsLowered;
end;
procedure TButtonClose.MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
// Emulate Button up state, since it is panel
TRzPanel(Sender).BorderOuter := fsRaised;
end;
destructor TButtonClose.Destroy;
begin
inherited Destroy;
end;
procedure TForm7.FormCreate(Sender: TObject);
begin
// Create Panel Button on the fly
MyCloseButton := TButtonClose.Create(self);
MyCloseButton.Caption := 'My Button';
MyCloseButton.Left := 10;
MyCloseButton.Top := 10;
// Don't forget to place component on the form
MyCloseButton.Parent := self;
end;
procedure TForm7.FormMouseEnter(Sender: TObject);
begin
if Assigned(RzBmpButton1) then
RzBmpButton1.Visible := False;
// Hide when mouse leave the button
// Check first if myCloseButton Assigned or not before set visible property
if Assigned(MyCloseButton.CloseButton) then
MyCloseButton.CloseButton.Visible := False;
end;
procedure TForm7.RzBmpButton1Click(Sender: TObject);
begin
TControl(Sender).Parent.Free;
end;
procedure TForm7.RzBmpButton1MouseEnter(Sender: TObject);
begin
RzBmpButton1.Visible := True;
end;
procedure TForm7.RzPanel1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
TRzPanel(Sender).BorderOuter := fsLowered;
end;
procedure TForm7.RzPanel1MouseEnter(Sender: TObject);
begin
RzBmpButton1.Visible := True;
end;
procedure TForm7.RzPanel1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
TRzPanel(Sender).BorderOuter := fsRaised;
end;
procedure TForm7.CHButton1Click(Sender: TObject);
begin
FreeAndNil(Sender);
end;
end.