Hallo Jens,
also im Grunde ist der Verweis auf die Advanced Demo nicht falsch. Dort wird eigentlich alles gezeigt was man wissen muss. Problem hierbei ist allerdings, dass alles sehr stark zerpflückt wurde um die verwendeten Klassen bei mehreren Demos verwenden zu können. Ich hatte damals das gleiche Problem und musste mich da durch kämpfen.
Aber kommen wir mal zum eigentlich wichtigen Teil des Beitrages. Als allererstes solltest du dir eine neue Klasse erstellen die das Interface
IVTEditLink
implementiert. Das Interface ist die Grundlage dafür, dass der Editor überhaupt funktionieren kann. Es sei denn du machst wirklich alles von Hand. Und das willst du ja nicht (wäre aus schwachsinnig).
Die Definition des Interfaces von IVTEditLink steht in der VirtualTrees.pas, trotzdem poste ich diese hier, damit das alles etwas zusammenhängt.
Delphi-Quellcode:
IVTEditLink = interface
['{2BE3EAFA-5ACB-45B4-9D9A-B58BCC496E17}']
function BeginEdit: Boolean; stdcall; // Called when editing actually starts.
function CancelEdit: Boolean; stdcall; // Called when editing has been cancelled by the tree.
function EndEdit: Boolean; stdcall; // Called when editing has been finished by the tree. Returns True if successful, False if edit mode is still active.
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
// Called after creation to allow a setup.
function GetBounds: TRect; stdcall; // Called to get the current size of the edit window
// (only important if the edit resizes itself).
procedure ProcessMessage(var Message: TMessage); stdcall;
// Used to forward messages to the edit window(s)-
procedure SetBounds(R: TRect); stdcall; // Called to place the editor.
end;
Delphi-Quellcode:
TEditEditLink = class (TInterfacedObject, IVTEditLink)
private
FEdit: TEdit;
FTree : TVirtualStringTree;
FNode : PVirtualNode;
FColumn : Integer;
procedure GetNodeText;
public
destructor Destroy; override;
function BeginEdit: Boolean; virtual; stdcall;
function CancelEdit: Boolean; virtual; stdcall;
function EndEdit: Boolean; virtual; stdcall;
function GetBounds: TRect; virtual; stdcall;
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
procedure ProcessMessage(var Message: TMessage); virtual; stdcall;
procedure SetBounds(R: TRect); stdcall;
end;
Ich hoffe ich habe jetzt an dieser Stelle nichts vergessen da ich alles bei mir mit abgeleiteten Klassen gebaut habe. Ich habe mir eine BaseEditor Klasse gebaut wie man sie in der AdvancedDemo auch findet. Diese macht diverse grundlegene Dinge die man sonst bei jeder Art von Komponente noch einmal machen müsste. Kommen wir jetzt zur implementierung der einzelnen Methoden.
In der PrepareEdit Funktion werden zuerst einmal alle wichtigen Komponenten zwischengespeichert mit denen man arbeiten muss. Also der eigentliche Tree von dem der Editvorgang gestartet wurde, die Node die editiert werden soll und abschließen noch die Column in der editiert werden soll.
Delphi-Quellcode:
function TEditEditLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean;
begin
Result := True;
FTree := Tree as TVirtualStringTree;
FNode := Node;
FColumn := Column;
end;
Delphi-Quellcode:
function TEditEditLink.BeginEdit: Boolean;
begin
// Hier kann man diverse Dinge tun, die direkt zu Beginn des Editvorganges gemacht werden sollen
// Als Beispiel bei einem Editfeld könnte alles selektiert werden
Self.FEdit.Show;
Self.FEdit.SetFocus;
Self.GetNodeText;
Self.FEdit.SelectAll;
end;
Die Funktion GetNodeText kommt von mir und soll den Text der in der Node steht einfach mal in das neu erstellte Editfeld "kopieren".
Delphi-Quellcode:
procedure TEditEditLink.GetNodeText;
begin
Self.FEdit.Text := FTree.Text[FNode, FColumn];
end;
Im EndEdit sollten die Änderungen dann gespeichert werden. Ich habe mir jetzt einfach mal einen kleinen Codeausschnitt aus meinen Editorklassen kopiert.
Delphi-Quellcode:
function TBaseDataEditLink.EndEdit: Boolean;
begin
try
SaveChanges;
except
on e:
Exception do
MessageBox(FTree.Parent.Handle, PChar(e.
Message), '
Fehler', MB_OK
or MB_ICONERROR
or MB_TASKMODAL);
end;
Result := True;
end;
In der SetBounds Procedure wird der erstellten Komponente die Größe zugeteilt die diese haben soll. Die GetColumnBounds Procedure verlangt als zweiten Parameter einen Var-Parameter der die linke Position der Column zurückgibt. Ich weiß nicht mehr wieso ich hier eine Dummy Variable benutzt habe, kann sein, dass hier irgendwo sonst was überschrieben wurde. Kannst du aber gerne ändern bzw. ausprobieren.
Delphi-Quellcode:
procedure TBaseDataEditLink.SetBounds(R: TRect);
var
Dummy : Integer;
begin
FTree.Header.Columns.GetColumnBounds(FColumn, Dummy, R.Right);
R.Left := Dummy;// + FTree.Margin * 2;
FEdit.Width := R.Width;
R.Bottom := Abs(R.Top) + Abs(FTree.NodeHeight[FTree.FocusedNode]);
InflateRect(R, 0, 1);
FEdit.BoundsRect := R;
end;
GetBounds wird von der Treeklasse benötigt, um die "Ränder" der erstellten Komponente zu ermitteln um diese später im Tree richtig zu zeichnen.
Delphi-Quellcode:
function TBaseDataEditLink.GetBounds: TRect;
begin
Result := FEdit.BoundsRect;
end;
Die ProcessMessages Procedure hatte ich mir auch nur aus der AdvancedDemo herauskopiert. Die soll wohl nur bewirken, dass alle Nachrichten des Trees an den Editor weitergeleitet werden.
Delphi-Quellcode:
procedure TBaseDataEditLink.ProcessMessage(var Message: TMessage);
begin
if Assigned(FEdit) then
FEdit.WindowProc(Message);
end;
Die CancelEdit Funktion macht eigentlich das was der Name sagt bzw. wird bei Abbrechen des Editiervorganges ausgeführt.
Delphi-Quellcode:
function TBaseDataEditLink.CancelEdit: Boolean;
begin
Result := True;
FEdit.Hide;
end;
Im Destructor noch das Editfeld aufräumen damit nichts mehr im Speicher zurückbleibt.
Delphi-Quellcode:
destructor TBaseDataEditLink.Destroy;
begin
FEdit.Free;
inherited Destroy;
end;
So! An dieser Stelle hätten wir unsere Klasse nun fertig sofern ich nichts vergessen habe. Falls doch, einfach darauf hinweisen. Jetzt müssen wir den Editor nur noch in unserem eigentlichen Programm verwenden. Hierzu müssen wir das OnCreateEditor Event des Trees einbinden.
Hier kann noch der Sender, die Column und die Node abgefragt werden um abhängig davon einen entsprechenden Editor zu erzeugen. Der EditLink Parameter ist ein out Parameter und verlangt jetzt eine Instanz eines Interfaces. Hier einfach eine Instanz der vorhin erstellten Klasser erzeugen.
Delphi-Quellcode:
procedure TfrmMain.vstCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
out EditLink: IVTEditLink);
begin
EditLink := TEditEditLink.Create;
// Hier sollte eigentlich ein Kommentar stehen den ich jetzt über die Methode gesetzt habe.
end;
Zusätzlich zum OnCreateEditor Event gibt es noch die beiden Events
OnEditing
,
OnEdited
und
OnEditCancelled
. Im
OnEditCancelled
Event kann darauf reagiert werden, was bei Abbruch des Editiervorganges passieren soll. Bspw. kann hier alles wieder auf einen bestimmten Status zurückgesetzt werden oder was auch immer. Im
OnEditing
Event wird eigentlich nur der Start des Editiervorganges mit Hilfe des
Allowed
Parameters erlaubt bzw. verweigert (abhängig von Node und Column und evtl. anderen Faktoren). Und schlussendlich im
OnEdited
Event kann dann der geänderte Status übernommen und gespeichert/verarbeitet werden.
Hoffentlich hat dir diese kleine Einführung geholfen das Thema besser zu verstehen. Ansonsten einfach hier nochmal nachhaken. Ich hatte mich damals auch ewig lange mit diese Thema auseinander gesetzt bis ich es endlich mal verstanden hatte.