Nov
1
Erstellt von:
Torsten Weggen
01.11.2006 16:17
Die beiden Microsoft Steuerelemente bieten eine sinnvolle Erweiterung der mit VFP mitgelieferten Controls. Das Look and Feel der Masken ist mehr Windows-Like und sieht nicht so „altbacken“ aus, wie es ansonsten viele VFP-Anwendungen sind. Leider gibt es bei der Programmierung einige Klippen zu umschiffen. Das Handling ist etwas anders, als man es von den VFP-Controls gewöhnt ist. Diese Session bietet dem Einsteiger alle benötigten Informationen, um die Controls sinnvoll in die eigene Applikation einzubinden.
Das MS-Treeview
Das MS-Treeview zeigt eine hierarchisch geordnete Liste von Node-Objekten, welche wiederum aus einer Caption sowie optional einer Bitmap bestehen. Ein Treeview wird typischerweise verwendet, um hierarchisch geordnete Daten wie eine Directory-Struktur oder 1-m-n Beziehungen in Datenbanken darzustellen.
Nach dem Erzeugen eines Treeviews auf einer Form kann man Nodes hinzufügen, löschen, neu arrangieren oder anderweitig manipulieren durch Setzen von Properties oder Aufruf von Methoden des Treeviews. Man kann Nodes programmatisch auf- und zuklappen um Childnodes anzuzeigen oder wieder zu verstecken. Vier Events (Collapse, Expand, NodeClick und NodeCheck) bieten ebenfalls die Möglichkeit, programmatisch auf das Verhalten einzuwirken.
Man kann im Treeview programmatisch navigieren über eine Referenz auf ein Node-Objekt über die Properties Root, Parent, Child, Firstsibling, Next, Previous und LastSibling. Der Benutzer kann ebenso interaktiv über Tastatureingaben durch das Treeview navigieren. Die Pfeil-auf bzw. Pfeil-ab Tasten bewegen den Focus durch alle geöffneten Nodes. Die Node-Objekte werden dabei von links nach rechts bzw. von oben nach unten selektiert. Am Ende des Treeviews springt der Focus bei Pfeil-ab automatisch wieder auf den ersten Knoten und umgekehrt, wobei der Tree ggfs. automatisch gescrollt wird. Pfeil-links und Pfeil-rechts springen ebenfalls durch die expandierten Knoten mit der Besonderheit, das ein nicht geöffneter Knoten bei Pfeil-rechts zunächst expandiert wird und erst der zweite Pfeil-rechts-Tastendruck auf den nächsten (Child-) Node springt. Umgekehrt führt der Pfeil-links Tastendruck auf einen expandierten Knoten zunächst zum Collapse und im nächsten Tastendruck dann zum Navigieren auf den übergeordneten Node. Wenn der Benutzer eine ANSI-Taste drückt, springt der Focus auf den nächstliegenden Node dessen Caption mit der gedrückten Taste beginnt.
Es gibt unterschiedliche visuelle Einstellungen (Styles) für das Treeview . Acht Kombinationen von Text, Bitmaps, Linien und und +/- Zeichen sind einstellbar.
Das Treeview Control verwendet das MS-Imagelist-Control um anzuzeigende Bitmaps und Icons zu speichern. Ein Treeview kann immer nur ein Imagelist-Control verwenden, mit der Folge, das die Icons immer die selbe Größe haben, wenn im Style eine Kombination zur Anzeige von Bitmaps gewählt ist.
Das Treeview-Control findet sich in der Datei MSCOMCTL.OCX, welche üblicherweise im System32-verzeichnis zu finden ist. Liefern Sie eine Applikation aus, die das Treeview verwendet, sollten Sie natürlich in der Setup-Routine das OCX mitliefern und auch auf dem Zielrechner registrieren. Installshield macht dies automatisch, ansonsten hilft „regsvr32 mscomctl.ocx“.
Treeview Properties,Methoden und Events
Treeview Properties
| Name | Werte | Beschreibung |
| Checkboxes | (boolean) .T. oder .F. | Wenn .T., wird vor jedem Knoten eine Checkbox angezeigt |
| DropHighlight | Node-Objekt | Das DropHighlight Property wird typischerweise in Kombination mit der HitTest-Methode bei Drag-and-Drop-Operationen verwendet. Wenn der Cursor dann über ein Node-Objekt bewegt wird, kann über die HitTest-Methode eine Referenz auf den „überfahrenen“ Knoten ermittelt werden. Wenn dieser Knoten dann dem DropHighlight Property zugewiesen wird, erscheint dieses blau hinterlegt |
| FullRowSelect | (boolean) .T. oder .F. | Wenn .T., wird die komplette Zeile eines selektierten Nodes markiert, ansonsten nur seine Caption |
| HotTracking | (boolean) .T. oder .F. | Wenn .T., wird der entsprechende Node beim Überfahren mit der Maus selektiert |
| Indentation | (numeric) Pixel | Gibt den Einzug von Parent zu Child an. |
| Labeledit | 0,1 | Gibt an, ob ein Node durch anklicken editiert werden kann. 0 (default) : Das BeforeLableEdit-Event wird gefeuert, wenn ein Benutzer auf den Node klickt 1: Das BeforeLabelEdit-Event wird nur gefeuert, wenn vorher die StartLabelEdit – Methode aufgerufen wird |
| LineStyle | 0,1 | 0: (Default) Nur Linien zwischen den Childs und ihrem Parent 1: Zusätzlich auch Linien zwischen den Root-Nodes |
| Nodes | Node-Collection | Enthält eine Referenz auf die Node-Collection (readonly). Nodes können manipuliert werden über Standard-Collection Methoden wie Add oder Remove. Jedes Element der Collection kann entweder über seinen numerischen Index oder über seinen alphanumerischen Key erreicht werden |
| PathSeparator | (char) | Default = „\“. Das Fullpath-Property enthält den kompletten Pfad der Captions, getrennt durch den hier definierten Path-Separator |
| Scroll | (boolean) .T. oder .F. | Wenn .T., werden (ggfs.) Scrollbars angezeigt |
| SelectedItem | Node-Objekt | Referenz auf den aktuell selektierten Node. (read/write) |
| SingleSel | (boolean) .T. oder .F. | Wenn .T., wird ein Node automatisch expandiert, wenn er selektiert wird |
| Sorted | (boolean) .T. oder .F. | Wenn .T. werden die Knoten innerhalb einer Ebene alphanumerisch anhand der Caption sortiert angezeigt |
| Style | (integer) 0-7 | Stil des Treeviews: 0: Nur Text - Image und Text
- +/- und text
- +/-, Image und Text
- Linien und Text
- Linien, Images und text
- Linien, +/- und Text
- (Default) Linien, +/-, Image und Text
|
Treeview-Methoden
| Name | Parameter | Beschreibung |
| GetVisibleCount | ../. | Gibt die Anzahl der Node-Objekte an, die maximal im Fenster angezeigt werden können. Node-Objekte am Ende des Treeviews, die nur halb ins Fenster passen werden hierbei mitgezählt |
| HitTest | X,Y | Liefert die Referenz auf ein Node-Objekt, welches sich an den Koordinaten x,y befindet |
| StartLabelEdit | ./. | Wenn das LabelEdit-Property auf 1 (manual) gesetzt ist, kann mit dieser Methode eine LabelEdit-Operation eingeleitet werden. |
Treeview-Events
| Name | Parameter | Beschreibung |
| AfterLabelEdit | Cancel, Newstring | Wird gefeuert nach einer LabelEdit-Operation. Wird im Event der Cancel auf einen Wert > 0 gesetzt (oder auf .T.), wird die Labeledit-Operation abgebrochen und der ursprüngliche Wert wiederhergestellt. Newstring enthält den neuen Wert der Caption oder .NULL., wenn abgebrochen wurde |
| BeforeLabelEdit | Cancel | Wird gefeuert, wenn der User auf einen Node klickt (LabelEdit = 0) oder Die Methode StartLabelEdit aufruft (LabelEdit = 1). Wird im Eventcode der Wert von Cancel auf > 0 oder .T. gesetzt, wird die Operation abgebrochen |
| Collapse | Node | Wird gefeuert, wenn ein Node geschlossen wird. Node enthält dann eine Referenz auf den geschlossenen Node |
| Expande | Node | Wird gefeuert, wenn ein Node geöffnet wird. Node enthält dann eine Referenz auf den geöffneten Node |
| NodeCheck | Node | Wird gefeuert, wenn ein Node angecheckt oder abgecheckt wird (CheckBoxes = .T.). Node enthält dann eine Referenz auf den entsprechenden Node |
| NodeClick | Node | Wird gefeuert, wenn ein Node angeklickt wird. Node enthält dann eine Referenz auf den entsprechenden Node |
Nodes Properties und Methoden
Jeder Node sowie die Node-Collection haben eigene Properties und Methoden, die das Aussehen der Nodes beeinflussen.
Node-Properties
| Name | Werte | Beschreibung |
| Bold | (boolean) .T. oder .F. | .T, wenn der Knoten fett angezeigt wird (read/write) |
| Checked | (boolean) .T. oder .F. | .T., wenn der Knoten angecheckt ist (read/write) |
| Child | Node | Enthält Referenz auf den ersten Child-Node des Knoten (read) |
| Children | (integer) | Enthält Anzahl der Child-Knoten eines Knoten (read) |
| Expanded | (boolean) .T. oder .F. | .T., wenn der Knoten geöffnet ist |
| ExpandedImage | Integer oder String | Index oder Key des zugehörigen Images im ImageList (read/write) |
| FirstSibling | Node | Referenz auf den ersten Knoten in der selben Ebene (read) |
| Fullpath | String | Kompletter Pfad vom Root bis zum aktuellen Knoten, getrennt durch das Zeichen aus „PathSeparator“ (read) |
| Icon | Integer oder String | Index oder Key des zugehörigen Images im ImageList (read/write) |
| LastSibling | Node | Referenz auf den letzten Knoten der selben Ebene (read) |
| Next | Node | Referenz auf den nächsten Knoten der selben Ebene (read) |
| Parent | Node | Referenz auf den übergeordneten Knoten (read) |
| Previous | Node | Referenz auf den vorhergehenden Knoten der selben Ebene |
| Root | Node | Referenz auf den obersten Knoten, der zu dem aktuellen Knoten gehört (read) |
| Selected | (boolean) .T. oder .F. | .T., wenn der aktuelle Knoten selektiert ist |
| SelectedImage | Integer oder String | Index oder Key des zugehörigen Images im ImageList (read/write) |
| Sorted | (boolean) .T. oder .F. | .T., wenn die unmittelbaren Childs des Knoten alphabetisch sortiert werden sollen. Nachträglich zugefügte Knoten werden nicht sortiert, das Sorted-Property muß dann erneut auf .T. gesetzt werden (read/write) |
Node-Collection Methoden
| Name | Parameter | Beschreibung |
| Add | Relative, Relationship, key, text, image, selectedimage | Fügt einen Knoten der Nodes-Collection hinzu. PARAMETER: Relative: Index oder Key des zugehörigen Knoten. (optional) Relationship: Relation des neuen Knoten zum zugehörigen Knoten (optional) · 0: Wird vor allen anderen Knoten derselben Ebene des zugehörigen knoten eingefügt · 1: Wird am Ende der Knoten der selben Ebene des zugehörigen Knoten eingefügt · 2: Wird direkt nach dem zugehörigen Knoten eingefügt · 3: Wird direkt vor den zugehörigen Knoten eingefügt · 4: Wird ein Child des zugehörigen Knotens Key: Eindeutiger String zum identifizieren des Knoten (optional) Text: Caption des Knoten Image : Index oder Key des zugehörigen Images im ImageList (read/write) SelectedImage: Index oder Key des zugehörigen Images im ImageList (read/write) |
| Clear | | Löscht alle Nodes der Collection |
| EnsureVisible | | Sorgt dafür, das ein Node im sichtbaren Bereich angezeigt wird. Ggfs. Werden seine Parent-Nodes expanded |
| Remove | | Entfernt den Knoten aus dem Treeview |
Das Treeview in VFP
Wir erzeugen uns nun einen Container, der ein Treeview enthält. Dazu muß des MS-Treeview von VFP auch erreichbar sein. Um dies zu erreichen, öffnen wir in VFP „Tools-Options“ und dort den Reiter „Controls“. Dort sollten unter „Active-X-Controls“ folgende angehakt sein:
- Microsoft Treeview Control 6.0 (SP2)
- Microsoft Listview Control 6.0 (SP2)
- Microsoft ImageList Control 6.0 (SP2)
Nun erzeugen wir uns eine neue Containerklasse , wählen in der Steuerelemente-Toolbar den zweiten Button und dort „Active-X“ Jetzt haben wir auf der Toolbar unsere 3 Controls und können das Treeview dem Container hinzufügen. Wir nennen es „oleTreeview“.

