Hallo,
ich habe in der MFC mit der Voreinstellung SDI ein Programm
programmiert, dass 2 verschiedene Fenster verwendet.
Hierbei habe ich mich von den folgenden Links inspirieren lassen.
http://msdn.microsoft.com/de-de/library/cc468015%28v=vs.71%29.aspxhttp://www.codeguru.com/Cpp/W-D/doc_view/viewmanagement/article.php/c6121/
Um das ganze etwas zu verdeutlichen habe ich 2 Bilder angehangen.
Umgeschaltet wird über die Menüleiste.
Die eigendliche Frage bezieht sich auf das Speicherprinzip. Bei einer
"normalen" SDI-Anwendung schreibt die Litertur vor, dass die
Dokument-Klasse alle Daten speichert und die View-Klasse die Daten
anzeigt.
Um aus der View-Klasse herraus auf die Daten der Dokument-Klasse
zugreifen zu können gibt es folgenden Befehl:
1
CDocument*pDoc=GetDocument();
Bei meiner Applikation wird es nun später notwendig sein, das
Einstellungen sozusagen global vorgenommen werden müssen. Also es ist
notwendig, dass Daten zwischen den beiden View-Klasse ausgetauscht
werden. Desweiteren werden mehrere Klassen angelegt auf die beide
Fenster zugreifen müssen.
Frage 1: Wie kann ich das am sinnvollen realisieren?
Ich wollte es so machen, dass beide View-Klasse die selbe Doc-Klasse
verwenden und ich in dieser alle Daten speicher auf die beide Fenster
zugreifen müssen. In der Doc-Klasse würde ich dann z.B. weitere Klassen
anlegen und beiden Fenstern einen Pointer auf diese Klasse übergeben.
Hierbei trat ein Problem auf. Für dieses sind 3 Klassen relevant.
CProjekt1Doc, CProjekt1View und CChartView
Die ersten beiden wurden von der Entwicklungsumgebung erstellt. Das 3te
wird dynamisch während der Laufzeit erstellt (Links am Anfang).
In der .cpp von CProjekt1View kann ich mit folgender Funktion auf den
Pointer von CProjekt1Doc zugreifen.
1
CProjekt1Doc*pDoc=GetDocument();
In der .cpp von CChartView kann ich nur auf einen "allgemeinen" Pointer
der Klasse dokument zugreifen. Von hier müsste auch ein Zugriff auf
CProjekt1Doc möglich sein.
1
CDocument*pDoc=GetDocument();
Gibt es eine Möglichkeit, auch in der .cpp Datei der Klasse CChartView
auf den Pointer des Dokuments zuzugreifen?
Ich könnte nun etliche Sachen versuchen mit globalen Variablen oder ich
könne der Klasse CChartView den Pointer von der Dokument-Klasse
ausgehend mit einer Settermethode übergeben. Aber mich beschleicht die
Frage ob ich nicht einen grundlegenden Fehler bei der MFC Struktur
gemacht habe.
Bin für sämtlichen Ratschläge und Tipps sehr dankbar.
Beim Erzeugen eines View wird doch gewöhnlich ein CCreateContext* als
einer der Parameter übergeben?! Wenn man nun
CCreateContext::m_pCurrentDoc vorher mit dem Pointer zum Document
initialisiert erhalten alle mit diesem CreateContext erzeugten Views das
gleiche Dokument.
Nun ist es aber so, dass die von mir erstelle Klasse CChartView
automatisch eine Methode GetDocument erstellt, welche als Rückgabewerte
einen Pointer auf CDocument hat.
1
CDocument*pDoc=GetDocument();
Dieser Pointer zeigt natürlich auf mein Dokument. Aber mein Dokument hat
eigendlich den Pointer CProjekt1Doc und nur wenn ich diesen Pointer
verwende erkennt Intellitrace, dass die Klasse weitere von mir erstelle
Attribute und Methoden hat. Das heißt also, dass ich eine Typeumwandlung
machen muss.
1
CDocument*pDoc=(CProjekt1Doc*)GetDocument();
Das klappt auch. Nur sollte die Methoden GetDocument() schon direkt den
passenden Pointer zurückliefern.
>> Das klappt auch. Nur sollte die Methoden GetDocument() schon direkt den> passenden Pointer zurückliefern.
Kann sie nicht, denn GetDocument ist eine Memberfunktion, die deine
Viewklasse von der Basisklasse CView geerbt bekommt.
Es ist ok, wenn du dir den Pointer entsprechend zurechtcastest. Was
anderes macht der vom Wizzard generierte Code im anderen View auch
nicht. Sinnvollerweise wirst du dir eine GetDocument Funktion in deine
eigene Viewklasse machen, die diesen Cast verbirgt.
> Ich wollte es so machen, dass beide View-Klasse die selbe> Doc-Klasse verwenden und ich in dieser alle Daten speicher auf> die beide Fenster zugreifen müssen.
Endlich mal jemand, der das Doc/View Prinzip kapiert hat.
Genau so macht man das:
Im Document werden die Daten einer Anwendung gehalten und manipuliert.
Die Views sind dafür zuständig diese Daten anzuzeigen, manipulieren
die Daten selber aber nicht. Wohl aber können sie Befehlsempfänger
von zb einem Menü oder einem Mausklick sein, welches dann den Befehl
weiterreicht an das Document. Das Document führt die Manipulation durch
und benachricht dann wiederrum alle Views, dass sich die Daten geändert
haben.
>Es ist ok, wenn du dir den Pointer entsprechend zurechtcastest. Was>anderes macht der vom Wizzard generierte Code im anderen View auch>nicht. Sinnvollerweise wirst du dir eine GetDocument Funktion in deine>eigene Viewklasse machen, die diesen Cast verbirgt.
Danke für den Tipp.
>Endlich mal jemand, der das Doc/View Prinzip kapiert hat.
Danke das freut mich. Ich werde das also so realisieren. Wobei das zum
Teil nicht ganz einfach ist Daten nicht von der VIEW-Klasse aus zu
manipulieren. Ich habe zum Teil vor im Dokument mehrere Klasse
anzulegen, welche Daten speichert. Diese Klasse verwaltet die Daten und
von dem VIEWs ausgehend würde ich auf diese Klasse zugreifen. Aber im
Endeffekt wird die eigendliche Änderung dann von den Klassen ausgeführt.
Im Moment bekomme ich nur noch ein Problem bei einem verschatelten
inkludieren von Headern. Das kann ich aber spätestens heute Abend näher
Erleutern.
Nun möchte ich gerne das Problem mit dem verschachtelten Includieren
erklären. Vielleicht findet sich jemand der versteht was dort vorgeht.
Vielleicht hilft es mir auch schon das hier ausführlich zu schildern.
Es existiert die Klasse Projekt1Doc
Projekt1Doc.h:
1
#pragma once
2
#include"Daten_abrufen.h"
3
#include"Mycug.h"
4
#include"CGraph.h"
Projekt1Doc.cpp:
1
#pragma once
2
// Projekt1Doc.cpp: Implementierung der Klasse CProjekt1Doc
3
//
4
5
#include"stdafx.h"
6
#include"Daten_abrufen.h"
7
// SHARED_HANDLERS können in einem ATL-Projekt definiert werden, in dem Vorschau-, Miniaturansichts-
8
// und Suchfilterhandler implementiert werden, und die Freigabe von Dokumentcode für das Projekt wird ermöglicht.
9
#ifndef SHARED_HANDLERS
10
#include"Projekt1.h"
11
#endif
12
13
#include"Projekt1Doc.h"
14
15
#include<propkey.h>
16
17
#ifdef _DEBUG
18
#define new DEBUG_NEW
19
#endif
Wie zu sehen ist werden in dem Header "Projekt1Doc.h" 3 Header
eingefügt. Hierbei handelt es sich um Klassen welche in der Headerdatei
statisch erstellt werden.
1
MyCugm_grid;
2
CDaten_abrufenDaten_abrufen;
3
CGraphGraph;
Das Problem ist, dass sobald ich in dem Header "CGraph.h" den Header
"Projekt1Doc.h" inkludiere bekomme ich 10 Fehlermeldungen (siehe
Anhang).
Diese beziehen sich auf die letzte Zeile des Programmfragments.
A. R. schrieb:> Das Problem ist, dass sobald ich in dem Header "CGraph.h" den Header> "Projekt1Doc.h" inkludiere bekomme ich 10 Fehlermeldungen (siehe> Anhang).
Ist das jetzt ein Tippfehler?
Im Allgemeinen ist es nicht sinnvoll bzw. weißt des öfteren auf einen
Fehler hin, wenn du einen Zirkelinclude baust.
a.h
1
***Filea.h
2
#include"b.h"
3
4
....
und
b.h
1
***Fileb.h
2
#include"a.h"
3
4
....
braucht deine Graph Klasse wirklich Zugang zum Dokument? Warum braucht
sie das? Du bindest damit die Graph Klasse an diesen einen speziellen
Dokument Typ. Auf der anderen Seite ist Graph ein so schön allgemeiner
Name, das ich mir nicht vorstellen kann, dass der an ein bestimmtes
Dokument gekoppelt sein sollte.
Man kann allerdings so einen Zirkelinclude mit einer Forward-Deklaration
durchbrechen. Eine Forward-Deklaration sagt nur aus: Es gibt eine Klasse
mit einem bestimmten Namen, sagt aber nichts darüber aus, wie die Klasse
aussieht (welche Member sie hat, etc). Das macht aber nichts, zur
Deklaration eines Pointers reicht es aus, wenn der Compiler weiß, dass
es den Klassennamen auch wirklich gibt. Wie groß ein Pointer darauf sein
muss, weiß er ohnehin.
a.h
1
***Filea.h
2
#include"b.h"
3
4
classMyClassA
5
{
6
public:
7
voidfoo(MyClassB*pPointerToB);
8
};
und
b.h
1
***Fileb.h
2
3
classMyClassA;// es gibt eine Klasse namens MyClassA
4
5
classMyClassB
6
{
7
public:
8
voidfoo(MyClassA*pPointerToA);
9
};
Ich würd mir aber trotzdem besser überlegen, ob und wozu ein Graph
Objekt Kentniss vom Document haben soll.
A. R. schrieb:> Teil nicht ganz einfach ist Daten nicht von der VIEW-Klasse aus zu> manipulieren.
Kommt drauf an, was du exakt unter "manipulieren" verstehst. Natürlich
darf ein View sein Document befragen um zb einen Mausklick zuordnen zu
können.
Es spricht auch nichts dagegen, wenn der View einen Mausklick erst mal
in Dokument-gerechte Koordinaten umrechnet und dann dem Document
aufträgt, zum Datenwert, der zu diesen Koordinaten gehört, 100
dazuzuzählen.
Es kann auch sein, dass das Dokument für jeden Datenpunkt eine
Identifikation bereitstellt (eindeutige Nummer), die der View irgendwie
benutzt um damit einen Mausklick wieder dem Datenpunkt zuordnen zu
können.
Oder ....
Die Möglichkeiten sind da vielfältig. Wo ein Wille da auch ein Weg. Aber
im Prinzip läuft es immer aufs gleiche raus: Der View identifiziert
welchen Daten ein Mausklick entspricht und fordert das Document auf, den
Datensatz mit dieser Identifikation dieser und jener Manipulation zu
unterziehen.
> anzulegen, welche Daten speichert. Diese Klasse verwaltet die Daten und> von dem VIEWs ausgehend würde ich auf diese Klasse zugreifen. Aber im> Endeffekt wird die eigendliche Änderung dann von den Klassen ausgeführt.
Ja, ist doch super.
Der View fragt in seiner OnPaint Methode das Document nach den Klassen
und benutzt die Werte, die er von denen bekommt um die Anzeige zu
erzeugen.
Erst einmal vielen vielen Dank für die Mühe,
aber ich befürchte, dass ich jetzt ein paar etwas dumme Fragen stelle.
Also ich hoffe, dass du die Geduld mit mir nicht verlierst.
Ich verwende z.B. die Bilbiothek Ultimate Grid.
http://www.codeproject.com/KB/MFC/UltimateGrid_FAQ.aspx
Dies ist ein Art kleine Datenbank mit Oberfläche ähnlich wie in Excel.
Man kann einzelnen Zeilen und Spalten Werte zuweisen und auch wieder
abfragen, Spalten und Zeilen verschieben, austauschen ...
Die Klasse erstelle ich im Dokument.
1
MyCugm_grid;
Später führe ich dann in einer VIEW-Klasse im OnCreate folgenden
Programmcode aus.
Dadurch, dass ich der Klasse MyCug den this Pointer des VIEWs übergeben
habe ist diese Klasse in der Lage in meinem VIEW zu zeichnen. Dies wird
auch getan ohne das ich in der OnDraw Methode eine Methode der Klasse
aufrufen muss. Die Klasse ist auch in der Lage Tastendrücke sowie
Aktionen mit der Maus zu erkennen, so dass in dieser Klasse Funktionen
ergänzt werden könne die auf Perepherie reagieren.
Gleichzeitig werden in der Klasse aber auch alle Daten wie Zeilenbreite,
Zeilenhöhe, Inhalt der Felder etc. gespeichert.
Das bedeutet doch quasi, dass diese Bibliothek das View/Doc Prinzip
verletzt!? (Wie das mit der Abfrage der Perepherie und dem fehlenden
OnDraw Aufruf funktioniert weiß ich noch nicht würde ich aber später
gerne durch reverse engineering herausbekomme)
Ich wollte diese Bibliotheksfunktion in so weit Verwenden, dass ich
später auch die Daten in der Bibiothek abfrage (Daten sind nicht im
Dokument).
Sprich über die Symbolleiste wird eine Funktion aufgerufen die
Informationen über mehrere Dateien auf der Festplatte sammelt und in die
Datenbank schreibt. Auf diese Daten soll später zugegriffen werden
(Daten sind nicht im Dokument sondern in einer Klasse, die wiederum im
Dokument ist aber auch das Zeichen übernimmt).
So ähnlich wollte ich auch meine Klasse CGraph aufbauen. Also der Klasse
CGraph hätte ich den Pointer auf eine Viewobjekt übergeben, sodass die
Klasse CGraph in dem View-Fenster zeichnen kann. Gleichzeitig hätte ich
aber auch die zu zeichnenden Daten an die Klasse CGraph übergeben
(CGraph verfügt später über eine Kettenliste mit der sich weitere
Funktionsverläufe ergänzen lassen). Dies hätte den Vorteil, dass ich die
Klasse CGraph später auch für andere Projekte verwenden kann.
Ich hoffe das war einigermaßen verständlich formulier. Manchmal reicht
schon eine unterschiedliche Wortwahl um aneinander vorbeizuredn.
Dieser Aufbau bereitet mir auch das Problem mit dem verschachtelten
Inkludieren. Die Klasse CGraph wird im Dokument erstellt soll aber
später auch über das Dokument in der Lage sein auf die Datenbank
zuzugreifen. Also muss das Dokument die Klasse kennen um diese erstellen
zu können und gleichzeitig muss die Klasse das Dokument kennen um auf
anderen Klassen in diesem zugreifen zu können.
Das Problem könnte man wahrscheinlich mit einer Forward-Deklaration
durchbrechen, da das Dokument vorerst nicht wissen muss welche Methoden
und Attribute die Klasse CGraph hat.
Das Nachvollziehen solcher Gedankengänge ist sehr mühsam. Deshalb diesen
Tipp: Stell dir einfach vor du hast zur gleichen Zeit zwei identische
Views offen, die mit dem einen Dokument arbeiten. Z.B. durch ein
CSplitterWnd oder CTabCtrl realisiert. Auch in dieser Konfiguration muss
das funktionieren.
Das Grid deiner Applikation erscheint mir unter dieser Betrachtung mehr
als ein visuelles Element, gehört also ins View, nicht ins Dokument. Ins
Dokument gehört ein mehrdimensionales Daten-Array, welches nach jeder
Änderung im Grid aktualisiert wird.
MFC1 schrieb:> Das Nachvollziehen solcher Gedankengänge ist sehr mühsam. Deshalb diesen> Tipp: Stell dir einfach vor du hast zur gleichen Zeit zwei identische> Views offen, die mit dem einen Dokument arbeiten. Z.B. durch ein> CSplitterWnd oder CTabCtrl realisiert. Auch in dieser Konfiguration muss> das funktionieren.>> Das Grid deiner Applikation erscheint mir unter dieser Betrachtung mehr> als ein visuelles Element, gehört also ins View, nicht ins Dokument. Ins> Dokument gehört ein mehrdimensionales Daten-Array, welches nach jeder> Änderung im Grid aktualisiert wird.
So würde ich das Prinzipiell auch sehen.
Glücklicherweise haben die Designer mitgedacht und das ganze dort
2-geteilt: Das eine ist der Grid, der die Anzeige und das Handling macht
(also im Prinzip der View) und das andere ist die Datasource, die die
Daten hält. Und schon sind wir wieder bei Doc/View
Ich stell mir bei solchen Dingen meistens einfach vor
* ein View zeigt die Daten grafisch an
Balkendiagramm, Tortendiagramm, wie auch immer
* der andere in Tabellenform
> So ähnlich wollte ich auch meine Klasse CGraph aufbauen. Also der> Klasse CGraph hätte ich den Pointer auf eine Viewobjekt übergeben,> sodass die Klasse CGraph in dem View-Fenster zeichnen kann.
Kann man so machen.
Aber: entscheidet wirklich die CGraph Klasse (die, wenn ich das richtig
verstanden habe, die Daten hält), WIE die Daten darzustellen sind.
Oder ist es nicht eher so, dass der View eigentlich wissen sollte wie er
die Daten darstellen soll? Er holt sich vom Dokument die Daten in Form
eines Zugangs durch ein CGraph Objekt und holt sich dann aus diesem die
Werte, die er für eine Darstellung benötigt.
Diese Grid Klasse ist aber schon recht heftig (wie die meisten
derartiger Grids) und ich bin mir nicht sicher, ob das für einen MFC
Neuling, der sich das erste mal an MFC wagt, überhaupt sinnvoll ist.
Wenn ich dein Lehrer wäre, würde ich dir einen CFormView unterjubeln, wo
du das GUI mit 2 Buttons (Add, Delete), ein paar Edit-Boxen und einem
CListView Control realisieren sollst.
Also Vorübung das ganze mal 2-geteilt
der View zeigt das ganze grafisch an und zum Ändern der Daten klickt
der Benutzer in der Toolbar auf einen Button (oder wählt einen Menü-
eintrag) woraufhin sich ein Dialog öffnet, der mit den genannten
Elementen die Eingabe und Manipulation der Daten erlaubt. (Denn dann
hat man die wichtigsten Dinge in einer App: Doc/View, Einstelldialoge,
Toolbars + Menüs)
Dann machen wir erst mal unsere Hausaufgaben
* Datei speichern
* Datei speichern unter
* Datei lesen
* Document modified Flag
* wie geht das, dass sich Controls in einem Dialog gegenseitig
sperren oder freigeben, je nach Benutzerinteraktion und Daten-
situation
* App soll sich ihre letzte Einstellung/Größe/Position merken: Registry
* Daten/Bild in die Zwischenablage
* Daten/Bild drucken
* ...
und dann geht es los sich damit zu beschäftigen, wie man den Dialog los
werden kann. Ein 2-ter View ist nur eine Möglichkeit. Denkbar ist auch,
dass der View seinen Anzeigemodus umschalten kann, oder die
Änderungselemente in ihn integriert werden, oder er gesplittet wird oder
für die Anzeige ein eigenes Zeichen-Control implementiert wird, welches
auf den FormView geschoben wird, ...
Da gibts viele Varianten. Aber sich gleich mal einem fremden Control
auszuliefern, find ich nicht gut im Hinblick auf das Lernen wie man mit
der MFC arbeitet bzw. wie Document/View funktioniert. Woher weißt du
denn, wonach du (zb zur Beurteilung ob du es überhaupt verwenden willst)
in der Doku suchen sollst, wenn du nicht weißt, wie typische Dinge in
der MFC (zb Drucken, Daten speichern) funktionieren und MFC konform
angebunden werden.
Du merkst schon: Ich bin ein Verfechter der Schule "Wer
CNC-Programmierer werden will, muss trotzdem lernen mit einer Feile
umzugehen"
Danke für die Hilfe,
nachdem ich mir die halbe Woche noch sehr viele Gedanken über die
Strukturierung gemacht habe bin ich zu dem Entschluss gekommen, dass ich
meine Klasse zum Zeichnen des Charts in 2 oder 3 Klassen aufgliedere.
Eine Klasse wird die ganzen Daten halten und sich im Dokument befinden.
Die andere Klasse erstellt die gesamte Visuallisierung und befindet sich
im View. Dies ermöglicht es theoretisch, dass ich der Klasse im View
einen anderen Objektpointer aus dem Dokument übergebe und das View dann
andere Daten visualisieren kann bzw. ander Optionen erhält. Dies ist
zwar nich notwendig, währe aber theoretisch möglich.
>Glücklicherweise haben die Designer mitgedacht und das ganze dort>2-geteilt: Das eine ist der Grid, der die Anzeige und das Handling macht>(also im Prinzip der View) und das andere ist die Datasource, die die>Daten hält. Und schon sind wir wieder bei Doc/View
Das kann durchaus sein, dass eine solche Unterteilung intern gemacht
worden ist. Aber nach außen hin wird nur eine Klasse eingefügt. Diese
soll sich laut Anleitung im VIEW befinden. Dies macht auch Sinn, solange
die Klasse nur zum Anzeigen von Daten verwendet wird. Jedoch ermöglicht
die Klasse es auch Daten über das VIEW zu ändern. Diese Änderung der
Daten bezieht sich dann jedoch auf die Daten, welche in der Ultimate
Grid Klasse gespeichert sind. Wenn ich das Doc/View Konzept verstehe
dürften nur Daten verändert werden die sich im Dokument befinden. Da
dies nicht eingehalten wird kann eine andere VIEW Klasse nicht über das
Dokument auf die geänderten Daten zugreifen. Es muss ein Zugriff auf die
VIEW Klasse mit dem Ultimate Grid OBjekt erfolgen.
Dies lässt sich dann so umgehen, dass man den Dokument den Pointer zu
dem Ultimate Grid Objekt übergibt.
>Diese Grid Klasse ist aber schon recht heftig (wie die meisten>derartiger Grids) und ich bin mir nicht sicher, ob das für einen MFC>Neuling, der sich das erste mal an MFC wagt, überhaupt sinnvoll ist.
Das ganze ist sehr gut dokumentiert. Also bis jetzt bin ich ganz gut
zurecht gekommen. Also das Einfügen von Zeilen, Spalten, Formatierung
der Felder (Text und Hintergrundfarbe sowie Schriftart...) klappt schon.
Habe auch nicht erst gestern damit angefangen sondern eher November
2011. Ich gehe auch davon aus, dass ich später den Code noch abändern
muss, da ich jetzt noch nicht überschauen kann welcher Ansatz am besten
ist. Gerade wegen fehlenden Kenntnissen.
>Du merkst schon: Ich bin ein Verfechter der Schule "Wer>CNC-Programmierer werden will, muss trotzdem lernen mit einer Feile>umzugehen"
Macht auch durchaus Sinn! Ich habe auch zuerst angefangen mehrere
Tutorials durchzuarbeiten. Das Problem ist nur, dass das zum Teil sehr
anstrengen ist wenn keiner da ist den man Fragen kann. Viele Tutorials
haben auch nur sehr simple gehalten Beispiele und lassen sehr viele
Fragen offen.
>Dann machen wir erst mal unsere Hausaufgaben>* Datei speichern>* Datei speichern unter>* Datei lesen>* Document modified Flag>* wie geht das, dass sich Controls in einem Dialog gegenseitig> sperren oder freigeben, je nach Benutzerinteraktion und Daten-> situation>* App soll sich ihre letzte Einstellung/Größe/Position merken: Registry>* Daten/Bild in die Zwischenablage>* Daten/Bild drucken>* ...
Das ist eine sehr schöne Auflistung.