Datenbankbeispiel "Splitter"


Einleitung

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.

Beispiel

Aufgabe

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

Bentutzen Sie diese Datenbank und erstellen sie eine Benutzer- oder System-DSN im ODBC Manager in der Systemsteuerung mit dem Namen "splitter".
Wie das geht steht hier.

Erstellen Sie eine Visual Anwendung, welche in einem geteilten Fenster (Splitter Window) diese Datenbank in 4 Ansichten zeigt:

Inhalte der Ansichten:
  1. (links, oben) ermöglicht das Durchblättern und Editieren der Kundentabelle
    (Editfelder für die Attribute, Push-Buttons für
    Button Bedeutung
    Vor 1 Satz weiterblättern
    Zurück 1 Satz zurückblättern
    Löschen angezeigten Satz löschen
    Hinzufügen dargestellten Satz hinzufügen
    Update aktellen Satz mit dargestellten Daten überschreiben
  2. (rechts, oben) ermöglicht dieselben Operationen mit Adresstabelle
  3. (links, unten) ermöglicht dieselben Operationen mit Ländertabelle
  4. stellt Join-Daten dar:
    • Kunde : Name
    • Adresse : Strasse, Hausnummer
    • Land : Name
    In dieser Ansicht kann man einen Kundennamen eingeben, durch Bestätigung der Eingabe mittels Push-Button werden die restlichen Daten des Kunden dargestellt.
Bei Eingabe in den jeweiligen Ansichten ist zu prüfen, ob zu den eingegebenen Fremdschlüsseln die entsprechenden Einträge in der referenzierten Tabelle existieren.

Lösung in Prosa

Ich erkläre nicht mehr alle Einzelheiten wie zum Beispiel anlegen der Membervariablen für die Controls, also aufpassen. :-)
  1. Zuerst einmal ein neues Projekt anlegen:
  2. Erstellen einer Klasse, abgeleitet von CRecordSet, für jede Tabelle außer für die, die schon beim Erstellen des Projekts angegeben wurde. (Für die gibts ja schon eine Klasse)
    Ich beschreibe es hier am Beispiel der Tabelle Adresse


  3. Erstellen einer von CRecordSet abgeleiteten Klasse, die alle 3 Tabellen vereint. (Join) Dazu wieder neue Klasse erstellen und bei in dem Fenster, in dem man die jeweilige Tabelle auswählen kann, einfach alle 3 auswählen.
  4. Erstellen von 4 Dialogen im Dialogeditor. Hier am Beispiel Adresse.

    Aber aufpassen:
    In den Eigenschaften der Dialoge muss bei Stil : Untergeordnet ausgewählt werden, sonst gibt das Programm beim Start eine ASSERT-Fehlermeldung aus.
  5. Erstellen der Ansichten (Views) für jede von CRecordSet abgeleitete Klasse.
    Das sind insgesamt wieder 4 Klassen. Hier wieder am Beispiel der Tabelle Adresse.



  6. Nun müssen die Views noch eingetragen werden. Dazu braucht man eine neue Methode, die man am besten mit dem Klassenassistenten erstellt.
  7. In der Datei, in der sich die gerade erstellten Methode befindet, folgende Dateien Inkludieren:
    // MainFrm.cpp : Implementierung der Klasse CMainFrame
    //
    
    #include "stdafx.h"
    #include "splitter.h"
    
    #include "MainFrm.h"
    //Die Dokumentklasse
    #include "SplitterDoc.h"
    //Die 4 View Klassen
    #include "DKunde.h"
    #include "DLand.h"
    #include "DJoin.h"
    #include "DAdresse.h"
      		
  8. Eine Variable vom Typ CSplitterWnd zur Mainframe Klasse hinzufügen ( bei mir m_wndSplitter ) und die Methode OnClientCreate abändern:
    BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) 
    {
    	// TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
    	
    	// Folgende Zeile muss auskommentiert werden
    	// -> return CFrameWnd::OnCreateClient(lpcs, pContext);
    
    	// Geteiltes Fenster mit 2 Zeilen und 2 Spalten erstellen
    	m_wndSplitter.CreateStatic(this,2,2);
      
    	// Alle 4 Views an den in den ersten zwei Parametern übergebenen Positionen einfügen
    	m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(DKunde),CSize(350,250),pContext);
    	m_wndSplitter.CreateView(1,0,RUNTIME_CLASS(DAdresse),CSize(350,250),pContext);
    	m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(DLand),CSize(350,220),pContext);
    	m_wndSplitter.CreateView(1,1,RUNTIME_CLASS(DJoin),CSize(350,220),pContext);
    
    	return TRUE;
    }
        
  9. Funktionalität für die Buttons in den Views für Adresse, Kunde und Land implementieren.
    Hier am Beispiel von Adresse:
    void DAdresse::OnVor() 
    {
    	// Datensatzzeiger vorrücken
    	m_pSet->MoveNext();
    	// Daten in die Controls (auf den Bildschirm) schreiben
    	UpdateData(false);
    }
    
    void DAdresse::OnZurueck() 
    {
    	// Datensatzzeiger zurückrücken
    	m_pSet->MovePrev();
    	// Daten in die Controls (auf den Bildschirm) schreiben
    	UpdateData(false);
    }
    
    void DAdresse::OnAdd() 
    {
    	// Datenbank für Hinzufügen vorbereiten
    	m_pSet->AddNew();
    	// Spalte Index der Tabelle ist Autowert
    	// desshalb den von AddNew() generierten merken..
    	int merker = m_pSet->m_Index;
    	// Daten von den Controls in die Variablen kopieren
    	UpdateData();
    	// .. und Autowert von Index restaurieren
    	m_pSet->m_Index = merker;
    	// Einfügeoperation abschließen
    	m_pSet->Update();
    	// Aktualisieren der Datenbank
    	m_pSet->Requery();
    	// Daten in Controls kopieren
    	UpdateData(false);
    }
    
    void DAdresse::OnDel() 
    {
    	// Aktellen Datensatz löschen
    	m_pSet->Delete();
    	// Gelöschten wirklich entfernen
    	// er wird sonst noch als gelöscht angezeigt
    	m_pSet->Requery();
    	// Controls aktualisieren
    	UpdateData(false);
    }
    
    void DAdresse::OnUpdate() 
    {
    	// Aktuellen Datensatz zum Editieren vorbereiten
    	m_pSet->Edit();
    	// Benutzereingaben in die Variablen kopieren
    	UpdateData();
    	// Editieroperation abschließen
    	m_pSet->Update();
    }
        
  10. Den gejointen Recordset gesondert behandeln.
    Zeichnen mit CListCtrl, Eingabefeld und Button.

    Verbinden der Variablen.
  11. Zum Hinzufügen der Tabellenüberschriften die Methode OnInitialUpdate der von CRecordSet abgeleiteten Klasse ändern.
    void DJoin::OnInitialUpdate()
    {
    	BeginWaitCursor();
    	GetRecordset();
    	CRecordView::OnInitialUpdate();
    	if (m_pSet->IsOpen())
    	{
    		CString strTitle = m_pSet->m_pDatabase->GetDatabaseName();
    		CString strTable = m_pSet->GetTableName();
    		if (!strTable.IsEmpty())
    			strTitle += _T(":") + strTable;
    		GetDocument()->SetTitle(strTitle);
    	}
    	EndWaitCursor();
    
    	// Tabellenüberschriften setzen
    	m_list.InsertColumn(0,"Kunde",LVCFMT_LEFT,60);
    	m_list.InsertColumn(1,"Ort",LVCFMT_LEFT,60);
    	m_list.InsertColumn(2,"Strasse",LVCFMT_LEFT,80);
    	m_list.InsertColumn(3,"Hausnr",LVCFMT_LEFT,40);
    	m_list.InsertColumn(4,"Land",LVCFMT_LEFT,80);
    }
        
  12. Eine Methode für den Suchbutton schreiben.
    void DJoin::OnSuch() 
    {
    	// Natural Join (jedes mit jedem) verhindern
    	m_pSet->m_strFilter = "Kunde.Adresse = Adresse.Index AND Adresse.Land = Land.Index";
    
    	// Schauen, ob etwas im Eingabefeld steht
    	UpdateData();
    	if (m_kunde != "")
    		//Wenn ja, danach suchen
    		m_pSet->m_strFilter += " AND Kunde.Name = '"+m_kunde+"'";
    
    	// Recordset aktualisieren ( Suchregel wird angewendet )
    	m_pSet->Requery();
    	// Alte Items der CListCtrl löschen
    	m_list.DeleteAllItems();
    	// Solange Elemente vorhanden
    	while (!m_pSet->IsEOF())
    	{
    		// in CListCtrl einfügen
    		int item = m_list.InsertItem(0,m_pSet->m_kunde,-1);
    		m_list.SetItemText(item,1,m_pSet->m_ort);
    		m_list.SetItemText(item,2,m_pSet->m_strasse);
    		m_list.SetItemText(item,3,m_pSet->m_hausnummer);
    		m_list.SetItemText(item,4,m_pSet->m_land);
    		// Aktellen Datensatz um eins weitersetzen
    		m_pSet->MoveNext();
    	}
    }
        
  13. Fertig