Als nächstes füllen wir mal den INIT-Code des Containers, um ein paar Grundeinstellungen zu machen:
*-- Treeview konfigurieren
WITH THIS.oleTreeView
.Style = 7
.LineStyle = 1
.oleDragMode = 0
.oleDropMode = 0
.indentation = 12.00
.PathSeparator = "\"
.Sorted = .F.
.MousePointer = 0
.HideSelection = .T.
.SingleSel = .F.
.LabelEdit = 1
.HideSelection = .F.
.anchor = 0
.move(0,0,THIS.Width,THIS.Height)
.anchor = 15
ENDWITH
Die letzten 3 Zeilen sorgen dafür, dass das Treeview immer so groß wie unser Container ist.
Wenn wir nun den Container einer Form hinzufügen und diese starten, sehen wir, das der Treeview zwar da ist, aber noch keine Knoten enthält. Das holen wir jetzt nach und fügen im INIT folgendes ein:
WITH THIS.oleTreeview.Nodes
.Add(,1,"_ERSTER","Erster Knoten",0)
.Add(,1,"_ZWEITER","Zweiter Knoten",0)
.Add(,1,"_DRITTER","Dritter Knoten",0)
.Add("_ERSTER",4,"_VIERTER","Erster ChildKnoten",0)
.Add("_ERSTER",4,"_FÜNFTER","Zweiter ChildKnoten",0)
.Add("_ERSTER",4,"_SECHSTER","Dritter ChildKnoten",0)
.Add("_ERSTER",4,"_SIEBTER","Vierter ChildKnoten",0)
.Add("_FÜNFTER",4,"_ACHTER","Erster GrandChildKnoten",0)
.Add("_FÜNFTER",4,"_NEUNTER","Zweiter GrandChildKnoten",0)
.Add("_FÜNFTER",4,"_ZEHNTER","Dritter GrandChildKnoten",0)
ENDWITH
Und hier ist das Ergebnis:

