Mai 19, 2012 registrieren anmelden
 
Durchsuchen minimieren
  
 
 SupportNewsBlog   
Weblogs
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

  1. Image und Text
  2. +/- und text
  3. +/-, Image und Text
  4. Linien und Text
  5. Linien, Images und text
  6. Linien, +/- und Text
  7. (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“.

clip_image002

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:

clip_image004

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:

clip_image006

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 ?

clip_image008

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:

clip_image010

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

clip_image012

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.

clip_image014

Derzeit 2 Kommentare


Gravatar

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
Gravatar

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

Ihr Name:
Gravatar Preview
Ihre E-Mail-Adresse:
(Optional) Geben Sie Ihre E-Mail-Adresse an, um ein Gravatar-Bild festzulegen.
Ihre Web-Site:
Überschrift:
Kommentar:
Sicherheitscode
CAPTCHA image
Geben Sie die angegebenen Zeichen darunter ein.
Kommentar hinzufügen   abbrechen 
 
 Copyright 2010 by indisoftware GmbH   Nutzungsbedingungen  Datenschutzerklärung  Impressum