AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein GUI-Design mit VCL / FireMonkey / Common Controls VirtualStringTree: Exception beim editieren von Zellen
Thema durchsuchen
Ansicht
Themen-Optionen

VirtualStringTree: Exception beim editieren von Zellen

Ein Thema von VizeTE · begonnen am 10. Okt 2010 · letzter Beitrag vom 13. Okt 2010
Antwort Antwort
VizeTE

Registriert seit: 31. Dez 2002
178 Beiträge
 
Delphi 5 Enterprise
 
#1

VirtualStringTree: Exception beim editieren von Zellen

  Alt 10. Okt 2010, 18:06
Hallo,

ich setze mich gerade mit dem VirtualStringTree auseinander und komme auch ganz gut voran. Allerdings habe ich ein Problem beim bearbeiten von Einträgen. Das Ändern von Werten habe ich mir aus der PropertyForm aus der Advanced-Demo abgeguckt. (allerdings eine älter Version da ich auf der Homepage des Autors keine aktuelle Demo gefunden habe)

Ich habe in einer Beispielanwendung ein VirtualStringGrid mit zwei Spalten. Die erste Spalte wird mit Hilfe eines TEdit bearbeitet, die zweite Spalte mit einer TComboBox.
Wenn ich nun die Spalte mit dem Edit bearbeite und mit Enter bestätige so bekomme ich eine Zugriffsverletzung. Wenn ich jedoch das Edit nicht mit Enter bestätige und stattdessen einfach auf einen anderen Eintrag klicke gibt es die Zugriffsverletzung nicht und der Wert wird richtig übernommen.

Beim debuggen hat sich herausgestellt das der Fehler nicht in dem KeyDown-Event des EditLinks ausgelöst wird. Wenn ich den Inhalt aus der Event-Methode auskommentiere kommt es jedoch auch nicht mehr zu der Zugriffsverletzung.
Direkt nach dem die Eventmethode abgearbeitet wurde lande ich mit dem Debugger in "TWinControl.KeyDown" => "TWinControl.DoKeyDown" => ... bis ich schließlich in der Unit Forms in der Methode "GetParentForm" lande. Dort wird dann die Exception ausgelöst als versucht wird auf den übergebenen Parameter zuzugreifen.
Als Parameter wird jedoch "Self" aus der letzte TWinControl-Methode übergeben. Allerdings weiß ich nicht mal in welcher TWinControl-Instanz ich mich gerade befinde. (es läßt sich nicht mal Classname abrufen)

Vielleicht hat jemand eine Idee. Ich weiß nicht mehr weiter.

Ich verwende Delphi7 und VirtualTreeview sollte in der Version 4.8.6 vorliegen. (ich habe auf die Schnelle nichts gefunden wo ich das kontrollieren kann)

Anbei mal mein Quellcode des Hauptformulars wo alles drin ist was ich mache. Im Anhang füge ich noch ein Archiv mit einer ausführbaren Beispielanwendung und dem komplette Quellcode an.

Wahrscheinlich ist es mal wieder ein dumme Kleinigkeit.
Vielen Dank schon mal.

Delphi-Quellcode:
unit Main_F;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, VirtualTrees;

type
  TMainForm = class;

  TTreeData = record
    Id : integer;
    Name : string;
    Quantity : integer;
  end;
  PTreeData = ^TTreeData;

  TMainForm = class(TForm)
    VST: TVirtualStringTree;
    procedure VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
    procedure VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType;
      var CellText: WideString);
    procedure VSTEditing(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; var Allowed: Boolean);
    procedure VSTNewText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; NewText: WideString);
    procedure VSTCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; out EditLink: IVTEditLink);
    procedure FormShow(Sender: TObject);

  protected
    function AddNodeToTree(Tree: TCustomVirtualStringTree; Parent: PVirtualNode; Node: TTreeData): PVirtualNode;

    procedure LoadData;
  public
  end;

  // Our own edit link to implement several different node editors.
  TPropertyEditLink = class(TInterfacedObject, IVTEditLink)
  private
    FColumn : integer; // The column of the node being edited.
    FEdit : TWinControl; // One of the property editor classes.
    FNode : PVirtualNode; // The node being edited.
    FOldEditProc : TWndMethod; // Used to capture some important messages
                                       // regardless of the type of edit control we use.
    FTree : TVirtualStringTree; // A back reference to the tree calling.
  protected
    procedure EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure EditWindowProc(var Message: TMessage);
  public
    destructor Destroy; override;

    procedure ProcessMessage(var Message: TMessage); stdcall;
    procedure SetBounds(R: TRect); stdcall;

    function BeginEdit: boolean; stdcall;
    function CancelEdit: boolean; stdcall;
    function EndEdit: boolean; stdcall;
    function GetBounds: TRect; stdcall;
    function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): boolean; stdcall;
  end;