Ok, das ist schon mal sehr gut. Aber meistens möchte man doch Daten anzeigen, die aus einer oder mehreren Tabellen kommen. Betrachten wir zunächst einmal den Fall, wir haben eine Tabelle mit einem eigenen Primärschlüssel ID, einem Verweis auf den Parent (idparent) und einer Caption (Adresse hat Unteradressen, hat weitere Unteradressen usw.)
Wir fügen also eine Methode „Filltreeview ein, die aus den Daten der Tabelle den Treeview füllt:
WITH THIS.oleTreeview.Nodes
.clear()
*-- Wir holen uns alle Knoten in einen Cursor
SELECT * FROM treetable ORDER BY idparent,id INTO CURSOR curtree
IF _TALLY > 0
SELECT curtree
SCAN
*-- Wir fügen den Knoten ein
lcNodeKey = "_"+TRANSFORM(curtree.id)
lcParentKey = "_"+TRANSFORM(curtree.idparent)
IF curtree.idparent = 0
loNode = .Add(,1,lcNodekey,ALLTRIM(curtree.caption),0)
ELSE
loNode = .Add(lcParentKey,4,lcNodekey,curtree.caption,0)
ENDIF
ENDSCAN
ENDIF
ENDWITH
Im INIT des Containers fügen wir dann noch THIS.filltreeview hinzu und unser Treeview wird beim Start direkt aus der Tabelle gefüllt. Das funktioniert auch wunderbar, wenn nicht sehr viele Datensätze dem Treeview hinzugefügt werden müssen. Bei mehreren 1000 Datensätzen kann es durchaus sein , das das Füllen des Treeviews mehrere Minuten dauert und das ist keinem Anwender zuzumuten. Also sorgen wir dafür, das erst beim Öffnen eines Knotens die Unterknoten aufgebaut werden. Damit unser „+“-Zeichen aber angezeigt wird, erzeugen wir einen Dummy-Unterknoten (wenn Child-Datensätze vorhanden sind). Dies lässt sich mit 2 Methoden wunderbar erledigen:
SelectChilds
Sorgt für das Selektieren der Daten bezogen auf einen übergeordneten Knoten (alle Datensätze mit idparent = 0 oder = x). Dabei wird auch gleich die Anzahl Ihrer untergeordneten Datensätze erimittelt.
LPARAMETERS tcCursor, tnIdParent
*-- Wir selektieren uns die Child-Knoten
SELECT * FROM treetable WHERE idparent = tnIdParent INTO CURSOR tmpchilds
*-- Wir besorgen uns nun die Anzahlen der in der vorherigen Selektion enthaltenen ids
SELECT idparent,COUNT(*) as Anzahl ;
FROM Treetable ;
WHERE idparent IN (SELECT id FROM tmpChilds);
GROUP BY idparent;
INTO CURSOR tmpGrandchilds
*-- Diese beiden Informationen werden nun zusammengebracht
SELECT tmpChilds.*, NVL(tmpGrandChilds.Anzahl,0) AS Anzahl ;
FROM tmpChilds;
LEFT OUTER JOIN tmpGrandchilds ON tmpchilds.id = tmpGrandchilds.idparent;
INTO CURSOR (tcCursor)
IF USED(tcCursor)
SELECT (tcCursor)
lnRet = RECCOUNT(tcCursor)
ELSE
lnRet = 0
ENDIF
RETURN lnRet
FillChilds
Füllt die mit SelectChilds ermittelten Datensätze in den Treeview ein
*** ActiveX Control Event ***
LPARAMETERS toNode
WITH THIS.oleTreeview
*-- Wir locken die Form, damit keiner zwischendurch was klicken kann
.MousePointer = 11 && ccHourglass
LockWindowUpdate(THISFORM.HWnd)
.Visible = .F.
ltStart = DATETIME()
llFetchData = .T.
IF VARTYPE(toNode) # "O" OR ISNULL(toNode)
lnIdParent = 0
ELSE
lnIdParent = INT(VAL(SUBSTR(toNode.key,2)))
IF LEFT(toNode.Child.key,1) = "d"
.Nodes.Remove(toNode.child.key)
ELSE
llFetchData = .F.
ENDIF
ENDIF
IF llFetchData
THIS.SelectChilds("curchild",lnIdParent)
IF _TALLY > 0
SELECT curchild
SCAN
*-- Wir fügen den Knoten + einen Dummy-Unterknoten ein
lcNodeKey = "_"+TRANSFORM(curchild.id)
IF lnIdParent = 0
loNode = .Nodes.Add(,1,lcNodekey,curchild.caption,0)
ELSE
lcParentKey = "_"+TRANSFORM(lnIdParent)
loNode = .Nodes.Add(lcParentkey,4,lcNodekey, curchild.caption,0)
ENDIF
IF curchild.Anzahl > 0
lcDummyKey = "d"+TRANSFORM(curchild.id)
loDummyNode = .Nodes.Add(lcNodeKey,4,lcDummykey,"",0)
ENDIF
ENDSCAN
ENDIF
ENDIF
.Visible = .T.
LockWindowUpdate(0)
.MousePointer = 0 && ccDefault
ENDWITH
Besonders hierbei ist zu beachten, das während einer Fülloperation das Treeview „eingefroren“ wird und auf unsichtbar gesetzt wird, da sonst nach jedem Einfügen eines Knoten das Treeview refresht wird. Dies kostet unheimlich Performance ! Ein THISFORM.Lockscreen reicht übrigens nicht, da das Treeview in einem komplett anderen Thread läuft. Die Definition für LockWindowUpdate bringen wir im INIT unter:
lnRes = ADLLS(laJunk )
IF lnRes = 0 OR !(ASCAN(laJunk,'LockWindowUpdate', 1, -1, 1, 15 ) > 0)
DECLARE INTEGER LockWindowUpdate IN Win32API INTEGER nHandle
ENDIF
Dann setzen wir auch noch den Mousepointer um, so das der Anwender auch mitbekommt, das der Rechner auch am ackern ist. Jetzt müssen wir nur noch dafür sorgen, das im Expand-Event unsere Methode zum Füllen der Childs auch angesprungen wird:
*** ActiveX Control Event (oleTreeview.Expand) ***
LPARAMETERS toNode
THIS.Parent.FillChilds(toNode)
Images verwenden
Bisher haben wir ohne Icons im Treeview gearbeitet. Das wollen wir jetzt nachholen. Als erstes fügen wir aus unserer Toolbar ein ImageListobjekt dem Container hinzu und nennen es oleimageList. Dann klicken wir mit Rechts auf das Control im Container und wählen die Eigenschaften:

