Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Komponente um Prozeduren erweitern


Coder - Mi 20.06.07 19:12
Titel: Komponente um Prozeduren erweitern
Hi
Ich würde gern eine Komponente um Prozeduren erweitern, um diese in eine eigene Unit zu schreiben.
Jedoch soll es nicht zu kompliziert werden d.h. ich will die erweiterte Komponente nicht extra in der IDE installieren sondern die Eigenschaften des Vorfahren übernehmen, der bereits auf die Form gesetzt und richtig konfiguriert ist.
Dazu hab ich dieses Testprojekt erstellt.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

  TMyButton = class(TButton)
    procedure MakeCaption;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TMyButton.MakeCaption;
begin
  Self.Caption := '123';
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TMyButton(Button1).MakeCaption;
end;

Das funktioniert soweit gut.
Aber kann ich den TButton in ein TMyButton "umwandeln" und dabei die Eigenschaften in einem Rutsch übernehmen, sodass ich mir das TMyButton( sparen kann?

MfG


mkinzler - Mi 20.06.07 19:14

Garnicht, da Sender ja vom typ TComponent ist.


Coder - Fr 03.08.07 02:33

Das obere Beispiel funktioniert.

Aber jetzt hab ich mal probiert der Komponente eine weitere Variable anzuhängen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

  TMyButton = class(TButton)
  public
    s: String;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  TMyButton(Button1).s := 'Test';
  ShowMessage(TMyButton(Button1).s);
end;

So bekommt man eine Zugriffsverletzung.
Das erscheint mir auch irgendwie logisch, da wohl gar kein Speicher für die Variable reserviert wird, oder?
Wie könnte man das lösen, eine Komponente mit Funktionen, Prozeduren und Variablen einfach zu erweitern?
Ich will vermeiden, dass jemand, der meinen Quellcode kompilieren will, erst noch zig Komponenten in seine IDE installieren muss.

MfG


Logikmensch - Fr 03.08.07 07:01

Dass das nicht richtig funzt liegt daran, dass ein Nachkomme auch als solcher Createt werden muss, damit die Instanz richtig initialisiert wird. Mit Deinem TypeCast umgehst Du dies. Da Dein Button1 nur als TButton instantiiert ist, fehlen ihm Deine neuen Eigenschaften, sprich Deine neue Variable und dann gibt's unweigerlich Probleme. Das Prinzip der OOP richtig zu benutzen ist Grundlage für gutes OOP, daher ist mir jetzt nicht klar, was Du eigentlich einsparen willst.

Wenn Du ohne eine neue Kompo zu erstellen, eine Kompo um weitere Fähigkeiten erweitern willst, dann erstelle diese nicht mit dem Formulardesigner, sondern zur Laufzeit. Das Hinzufügen eines Buttons ist doch recht simpel:

innerhalb des private-Abschnitts Deines Forms eintragen:

Delphi-Quelltext
1:
2:
3:
...
Fmybutton:Tmybutton;
...


innerhalb des FormCreate-Events einzutragen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
....
Fmybutton:=Tmybutton.Create(Self);
Fmybutton.Parent:=Self; //oder irgendeine andere Kompo, wo der Knopf drauf platziert werden soll
//und dann natürlich die Button-Eigenschaften setzen:
Fmybutton.Caption:='TestKnopf';
Fmybutton.OnClick:=mybuttonclick;
...


innerhalb des FormDestroy-Events einzutragen:

Delphi-Quelltext
1:
2:
3:
...
Fmybutton.Free;
...


So, dann kannst Du Dich in Deinem Code mit Deinem Knöpfchen freuen. :-)


Coder - Fr 03.08.07 15:06

Jupp. Nur muss ich dann die ganzen Eigenschaften der Komponente auch zur Laufzeit setzen.
Beim VirtualTreeView sind das ne ganze Menge.
Und das ist mir für nur eine zusätzliche Eigenschaft zu viel Aufwand.
Da lass ichs lieber mit OOP.

MfG