var
  MainForm: TMainForm;

implementation

uses
  StdCtrls;

{$R *.dfm}

{ TMainForm }

function TMainForm.AddNodeToTree(Tree: TCustomVirtualStringTree;
  Parent: PVirtualNode; Node: TTreeData): PVirtualNode;
var
  pData : PTreeData;
begin
  Result := Tree.AddChild(Parent);
  pData := Tree.GetNodeData(Result);
  Tree.ValidateNode(Result, false);
  pData^.Id := Node.Id;
  pData^.Name := Node.Name;
  pData^.Quantity := Node.Quantity;
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  //VST.Header.Columns[0].Options := VST.Header.Columns[0].Options + [coVisible];
  //VST.Header.Columns[1].Options := VST.Header.Columns[1].Options + [coVisible];

  LoadData;
end;

procedure TMainForm.LoadData;
var
  pNode : PVirtualNode;
  rData : TTreeData;
begin
  VST.NodeDataSize := SizeOf(TTreeData);
  VST.BeginUpdate;

  rData.Id := 1;
  rData.Name := 'Test 1';
  rData.Quantity := 1;
  pNode := AddNodeToTree(VST, nil, rData);

  rData.Id := 2;
  rData.Name := 'Test 2';
  rData.Quantity := 7;
  AddNodeToTree(VST, pNode, rData);

  VST.EndUpdate;
end;

procedure TMainForm.VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  pData: PTreeData;
begin
  inherited;
  pData := Sender.GetNodeData(Node);
  if Assigned(pData) then
    pData^.Name := '';
end;

procedure TMainForm.VSTGetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: WideString);
var
  iUnitIdx : integer;
  pData : PTreeData;
begin
  pData := Sender.GetNodeData(Node);

  if Assigned(pData) then
    case Column of
      0: CellText := IntToStr(pData^.Id) + ' - ' + pData^.Name;
      1: CellText := FormatFloat('0.##', pData^.Quantity);
    end;

  inherited;
end;

procedure TMainForm.VSTEditing(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
begin
  Allowed := true;
end;

procedure TMainForm.VSTNewText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; NewText: WideString);
var
  iUnitIdx : integer;
  pData : PTreeData;
begin
  inherited;

  pData := Sender.GetNodeData(Node);
  if not Assigned(pData) then
    Exit;

  case Column of
    0 : pData^.Name := NewText;
    1 : if StrToIntDef(NewText, -1) < 0 then
          MessageDlg('Bitte geben Sie eine gültige Menge ein.', mtInformation, [mbOK], 0)
        else
          pData^.Quantity := StrToInt(NewText);
  end;
end;

procedure TMainForm.VSTCreateEditor(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink);
begin
  EditLink := TPropertyEditLink.Create;
end;

{ TPropertyEditLink }

function TPropertyEditLink.BeginEdit: boolean;
begin
  Result := true;
  FEdit.Show;
  FEdit.SetFocus;

  // Set a window procedure hook (aka subclassing) to get notified about important messages.
  FOldEditProc := FEdit.WindowProc;
  FEdit.WindowProc := EditWindowProc;
end;

