[MFC]FormView über Button in MDI-Applikation öffnen?

alphacast

Ensign
Registriert
Apr. 2004
Beiträge
250
Hallo,

ich suche schon ne Weile und konnte bisher nicht viel finden. Ich habe unter Visual Studio .NET eine vom Assistenten generierte MFC-Anwendung (MDI), deren View-Klasse von CView abgeleitet wurde.

Wenn ich jetzt in der Standardsymbolleiste einen Button platziere, eine neue Klasse, die von CFormView abgeleitet wurde samt passender Dialog-Ressource erstellt habe ... wie kann ich es nun schaffen, dass ein Klick auf diesen Button ein Child-Window mit dieser Form-View öffnet?

Danke sehr ... :)
 
Hallo alphacast,

ein bisschen Code:

Code:
	// Das steht in InitInstance
	mpDocTemplate = new CMultiDocTemplate(IDR_MAINFRAME,
	RUNTIME_CLASS(CMyDoc),
	RUNTIME_CLASS(CChildFrame), // Benutzerspezifischer MDI-Child-Rahmen
	RUNTIME_CLASS(CMyView));

	// Daraus eine Methode machen die vom Button aus aufgerufen wird
	CDocument* lpDoc = 0;

	lpDoc = mpDocTemplate->CreateNewDocument();
	if( lpDoc )
	{
		CFrameWnd* lpFrame = mpDocTemplate->CreateNewFrame(lpDoc, 0 );
		if( lpFrame )
		{
			mpDocTemplate->InitialUpdateFrame(lpFrame,lpDoc,TRUE);
		}
	}

Wenn Du nur ein DocTemplate in Deiner MDI Anwendung hast reicht es auch aus ein ID_FILE_NEW an das MainFrm fenster zu schicken:

Code:
	AfxGetMainWnd()->PostMessage( WM_COMMAND, MAKELONG(ID_FILE_NEW,0),0);


MfG

Arnd
 
Zuletzt bearbeitet:
Danke, das löst wohl den großen Teil! :)

Ich habe aber gerade ein Problem, welches unter VS 6.0 nie auftrat, weil es so schön einfach war.

VS 6.0:
- Ich erweitere die Standard-Toolbar um einen Button und vergebe eine ID, nennen wir ihn ID_KNUT.

- Im Klassenassistenten/Nachrichtenzuordnungstabelle wähle ich meinetwegen die DOC-Klasse, dann ID_KNUT, COMMAND, Funktion hinzufügen ... fertig: Button ist behandelt und kann nun "theoretisch" benutzt werden.

VS .NET:
- Ich erweitere die Standard-Toolbar um einen Button und ... blabla wie oben eben ...

- Da es keinen Klassenassistenten mehr gibt, gehe ich zur Klassenansicht, markiere die DOC-Klasse (oder MainFrm oder ...) und möchte nun unten gerne ein Ereignis hinzufügen (Blitz-Symbol). Jedoch ist in dieser Liste nicht mein ID_KNUT drin; im String-Table ist er aber wohl. Und bei manueller Eintragung in die MessageMap der DOC-Klasse und Deklaration der OnHugo() geht es auch. Aber warum zum Geier kann ich das nicht automatisch machen wie unter 6.0. Wo ist mein Fehler? :freak:

Dank dem der denkt! :)
 
Hallo alphacast,

ich habe dein Problem jetzt nicht nachvollzogen. Bisher hat das bei mir aber funktioniert. Mein Eindruck vom VS.Net 2003 ist aber ebenfalls sehr gemischt.

Die Oberfläche hat einige gravierende Bugs und die Chance das diese behoben werden sind wahrscheinlich sehr gering. Schliesslich gibt es bisher kein Servicepack und bald kommt die neue Version raus.

Probier doch mal ob der Eintrag auftaucht, wenn er auch in einem Menü vorhanden ist.

MfG

Arnd
 
Hi,