Logikmensch - Fr 03.08.07 18:13

Stimmt, bei komplexeren Objekten ist es mit dem Designer einfacher. Ich verstand Dich so, dass es Dir "nur" um einen Button geht.

Ich hoffe, ich war nicht zu schroff. Meine ersten Versuche in der Komponentenentwicklung gingen in dieselbe Richtung wie Dein Typecast-Versuch. :-)


Reinhard Kern - Fr 03.08.07 18:19

user profile iconCoder hat folgendes geschrieben:
Jupp. Nur muss ich dann die ganzen Eigenschaften der Komponente auch zur Laufzeit setzen.
Beim VirtualTreeView sind das ne ganze Menge.
Und das ist mir für nur eine zusätzliche Eigenschaft zu viel Aufwand.
Da lass ichs lieber mit OOP.

MfG


Hallo,

ich mache mir einfach eine neue Klasse, die die zugrundeliegende Komponente "benutzt" und erweitert:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
  TRKStringGrid = class (TComponent)
    private
    FHeader : TLabel;
    public
    FStringGrid : TStringGrid;
    NextRow,NextEntry : integer;
    constructor create (AOwner : TComponent; myGrid : TStringGrid); 
    procedure AssignHeader (H : TLabel);
    procedure SetHeader (txt : string);
    procedure SetHItem (txt : string; cix : integer);
    procedure SetC0Item (txt : string; rix : integer);
    procedure SetC0Number (rn : integer);
    procedure SetColText (txt : string; cix : integer);
    procedure SetColTextDef (deftxt,txt : string; cix : integer);
    procedure SetColInt (v : longint; cix : integer);
    procedure SetColBool (b : boolean; cix : integer);
    procedure Sort (col : integer; num,renumber : boolean);
    procedure Pack;
    procedure AutoAlign (h_only : boolean);
    procedure ClearGrid (irows : integer);
    procedure NewRow;
    procedure NewEntry;
    function  GetSelectedRow : integer;
    function  GetSelectedCode : ShortString;
    function  GetColText (c,r : integer) : ShortString;
    function  GetColInt (c,r : integer) : longint;
    end;


implementation

uses BLMain;

 constructor TRKStringGrid.create (AOwner : TComponent; myGrid : TStringGrid);
 begin
 inherited create (AOwner);
 FStringGrid := myGrid;
 end;

 procedure TRKStringGrid.AssignHeader (H : TLabel);
 begin
 FHeader := H;
 end;

 procedure TRKStringGrid.SetHeader (txt : string);
 begin
 if Assigned (FHeader) then FHeader.Caption := txt;
 end;
 
 procedure TRKStringGrid.SetHItem (txt : string; cix : integer);
 begin
 if Assigned (FStringGrid) then
   FStringGrid.Cells [cix,0] := txt;
 end;

.....


Geht in beliebigem Umfang, ich muss eben nur beim Create mit der Komponente "connecten". Im Beispiel sind es 2, ein Label und ein Stringgrid. Das Label benutze ich nur manchmal.

Gruss Reinhard


dummzeuch - So 05.08.07 13:52
Titel: den Compiler reinlegen
Hi,

es gibt auch die Moeglichkeit, dem Compiler vorzuspiegeln, dass eine Komponente von einem abgeleiteten Typ ist:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
unit Unit1;

interface

[...]

type
  TEdit = class(Controls.TEdit)
  public
    procedure doSomething;
  end;

type
  TForm1=class(TForm)
    Edit1: TEdit;
    [...]
  end;
[...]

implementation

[...]
procedure TEdit.doSomething;
[...]
end.


Damit hat ploetzlich jede TEdit-Komponente auf dem Formular eine zusaetzliche Methode doSometing, die man wie folgt aufrufen kann:


Delphi-Quelltext
1:
2:
3:
4:
procedure TForm1.Edit1Click(Sender: TObject);
begin
  (Sender as TEdit).doSomething;
end;


