Visual C++ MFC Datenbankprogrammierung Tutorial 5


Datenbankbeispiel 5: Mehrere Tabellen verknüpfen mit TreeControl und Dialog

Einleitung

In diesem fünften und fortgeschrittenen Datenbank-Tutorial zeige ich, wie man mit Visual C++ und MFC mehrere Datenbanktabellen verknüpft und die Ergebnisse in einer hierarchischen Baumstruktur anzeigt. Um diese Anleitung nicht zu lang werden zu lassen, schreibe ich immer nur die Dinge auf, die verändert werden müssen. Bei Codeabschnitten werden die Teile, die von mir verändert wurden, fett geschrieben.


Praktisches Beispiel

Aufgabenstellung

Es sollten vor Beginn dieses Projekts die Maßnahmen getroffen werden, die im Kapitel "Datenbankzugriff Vorbereitung" angegeben sind.

Visualisierung der Tabellenverknüpfung

Die Datenbank "Nordwind" enthält unter anderem die Tabellen: Erstellen Sie ein Programm, welches in einem CTreeCtrl in oberster Ebene jeweils ein Bestelldatum anzeigt, zu dem Bestellungen vorliegen.
In der Ebene darunter werden die Firmen der einzelnen Bestellungen angezeigt.
Bei Anklicken einer Firma wird in einem modalen Dialog angezeigt:

Lösung - Schritt für Schritt Anleitung

  1. Neues Projekt anlegen in Visual C++:
  2. Dialog-Design für Hauptfenster erstellen:
    Es wird nur ein TreeControl (CTreeCtrl) benötigt.
    Screenshot: Dialog-Editor mit TreeControl

    Um die Linien und Schaltflächen (Plus/Minus-Icons) beim TreeControl zu erhalten, müssen die Eigenschaften editiert werden:
    Screenshot: TreeControl-Eigenschaften für Linien und Buttons
  3. Membervariable für das TreeControl erstellen:
    Es wird eine Membervariable vom Typ CTreeCtrl für den Zugriff benötigt.
    Dazu muss der Klassenassistent geöffnet werden (Strg+W oder View -> ClassWizard).
    Screenshot: ClassWizard für TreeControl-Membervariable
  4. Restliche Tabellen einbinden (Kunden und Personal):
    Dazu den Klassenassistent öffnen, alle Membervariablen für die Spalten in der Klasse CDb5Set löschen und auf "Spalten aktualisieren" drücken.
    Screenshot: Spalten im ClassWizard aktualisieren
    Nun ist die Datenbank noch einmal auszuwählen.
    Screenshot: Datenbank erneut auswählen
    Hier können nun alle drei benötigten Tabellen markiert werden.
    Screenshot: Alle Tabellen (Bestellungen, Kunden, Personal) auswählen
  5. Membervariablen für benötigte Spalten anlegen:
    Benötigt werden folgende Spalten mit entsprechenden Variablen:
  6. Tabellennamen in GetDefaultSQL() einfügen:
    CString CDb5Set::GetDefaultSQL()
    {
        return _T("[Bestellungen],[Personal],[Kunden]");
    }
  7. TreeControl bei Programmstart mit Daten füllen (OnInitialUpdate):
    void CDb5View::OnInitialUpdate()
    {
        m_pSet = &GetDocument()->m_db5Set;
        CRecordView::OnInitialUpdate();
        GetParentFrame()->RecalcLayout();
        ResizeParentToFit();
    
        // Nach Bestelldatum sortieren
        m_pSet->m_strSort = "[Bestellungen].[Bestelldatum]";
        
        // Um zu verhindern, dass die Datenbank jeden Datensatz mit jedem verknüpft (Cross Join), 
        // müssen sinnvolle Verknüpfungen angegeben werden
        m_pSet->m_strFilter = "[Bestellungen].[Personal-Nr]=[Personal].[Personal-Nr] "
                             "AND [Bestellungen].[Kunden-Code]=[Kunden].[Kunden-Code]";
        
        // Erneutes Laden der Tabellen, um Sortierung und Filter zu aktivieren
        m_pSet->Requery();
    
        CTime oldtime;
        // Alle Datensätze durchlaufen
        while (!m_pSet->IsEOF())
        {
            // Zum Merken des aktuellen Root-Items (oberste Ebene)
            HTREEITEM parent;
            
            // Nur wenn neues Root Item benötigt wird (neues Datum)
            if (oldtime != m_pSet->m_Bestelldatum)
            {
                // Neues Datum merken
                oldtime = m_pSet->m_Bestelldatum;
                
                // Neues Parent-Element im TreeControl anlegen
                parent = m_tree.InsertItem(m_pSet->m_Bestelldatum.Format("%d.%m.%y"));
            }
            
            // Firmenname als Unterelement des aktuellen Parent einfügen
            HTREEITEM item = m_tree.InsertItem(m_pSet->m_Firma, parent);
            
            // Bestellnummer als Item-Daten speichern für spätere Identifikation
            m_tree.SetItemData(item, m_pSet->m_BestellNummer);
            
            // Zum nächsten Datensatz springen
            m_pSet->MoveNext();
        }
    }
  8. Detaildialog für Firmeninformationen erstellen:
    In den Ressourcen einen neuen Dialog einfügen.
    Screenshot: Neuen Dialog in Ressourcen erstellen
  9. Klasse für den Dialog anlegen:
    Klassenassistent öffnen, Frage nach Klassenerstellung mit "Ja" beantworten und Klassennamen eingeben (z.B. "CDetailDialog").
    Screenshot: Dialogklasse im ClassWizard erstellen
  10. Membervariablen für Dialog-Eingabefelder erstellen:
    Screenshot: Membervariablen für Dialog-Steuerelemente
  11. Dialog-Klasse in db5view.cpp einbinden:
    // db5View.cpp : Implementierung der Klasse CDb5View
    //
    
    #include "stdafx.h"
    #include "db5.h"
    
    #include "db5Set.h"
    #include "db5Doc.h"
    #include "db5View.h"
    
    #include "DetailDialog.h"  // Einbindung der Dialogklasse
  12. Event-Handler für TreeControl-Klicks erstellen:
    Die beste Wahl ist TVN_SELCHANGED, da NM_CLICK nicht zielführend ist (man erfährt nicht, auf welches Item gedrückt wurde).
    Screenshot: Event-Handler für TreeControl im ClassWizard
  13. Code für den Klick-Handler:
    void CDb5View::OnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult) 
    {
        NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
    
        if (pNMTreeView != NULL)
        {
            // Überprüfen, ob ein gültiges Item mit Daten ausgewählt wurde
            if (pNMTreeView->itemNew.lParam != NULL)
            {
                // Filter für spezifische Bestellung setzen
                m_pSet->m_strFilter.Format("[Bestellungen].[Bestell-Nr] = %d", 
                                          pNMTreeView->itemNew.lParam);
                
                // JOIN-Bedingungen hinzufügen, um Cross Join zu verhindern
                m_pSet->m_strFilter += " AND [Bestellungen].[Personal-Nr]=[Personal].[Personal-Nr] "
                                      "AND [Bestellungen].[Kunden-Code]=[Kunden].[Kunden-Code]";
                
                // Datensatz mit Filter laden
                m_pSet->Requery();
    
                // Dialog erstellen und mit Daten füllen
                CDetailDialog dia;
                dia.m_firma = m_pSet->m_Firma;
                dia.m_land = m_pSet->m_Kunde_Land;
                dia.m_bestelldatum = m_pSet->m_Bestelldatum.Format("%d.%m.%y");
                dia.m_vorname = m_pSet->m_Vorname;
                dia.m_nachname = m_pSet->m_Nachname;
                
                // Dialog modal anzeigen
                dia.DoModal();
            }
        }
        
        *pResult = 0;
    }
  14. Projekt kompilieren und testen: Die Anwendung zeigt jetzt Bestellungen hierarchisch nach Datum und Firma gruppiert an. Bei Klick auf eine Firma öffnet sich ein Dialog mit detaillierten Informationen.
Zusammenfassung der gelernten Konzepte:
1. Mehrere Datenbanktabellen in einem MFC-Projekt verknüpfen
2. SQL JOINs mit m_strFilter in CRecordSet implementieren
3. Hierarchische Darstellung mit TreeControl und ItemData
4. Dynamische Filterung bei Benutzerinteraktion
5. Modale Dialoge für Detailinformationen
6. Komplexe Datenbankabfragen mit Visual C++ MFC
Programmierecke.NET