na ganz einfach... ich möchte die normale Toolbar (da wo "Neu", "Öffnen", usw. drin ist) um einen Button erweitern. Dafür geht man in die Ressourcenansicht und malt ihn sich z.B. Nun war es doch unter Visual C++ 6.0 so, dass man ihn dadurch anklickbar machte, dass im Klassenassistenten unter der Nachrichtenzuordnungstabelle einfach eine Klasse ausgewählt wurde (z.B. die Doc) und dann hierfür über die Objekt-ID des neuen Buttons eine Message-Handling-Funktion OnNeuerButton() eingefügt wurde. Kompiliert man jetzt ist der Button anklickbar und nicht mehr ausgegraut, als wenn man keine Behandlungsfunktion erstellt.

Visual Studio .NET 2003 hat ja keinen Klassenassistenten mehr, sondern löst das alles ein bisschen anders. Ich habe also meinen Button gemalt (Ressourcenansicht, Symbolleisteneditor). Führt man jetzt aus, ist dieser natürlich ausgegraut. Also Behandlungsroutine hinzufügen, gut aber wie?

Klassenansicht, Messages im Eigenschaftsdialog auswählen, ID des Buttons auswählen und dann die Funktion erstellen. So sollte es doch gehen, tut es aber nicht, da die ID des Buttons für keine Klasse sichtbar wird, nur die allgemeinen wie ID_FILE_OPEN usw. Trage ich Command(ID_NEUERBUTTON, OnNeuerButton) aber manuell in die Message-Map eines Dokuments ein, so wird der Button aktiv.

Kurz: Wie füge ich eine Behandlungsfunktion für den neuen Button der Symbolleiste ein, wenn es nicht geht so wie ich es versucht hab?

DAAANKE!

P.S.: An einen Bug hab ich auch schon gedacht, aber meistens macht man ja doch eher sich selbst dafür verantwortlich.

EDIT: Hattest recht, es ist wohl doch ein Bug! Lege ich einen Menüeintrag mit der gleichen ID an, kann ich sie plötzlich wählen und der Button wird auch aktiv. Da die Handler erhalten bleiben, wenn ich den Eintrag wieder entferne lässt sich das Problem also umgehen. Sowas, gibts dafür nen Fix?

Was neues: Wie mache ich es, dass beim Start kein neues Dokument aufgerufen wird (Tipp reicht)?
 
Zuletzt bearbeitet:
Hallo Alphacast,

Du wolltest nur einen Tip? Dann debug mal diese Zeilen:

Code:
	CCommandLineInfo cmdInfo;
	ParseCommandLine(cmdInfo);
	if (!ProcessShellCommand(cmdInfo))
		return FALSE;

Für den "Bug" wird die Lösung wohl VS 2005 heissen, sofern es da funktioniert. Was auch lustig ist, bzw mich schon zur Verzweiflung getrieben hat ist die Verwaltung des Workspaces. Wenn man mehrere MFC Anwendungen in einer Projektmappe hat, so existieren zwangsweise auch Dateien (z.B. MainFrm.cpp) doppelt. Eben in verschiedenen Verzechnissen. Fügt man einen Ereignishandler dann zu einem CMainFrame hinzu, so landet der Code in einem Projekt und die Deklaration in einem anderen.

Ich kann z.B. auch keine .h Datei zu einem Projekt hinzufügen, beim abspeichern der Datei gibt es einen Fehler. Mit .cpp Dateien geht es. Usw ....

MfG

Arnd
 
Zuletzt bearbeitet:
Danke, hatte ich auch schon rausgefunden! :)

Wenn ich von der Doc-Klasse auf die Textfelder der FormView zugreifen möchte, kann ich dann wie in Java einfach ... textfeld1.setText("LaLiLu"); aufrufen oder geht das unter MFC anders?
 
Hallo aplphacast,

nein das ist nicht so ganz einfach. Schliesslich gibt es nur ein Doc Objekt aber eventuell mehrere View Objekte. D.h. zuerst musst Du mal den View finden (GetFirstViewPosition, GetNextViewPosition) und dann kannst Du darin ein Control mit Inhalt befüllen.

Daher ist es nicht immer sinnvoll den Code in das Doc zu stecken. Das Document beinhaltet eigentlich nur die Daten, die Steuerung der Anwendung würde ich im View erledigen.

Auch die Ereignishandler sollten im View sein. Du machst das glaube ich anders, wenn ich Deine bisherigen Posts richtig verstanden habe.

Wenn Du aber den View hast ist es einfach. Nur zusätzlich ist es sehr sinnvoll zu prüfen ob das Objekt auch "lebt". D.h. folgender Code sollte benutzt werden um ein Editcontrol zu füllen:

Code:
if( IsWindow( mEditControl.m_hWnd ) )
{
  mEditControl.SetWindowText( "Hallo Welt" );
}

MfG

Arnd
 
Ahso, DANK!

Richtig, ich habe eine Anwendung mit drei FormViews und drei Docs. Ich hab ja im Ressourceneditor IDs für alle Controls vergeben, für ein Textfeld z.B. "IDC_CAM".

Wenn ich nun per

CView* vw = GetNextView(GetFirstViewPosition());

die (Form)View hole, vorher bei der View für meine Textfeld-ID eine Steuerelementvariable deklariert habe, warum kann ich aus der Doc dann icht per

vw->m_CAM.SetWindowText("BLA");

zugreifen (kein Zugriff auf m_CAM aus der DOC, ist aber public)?
 
Zuletzt bearbeitet:
Hallo alphacast,

ich mache das so:

Code:
	POSITION posView = GetFirstViewPosition();
	while( posView )
	{
		CView* lpView = GetNextView( posView );
		if( lpView )
			lpView->SendMessage( ... );
	}

Dein Code hat bei mir nicht kompiliert. Wenn der Zugriff nicht geht, welche Fehlermeldung kommt denn? Du vergisst das das keine 1:1 Zuordnung ist. Du kannst zu EINEM Doc auch mehrere Formviews haben.

MfG

Arnd
 
Das Projekt liegt auf Arbeit ... aber ich glaube "ID_CAM ist kein Element von CAM_FormView" oder so ähnlich. Also als wenn es nicht vorhanden wäre.... ist aber public in der View.

Muss man das in dieses SendMessage verpacken?
 
Dann wird in der Zuordnung von Ressource und Variable etwas schiefgelaufen sein. Prüfe mal ob die Variable auch der ID zugeordnet ist und auch die Variable in der Headerdatei des View existiert.

Das SendMessage war nur ein Beispiel. Du kannst auch eine Methode des View direkt aufrufen.

MfG

Arnd
 
Eigentlich nicht ...

Header:
public: CEdit m_CAM;

CPP:
void CAM_FormView:: DoDataExchange(CDataExchange* pDX)
{
CFormView:: DoDataExchange(pDX);

DDX_Control(pDX, IDC_CAM, m_CAM);
}

Innerhalb der FormView kann ich auf die Member auch normal zugreifen. Wenn ich es aber aus der Doc so mache:

POSITION pos = GetFirstViewPosition();
CView* vw = GetNextView(pos);
vw->m_CAM.SetWindowText("BLA");

dann bekomme ich folgende Fehler:
error C2039: 'm_CAM': Ist kein Element von 'CView'
error C2228: Der linke Teil von '.SetWindowTextA' muss eine Klasse/Struktur/Union sein

Danke...
 
Ok,

Du musst auf die Klasse Deiner Formview casten. In CView ist m_CAM sicher nicht enthalten.

Code:
POSITION pos = GetFirstViewPosition();
CAM_FormView* vw = (CAM_FormView*) GetNextView(pos);
vw->m_CAM.SetWindowText("BLA");

Wenn Dir das casten nicht gefällt, benutze SendMessage oder noch besser PostMessage um eine Nachricht an den View zu schicken und setze in dem entsprechenden Ereignishandler die Variable.

MfG

Arnd
 
Zuletzt bearbeitet:
Mmh :freak:, ja na klar ... danke! Jetzt gehts auch.

Nächstes ist jetzt Timer und zusätzliche Threads einzubauen... mal sehen! :)
 
Viel Spass.

Lustig wird bei den Threads vor allem das Progamm zu beenden :-).

Gerade bei Threads ist es wichtig keine direkten Zugriffe auf Controls zu machen und auch abzufragen ob diese noch "leben" (IsWindow benutzen!).

Dann kommt z.B. PostMessage ins Spiel.

Ein guter Test ist eine wilde Bedienung aller möglichen Tasten/Mauskombinationen und dann mit Alt+F4 das Programm beenden. Das sollte es ohne abzustürzen überleben.

MfG

Arnd
 
Hallo,

so ... bin ein ganzes Stück weitergekommen. Zwei Sachen hab ich nun: Wie bekomme ich einen Button in der Standardtoolbar dazu, dass er optisch gedrückt bleibt und gibt es eine ganz einfache Möglichkeit ein modales Popup-Fenster (z.B. für Fehlermeldungen) einzublenden?

Thx. :)
 
Hallo alphacast,

die einfachste Möglichkeit ist im View den UpdateHandler zu nutzen:

Code:
void CMyView::OnUpdateControl(CCmdUI *pCmdUI)
{
	pCmdUI->SetCheck( mbSelected );
	pCmdUI->Enable( TRUE );
}

Für ein Popup Fenster kannst Du z.B. einfach CAboutDlg kopieren und eine Listbox reinsetzen in dem dann die Fehlermeldungen angezeigt werden. Ansonsten leg einfach einen neuen Dialog an, generiere dazu eine Klasse (z.B. CNewDialog) und mache damit folgendes:
Code:
  CNewDialog lDlg;

  lDlg.DoModal();

IMHO schöner wäre aber eine eigene View damit sich das Fenster dann genauso bedienen lässt wie die anderen MDI Dokumente.

MfG

Arnd
 
Danke, Button funktioniert. :)

Ich habe mir jetzt einen neuen Dialog mit einem Static-Text-Label IDC_ERR_TXT und einem OK-Button erstellt.

Die Definition und Deklaration mache ich in den App-Dateien.

cpp:
ErrorDlg errdlg;

h:
extern Errordlg errdlg;

ich kann zwar jetzt überall mit errdlg.DoModal() den Dialog anzeigen lassen, aber wenn ich mittels

errdlg.m_ERR_TXT.SetWindowText("BLA");

oder

errdlg.SetDlgItemText(IDC_ERR_TXT, "BLA");

den Text ändern möchte, stürzt es ab.

Was hab ich hier verbockt?
 
Hallo alphacast,

wie Du Dich vielleicht erinnerst, habe ich schön öfter erwähnt das man IsWindow benutzen sollte bevor man auf ein Control zugreift :-).
D.h. man kann SetWindowText nur benutzen wenn das Objekt auch "lebt". Da bei:
Code:
CErrorDlg lErrorDlg;

lErrorDlg.SetWindowText("Hallo Welt" );
lErrorDlg.DoModal();

Bis zum SetWindowText nur der Konstruktor gelaufen ist, ist der Dialog bisher nicht erstellt worden. Deswegen auch der Absturz.
D.h. der Text muss im OnInitDialog gesetzt werden.

Code:
CErrorDlg::CErrorDlg(char* apTtitle=0)

CErrorDlg::CErrorDlg(char* apTtitle)
{
  if( apTitle )
    mTitle = apTitle;
}

void CErrorDlg::setTextParam( CString aText)
{
  mText = aText;
}

BOOL CErrorDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

   SetWindowText( mTitle );
   if( IsWindow( mTextCtrl.m_hWnd ) )
   {
        mTextCtrl.SetWindowText( mText );
   }
    return TRUE;
}

CErrorDlg lErrorDlg("Hallo Welt");

lErrorDlg.setTextParam("Hallo Welt2" );
lErrorDlg.DoModal();

MfG

Arnd
 
Zurück
Oben