Forum: PC-Programmierung Visual C++ MDI Probleme


von Sebastian (Gast)


Lesenswert?

So hab mal wieder ein Problemchen mit Visual C++.
Ich hab ein MDI Projekt mit verschiedenen Documenten. Jetzt möchte ich 
einen ModalenDialog über das Menü aufrufen. Dieser verfügt über mehrere 
ComboBoxen, die vorinitialisiert werden. Werden diese Werte geändert und 
auf "OK" geklickt, so speichert er die neuen Werte und bei einem 
erneuten Aufruf werden die geänderten Werte angezeigt.
Ich dachte ich deklarier mir eine globale Instanz dieses Dialogs in der 
vom Assistent erstellen "TestApp.h" und definier ihn dann in der 
"InitInstance()". Jetzt werden in der "InitInstance()" die Comboboxen 
vorinitialisiert.

Dem Menü wurde ein Eintrag zum Aufruf des Dialogs hinzugefügt. Die 
zugehörige Funktion ist in der MainFrame-Klasse:

void CMainFrame::OnTest()
{
  testDlg.DoModal();
}

Für die Comboboxen stehen jeweils zwei Membervariablen zur Verfügung, 
eine Integer und eine Control. Die Integer dient lediglich zur 
Zwischenspeicherung der Werte und werden in der InitInstance gesetzt. 
Der Dialog macht bisher nur folgendes:


BOOL CTestDlg::OnInitDialog()
{
  CDialog::OnInitDialog();
  m_usb_port.SetCurSel(m_iPort);

  return TRUE;
}

void CTestDlg::OnOK()
{
  m_iPort = m_usb_port.GetCurSel();
  CDialog::OnOK();
}

Kompilieren funktioniert soweit auch, leider macht der Linker mir 
Probleme. Bei testDlg.DoModal() meckert er rum.

"Nichtaufgeloestes externes Symbol "class CTestDlg testDlg"


Hoffe ich hab das meiste wichtige gesagt, sonst einfach noch nachfragen.

Falls mir jmd weiterhelfen könnte wäre ich dankbar.

von Karl H. (kbuchegg)


Lesenswert?

Sebastian wrote:

> Falls mir jmd weiterhelfen könnte wäre ich dankbar.

Ein nichtmodaler Dialog wird völlig anders erzeugt und angezeigt.
Ich stell dir was zusammen.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Ist die Datei, in der CTestDlg implementiert ist, überhaupt zum Projekt 
hinzugefügt?

Üblicherweise müsste das ja testdlg.cpp und testdlg.h sein - letztere 
bindest Du wohl auch korrekt ein, da sonst der Compiler motzen würde.
Aber ist testdlg.cpp auch Bestandteil des Projekts?

Das Fehlen würde nämlich die von Dir erwähnte Linkerfehlermeldung 
erklären.

von Sebastian (Gast)


Lesenswert?

Die Datei wurde mit dem Klassenassistenten erzeugt und ist auch im 
Projekt vorhanden. Das Problem ist, das ich in der MainFrame-Klasse 
nicht auf testDlg zugreifen kann. In der TestApp geht dies ja, also 
vorhanden ist die Klasse und auch die Instanz davon.
Irgendwie klappt das mit der globalen Deklaration nicht.

Die "InitInstance" der CTestApp wird doch VOR der "OnCreate" der 
CMainFrame aufgerufen oder?


@Karl-Heinz

Der Dialog soll ja Modal sein.

Das Problem liegt glaub ich nur wo bzw. wie ich die Instanz des testDlg 
global Verfügbar mache.

von Sebastian (Gast)


Lesenswert?

Deklaration:

TestApp.h
extern CtestDlg testDlg;



Definition:

TestApp.cpp

BOOL CTestApp::InitInstance()
{
  CtestDlg testDlg;
.
.
.
.

}

so mach ich das mit der Deklaration und der Definition. Dann muss ich 
doch eigentlcih nur die "TestApp.h" in meine MainFrame-Klasse includen 
und müsste dann auf den testdlg zugreifen können oder?

von Karl H. (kbuchegg)


Lesenswert?

Sebastian wrote:

> @Karl-Heinz
> Der Dialog soll ja Modal sein.

Ooops. Da hab ich was falsch gelesen.

von Karl H. (kbuchegg)


Lesenswert?