function TPropertyEditLink.CancelEdit: boolean;
begin
  Result := true;

  // Restore the edit's window proc.
  FEdit.WindowProc := FOldEditProc;
  FEdit.Hide;
end;

destructor TPropertyEditLink.Destroy;
begin
  FreeAndNil(FEdit);
  inherited;
end;

procedure TPropertyEditLink.EditKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  bCanAdvance: Boolean;
begin
  case Key of
    VK_RETURN,
    VK_UP,
    VK_DOWN:
      begin
        // Consider special cases before finishing edit mode.
        bCanAdvance := Shift = [];
        if FEdit is TComboBox then
          bCanAdvance := bCanAdvance and not TComboBox(FEdit).DroppedDown;

        if bCanAdvance then
        begin
          FTree.EndEditNode;
          if Key = VK_UP then
            FTree.FocusedNode := FTree.GetPreviousVisible(FTree.FocusedNode)
          else
            FTree.FocusedNode := FTree.GetNextVisible(FTree.FocusedNode);
          FTree.Selected[FTree.FocusedNode] := true;
          Key := 0;
        end;
      end;
  end;
end;

procedure TPropertyEditLink.EditWindowProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_KILLFOCUS: FTree.EndEditNode;
  else
    FOldEditProc(Message);
  end;
end;

function TPropertyEditLink.EndEdit: boolean;
var
  aBuffer : array[0..1024] of Char;
  iDummy : integer;
  pData : PTreeData;
  rPoint : TPoint;
  sString : WideString;
begin
  // Check if the place the user click on yields another node as the one we
  // are currently editing. If not then do not stop editing.
  GetCursorPos(rPoint);
  rPoint := FTree.ScreenToClient(rPoint);
  Result := FTree.GetNodeAt(rPoint.X, rPoint.Y, True, iDummy) <> FNode;

  if Result then
  begin
    // restore the edit's window proc
    FEdit.WindowProc := FOldEditProc;
    pData := FTree.GetNodeData(FNode);
    if FEdit is TComboBox then
      sString := TComboBox(FEdit).Text
    else
    begin
      GetWindowText(FEdit.Handle, aBuffer, 1024);
      sString := aBuffer;
    end;

    if Assigned(pData) then
      case FColumn of
        0 : begin
               pData^.Name := sString;
               FTree.InvalidateNode(FNode);
             end;
        1 : if StrToIntDef(sString, -1) >= 0 then
             begin
               pData^.Quantity := StrToInt(sString);
               FTree.InvalidateNode(FNode);
             end else
               MessageDlg('Geben Sie eine gültige Mengenangabe ein.', mtWarning, [mbOK], 0);
      end;

    FEdit.Hide;
  end;
end;

function TPropertyEditLink.GetBounds: TRect;
begin
  Result := FEdit.BoundsRect;
end;

function TPropertyEditLink.PrepareEdit(Tree: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex): boolean;
var
  i : integer;
  pData : PTreeData;
  oComboBox : TComboBox;
  oEdit : TEdit;
  sItem : string;
begin
  Result := true;
  FTree := Tree as TVirtualStringTree;
  FNode := Node;
  FColumn := Column;

  // determine what edit type actually is needed
  FreeAndNil(FEdit);
  pData := FTree.GetNodeData(Node);

  case Column of
    0 : begin
          oEdit := TEdit.Create(Tree);
          oEdit.Visible := false;
          oEdit.Parent := Tree;
          oEdit.OnKeyDown := EditKeyDown;

          if Assigned(pData) then
            oEdit.Text := pData^.Name
          else
            oEdit.Text := '';

          FEdit := oEdit;
          oEdit := nil;
        end;
    1 : if Assigned(pData) then
        begin
          oComboBox := TComboBox.Create(Tree);
          oComboBox.Visible := false;
          oComboBox.Parent := Tree;
          oComboBox.Style := csDropDownList;

          oComboBox.Items.Clear;
          for i := 1 to 5 do
          begin
            oComboBox.Items.Add(IntToStr(i));
            if pData^.Quantity = i then
              oComboBox.ItemIndex := Pred(oComboBox.Items.Count);
          end;

          oComboBox.OnKeyDown := EditKeyDown;
          FEdit := oComboBox;
          oComboBox := nil; //Referenz wurde in FEdit gespeichert!
        end else
          Result := false;
  else
    Result := false;
  end;