Hier können wir nun die benötigten Bitmaps in das Control laden. Sie erhalten dabei eine Indexnummer aufsteigend von 1, je nach Reihenfolge in dem die Images zugefügt werden. In unserer Methode FillChilds erweitern wir jetzt die .Add-Befehle um die (in der Tabelle enthaltene) gewünschte Imagenummer.
Nodes.Add(,1,lcNodekey,curchild.caption, curchild.icon)
Im Init des Containers sorgen wir noch dafür, das das Treeview sein ImageList-Contro auch kennt:
THIS.oleTreeview.ImageList = THIS.oleimageList
Und so sieht das Ganze dann aus. Schon ganz nett, gell ?

Bei dieser Vorgehensweise müssen im Übrigen die bmps dem Kunden nicht ausgeliefert werden, sie stecken fest im Image-Control.
Möchte man etwas flexibler sein, kann man die Images auch zur Laufzeit in das ImageList-Control laden. Dabei gilt es aber folgendes zu beachten: Die Bitmaps müssen dem Kunden mitgeliefert werden. Einkompilieren reicht nicht !
Hier die Methode, die für das Laden der Images in das ImageList-Control sorgt:
WITH THIS.oleImageList.ListImages
lnFiles = ADIR(laBmps,"bmps\*.bmp")
FOR lnLoop = 1 TO lnFiles
.Add(,JUSTSTEM(laBmps[lnLoop,1]),LoadPicture("bmps\"+ laBmps[lnLoop,1]))
ENDFOR
ENDWITH
Bitte darauf achten, das das Laden der Images vor dem Zuweisen des imageList-Controls an den Treeview passieren muß !
Eine allgemeine Treeview-Klasse
Wie ich am Anfang schon erwähnt habe, gibt es eigentlich zwei klassische Fälle zum Verwenden eines Treeviews. Dies sind Daten aus einer Tabelle, die einen Verweis auf sich selbst enthalten oder eben Daten aus mehreren Tabellen, die jeweils über Fremdschlüssel miteinander verbunden sind. Hierbei ist dann die Anzahl der Ebenen durch die Anzahl der Tabellen festgelegt, während im ersten Fall beliebig viele Hierarchieebenen vorhanden sein können.
Wenn wir nun ein Array einführen, in dem die Beziehungen definiert sind, können wir durch einfaches Einstellen des Arrays unser Treeview definieren:
Zuächst einmal unseren bisherigen Fall mit einer Tabelle und Referenz auf sich selbst:
DIMENSION THIS.aTreedef[1,5]
THIS.aTreedef[1,1] = "TREETABLE" && Key (Tabellenname)
THIS.aTreedef[1,2] = "id" && Feld Primärschlüssel
THIS.aTreedef[1,3] = "idparent" && Feld Foreign Key
THIS.aTreedef[1,4] = "caption" && Feld des Anzeigetext
THIS.aTreedef[1,5] = "F,ICON" && Feld Icon
Das Image ist hierbei in der Tabelle hinterlegt und zwar im Feld „Icon“
Eine mehrstufige Hierarchie würde dann so aussehen:
DIMENSION THIS.aTreedef[3,5]
THIS.aTreedef[1,1] = "ADRESS" && Key (Tabellenname)
THIS.aTreedef[1,2] = "idadress" && Feld Primärschlüssel
THIS.aTreedef[1,3] = "0" && Feld Foreign Key
THIS.aTreedef[1,4] = "name" && Feld Anzeigetext
THIS.aTreedef[1,5] = "D,ROOT" && Define, Icon Fix = “ROOT”
THIS.aTreedef[2,1] = "RECHNUNG"
THIS.aTreedef[2,2] = "idRechnung"
THIS.aTreedef[2,3] = "idAdress"
THIS.aTreedef[2,4] = "Rechnr"
THIS.aTreedef[2,5] = "D,CHILD"
THIS.aTreedef[3,1] = "RECHPOS"
THIS.aTreedef[3,2] = "idRechpos"
THIS.aTreedef[3,3] = "idRechnung"
THIS.aTreedef[3,4] = "postext"
THIS.aTreedef[3,5] = "D,GRAND"
Und so siehts dann aus:

Damit dies generisch funktioniert, müssen wir unsere Methoden „SelectChilds“ und „FillCHilds“ natürlich anpassen. Die fertigen Methoden findet Ihr auf der CD mit dem Begleitmaterial.
Wichtig zu wissen ist an dieser Stelle die Bildung des Keys für die einzelnen Nodes. Sie sind nach dem Muster
“_“+Name der Tabelle + „_“ + Wert des Primärschlüssels aufgebaut, so das man über die Informationen des Node-Keys jederzeit auf den ursprünglichen Datensatz kommen kann.
Damit man nun mit dem Treeview seine Masken vernünftig steuern kann, benötigen wir noch zwei Methoden (NodeSelect + NodeContextmenu), die ausgelöst werden, wenn jemand einen Node auswählt (entweder per Maus oder per Tastatur). Diese Methoden bekommen den Node als Parameter mit. Über die Key-Eigenschaft kann man dann den dazu passenden Datensatz ermitteln:
LPARAMETERS toNode
lnID = INT(VAL(GETWORDNUM(toNode.Key,2,"_")))
lcKey = GETWORDNUM(toNode.Key,1,"_")
Da das NodeClick-Event keine Information über die Maustaste liefert, mit der auf den Node geklickt wurde, benutzen wir einen Trick. Wir fangen den MouseDown-Event ab. Über die HitTest-Methode bekommen wir dann eine Referenz auf den angeklickten Node:
*** ActiveX-Steuerelementereignis (oleTreeview.MouseDown) ***
LPARAMETERS tnbutton, shift, tnx, tny
LOCAL loNode AS Object,;
lnID AS Integer,;
lcKey AS String
loNode = THIS.HitTest(tnX * THIS.Parent.nxtwips, tny * THIS.Parent.nytwips)
IF !ISNULL(loNode)
THIS.SelectedItem = loNode
IF tnButton = 2 && Rechtsklick
*-- Was machen mit loNode
THIS.Parent.NodeContextMenu(loNode)
ELSE
THIS.Parent.NodeSelect(loNode)
ENDIF
ENDIF
Die hierbei verwendeten Konstanten THIS.Parent.nxtwips bzw. THIS.Parent.nytwips ermitteln wir im Init unseres Containers:
#Define cnLOG_PIXELS_X 88
#Define cnLOG_PIXELS_Y 90
#Define cnTWIPS_PER_INCH 1440
* Declare some Windows API functions.
Declare integer GetActiveWindow in WIN32API
Declare integer GetDC in WIN32API integer iHDC
Declare integer GetDeviceCaps in WIN32API integer iHDC, integer iIndex
* Get a device context for VFP.
liHWnd = GetActiveWindow()
liHDC = GetDC(liHWnd)
* Get the pixels per inch.
liPixelsPerInchX = GetDeviceCaps(liHDC, cnLOG_PIXELS_X)
liPixelsPerInchY = GetDeviceCaps(liHDC, cnLOG_PIXELS_Y)
* Get the twips per pixel.
THIS.nxtwips = ( cnTWIPS_PER_INCH / liPixelsPerInchX )
THIS.nytwips = ( cnTWIPS_PER_INCH / liPixelsPerInchY )
Jetzt fehlt eigentlich nur noch ein bisschen Tastatureingaben-Bearbeitung und unser Basis-Treeview ist so gut wie fertig!
*** ActiveX-Steuerelementereignis (oleTreeview.KeyUp)***
LPARAMETERS keycode, shift
LOCAL lnAnz AS Integer,;
lnID AS Integer,;
lcKey AS String
lnAnz = THIS.Nodes.Count
IF lnAnz > 0
DO CASE
CASE keycode = 38 && UPARROW
THIS.Parent.NodeSelect(THIS.SelectedItem)
CASE keycode = 40 && DOWNARROW
THIS.Parent.NodeSelect(THIS.SelectedItem)
CASE keycode = 33 && PGUP
THIS.Parent.NodeSelect(THIS.SelectedItem)
CASE keycode = 34 && PGDN
THIS.Parent.NodeSelect(THIS.SelectedItem)
CASE keycode = 36 && POS1
THIS.Parent.NodeSelect(THIS.SelectedItem)
CASE keycode = 35 && ENDE
THIS.Parent.NodeSelect(THIS.SelectedItem)
CASE keycode = 187 && +
THIS.Parent.fillchilds(THIS.SelectedItem)
THIS.SelectedItem.Expanded = .T.
THIS.SetFocus()
CASE keycode = 189 && -
THIS.SelectedItem.Expanded = .F.
OTHERWISE
THIS.Parent.NodeSelect(THIS.SelectedItem)
ENDCASE
ENDIF
Dieser EventCode sorgt dafür, das bei Tastatureingaben unsere Methode Nodeselect auch ausgeführt wird. Zusätzlich sorgen wir dafür, das beim Tippen der + bzw. – Taste der Node aus- oder eingeklappt wird.
Das MS-Listview
Das MS-Listview-Control dient zum Anzeigen von nicht-hierarchischen Daten und ähnelt (z.T. dem VFP-Grid. Es zeigt seine Informationen in 4 verschiedenen Ansichten. Man kann seine Items mit oder ohne Columnheader sowie mit kleinen oder grossen Icons (zusätzlich zum Text) anzeigen. Das View-Property bestimmt über das Aussehen (Grosse Icons, kleine Icons, Liste oder Report). Das Labelwrap-Property bestimmt, ob der Text umgebrochen wird oder nicht. Das ListView enthält (ähnlich wie beim Treeview) wiederum eine Collection von Listitems sowie zusätzlich eine Collection von Columnheaders (als Spaltenüberschriften).
Listview Properties,Methoden und Events
Listview Properties
|
Name
|
Werte
|
Beschreibung
|
|
AllowColumnReorder
|
(boolean) .T. oder .F.
|
Wenn .T., kann der Benutzer die Spalten neu anordnen
|
|
Arrange
|
0,1,2
|
Entscheidet darüber, wie die Icons im Listview angeordnet werden 0: (Default) Kein, 1: AutoLeft, 2: AutoTop
|
|
CheckBoxes
|
(boolean) .T. oder .F.
|
Wenn .T., wird vor jedem ListItem eine Checkbox angezeigt
|
|
ColumnHeaderIcons
|
imageList
|
Bekommt einen Verweis auf das ImageList-Control, welches die header-Icons enthält
|
|
ColumnsHeaders
|
|
Enthält eine Referenz auf eine Collection von Columnheader-Objekten
|
|
DropHighLight
|
ListItem
|
Das DropHighlight Property wird typischerweise in Kombination mit der HitTest-Methode bei Drag-and-Drop-Operationen verwendet. Wenn der Cursor dann über ein Node-Objekt bewegt wird, kann über die Hittest-Methode eine Referenz auf den „überfahrenen“ Knoten ermittelt werden. Wenn dieser Knoten dann dem DropHighlight Property zugewiesen wird, erscheint dieses blau hinterlegt
|
|
FlatScrollbar
|
(boolean) .T. oder .F.
|
Wenn .T. erscheinen Scrollbars im Flat-Style
|
|
FullRowSelect
|
(boolean) .T. oder .F.
|
Wenn .T., wird die komplette Zeile eines selektierten Listitems markiert.
|
|
Gridlines
|
(boolean) .T. oder .F.
|
Wenn .T., werden Gridlines angezeigt
|
|
HideColumnheaders
|
(boolean) .T. oder .F.
|
Wenn .T. werden die Spaltenheader nicht angezeigt
|
|
HotTracking
|
(boolean) .T. oder .F.
|
Wenn .T., wird der entsprechende Header beim Überfahren eines Listitems mit der Maus markiert
|
|
Hoverselection
|
(boolean) .T. oder .F.
|
Wenn .T., wird das entsprechende ListItem beim Überfahren mit der Maus selektiert
|
|
Icons
|
imageList
|
Bekommt einen Verweis auf das ImageList-Control, welches die grossen Icons enthält
|
|
LabelEdit
|
0,1
|
Gibt an, ob ein ListItem durch anklicken editiert werden kann.
0 (default) : Das BeforeLableEdit-Event wird gefeuert, wenn ein Benutzer auf den Node klickt
1: Das BeforeLabelEdit-Event wird nur gefeuert, wenn vorher die StartLabelEdit – Methode aufgerufen wird
|
|
LabelWrap
|
(boolean) .T. oder .F.
|
Wenn .T., wird das Label im Icon-View umgebrochen
|
|
ListItems
|
|
Referenz auf die Collection der ListItems
|
|
MultiSelect
|
(boolean) .T. oder .F.
|
Wenn .T., können mehrere ListItems per SHIFT bzw. CTRL-Click selektiert werden. Auch die entsprechenden Tastenkombinationen funktionieren
|
|
PictureAlignment
|
0,1,2,3,4,5
|
Entscheided über das Alignment des Images
0: Oben links
1: Oben rechts
2: Unten links
3:Unten rechts
4: Zentriert
5: (Default) gekachelt
|
|
SelectedItem
|
ListItem
|
Referenz auf das aktuell selektierte ListItem
|
|
SmallIcons
|
imageList
|
Bekommt einen Verweis auf das ImageList-Control, welches die kleinen Icons enthält
|
|
Sorted
|
(boolean) .T. oder .F.
|
Wenn .T., wird (alphanumerisch sortiert) angezeigt
|
|
SortKey
|
(integer)
|
0: Sortierung nach den listItems (1. Spalte)
>=1: Sortierung nach den weiteren Spalten
|
|
SortOrder
|
0,1
|
0: Aufsteigend
1: Absteigend
|
|
TextBackGround
|
0,1
|
0: Hintergrund des Textes ist transparent
1: Hintergrund des Textes ist in der Farbe der BackColor-Eigenschaft
|
|
View
|
0,1,2,3
|
0: Grosses Icon + Text darunter
1: Kleines Icon und Text rechts daneben (vertikal Scrollen)
2: Kleines Icon und Text rechts daneben (horizontal Scrollen)
3: Liste mit kleinem Icon und allen SubItems
|
Listview Methoden
|
Name
|
Parameter
|
Beschreibung
|
|
FindItem
|
String,
value,
index,
match
|
Gibt die Referenz auf ein Listitem-Objekt zurück, das die folgenden Kriterien erfüllt:
String: Suchstring
Value: 0= Suche im text, 1= Suche im Subitem, 2= Suche im Tag
Index: (integer) Index des Listitems, ab dem gesucht werden soll
Match: 0= Ganzer Text, 1= nur Textteil
|
|
GetFirstVisible
|
./.
|
Gibt eine Referenz auf das oberste sichtbare ListItem zurück
|
|
HitText
|
X,y
|
Liefert die Referenz auf ein ListItem-Objekt, welches sich an den Koordinaten x,y befindet
|
|
StartLabelEdit
|
./.
|
Wenn das LabelEdit-Property auf 1 (manual) gesetzt ist, kann mit dieser Methode eine LabelEdit-Operation eingeleitet werden.
|
ListView Events
|
Name
|
Parameter
|
Beschreibung
|
|
|
AfterLabelEdit
|
Cancel, Newstring
|
Wird gefeuert nach einer LabelEdit-Operation. Wird im Event der Cancel auf einen Wert > 0 gesetzt (oder auf .T.), wird die Labeledit-Operation abgebrochen und der ursprüngliche Wert wiederhergestellt.
Newstring enthält den neuen Wert der Caption oder .NULL., wenn abgebrochen wurde
|
|
BeforeLabelEdit
|
Cancel
|
Wird gefeuert, wenn der User auf ein ListItem klickt (LabelEdit = 0) oder Die Methode StartLabelEdit aufruft (LabelEdit = 1). Wird im Eventcode der Wert von Cancel auf > 0 oder .T. gesetzt, wird die Operation abgebrochen
|
|
|
ColumnClick
|
ColumnHeader
|
Wird gefeuert, wenn der Anwender auf einen ColumnHeader klickt.
|
|
|
ItemClick
|
ListItem
|
Wird gefeuert, wenn der Anwender auf ein ListItem klickt.
|
|
ListItems Properties und Methoden
Jedes ListItem hat wiederum eigene Properties, die das Aussehen beeinflussen. Die Collection hat Methoden zum Hinzufügen und Entfernen von Items / SubItems zur Collection
Listitem Properties
|
Name
|
Werte
|
Beschreibung
|
|
Ghosted
|
(boolean) .T. oder .F.
|
Wenn .T., wird das ListItem ausgegraut dargestellt
|
|
Icon
|
Integer oder String
|
Index oder Key des zugehörigen Images im ImageList (read/write)
|
|
Selected
|
(boolean) .T. oder .F.
|
.T., wenn das ListItem selektiert ist
|
|
SmallIcon
|
Integer oder String
|
Index oder Key des zugehörigen Images im ImageList (read/write)
|
|
SubItems
|
|
Referenz auf eine Collection von Strings, die die Texte der weiteren Spalten enthalten
zB. loItem.SubItems(5) = „Inhalt der sechsten Spalte“
|
|
ToolTipText
|
String
|
Der String, der als ToolTipText angezeigt werden soll, wenn der Anwender mit der Maus über ein ListItem fährt
|
ListItems-Collection Methoden
|
Name
|
Parameter
|
Beschreibung
|
|
Add
|
Index,
Key,
Text,
Icon,
SmallIcon
|
Fügt ein ListItem der ListItems-Collection hinzu. PARAMETER:
Index: Position, an der das ListItem eingefügt werden soll.
Default: am Ende
Key: Eindeutiger String zum Identifizieren des ListItems (optional)
Text: Caption des ListItems
Icon : Index oder Key des zugehörigen Images im ImageList (read/write)
SmallIcon: Index oder Key des zugehörigen Images im ImageList (read/write)
|
|
Clear
|
|
Löscht alle ListItems der Collection
|
|
EnsureVisible
|
|
Sorgt dafür, das ein ListItem im sichtbaren Bereich angezeigt wird.
|
|
Remove
|
|
Entfernt das ListItem aus dem ListView
|
Das ListView in VFP
In vielen Dingen verhält sich das Listview wie ein Treeview. Im Unterschied zum Treeview benötigen wir aber z.B drei ImageList-Controls. Eins welches die Bilder für die Anzeige der Sortierreihenfolge im Header enthält, eines für die Anzeige von kleinen Icons und eins für die Anzeige von großen Icons

Im InitCode initialisieren wir wieder unsere ole-Controls und machen ein paar Basiseinstellungen:
*-- Code for PixelToTwips method
Local liHWnd, liHDC, liPixelsPerInchX, liPixelsPerInchY
#Define cnLOG_PIXELS_X 88
#Define cnLOG_PIXELS_Y 90
#Define cnTWIPS_PER_INCH 1440
* Declare some Windows API functions.
Declare integer GetActiveWindow in WIN32API
Declare integer GetDC in WIN32API integer iHDC
Declare integer GetDeviceCaps in WIN32API integer iHDC, integer iIndex
* Get a device context for VFP.
liHWnd = GetActiveWindow()
liHDC = GetDC(liHWnd)
* Get the pixels per inch.
liPixelsPerInchX = GetDeviceCaps(liHDC, cnLOG_PIXELS_X)
liPixelsPerInchY = GetDeviceCaps(liHDC, cnLOG_PIXELS_Y)
* Get the twips per pixel.
THIS.nxtwips = ( cnTWIPS_PER_INCH / liPixelsPerInchX )
THIS.nytwips = ( cnTWIPS_PER_INCH / liPixelsPerInchY )
lnRes = ADLLS(laJunk )
IF lnRes = 0 OR !(ASCAN(laJunk,'LockWindowUpdate', 1, -1, 1, 15 ) > 0)
DECLARE INTEGER LockWindowUpdate IN Win32API INTEGER nHandle
ENDIF
WITH THIS.oleImageListSmall
.ImageWidth = 16
.ImageHeight = 16
ENDWITH
WITH THIS.oleImageListBig
.ImageWidth = 32
.ImageHeight = 32
ENDWITH
THIS.InitListDef()
THIS.LoadImages()
WITH THIS.oleListview
.Icons = THIS.oleImageListBig
.SmallIcons = This.oleImageListSmall
.ColumnHeaderIcons = This.oleImageHeader
.FullRowSelect = .T.
.Sorted = .F.
.SortOrder = 0
.SortKey = 0
.View = 3
.LabelEdit = 1
.MultiSelect = .T.
.HideSelection = .F.
.oleDragMode = 0
.oleDropMode = 0
.gridlines = .T.
.anchor = 0
.move(0,0,THIS.Width,THIS.Height)
.anchor = 15
FOR lnLoop = 1 TO ALEN(THIS.aColumns,1)
.ColumnHeaders.Add(,THIS.aColumns[lnLoop,1],THIS.aColumns[lnLoop,2],THIS.aColumns[lnLoop,5],0,0) && column heading 0
ENDFOR
ENDWITH
Im Init werden hier gleich die Header definiert. Diese ergeben sich natürlich aus der zugrunde liegenden Tabelle. Um dies analog zum Treeview zu handhaben, benutzen wir hier auch ein Array mit Definitionseinträgen, welches in der Methode INITLISTDEF gefüllt wird:
DIMENSION THIS.aListDef[2]
THIS.aListDef[1] = "F,Bild" && Feld mit Image (alternativ: "D,ROOT")
THIS.aListDef[2] = "idadress" && Feld mit Primärschlüssel
DIMENSION THIS.aColumns[7,6]
THIS.aColumns[1,1] = "idAdress" && Feldname
THIS.aColumns[1,2] = "Nr" && Caption des Headers
THIS.aColumns[1,3] = "I" && Datentyp
THIS.aColumns[1,4] = RGB(0,0,255) && Farbe
THIS.aColumns[1,5] = 53 && Breite
THIS.aColumns[1,6] = .T. && Bold
THIS.aColumns[2,1] = "Name"
THIS.aColumns[2,2] = "Name"
THIS.aColumns[2,3] = "C"
THIS.aColumns[2,4] = RGB(0,0,0)
THIS.aColumns[2,5] = 100
THIS.aColumns[2,6] = .T.
THIS.aColumns[3,1] = "Strasse"
THIS.aColumns[3,2] = "Straße"
THIS.aColumns[3,3] = "C"
THIS.aColumns[3,4] = 0
THIS.aColumns[3,5] = 106
THIS.aColumns[3,6] = .F.
THIS.aColumns[4,1] = "Plz"
THIS.aColumns[4,2] = "Postleitzahl"
THIS.aColumns[4,3] = "C"
THIS.aColumns[4,4] = RGB(255,0,0)
THIS.aColumns[4,5] = 78
THIS.aColumns[4,6] = .F.
THIS.aColumns[5,1] = "Ort"
THIS.aColumns[5,2] = "Ort"
THIS.aColumns[5,3] = "C"
THIS.aColumns[5,4] = 0
THIS.aColumns[5,5] = 92
THIS.aColumns[5,6] = .F.
THIS.aColumns[6,1] = "DatGen"
THIS.aColumns[6,2] = "Angelegt"
THIS.aColumns[6,3] = "T"
THIS.aColumns[6,4] = 0
THIS.aColumns[6,5] = 129
THIS.aColumns[6,6] = .F.
THIS.aColumns[7,1] = "Groesse"
THIS.aColumns[7,2] = "Größe"
THIS.aColumns[7,3] = "N"
THIS.aColumns[7,4] = 0
THIS.aColumns[7,5] = 100
THIS.aColumns[7,6] = .F.
Weiterhin haben wir genauso wie beim Treeview eine SELECTITEMS – Methode. Diese ist allerdings viel einfacher, da wir es hier nicht mit hierarchischen Daten zu tun haben. Als Parameters bekommt die Methode den Namen des zu erzeugenden Cursors sowie eine Filterbedingung mit. Der eigentliche SELECT-Befehl steht im Property „cSelect“ unseres Containers
LPARAMETERS tcCursor,tcFilter
lcSelect = THIS.cSelect + " WHERE " + tcFilter + " INTO CURSOR " +tcCursor
&lcSelect
IF USED(tcCursor)
SELECT (tcCursor)
lnRet = RECCOUNT(tcCursor)
ELSE
lnRet = 0
ENDIF
RETURN lnRet
Nun brauchen wir noch die FILLITEMS-Methode, um die Daten aus dem Cursor in unser Listview zu füllen:
LPARAMETERS tcFilter
tcFilter = EVL(tcFilter,"1=1")
WITH THIS.oleListview
*-- Wir locken die Form, damit keiner zwischendurch was klicken kann
.MousePointer = 11 && ccHourglass
LockWindowUpdate(THISFORM.HWnd)
.Visible = .F.
.ListItems.Clear()
.Sorted = .F.
.SortKey = 0
.SortOrder = 0
FOR lnLoop = 1 TO THIS.oleListview.ColumnHeaders.Count
lcType = THIS.aColumns[lnLoop,3]
lnAlign = IIF(INLIST(lcType,"N","I"),1,0)
THIS.ShowHeaderIcon(lnLoop - 1 ,0,.F.,lnAlign)
ENDFOR
THIS.SelectItems("curItems",tcFilter)
TRY
SELECT curItems
SCAN
luValue = EVALUATE("curItems."+THIS.aColumns[1,1])
luValue = IIF(VARTYPE(luValue) = "C",ALLTRIM(luValue),luValue)
lcPictureType = LEFT(THIS.aListDef[1],1)
lcPictureSource = SUBSTR(THIS.aListDef[1],3)
lcPicture = ALLTRIM(IIF(lcPictureType = "F",EVALUATE("curItems." + lcPictureSource),lcPictureSource))
lnId = EVALUATE("curItems."+ THIS.aListDef[2])
loListItem = .ListItems.Add(,"_"+TRANSFORM(lnId),luValue,lcPicture,lcPicture)
loListItem.ForeColor = THIS.aColumns[1,4]
loListItem.Bold = THIS.aColumns[1,6]
FOR lnLoop = 2 TO ALEN(THIS.aColumns,1)
luValue = EVALUATE("curItems."+THIS.aColumns[lnLoop,1])
DO CASE
CASE VARTYPE(luValue) = "C"
luValue = ALLTRIM(luValue)
CASE VARTYPE(luValue) = "T"
luValue = EVL(luValue,{^1970-01-01 00:00:01})
ENDCASE
loListItem.SubItems[lnLoop-1] = luValue
loListItem.ListSubItems[lnLoop-1].ForeColor = THIS.aColumns[lnLoop,4]
loListItem.ListSubItems[lnLoop-1].Bold = THIS.aColumns[lnLoop,6]
ENDFOR
ENDSCAN
CATCH TO loError
ENDTRY
.Visible = .T.
LockWindowUpdate(0)
.MousePointer = 0 && ccDefault
ENDWITH
Wie wir hier sehen, enthält das eigentliche ListItem nur den Text der ersten Spalte. Die Inhalte der weiteren Spalten werden als ListSubItem hinterlegt.
Da das ListView mit seiner Sorted-Eigenschaft leider nur eine alphanumerische Sortierung verwendet, müssen wir uns hier etwas einfallen lassen. Den Datentyp der einzelnen Spalten haben wir ja in unserem Array hinterlegt. Zur Sortierung verwenden wir nun folgenden Trick: Wir speichern einfach für alle Items den Anzeigewert in die Tag-Eigenschaft. In die text-Eigenschaft kommt dann ein gewandelter String, nach dem wir ordentlich sortieren können (z.B 1,2,13 wird zu 001,002,013). Jetzt stellen wir die „Sorted“-Eigenschaft auf .T. und schreiben in das Text-Property wieder den Originalwert (Methode TYPEDSORT) !
LPARAMETERS tnSpalte, tnOrder
IF VARTYPE(tnOrder) # "N" OR NOT INLIST(tnOrder,0,1)
*-- Wenn die Sortierreihenfolge nicht mitgegeben wird
*-- und tnSpalte gleich der zuletzt gewählten Spalte ist, drehen wir die Reihenfolge um
*-- Bei einer neuen Spalte sortieren wir immer aufsteigend
lnOldSpalte = THIS.oleListView.SortKey + 1
IF lnOldSpalte = tnSpalte
tnOrder = 1 - THIS.oleListview.Sortorder
ELSE
tnOrder = 0
ENDIF
ENDIF
lnIndex = tnSpalte - 1 && Wir rechnen immer 0-basiert !
lcType = THIS.aColumns[tnSpalte,3]
IF lcType = "C"
THIS.oleListView.SortKey = lnIndex
THIS.oleListView.Sorted = .T.
THIS.oleListView.SortOrder = tnOrder
ELSE
lnAnz = THIS.oleListView.ListItems.Count
*-- Originalwert in Tag-Eigenschaft speichern und in Text-Eigenschaft den sortierbaren Wert
FOR lnLoop = 1 TO lnAnz
IF lnIndex = 0
loItem = THIS.oleListView.ListItems.Item(lnLoop)
ELSE
loItem = THIS.oleListView.ListItems.Item(lnLoop).ListSubItems(lnIndex)
ENDIF
WITH loItem
.Tag = .Text
DO CASE
CASE lcType = "T"
luValue = CTOT(.Text)
.Text = TRANSFORM(YEAR(luValue))+ ;
RIGHT("0" + TRANSFORM(MONTH(luValue)),2) +;
RIGHT("0" + TRANSFORM(DAY(luValue)),2) +;
RIGHT("0" + TRANSFORM(HOUR(luValue)),2) +;
RIGHT("0" + TRANSFORM(MINUTE(luValue)),2) +;
RIGHT("0" + TRANSFORM(SEC(luValue)),2)
.Tag = TTOC(luValue)
CASE lcType = "D"
luValue = CTOD(.Text)
.Text = DTOS(luValue)
CASE lcType = "N"
luValue = VAL(.Text)
.Text = STRTRAN(TRANSFORM(luValue,"999999999999999999999999999999.999999999999999999999999999999")," ","0")
CASE lcType = "I"
luValue = INT(VAL(.Text))
.Text = STRTRAN(TRANSFORM(luValue,"999999999999999999999999999999")," ","0")
ENDCASE
ENDWITH
ENDFOR
*-- Sortierung durchführen
THIS.oleListView.SortKey = lnIndex
THIS.oleListView.Sorted = .T.
THIS.oleListView.SortOrder = tnOrder
*-- Originaltext wiederherstellen
FOR lnLoop = 1 TO lnAnz
IF lnIndex = 0
loItem = THIS.oleListView.ListItems.Item(lnLoop)
ELSE
loItem = THIS.oleListView.ListItems.Item(lnLoop).ListSubItems(lnIndex)
ENDIF
WITH loItem
.Text = .Tag
ENDWITH
ENDFOR
ENDIF
*-- Die Header-Sortiericons + Alignment neu setzen
FOR lnLoop = 1 TO THIS.oleListview.ColumnHeaders.Count
lcType = THIS.aColumns[lnLoop,3]
lnAlign = IIF(INLIST(lcType,"N","I"),1,0)
IF lnLoop = tnSpalte
THIS.ShowHeaderIcon(lnLoop - 1,IIF(THIS.OleListView.SortOrder=0,0,1),.T.,lnAlign)
ELSE
THIS.ShowHeaderIcon(lnLoop - 1,0,.F.,lnAlign)
ENDIF
ENDFOR
Leider wird im Listview unser Sortiericon immer vor der Caption angezeigt. Dieses lässt sich mit etwas API-Zauberei aber umgehen (Methode SHOWHEADERICON):
LPARAMETERS tnColNum AS Integer,tnImgIconNum AS Integer, tlShowImage As Boolean, tnJustification AS Integer
IF VARTYPE(tnJustification) = "L"
tnJustification = -1
ENDIF
DECLARE INTEGER StrDup IN shlwapi STRING @lpsz
DECLARE INTEGER LocalFree IN kernel32 INTEGER hMem
DECLARE INTEGER SendMessage IN user32 INTEGER hWnd, INTEGER Msg, INTEGER wParam, STRING @lParam
* DECLARE INTEGER SendMessage IN User32 integer hwnd, integer wmsg, integer wParam , integer lParam
#DEFINE LVM_FIRST 0x1000
#DEFINE LVM_GETHEADER LVM_FIRST + 31
#DEFINE HDF_CURRENT -1
#DEFINE HDF_LEFT 0
#DEFINE HDF_RIGHT 1
#DEFINE HDF_CENTER 2
#DEFINE HDI_BITMAP 0x10
#DEFINE HDI_IMAGE 0x20
#DEFINE HDI_FORMAT 0x4
#DEFINE HDI_TEXT 0x2
#DEFINE HDF_BITMAP_ON_RIGHT 0x1000
#DEFINE HDF_BITMAP 0x2000
#DEFINE HDF_IMAGE 0x800
#DEFINE HDF_STRING 0x4000
#DEFINE HDM_FIRST 0x1200
#DEFINE HDM_SETITEM HDM_FIRST + 4
#DEFINE HDM_SETIMAGELIST HDM_FIRST + 8
#DEFINE HDM_GETIMAGELIST HDM_FIRST + 9
*-- Get a handle to the listview header control
lhHeader = SendMessage(THIS.oleListView.object.hwnd, LVM_GETHEADER,0,0)
lcItem = THIS.oleListView.ColumnHeaders(tnColNum + 1).Text
lcItem = STRTRAN(lcItem, Chr(0),"") + Chr(0)
lnItemPtr = StrDup(@lcItem)
*-- Set up the required structure members
lnMask = HDI_IMAGE + HDI_FORMAT
IF tnJustification = -1
*-- Get the current text alignment
tnJustification = THIS.oleListView.ColumnHeaders(tnColNum + 1).Alignment
ENDIF
IF tlShowImage
lnfmt = HDF_STRING + HDF_IMAGE + HDF_BITMAP_ON_RIGHT + tnJustification
lniImage = tnImgIconNum
ELSE
lnfmt = HDF_STRING + tnJustification
lniImage = 0
ENDIF
lcBuffer = THIS.num2dword(lnMask) +; &&mask
THIS.num2dword(0) +; && cxy
THIS.num2dword(lnItemPtr) + ; && pszText
THIS.num2dword(0) + ; &&hbm
THIS.num2dword(Len(lcItem)) +; && cchTextMax
THIS.num2dword(lnFmt) + ; && fmt
THIS.num2dword(0) + ; && lparam
THIS.num2dword(lniImage) +; && iImage
THIS.num2dword(0) && iOrder
*-- Modify the header
SendMessage (lhHeader, HDM_SETITEM, tnColNum, @lcBuffer)
LocalFree(lnItemPtr)
Im Columnclick des Headers sorgen wir nun dafür, das unsere Sortierung auch durchgeführt wird:
*** ActiveX Control Event (olelistview.Columnclick) ***
LPARAMETERS toColumnHeader
THIS.MousePointer= 11
LockWindowUpdate(THISFORM.HWnd)
THIS.Visible = .F.
THIS.Parent.typedSort(toColumnHeader.Index)
IF !ISNULL(THIS.SelectedItem)
THIS.SelectedItem.EnsureVisible()
ENDIF
THIS.Visible = .T.
LockWindowUpdate(0)
THIS.MousePointer = 0
Jetzt benötigen wir nur noch analog zum Treeview die Methoden zur Reaktion auf den Linksclick (ITEMSELECT) und den Rechtsklick (ITEMCONTEXTMENU) sowie die Methoden zum Reagieren auf Tastatur (olelistview.keyup) und Maus (olelistview.mouseup), die in dem Beispielprojekt auf der CD nachgesehen werden können.

Derzeit 2 Kommentare
Re: DevCon 2006 – D-TREE – Einführung in Listview und Treeview
Ich freue mich aus, Sie können mit mir ein Beispiel nennen? Dank
Von thang am
31.05.2010 11:39
|
AW: DevCon 2006 – D-TREE – Einführung in Listview und Treeview
Sorry but I have no idea what you mean ?
Von Torsten Weggen am
31.05.2010 13:42
|