Windows Programmierung: Baumdiagramm
Willemers Informatik-Ecke

Einführende Informationen über die Programmierung von Kontrollelementen werden hier vorausgesetzt.

Stile

Man kann mit Hilfe der Funktionen GetWindowLong und SetWindowLong nach der Erzeugung des Kontrollelementes Stile ändern oder anfragen.

TVS_HASLINES
durch Linien zwischen Eltern und Kindern wird die Hierarchie besser sichtbar gemacht.
TVS_LINESATROOT
Auch die Wurzelelemente bekommen eine Linie, die zum linken Rand führt.
TVS_HASBUTTONS
Durch Doppelklicken auf das Elternelement kann der Anwender zwar auf- und zuklappen. Deutlicher wird es durch Voranstellen einer Schaltfläche, die dann auch nur einmal geklickt werden muß. Es muß neben TVS_HASLINES auch TVS_LINESATROOT und TVS_HASBUTTONS gesetzt werden.
TVS_EDITLABELS
Dies ermöglicht dem Anwender das Editieren der Labels
TVS_SHOWSELALWAYS
Die Selektion wird aufrecht erhalten, wenn das Element seinen Fokus verliert.

Ansteuerung

Erzeugung eines Kontollelements

Ein Tree View wird mit der Funktion CreateWindowEx erzeugt. Die einfache CreateWindow funktioniert nicht. Als Fensterklasse wird die Konstante WC_TREEVIEW verwendet. Dazu muß die Common Control Header Datei eingebunden werden.

#include "commctrl.h"

  InitCommonControls();
  hTree = CreateWindowEx(0, WC_TREEVIEW , "Tree", 
          WS_VISIBLE|WS_CHILD|WS_BORDER| TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS,
          1, 1, 300, 100, // Positionen auf dem Elternfenster
          hWnd, (HMENU)ID_TREE, hInst, 0);

Im Dialog wird sie einfach in der Ressource definiert.

Kommt es zu nicht aufgelösten Externals, sollte man prüfen, ob die Library für die Common Controls (comctl32.lib) eingebunden wird.

Hinzufügen eines Elements

Eine TVITEM-Struktur beschreibt ein Element. Für das Einfügen wird die TVINSERTSTRUCT-Struktur benötigt. Sie beschreibt das Eltern-Element und das Element, hinter dem das neue Element einsortiert werden soll. Die Adresse der Einfügestruktur wird dem Tree View mit der Nachricht TVM_INSERTITEM zugesandt.

HTREEITEM AddItemToTree(HWND hwndTV, LPSTR lpszItem, HTREEITEM parent, HTREEITEM brother)
{ 
TVITEM tvi;
TVINSERTSTRUCT tvins; 
HTREEITEM hItem; 

  tvi.mask = TVIF_TEXT;
  tvi.pszText = lpszItem; 
  tvi.cchTextMax = lstrlen(lpszItem);  

  // die Insert-Struktur wird vorbereitet
  tvins.item = tvi;
  if (brother) {
    tvins.hInsertAfter = brother;
  } else {
    tvins.hInsertAfter = TVI_FIRST;
  }
  if (parent==0) {
    tvins.hParent = TVI_ROOT;
  } else {
    tvins.hParent = parent;
  }

  // Nachricht zum Einbinden an das Kontrollelement versenden
  hItem = (HTREEITEM)SendMessage(hwndTV, TVM_INSERTITEM, 0, (LPARAM)&tvins);  
  return hItem;
} 
 . . .
  HTREEITEM opa,papa,onkel,bruder, ich;

  opa = AddItemToTree(hTree, "Opa", 0,0);
  papa = AddItemToTree(hTree, "Papa", opa,0);
  onkel = AddItemToTree(hTree, "Onkel", opa, papa);
  bruder = AddItemToTree(hTree, "Bruder", papa, 0);
  ich = AddItemToTree(hTree, "ich", papa, 0);

Zwei Parameter bestimmen die Position des neuen Element. Das Elternelement und der direkte Nachbar, hier als brother bezeichnet. Beides wird in der Insert-Struktur festgelegt.

Verschieben eines Elements (Dragging)

Um das Verschieben eines Elementes zu verfolgen, werden die Nachrichten TVN_BEGINDRAG oder TVN_BEGINRDRAG, WM_MOUSEMOVE und WM_LBUTTONUP oder WM_RBUTTONUP beobachtet. Hat das Item ein Image, muß es gezeichnet werden, wenn es verschoben wird.

static HWND hTree=0;
static int g_fDragging = FALSE;
POINTS Pos;
static HTREEITEM hItem;
HTREEITEM htiTarget;  // handle to target item 
TVHITTESTINFO tvht;  // hit test information
 . . .
	case WM_NOTIFY: 
		switch (((LPNMHDR) lParam)->code) {
		case TVN_BEGINDRAG:
			// lParam wird auf LPNMTREEVIEW gecastet und weiterverarbeitet
			LPNMTREEVIEW lpnmtv = (LPNMTREEVIEW) lParam;
			hTree = lpnmtv->hdr.hwndFrom; 
			HIMAGELIST himl;

			RECT rcItem;
			himl = TreeView_CreateDragImage(hTree, lpnmtv->itemNew.hItem);
			TreeView_GetItemRect(hTree, lpnmtv->itemNew.hItem, &rcItem, TRUE); 
			ImageList_BeginDrag(himl, 0, 0, 0); 

			ShowCursor(FALSE); 
			SetCapture(GetParent(lpnmtv->hdr.hwndFrom)); 
			g_fDragging = TRUE; 
			break; 
		}
		break; 
		
	case WM_MOUSEMOVE:
		if (g_fDragging) { 
			Pos = MAKEPOINTS(lParam);
			ImageList_DragMove(Pos.x, Pos.y);

			tvht.pt.x = Pos.x; 
			tvht.pt.y = Pos.y; 
			if ((htiTarget = TreeView_HitTest(hTree, &tvht)) != NULL) { 
				TreeView_SelectDropTarget(hTree, htiTarget); 
			} 
		} 
		break;
	case WM_LBUTTONUP:
		if (g_fDragging) {
			ImageList_EndDrag();
			ReleaseCapture();
			ShowCursor(TRUE);
			g_fDragging = FALSE;
		}
		break;

Editieren der Labels

Das Tree View muß mit dem Stil TVS_EDITLABELS erzeugt worden sein. Dann kann bereits ein Label angeklickt werden und Eingaben werden entgegengenommen. Allerdings wird die Eingabe direkt anschließend wieder verworfen. Um Änderungen im Label zu gestatten, braucht man nur die WM_NOTIFY mit dem Code TVN_ENDLABELEDIT zu fangen und return TRUE zu geben. Um dazwischen auf den eingegebenen Text zuzugreifen, wird ein Zugriff auf den Text des Items durchgeführt. Das Beispiel zeigt die Eingabe in einer Messagebox.

	case WM_NOTIFY: 
		switch (((LPNMHDR) lParam)->code) {
		case TVN_ENDLABELEDIT:
			pItem = (LPNMTVDISPINFO) lParam;
			MessageBox(hWnd, pItem->item.pszText, "entry", MB_OK);
			return TRUE;

Wurde das Editieren abgebrochen, wird das pszText-Element 0 sein.

Das Element pItem->item ist nicht das Original TV_ITEM des Baumes, aber die Elemente hItem, lParam und pszText stimmen mit dem Original überein. Für das normale Editieren des Titels reicht das normalerweise auch. Wird aber beispielsweise das Image in Abhängigkeit des Textes verändert, müsste man das Element explizit mit TreeView_GetItem holen.

Soll das Editieren komplett verhindert werden, wird die Nachricht TVN_BEGINLABELEDIT gefangen und TRUE zurückgegeben. Auch hier kann über lParam in gleicher Weise auf das Item zugegriffen werden und so beispielsweise eine bestimmte Itemgruppe ausgegrenzt werden.

Ereignisse

Das TreeView sendet WM_NOTIFY-Nachrichten an das Elternfenster. Der code kann folgende Werte annehmen:

Nachricht Beschreibung
TVN_BEGINDRAG Start einer Drag-And-Drop Aktion
TVN_BEGINRDRAG Start einer Drag-And-Drop Aktion über rechte Maustaste
TVN_BEGINLABELEDIT Start Editieren eines Labels
TVN_ENDLABELEDIT Ende Editieren eines Labels
TVN_DELETEITEM Item wird gelöscht
TVN_GETDISPINFO Das Kontrollelement erfragt Informationen zur Darstellung vom Elternfenster
TVN_SETDISPINFO Die Information des Elternfensters für das Item muß erneuert werden
TVN_ITEMEXPANDED Eine Liste von Kind-Items ist auseinandergezogen oder zusammengeklappt worden
TVN_ITEMEXPANDING Eine Liste von Kind-Items ist kurz davor, auseinandergezogen oder zusammengeklappt zu werden
TVN_KEYDOWN Taste wurde gedrückt
TVN_SELCHANGED Selektion zwischen den Items hat gewechselt
TVN_SELCHANGING Selektion zwischen den Items vor dem Wechsel