Auf diese Weise kann man einer Komponente auch neue Eventhandler, Messagehandler und Properties verpassen. Funktioniert wunderbar, auch ueber Unit-Grenzen hinweg, man muss nur den Gueltigkeitsbereich des Typs beachten.

Im Grunde genommen ist dies nichts anderes als das Erstellen einer abgeleiteten Komponente, nur dass man sie nicht in der IDE installiert und dass sie denselben Namen hat wie eine Standard-Komponente.

twm

Moderiert von user profile iconTino: Code- durch Delphi-Tags ersetzt


Reinhard Kern - So 05.08.07 19:46
Titel: Re: den Compiler reinlegen
user profile icondummzeuch hat folgendes geschrieben:
Hi,

es gibt auch die Moeglichkeit, dem Compiler vorzuspiegeln, dass eine Komponente von einem abgeleiteten Typ ist:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
unit Unit1;

interface

[...]

type
  TEdit = class(Controls.TEdit)
  public
    procedure doSomething;
  end;

type
  TForm1=class(TForm)
    Edit1: TEdit;
    [...]
  end;
[...]

implementation

[...]
procedure TEdit.doSomething;
[...]
end.


Damit hat ploetzlich jede TEdit-Komponente auf dem Formular eine zusaetzliche Methode doSometing, die man wie folgt aufrufen kann:


Delphi-Quelltext
1:
2:
3:
4:
procedure TForm1.Edit1Click(Sender: TObject);
begin
  (Sender as TEdit).doSomething;
end;


Auf diese Weise kann man einer Komponente auch neue Eventhandler, Messagehandler und Properties verpassen. Funktioniert wunderbar, auch ueber Unit-Grenzen hinweg, man muss nur den Gueltigkeitsbereich des Typs beachten.

Im Grunde genommen ist dies nichts anderes als das Erstellen einer abgeleiteten Komponente, nur dass man sie nicht in der IDE installiert und dass sie denselben Namen hat wie eine Standard-Komponente.

twm


Hallo,

damit verarschst du nicht den Compiler, der unterscheidet sicher zwischen Unit1.TEdit und Controls.TEdit, sonst würde das ganze ja auch nicht funktionieren. Für dein Unit1.TEdit wird ganz ordentlich eine erweiterte Methodentabelle angelegt mit doSomething.

Vielmehr legst du die kleinen Hilfsprogramme wie den Object-Inspector herein - nehme ich an, sonst wäre das ganze wenig sinnvoll. Wenn ich Unit1.TEdit nicht damit bearbeiten kann, habe ich ja nichts von der ganzen Übung.

Gruss Reinhard


BenBE - So 05.08.07 20:26

Ich bezweifle, dass das wirklich stabil laufen wird, da die VCL Komponenten-Klassen allein an ihrem Namen unterscheided, wodurch nicht sicher ist, ob Controls.TEdit oder Unit1.TEdit in der DFM-Resource gemeint ist. Und selbst wenn es korrekt aufgelöst wird, besteht immer noch das Problem, ob er wirklich eine Instanz von Unit1.TEdit angelegt wurde (oder doch nur von Controls.TEdit, was dann ziemlich krachen würde ...


dummzeuch - So 05.08.07 21:07

Hi,

user profile iconBenBE hat folgendes geschrieben:
Ich bezweifle, dass das wirklich stabil laufen wird, da die VCL Komponenten-Klassen allein an ihrem Namen unterscheided, wodurch nicht sicher ist, ob Controls.TEdit oder Unit1.TEdit in der DFM-Resource gemeint ist. Und selbst wenn es korrekt aufgelöst wird, besteht immer noch das Problem, ob er wirklich eine Instanz von Unit1.TEdit angelegt wurde (oder doch nur von Controls.TEdit, was dann ziemlich krachen würde ...


Es funktioniert und ist stabil. Was nicht klappt, sind neue published properties, die wuerden nicht gestreamt.

twm


Coder - Mi 08.08.07 16:08

Klasse! Dann werd ich das so machen wie dummzeuch.
Danke an alle :zustimm: