C++ und MFC
erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net)    (Stand: 02.03.2003)

Zurueck zum Inhaltsverzeichnis
 

12. Datenbanken

Datenbankmanagementsysteme (DBMS) wie z.B. MS Access oder Oracle bieten eine geordnete Sammlung von Daten, die man sich vereinfacht in Tabellenform vorstellen kann. Die Spalten der Tabelle sind die Felder mit den entsprechenden Feldnamen (z.B. Name, Vorname, Telefon) und die Reihen die aufeinander folgenden Datensätze. Ein wichtiges Abfragewerkzeug in diesem Zusammenhang ist die Sprache SQL.

Wie kann man aber auf solche Datenbanken in der Windows-Programmierung mit C++/MFC zugreifen?

Zunächst erstellen wir der Einfachheit halber eine ganz einfache "Datenbank" in MS Excel. Wir wählen dieses Kalkulationsprogramm, in dem man nebenbei auch Daten speichern kann, weil es einfach zu bedienen und auf vielen Rechnern beheimatet ist. Ansonsten werden wir zu sehr von den Details der DBMS abgelenkt. Wir wollen aber rasch mit der MFC-Programmierung beginnen.

Als Feldnamen verwenden wir Name, Vorname und  Hobby.

Geben Sie dem Excel-Tabellen-Bereich "Tabelle1!$A$1:$C$7" den Namen "ExcelTestDatenbank".
Das funktioniert z.B. über das Excel-Menü "Einfügen - Namen - Festlegen".
Speichern Sie die Tabelle bitte unter dem Filenamen "C:\Daten.xls" ab.
Damit verfügen wir nun über eine kleine Testdatenbank.

Im nächsten Schritt bauen wir uns ein "MFC-Lese-Programm" auf diese Datenbank:

Wir erstellen ein neues Projekt (Menü Datei-Neu-Projekte) namens "ExcelDaten". Als Typ wählen wir MFC-Anwendungs-Assistent(exe).
In Schritt 1 entscheiden wir für SDI. Im Schritt 2 erfolgt nun die entscheidende Auswahl:

Hier wählen wir "Datenbankansicht mit Dateiunterstützung". Durch diese Auswahl wird der Button "Datenquelle" aktiviert.
Nach Betätigung dieser Schaltfläche öffnet sich der Dialog "Datenbankoptionen". Wir können hier zwischen ODBC, DAO und OLE DB wählen.
Wir entscheiden uns für ODBC (Open Database Connectivity: Standardprotokoll für den Zugriff auf relationale Datenbanken, die auf SQL basieren) und wählen dort Excel-Dateien.

Den Recordset-Typ belassen wir zunächst auf "Snapshot".
Nach "OK" gelangen wir zu "Arbeitsmappe auswählen". Hier wählen wir unser File "C:\Daten.xls".

Die nächste Auswahl wird gefordert:

Denn in einer Excel-Arbeitsmappe könnte eine Vielzahl von Namensbereichen existieren.
Wir wählen unseren Namensbereich "ExcelTestDatenbank".

Damit ist Schritt 2 abgeschlossen.

Die restlichen Schritte akzeptieren wir ohne Änderungen.
In Schritt 6 sehen wir folgende Zusammenstellung:

Als Basisklasse für unsere View empfiehlt uns der Assistent die MFC-Klasse CRecordView.
Das ist eine gute Wahl für den Anfang.

Bevor wir unsere Dialogfläche bearbeiten, unternehmen wir eine Wanderung durch das erzeugte Code-Gerüst, um die Verbindung zu unserer Datenbank zu verfolgen. Bedingt durch unsere übersichtliche Test-Datenbank sieht der Header und die Implementierung der Klasse CExcelDatenSet sehr einfach aus:

// ExcelDatenSet.h : Schnittstelle der Klasse CExcelDatenSet
class CExcelDatenSet : public CRecordset
{
public:
 CExcelDatenSet(CDatabase* pDatabase = NULL);
 DECLARE_DYNAMIC(CExcelDatenSet)

// Feld-/Parameterdaten
 //{{AFX_FIELD(CExcelDatenSet, CRecordset)
 CString m_Name;
 CString m_Vorname;
 CString m_Hobby;
 //}}AFX_FIELD

// Überladungen
 // Vom Klassenassistenten generierte Überladungen virtueller Funktionen
 //{{AFX_VIRTUAL(CExcelDatenSet)
 public:
 virtual CString GetDefaultConnect(); // Standard-Verbindungszeichenfolge
 virtual CString GetDefaultSQL();  // Standard-SQL für Recordset
 virtual void DoFieldExchange(CFieldExchange* pFX); // RFX-Unterstützung
 //}}AFX_VIRTUAL
 ...
};

// CExcelDatenSet Implementierung

IMPLEMENT_DYNAMIC(CExcelDatenSet, CRecordset)

CExcelDatenSet::CExcelDatenSet(CDatabase* pdb) : CRecordset(pdb)
{
 //{{AFX_FIELD_INIT(CExcelDatenSet)
 m_Name = _T("");
 m_Vorname = _T("");
 m_Hobby = _T("");
 m_nFields = 3;
 //}}AFX_FIELD_INIT
 m_nDefaultType = snapshot;
}

CString CExcelDatenSet::GetDefaultConnect()
{
 return _T("ODBC;DSN=Excel-Dateien");
}

CString CExcelDatenSet::GetDefaultSQL()
{
 return _T("[ExcelTestDatenbank]");
}

void CExcelDatenSet::DoFieldExchange(CFieldExchange* pFX)
{
 //{{AFX_FIELD_MAP(CExcelDatenSet)
 pFX->SetFieldType(CFieldExchange::outputColumn);
 RFX_Text(pFX, _T("[Name]"), m_Name);
 RFX_Text(pFX, _T("[Vorname]"), m_Vorname);
 RFX_Text(pFX, _T("[Hobby]"), m_Hobby);
 //}}AFX_FIELD_MAP
}

Die Code-Zeilen, die unsere spezielle Datenbank berühren, wurden oben hervorgehoben.
Zum einfachen Erkennen des Zusammenhangs sind diese Zeilen hier noch einmal dargestellt:

//C___Set

CString m_Name;
CString m_Vorname;
CString m_Hobby;

m_Name = _T("");
m_Vorname = _T("");
m_Hobby = _T("");

CString CExcelDatenSet::GetDefaultConnect()
{
 return _T("ODBC;DSN=Excel-Dateien");
}

CString CExcelDatenSet::GetDefaultSQL()
{
 return _T("[ExcelTestDatenbank]");
}

RFX_Text(pFX, _T("[Name]"), m_Name);
RFX_Text(pFX, _T("[Vorname]"), m_Vorname);
RFX_Text(pFX, _T("[Hobby]"), m_Hobby);
 

Zusammengefaßt kann man sagen, daß wir hier einen ODBC-Zugriff auf eine Excel-Datei durchführen. Der  ODBC-Begriff DSN steht übrigens für Data Source Name. Der Name unserer Datenbank ist ExcelTestDatenbank. Die Datenbank-Felder Name, Vorname und Hobby werden in die CString-Variablen m_Name, m_Vorname und m_Hobby der Klasse CExcelDatenSet überführt.
Was hier völlig fehlt, ist unser Filename "C:\Daten.xls". Verfolgen Sie die Auswirkungen hiervon bitte genau.
Der obige Code ist der zentrale Teil der Datenbankanbindung.

Alles, was nun folgt, unterstützt nur die Auswahl und Visualisierung dieser Daten.
Zur Darstellung der Daten kehren wir in unsere Ressource IDD_EXCELDATEN_FORM zurück.

Wir benötigen drei Edit-Felder zur Datenanzeige, denen wir drei Static-Felder zur Anzeige des Feldnamens vorstellen.
Damit wir einfach auf den Inhalt dieser Edit-Felder zugreifen können, erzeugen wir mit dem MFC-Klassen-Assistenten die Member-Variablen m_Name, m_Vorname und m_Hobby.

Diese Variablen gehören zur Klasse CExcelDatenView (nicht verwechseln mit den gleichnamigen Variablen der der Klasse CExcelDatenSet).

Nun können wir die einzelnen Felder eines Datensatzes unseren Edit-Feldern zuordnen. Es fehlt uns noch ein Zeiger auf die einzelnen Datensätze. Aber MFC hat schon wie gewohnt vorgesorgt:

// ExcelDatenView.h : Schnittstelle der Klasse CExcelDatenView

class CExcelDatenSet;

class CExcelDatenView : public CRecordView
{
protected: // Nur aus Serialisierung erzeugen
 CExcelDatenView();
 DECLARE_DYNCREATE(CExcelDatenView)

public:
 //{{AFX_DATA(CExcelDatenView)
 enum { IDD = IDD_EXCELDATEN_FORM };
 CExcelDatenSet* m_pSet;
 CString m_Name;
 CString m_Vorname;
 CString m_Hobby;
 //}}AFX_DATA

Der Klassen-Assistent hat der View-Klasse einen Zeiger auf die Set-Klasse zugefügt. Damit können wir auf einfache Weise in unserer Datenbank navigieren.

Zunächst wollen wir, daß das Programm beim Start zu unserem ersten Datensatz springt.
Hierzu verwenden wir die Member-Funktion OnInitialUpdate().

Dort fügen wir folgenden Code hinzu:

void CExcelDatenView::OnInitialUpdate()
{
 m_pSet = &GetDocument()->m_excelDatenSet;
 CRecordView::OnInitialUpdate();
 GetParentFrame()->RecalcLayout();
 ResizeParentToFit();
 OnRecordFirst();
}

Es gibt aber noch keine Funktion OnRecordFirst? Stimmt, das werden wir aber sofort ändern:

Im Klassen-Assistent gibt es unter Nachrichtenzuordnungstabellen die Bezeichner ID_RECORD_FIRST, ..._LAST, ..._NEXT und ..._PREV.
Diesen vier Nachrichten ordnen Sie bitte vier Funktionen zu. Bei ID_RECORD_FIRST ist dies OnRecordFirst().

Zusätzlich lagern wir zur Vermeidung von Wiederholungen die Darstellung der Daten in die eigene Member-Funktion "void ShowData()" aus:


 

Das sieht nun wie folgt aus:

void CExcelDatenView::OnRecordFirst()
{
 m_pSet->MoveFirst();
 ShowData();
}

void CExcelDatenView::OnRecordLast()
{
 m_pSet->MoveLast();
 ShowData();
}

void CExcelDatenView::OnRecordNext()
{
 m_pSet->MoveNext();
 ShowData();
}

void CExcelDatenView::OnRecordPrev()
{
 m_pSet->MovePrev();
 ShowData();
}

void CExcelDatenView::ShowData()
{
 m_Name    = m_pSet->m_Name;
 m_Vorname = m_pSet->m_Vorname;
 m_Hobby   = m_pSet->m_Hobby;
 UpdateData(FALSE);
}
 

Nun kompilieren und starten wir. Aha, da möchte das Programm noch wissen, welche Arbeitsmappe wir denn gerne hätten.
Das ist der fehlende Filename in unserem Programm:

Wählen Sie bitte C:\Daten.xls aus.

... und schon greifen wir auf den ersten Datensatz unserer Datenbank zu.
Sie sehen oben in der Toolbar die vier Pfeilsymbole. Diese entsprechen den Bezeichnern ID_RECORD_FIRST, ..._LAST, ..._NEXT und ..._PREV, die zu unseren Funktionen OnRecord...() führen. Damit navigieren wir durch die Datenbank.
 

An diesem einfachen Beispiel haben Sie gesehen, welche hervorragenden Werkzeuge MFC bietet, um mittels C++ mit Datenbanken ins Gespräch zu kommen. Jetzt ist der richtige Zeitpunkt, um das Problem mit dem fehlenden Filenamen anzugehen. Das Thema heißt ODBC-Treiber. Schauen Sie sich noch einmal die entscheidende Stelle im Code an:

CString CExcelDatenSet::GetDefaultConnect()
{
 return _T("ODBC;DSN=Excel-Dateien");
}

Als DSN wurde hier allgemein Excel-Dateien übergeben. Wo finden wir diese DSN?
Gehen Sie zu Ihrem Desktop und öffnen Sie Arbeitsplatz - Systemsteuerung - ODBC-Datenquellen (32 bit):

Dort fügen wir einen neuen Namen ein, der mit unserer Arbeitsmappe in C:\Daten.xls verbunden ist.
Durchlaufen Sie bitte die Dialoge nach Betätigen der Schaltfläche "Hinzufügen" und wählen Sie als Datenquellenname "ExcelTest" und als Arbeitsmappe "C:\Daten.xls". Nun verfügen Sie über einen selbst erstellten ODBC-Treiber auf diese Arbeitsmappe.

Im nächsten Schritt wird nun DSN=ExcelTest in unser Programm eingefügt:
 
CString CExcelDatenSet::GetDefaultConnect()
{
 return _T("ODBC;DSN=ExcelTest");
}

Ab jetzt startet das Programm direkt zu unserer Arbeitsmappe durch, ohne sich danach zu erkundigen.
 
 
 

wird fortgesetzt.
 
 
Zurueck zum Inhaltsverzeichnis