Sebastian wrote:
> Deklaration:
>
> TestApp.h
> extern CtestDlg testDlg;
>
>
>
> Definition:
>
> TestApp.cpp
>
> BOOL CTestApp::InitInstance()
> {
>   CtestDlg testDlg;
> .
> .
> .
> .
>
> }
>
> so mach ich das mit der Deklaration und der Definition. Dann muss ich
> doch eigentlcih nur die "TestApp.h" in meine MainFrame-Klasse includen
> und müsste dann auf den testdlg zugreifen können oder?

Äh. Nein.
Globale Variablen funktionieren völlig anders. Was du da hast ist keine
globale Variable sondern eine stinknormale funktionslokale Variable,
die beim Beenden der Funktion wieder verschwindet.

Und du solltest dir besser 2mal überlegen, ob du zu globalen
Variablen greifen willst. In dem Fall macht das zb. überhaupt
keinen Sinn. Erzeuge den Dialog dort wo du ihn brauchst, rufe
DoModal() darauf auf und lass das Dialog Objekt wieder ins
Nirwana verschwinden, wenn es der Aufrufer nicht mehr benötigt.
1
#include "testdlg.h"
2
3
...
4
5
void CIrgendeineKlasse::Machwas()
6
{
7
  CtestDlg dlg( AFxGetMainWnd() );
8
9
  if( dlg.DoModal() == IDOK ) {
10
    // mach was
11
  }
12
}
13
14
void CIrgendeineAndereKlasse::Machwas()
15
{
16
  CtestDlg dlg( AFxGetMainWnd() );
17
18
  if( dlg.DoModal() == IDOK ) {
19
    // mach was
20
  }
21
}

Allenfalls, wenn der Dialog von extern initialisiert werden soll,
und diese Initialisierung immer gleich, zb mit Werten aus der
Applikation stattfindet und der Dialog das nicht selbst in seiner
OnInitDialog Funktion macht, könnte man noch darüber nachdenken,
den ganzen Dialog in eine Funktion zu kapseln, zb in der Applikation,
und dann nur diese Funktion aufrufen zu lassen.
1
void CTestApp::ShowDialog()
2
{
3
  CtestDlg dlg( AFxGetMainWnd() );
4
5
  // dlg initialisieren
6
7
  if( dlg.DoModal() == IDOK ) {
8
    // mach was, zb Werte aus dem dlg abholen
9
  }
10
}

1
void CIrgendeineKlasse::Machwas()
2
{
3
  ((CTestApp*)AfxGetApp())->ShowDialog();
4
}

von Sebastian (Gast)


Lesenswert?

Ok habs hinbekommen. /*freu*/
Das Problem war die Definition, welche nicht in der InitInstance() 
stattfinden sollte sondern global in der CTestApp.

von Karl H. (kbuchegg)


Lesenswert?

Du hast meine Antwort gesehen? Die haben sich zeitlich überschnitten
und zeigt dir wie du das ohne globale Variablen machen kannst
bzw. machen solltest.

von Sebastian (Gast)


Lesenswert?

OK dann probier ich dein Vorschlag mal aus.
Problem ist halt, das ich mir ja die Werte der Comboboxen speichern muss 
und wenn ich jedesmal eine neue Instanz des Dialogs erzeug werden diese 
nicht gespeichert. Also bräuchte ich noch irgendwo anderst Variablen, 
die die Werte speichern und da schien mir den kompletten Dialog global 
zu machen als einfacher.

von Karl H. (kbuchegg)


Lesenswert?

Sebastian wrote:
> OK dann probier ich dein Vorschlag mal aus.
> Problem ist halt, das ich mir ja die Werte der Comboboxen speichern muss
> und wenn ich jedesmal eine neue Instanz des Dialogs erzeug werden diese
> nicht gespeichert. Also bräuchte ich noch irgendwo anderst Variablen,
> die die Werte speichern und da schien mir den kompletten Dialog global
> zu machen als einfacher.

Du sollst einen Dialog sowieso nicht zur globalen Speicherung von
Applikationsweiten Daten verwenden!
Ein Dialog zeigt Daten an und ermöglicht das Verändern oder Auswählen.
Aber gehalten werden die Daten in den Klassen wo sie hingehören.
Normalerweise sind das:
  Die Applikation  -  für Applikationsweite Daten
  Das Dokument     -  für Daten die nur dieses eine Dokument betreffen.