end;

procedure TPropertyEditLink.ProcessMessage(var Message: TMessage);
begin
  if FEdit <> nil then
    FEdit.WindowProc(Message);
end;

procedure TPropertyEditLink.SetBounds(R: TRect);
var
  iDummy: integer;
begin
  // Since we don't want to activate grid extensions in the tree (this would influence how the selection is drawn)
  // we have to set the edit's width explicitly to the width of the column.
  FTree.Header.Columns.GetColumnBounds(FColumn, iDummy, R.Right);
  FEdit.BoundsRect := R;
end;

end.
Angehängte Dateien
Dateityp: zip TreeView.zip (374,0 KB, 24x aufgerufen)
  Mit Zitat antworten Zitat
VizeTE

Registriert seit: 31. Dez 2002
178 Beiträge
 
Delphi 5 Enterprise
 
#2

AW: VirtualStringTree: Exception beim editieren von Zellen

  Alt 12. Okt 2010, 23:49
Inzwischen habe ich paar neue Informationen herausgefunden.

In der Methode TPropertyEditLink.EditKeyDown (also wenn Enter gedrückt wird) wird FTree.EndEditNode aufgerufen. Die wiederrum ruft TBaseVirtualTree.DoEndEdit auf. Dort wird FEditLink := nil gesetzt. Dadurch wird dieser Freigegeben da es sich um eine Instanz von TInterfacesObject handelt.
Nun wird in TPropertyEditLink.Destroy das Edit freigegeben. Danach wird jedoch noch die MainWndProc vom Edit aufgerufen. Hier knallt es dann da das Edit ja schon freigegeben wurde.

Jetzt weiß ich warum es passiert. Aber wie ich das am besten umgehen ist mir noch nicht ganz klar.

Hat jemand einen guten Vorschlag?

Geändert von VizeTE (13. Okt 2010 um 19:02 Uhr)
  Mit Zitat antworten Zitat
VizeTE

Registriert seit: 31. Dez 2002
178 Beiträge
 
Delphi 5 Enterprise
 
#3

AW: VirtualStringTree: Exception beim editieren von Zellen

  Alt 13. Okt 2010, 21:53
So ich glaube jetzt habe ich die Lösung.
Ich habe einfach alles etwas weniger dynamisch gestaltet und es scheint zu funktionieren.

Im Detail habe ich folgendes geändert:
  1. TPropertyEditLink habe ich nicht mehr von TInterfacedObject sonder von TObject abgeleitet. Entsprechend muss ich die Referenzzählung für das Interface selbst implementieren. Das habe ich so gemacht das die Zähl-Funktionen immer 1 zurückgeben. Dies hat zur Folge das das Objekt nicht mehr automatisch freigegeben wird.
    Nun erstelle ich eine Instanz dieser Klasse und speichere diese in einer Objektvariable des Formulars. Das Objekt wird erst beim Zerstören des Formulars freigegeben.
  2. Die Implement der Methode VSTCreateEditor habe ich so geändert das nun immer die Instanz aus der Objektvariable übergeben an statt eine neue Instanz er erzeugen.
  3. Der Klasse TPropertyEditLink habe ich zwei zusätzliche Objektvariablen spendiert. Eine für das Edit, die zweite für die ComboBox. Diese werden beim instanzieren von TPropertyEditLink angelegt und auch erst beim freigeben der TPropertyEditLink-Instanz zerstört.
  4. In der Funktion PrepareEdit erzeuge ich nun keine Edit bzw. keine ComboBox sonder weise die gewünschte Komponente aus der Objektvariablen FEdit zu.
Wenn nun eine Steuerelement wirklich mal eine verspätet Nachricht erhält wurde dieses noch nicht freigegeben und es gibt keine nervigen Exceptions mehr.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:12 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz