PDA

Vollständige Version anzeigen : Hauptmenü aufgebaut mittels Treeview


WeinGeist
16.10.2008, 19:24
Hallo Leute,

Mir ist öfter mal aufgefallen, dass nach einer einfachen Hauptmenüverwaltung gefragt wird. Hab mal etwas aus einem grösseren Projekt extrahiert.

Worauf man achten muss:
Man muss dafür sorgen, dass alle Clients mit der gleichen Version von Treeview arbeiten. Sonst haut das nicht hin. Sprich um mein Beispiel zu testen die beigelegte CommCtl ins Systemverzeichnis kopieren und die alte Umbenennen, sollte das Beispiel nicht funktionieren (Das ist sehr gut möglich, gibt X-Variationen).

Download-Link (http://www.weingeist.ch/AccessSamples/MenueTreeview-Weingeist.rar)

Habe zwar ein paar Ideen wie es auch noch etwas eleganter gelöst werden könnte, aber es funzt auch so sehr gut. Frei nachem Motto: Never Change a running System. ;)

Man muss sich quasi "fast" nicht um die Sub-Nodes selber kümmern, macht die Klasse alles selbst. Die Menüstruktur wird in einer Tabelle abgelegt, nach folgendem Muster:

1
1.1
1.2
1.2.1
1.2.2
1.3
1.4
2
2.1
2.2
2.3
...

Dadurch ist die Hanhabung sehr einfach und auch "Anfänger" täuglich.

Für die Nodes gibts verschiedenste Icons, welche auch mitgeliefert wurden.
Kleiner Tipp dazu: Nicht wild Icons entfernen im Image-Auflistungssteuerlement, diese lassen sich nicht mehr so einfach an die gleiche Stelle hinzufügen und der Index-Verschiebt sich. Sehr hässlich. ;) Daher besser keine Icons löschen wenn man sie nicht mehr braucht!

Grüsse und so
Weingeist

SaschaBHH
17.10.2008, 14:55
Hallo Urs,

danke für das Beispiel. Du haust förmlichst um Dich damit :)

RS
21.10.2008, 12:12
Eine schnelle & Übersichtliche Lösung.

Grüsse aus Wien
Rudolf

WeinGeist
21.10.2008, 12:41
Danke für die Rückmeldungen :)

Die Angesprochenen Verbesserungen, welche man machen könnte:
- Tabelle "dynamisch" halten mittels Properties für Tabellename, Felder etc.
- Rückgabe der auswahl als Event
- auswertung des Events in eigener Klasse fürs Hautmenü oder direkt im Formular (was ich jedoch bewusst nicht gemacht habe, würde erstere Variante machen).

Dadurch wäre die Klasse für jede X-Beliebige Tabelle verwendbar welche einen ähnlichen Aufbau hat. Würde der eigentlichen Verwendung einer Klasse, sprich Kapselung der Aufgaben und mehrfachverwendbarkeit besser entsprechen. ;)

Man könnte zusätzlich, ganz dem Relationalen DB-Design nach, alle Menüs in die Tabelle packen und mit einem zusätzlichen "Menü-ID"-Feld austatten.

Da ich das aktuell nur für das Hauptmenü in dieser Art brauche, war ich noch zu faul das umzusetzen, funktioniert ja so bestens. Wäre aber an sich kein grosser Akt.

Aber es soll ja auch nur ein Beispiel sein, die Idee und Verwendung des Treeviews sollte ja so ziemlich klar sein oder zumindest so, das man es verstehen kann und evtl. selber anpassen, wenn man sich etwas damit beschäftigt :)

WeinGeist
16.02.2010, 16:48
Download-Linke geht nicht mehr.

Anbei die Files

ibs01
12.01.2011, 14:38
Ich hole den Tread nun nochmal hoch.

Herzlichen Dank für die ausführliche Beschreibung und die Demo. Damit lassen sich die abläufe sehr gut nachvollziehen. Ich habe auch Basis Ihrer Datenbank eine vergleichbare erstellt und bekommen nun aber den Fehler, dass die Imagelist zuvor initialisiert werden muss.

Leider finden Ich in ihrem Klassenmodul keinen hinweis, wie die Imagelist initialisert wird. Sie weisen nur den Nodes entsprechende Icons über den Index aus der Tabelle zu.

Vieleicht können Sie mir hier einen Tipp geben.

Edit:

Ich hab nun den Fehler gefunden. Der Verweis auf die Imagelist in den Treeview properties hat gefehlt.

Danke

LG
Thomas

nobbie2005
16.01.2011, 16:13
hmm... bin ich blind? oder blond? :D
ich finde keine Tabelle... wo ist die "Datenquelle" für das Treeview?

WeinGeist
13.04.2011, 14:42
Autsch, das ging lange bis ich das gesehen habe... Irgendwie Mail nicht gesehen. Sorry.

Blind nicht, Blond weiss ich so durch den Bildschirm nicht, aber tippe darauf, dass du Systemobjekte nicht anzeigen lässt, sprich die Tabellen einfach nur ausgeblendet sind. ;)

Tabelle heisst USys_ HauptMenue

nobbie2005
25.04.2011, 12:10
ah, dann liegt es doch daran, dass ich blond "war" hehe, ich danke dir für deine Hilfe! :) Frohen Ostermontag noch :)

ibs01
10.05.2011, 11:16
Hallo Urs,

ich habe mittlerweile die Treeviewfunktionen in mehrere Formulare integriert. Funktioniert wirklich einfach und super. Zwei Fragen sind aber nun aufgetaucht:

1. Derzeit kann ich den Wert des ausgewählten Item bei Click auslesen, sprich ich kann den Wert anzeigen oder übergebem. Lässt sich dass auch irgendwie auf Doppelclick umstellen?

2. Ist es möglich über z.B einen Schalter (sitzt im selben Formular) der gedrückt wird den Wert des im Hauptmenu ausgewählten Items anzeigen zu lassen?

Vielleicht können Sie mir hier weiterhelfen.

LG
Tom

WeinGeist
10.05.2011, 13:31
Hi,

So trivial ist Punkt 1 leider nicht, weil es kein Dobule-Click Ereigniss auf Node-Ebene gibt. Es gibt allerdings einen Workaround.

1. Zwei Variablen auf Modulebene erstellen (--> Oberhalb der Subs etc.):
Private m_lMouseY As Long
Private m_lMouseX As Long


2. Mouse Down des TreeViews abfangen und Koordinaten auslesen:
Private Sub axHauptmenue_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Long, ByVal y As Long)
m_lMouseY = y
m_lMouseX = x
End Sub


3. Das Double-Click event des TreeViews auswerten:
Private Sub axHauptmenue_DblClick()
Dim vData As Variant

Dim oNode As Node

Set oNode = axHauptmenue.HitTest(m_lMouseX, m_lMouseY)

If oNode Is Nothing Then
'Clicked empty space
'Debug.Print "Empty Space"
Else
vData = oNode.Tag
If Not fIstLeer(vData(1, 2)) Then
MainMenu.Run CStr((vData(1, 2)))
End If
End If

ExitMethod:
Set oNode = Nothing
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 91
'MsgBox "Hauptmenü muss neu geladen werden", vbOKOnly, gcSoftwareTitelStd
Set MainMenu = Nothing
Set MainMenu = New clsMainMenu
Set MainMenu.theMainMenue = axHauptmenue
MainMenu.CreateMenu
Case Else
MsgBox Err.Number & " (" & Err.Description & ") in procedure axHauptmenue_DblClick of VBA Dokument Form_frmGeMain"
End Select
Resume ExitMethod
End Sub

Anmerkung: Objektname (axHauptmenue) weiss ich jetz grad nicht ob das im Beispiel auch axHauptmenue heisst, gleiches gilt für MainMenu.
Anmerkung2: Es gibt wohl auch einen Weg via der Api GetCursorPos, allerdings erhalte ich immer falsche Koordinaten, auch wenn ich sie korrekt in Twips umwandle. Wäre etwas schöner, weil kein MouseDown und keine Vars auf Modulebene benötigt werden.


Zu Punkt 2:
Das müsste eigentlich irgendwie so in der Art gehen:

Dim oNode As Node
Dim oNodeCol As Nodes
Set oNodeCol = axHauptmenue.Object.Nodes

For Each oNode In oNodeCol
If oNode.Selected = True Then
vData = oNode.Tag
Debug.Print vData(1, 2) ' Hier irgendwas was machen damit
End If
Next

WeinGeist
10.05.2011, 14:28
Habe mal noch ein wenig gesucht und wurde in den weiten meiner Codes fündig, dachte mir ich hätte sowas ähnliches schonmal irgendwo gebraucht. Habe eine Funktion von glaub DevAvish bissel abgeändert.

In einem neuen Modul folgende funktionen einfügen. Vielleicht gibts irgendwo schon PointApi, dann eben auskommentieren.


Private Type POINTAPI
x As Long
y As Long
End Type

Private Declare Function apiGetDC Lib "user32" Alias "GetDC" (ByVal hwnd As Long) As Long
Private Declare Function apiGetDeviceCaps Lib "gdi32" Alias "GetDeviceCaps" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function apiReleaseDC Lib "user32" Alias "ReleaseDC" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As Rect) As Long

Global Const TWIPSPERINCH = 1440
Global Const g_conTwipPerCentimeter As Double = 566.929133858

Private Const HWND_DESKTOP As Long = 0
Private Const LOGPIXELSX = 88
Private Const LOGPIXELSY = 90

Public Function GetCursorPosInHwnd(hwnd As Long) As POINTAPI
' Returns value in TWIPS
Dim pt As POINTAPI
Dim rc As Rect
Dim lngRet As Long

' Grab the current position of the cursor.
' Values returned are in Screen Coordinates.
lngRet = GetCursorPos(pt)

' Get the Window Rectangle of the Object.
' Values returned are in Screen Coordinates.
lngRet = GetWindowRect(hwnd, rc)

' Subtract the Cursor Y position from the Window Top coordinate.
' The result is the actual offset on the Cursor on the Detail Section WIndow.
' Convert Pixels back to Twips.
pt.x = (pt.x - rc.Left)
pt.y = (pt.y - rc.Top)

Call PixToTwipsCor(pt.x, pt.y)

GetCursorPosInHwnd = pt

End Function


Sub PixToTwipsCor(x As Long, y As Long)

'*************************************************************
' PURPOSE: Converts the two pixel measurements passed as
' arguments to twips.
' ARGUMENTS:
' X, Y: Measurement variables in pixels. These will be
' converted to twips and returned through the same
' variables "by reference."
'*************************************************************

'#### Deklarationen ####
Dim hdc As Long, hwnd As Long, RetVal As Long
Dim XPIXELSPERINCH, YPIXELSPERINCH

'#### Header/Init/Prüfung ####
' Retrieve the current number of pixels per inch, which is
' resolution-dependent.
hdc = apiGetDC(HWND_DESKTOP)

XPIXELSPERINCH = apiGetDeviceCaps(hdc, LOGPIXELSX)
YPIXELSPERINCH = apiGetDeviceCaps(hdc, LOGPIXELSY)

RetVal = apiReleaseDC(0, hdc)

'#### Programmteil ####
' Compute and return the measurements in twips.
x = (x / XPIXELSPERINCH) * TWIPSPERINCH
y = (y / YPIXELSPERINCH) * TWIPSPERINCH

End Sub

Im DoubleClick-Ereigniss brauchts dann anstelle von:
Set oNode = axHauptmenue.HitTest(m_lMouseX, m_lMouseY)

Das hier:
Dim oCoor As POINTAPI
oCoor = GetCursorPosInHwnd(Me.axHauptmenue.Object.hwnd)
Set oNode = axHauptmenue.HitTest(oCoor.x, oCoor.y)


Damit wird dann das MouseDown und Apis im Form überflüssig. Hoffe habe alles erwischt.
Werde bei gelegenheit mal ein Update aufspielen.

WeinGeist
11.05.2011, 10:36
Zusätzliche Anmerkung: Für alle die ein paar Tage Geduld haben, wird es eine neue Version geben. Code ist teilweise nicht sooo sauber. Extrahiere mal ein wenig aktuelles aus meinem grossen Projekt. Die Funktionalität wird dann direkt in der Klasse sein. Menüaufbau dann Wahlweise via Tabelle oder einfachem Code.

ibs01
11.05.2011, 13:46
Hallo Urs,

danke für die rasche Antwort. Die DBClick funktion hab ich bereits integriert und funktioniert auch soweit. Ich hab ursprünglich folgenden Code verwendet:


Private Sub axHauptmenue_DblClick()
'Dim sItem As String
'Dim Node As Object

If Me.axHauptmenue.SelectedItem.Children > 0 Then
Exit Sub
Else
On Error GoTo ErrorHandler

Dim vData As Variant
Dim MyForm As String
MyForm = Me.Name
'vData = Node.Tag
vData = Me.axHauptmenue.SelectedItem.Tag

If Len(Nz(vData(1, 2), vbNullString)) > 0 Then
MainMenu.Run CStr((vData(1, 2)))
End If

ExitMethod:
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 91
MsgBox "Hauptmenü muss neu geladen werden", vbOKOnly, gcSoftwareTitelStd
Set MainMenu = Nothing
Set MainMenu = New clsMainMenu
Set MainMenu.theMainMenue = axHauptmenue
MainMenu.CreateMenu
Case Else
MsgBox Err.Number & " (" & Err.Description & ") in procedure axHauptmenue_NodeClick of VBA Dokument Form_frmGeMain"
End Select
Resume ExitMethod

End If
End Sub



Hat auch soweite funktioniert.

Die 2. Funktion muss ich noch testen.



Danke nochmals.

WeinGeist
12.05.2011, 14:30
So, es hat sich herausgestellt, dass die Variante mit der API nicht zuverlässig funktioniert mit RDP (RemoteDesktop oder TerminalServer). Deshalb habe ich das in der neuen Beispieldatei wieder herausgestrichen.

Im Anhang findet sich die neue Version, sie ist nun deutlich sauberer programmiert und auch etwas flexibler einsetzbar. --> Tabelle nicht zwingend nötig.

Rothaus
28.07.2011, 01:43
Hallo Weingeist.
Dein Treeview ist wirklich Klasse, aber wie bringe ich es jetzt hin, dass ich Werte aus einer Tabelle da rein bekomme?

Weingeist_LoggedOut
28.07.2011, 06:45
Schön das es dir gefällt.

Schaue dir den Code im Formular frmGeMainTable an.
Dazu noch den Aufbau der Tabelle USys_Haupmenu. Dann kannst auch eigene Tabellen erstellen. Oder einfach die bestehende mit eigenen Menüpunkten ändern.

Grüssse und So