Gerade in der MFC mit ihrer Document/View Architektur ist es besonders
wichtig, dass man diese Untersscheidung zwischen Datenhaltung und
Datenpräsentation strikt einhält. Alles andere führt über kurz
oder lang zu Inkonsistenzen im Programm.

von Sebastian (Gast)


Lesenswert?

Das mit der Funktion ist glaub ich ne ganz gute Lösung. Werd das mal 
schnell implementieren und mich dann nochmal melden obs tut

von Sebastian (Gast)


Lesenswert?

Also legen ich in meiner CTestApp für jeden ComboBox ne int Variable an 
und initialisier sie.


Wie greif ich nun in der CMainFrame auf diese Variablen zu?

void CMainFrame::OnTest()
{
  CTestDlg testDlg;


  if(testDlg.DoModal()==IDOK)
  {
  CTestApp::m_iPort = testDlg.m_usb_port.GetCurSel();
  }
}

stimmt das so mit den Doppelpunkten?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Das mit den Doppelpunkten funktioniert so nicht.

Üblicherweise gibt es ein globales Applikationsobjekt, das über den 
Namen theApp referenziert werden kann.

Und dann kannst Du schreiben

  theApp.m_iPort = testDlg.m_usb_port.GetCurSel();

von Sebastian (Gast)


Lesenswert?

yupp das gibt es

von Sebastian (Gast)


Lesenswert?

Das wird in der TestApp.cpp definiert. Könnte ich das evtl. auch in die 
TestApp.h reinmachen bzw. dort deklarieren.
Das Problem ist, ich müsste in der MainFrame-Klasse die TestApp.cpp 
includen damit das funkioniert. Das kann ich aber nicht ohne Weiteres 
tun, da der Linker dann wegen Neudefinitionen rummeckert.

von Karl H. (kbuchegg)


Lesenswert?

Sebastian wrote:
> Also legen ich in meiner CTestApp für jeden ComboBox ne int Variable an
> und initialisier sie.
>
>
> Wie greif ich nun in der CMainFrame auf diese Variablen zu?
>
> void CMainFrame::OnTest()
> {
>   CTestDlg testDlg;
>
>
>   if(testDlg.DoModal()==IDOK)
>   {
>   CTestApp::m_iPort = testDlg.m_usb_port.GetCurSel();
>   }
> }
>

Hier solltest du dir gleich mal eine Frage stellen.
Warum ist diese Funktion in CMainFrame, wenn alles was sie tut
eine Variable in CTestApp zu manipulieren?

Das sollte eigentlich schon Indiz genug sein, dass diese
Funktion an der falschen Stelle ist und eigentlich in CTestApp
rein muss.

Nicht immer schlägt dir der Code Wizzard die richtige Klasse
zum Einbau einer Funktionalität vor! Der liegt auch oftmals
daneben:
  CMainFrame, wenn der Menühandler eigentlich in
              die CxxxApp Klasse sollte.
  CxxxView, wenn die Funktionalität eigentlich in
            die CxxxDoc Klasse sollte.

Immer nachsehen, was der Wizard vorschlägt und überlegen, ob das
richtig ist! Wenn es um Anzeigedinge (also: Farben umstellen oder
Fenstergrößen ändern oder Hilfsanzeigen ein/aus oder Zoomen) geht,
dann sind die CMainFrame bzw. die View Klassen richtig. Wenn es um 
Datenmanipulation geht, dann sind sie es aber nicht! Dann muss das
Menükommando zu der Klasse geroutet werden, welche die Daten
verwaltet. Diese sind: die Applikation bzw. die Dokumentklassen.

von Karl H. (kbuchegg)


Lesenswert?

Sebastian wrote:
> Das wird in der TestApp.cpp definiert. Könnte ich das evtl. auch in die
> TestApp.h reinmachen bzw. dort deklarieren.

Das musst du sogar.

Also ans Ende der TestApp.h

extern CTestApp theApp;

Aber du solltest nochmal drüber nachdenken, ob nicht die
Funktionalität, die du in CMainFrame machst an der falschen Stelle
sitzt.
Siehe: Beitrag "Re: Visual C++ MDI Probleme"

Ein Dialog, der Daten in der Applikation verändert, sollte
auch von der Applikation aus angestossen werden.
1) Hast du dann dieses Problem gar nicht
2) Sitzt die Funktionalität an der richtigen Stelle
3) Brauchst du keine globale Variablen (ok. die Applikation braucht
   man an allen Ecken und Enden. Aber trotzdem: je weniger Globale
   umso besser)

> Das Problem ist, ich müsste in der MainFrame-Klasse die TestApp.cpp
> includen damit das funkioniert.

cpp Files werden niemals includet!

von Sebastian (Gast)


Lesenswert?

Das mit der falschen Stelle leuchtet mir ein, habs jetzt auch in die 
CTestApp reingemacht. Das ganze sieht prinzipiell so aus

void CKGTesterApp::OnTest()
{
  CTestDlg testDlg;

  testDlg.m_cTest.SetCurSel(theApp.m_iTest);


  if(testDlg.DoModal()==IDOK)
  {
  theApp.m_iTest = testDlg.m_cTest.GetCurSel();

  }
}

jetzt bringt er aber ein Assertion Debug Failed für die Aufrufe 
"SetCurSel" und "GetCurSel", da m_hWnd 0x000000 enthält.

Umgehen könnte man dies mit einer einfachen IF-Abfrage, z.B.

if(IsWindow(testDlg.m_cTest.m_hWnd))
  {
    testDlg.m_cTest.SetCurSel(theApp.m_iTest);
  }

Aber er soll den Aufruf ja machen und nicht einfach bleiben lassen.
Warum steht in m_cTest.m_hWnd kein gültiger Wert drin? Der Dialog wurde 
doch definiert.

Noch ne kleine allgemeine Frage: Bei Membervariablen steht ja normal am 
Anfang ein "m_" gefolgt von einem Buchstaben für den Typ ( zB i für 
int). Wenn ich nun zB eine Membervariable vom Typ CComboBox mache, gibt 
es dafür auch "Regeln" was da für ein Buchstabe für den Typ hinkommt?

von Sebastian U. (sulmer)


Lesenswert?

Ok eigetnlcih logisch, der Dialog exisitiert aber meine ComboBoxen nicht 
oder?

Also die Aufrufe doch wieder in die Dialog-Klasse zu den jeweiligen 
Buttons packen und theApp in der CTestApp.h global deklarieren. Wäre 
jetzt mal eine Möglichkeit.

von Sebastian U. (sulmer)


Lesenswert?

Oh man, vielen Dank für eure Geduld.
Wenn man sonst nur C programmiert und jetzt schnell ne Oberfläche in 
Visual C++ erstellen soll, dann komm man schnell an seine Grenzen. 
Vorallem bei dem Zeug was der Assistent alles erstellt.

von Karl H. (kbuchegg)


Lesenswert?

Sebastian Ulmer wrote:
> Ok eigetnlcih logisch, der Dialog exisitiert aber meine ComboBoxen nicht
> oder?

Genau
An die Controls gehst du das erste mal in der OnInitDialog
Funktion ran. Erst dort können die Controls das erste mal
bearbeitet werden.

Abgeholt wird der aktuelle Control Stand wieder in der OnOK
des Dialogs.

> Also die Aufrufe doch wieder in die Dialog-Klasse zu den jeweiligen
> Buttons packen und theApp in der CTestApp.h global deklarieren. Wäre
> jetzt mal eine Möglichkeit.

Wäre ne Möglichkeit.
Ich machs meistens so, dass der Dialog ein paar public Variablen
bereitstellt, in die der Aufrufer seine Initialisierungen reinschreibt
und von wo er sich das Ergebnis wieder abholen kann.
Bei Editboxen, Checkboxen und dergleichen erzeugt der Wizard das
schon so und bei Comboboxen leg ich mir halt noch schnell ein
paar zusätzliche Variablen an

Ich hab aber auch Dialoge, die über die globale theApp (bzw.
AfxGetApp()) auf die Applikation zugreifen und sich die Werte
direkt von dort holen, bzw. dort wieder zurückschreiben.
Bei Applikationsweiten Einstellungen ist das ok. Wenn der Dialog
aber sich an den Daten eines Dokuments zu schaffen macht, dann kriegt
er seine Werte immer über besagte public-Variablen des Dialogs.

von Sebastian U. (sulmer)


Lesenswert?

Naja so wie es jetzt grad ist läufts.
Und wie sagt man so schön "Never change a running system"
also lass ichs mal so^^

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.