Neuer Applikationsrahmen
-
Grüße!
Schon vor ab eine Entschuldigung für die Textwand, aber ich habe die Erfahrung gemacht, dass es immer hilft, wenn man bei Fragen den Kontext angibt, anstatt einfach aus dem Kontext gerissene falsche Fragen zu stellen.
Ich möchte in naher Zukunft meinen MFC-Applikationsrahmen austauschen gegen einen moderneren mit MDI und Tab Groups. Zu diesem Zweck habe ich mir einfach einmal ein Testprojekt zusammengebastelt, in dem ich die Rahmemspezifischen Sachen ausprobiere, natürlich ohne irgendeine weitere Funktionalität.
Nun habe ich, wie sollte es auch anders sein, einige Ansprüche an die Rahmenfunktionalität, die so zumindest nicht direkt eingebaut sind. Da ich es gewohnt bin, dass es schnell kracht, wenn man an MFC vorbeiprogrammiert, wollte ich einmal die Meinung von euch einholen, bevor ich das so übernehme.
1. Für ein Dokument müssen dynamisch weitere Tabs mit speziellen Views erzeugt werden können.
a) Schließen:
Ein normales Dokument dieser Applikation hat einen "Ausführungsmodus" in dem eine beliebige Anzahl neuer Views erzeugt werden können. Während das Dokument in diesem Modus ist, soll es nicht geschlossen werden können. Das soll der Sicherheit dienen, denn es wäre mit Datenverlust verbunden, wenn der Benutzer während dieses Modus aus versehen das Dokument schließt. Außerdem wäre der Modus witzlos, wenn die erzeugten Views verschwänden.
Zu diesem Zweck überschreibe ich im Moment einfach die CanCloseFrame-Methode des Dokuments. Allerdings ist es für den Benutzer vielleicht etwas unintuitiv, dass er an den Tabs dann das Schließen-Symbol sieht und es funktionslos ist.
Ich habe im Internet unter http://www.3dbuzz.com/forum/threads/185778 ein Posting gefunden, das beschreibt, wie man Frame-Basiert die Schließen-Buttons der Tabs entfernt. Allerdings ist die Lösung meiner Meinung nach etwas unschön, gibt es da einen schöneren Weg? Am liebsten hätte ich mir gewünscht, das Tab-Basiert einstellen zu können, aber beim Code-Browsen von MFC habe ich dann einsehen müssen, dass es pro Tab keine solche Einstellung gibt.b) Dynamische Erzeugung von Views
Da die dynamisch erzeugten Views auch als Tab in den MDI Tabs landen sollen, brauchen sie meiner Meinung nach dafür auch gleich ein neues CMDIChildFrame. Ich behelfe mir da gerade mit folgender Methode:void Document::OnRunRun() { state_ = Executing; CDocTemplate* temp = GetDocTemplate(); CCreateContext context; context.m_pCurrentDoc = this; context.m_pCurrentFrame = 0; context.m_pLastView = 0; context.m_pNewDocTemplate = temp; context.m_pNewViewClass = NULL; frame_ = new ExecuteFrame( new ExecuteView() ); frame_->LoadFrame( IDR_MdiTestTYPE, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, &context); frame_->InitialUpdateFrame( this, TRUE ); }
Die erzeugten Views brauchen mehr Informationen, als was ein DYNCREATE-View mitbekommt, daher übernimmt die Klasse ExecuteFrame nur das CView::Create, bekommt aber ansonsten schon einmal eine fertige Instanz des Views und kümmert sich darum dann im OnCreateClient.
Ist das OK so?
2) Dieser "Ausführungsmodus" soll auch geskriptet ablaufen können.
Ich sprach ja von diesem Ausführungsmodus in Punkt 1. Nun habe ich eine Skript-Schnittstelle, die diese Ausführungs-Objekte laden / starten kann (Diese Objekte sind unabhängig von der Dokumentklasse aus Punkt 1!).
Ich habe dann darüber nachgedacht, wie man das machen könnte. Eine Möglichkeit wäre ja, dass das CMainFrame einfach eine Routine für soetwas anbietet, aber wegen der MDI-Tab-Groups ist es ja nötig, dass die dynamisch erzeugten Fenster von einem Dokument "gebacked" werden.
Ich meine deshalb, dazu würde passen, dass ich für solche Skripte eine eigene Dokumentklasse erzeuge und registriere
Im Moment denke ich mir das so, dass ich irgendwie dafür sorge, dass diese neue Dokumentklasse nur Dokumente (also Skripte) öffnen kann, sie aber nicht neu erzeugen kann (Die schreibt man sich mit einem Texteditor).
Nun ist es aber so, dass beim "Laden" des Dokument erst einmal gar kein View zur Verfügung steht (Die werden dann erst von den Ausführungsobjekten erzeugt, wenn der Skript gestartet ist).
Wenn das alles so Sinn macht, die Frage: Muss ich mir da eine "Proxy-View-Klasse" basteln, die ich im MultiDocTemplate registriere, oder kann ich dort auch NULL eintragen und meine Views hinterher selber erzeugen? Was wäre da die beste Herangehensweise?So, das war es erstmal. Vielen Dank für die Geduld und bis dann,
Deci
-
Ich versuch mal ein paar Fragen zu beantworten:
- Ein Document benötigt gar kein Doc Template.
Di kannst also einfach ein Dokument ohne View mit new erzeugen und das war es. Bei den Views sieht das anders aus.- Deinen Code zum Erzeugen eines neuen Fenster verstehe ich nicht.
Bzw. warum machst Du es nicht wie in der MFC schon in OnWindowNew.
Die Template Funktionen bringen eigentlich alles mit für das was Du brauchst,CDocTemplate* pTemplate = GetDocTemplateFromSomewhere(); ASSERT_VALID(pTemplate); CFrameWnd* pFrame = pTemplate->CreateNewFrame(pDocument, pActiveChild); if (pFrame == NULL) { TRACE(traceAppMsg, 0, "Warning: failed to create new frame.\n"); return; // command failed } pTemplate->InitialUpdateFrame(pFrame, pDocument);
HTH
- Mir ist nicht klar welches MDI Interface Du benutzt. Die Tabbed-Variante aus der neuen MFC? Oder die "alte"?
Das verwenden der SetFocus Nachricht finde ich auch blöd. IMHO lässt sich das gewisslich tiefer abfangen.
Da der Close Button grundsätzlich über EnableMDITabs gesteuter werden kann, sollten sich die relevanten Code Teile in der MFC schnell finden lassen.
-
Sooo,
ich kann leider erst jetzt wieder darauf zurückkommen, zu viel zu tun, zu wenig Zeit
Erst einmal Danke dafür, dass Du Dir die Zeit genommen hast, Dich durch mein Posting zu kämpfen.Zu dem Code zum Erzeigen des ChildFrames:
Das Dokument ist in seinem Template ja schon so eingestellt, dass es ein Default-Frame und einen Default-View hat (das sind die, die der Benutzer normalerweise benutzt, wenn das Teil nicht im Ausführungsmodus ist, also bei einem neuen Dokument oder wenn man eines lädt). Für den Ausführungsmodus brauche ich aber andere Views. Diese Views haben nun einen gewissen "Kontext", also sollen etwas ganz bestimmtes mit bestimmten Parametern darstellen. Wenn ich die nun per Dyncreate erzeugen ließe, müsste ich die Parameter irgendwie zwischenspeichern, sodass der View sie sich in OnInitialUpdate abholen könnte. Das finde ich nicht so prickelnd. Daher möchte ich die Views lieber selber passend erzeugen, sodass sie schon ab dem Konstruktor mit dem Dokument und allen nötigen Parametern initialisiert sind. Daher fällt doctemplate->CreateNewFrame irgendwie aus, weil das eben ein Standard-Frame mit Standard-View erzeugen würde und ich keine Parameter mitgeben kann.Ein Document benötigt gar kein Doc Template
Acknowledged! Das könnte ich bestimmt nutzen. MFC verwaltet ja die offenen Dokumente in den registrierten Doc-Templates, wenn ich das richtig sehe, das heißt ich müsste dann diesen Typ von Dokumenten irgendwie manuell in der CWinApp-Klasse verwalten? Kriege ich es dann noch irgendwie hin, dass MFC von diesem offenen Dokument bescheid weiß und das beim schließen auch fragt, ob noch gespeichert werden muss usw.?
Und ja, beim Tabbed-MDI ich meinte die Variante aus dem Feature-Pack. Ich debugge mich mal in die EnableMDITabs-Aufrufe, vielleicht finde ich ja einen guten Kontext um das besser abzufangen. Danke für den Tipp!
Viele Grüße,
Deci
-
Jeder View trägt sich in die Liste des Documents ein, für das er erzeugt wurde.
Beim Schließen des Frames des Views, fragt dieser das Document ab.Auch hier sind keien DocTemplates im Spiel.
Die Liste der Documente im Doctemlate sind "überflüssig".Ich habe oft Pseudo-MDI Oberflächen, oder sagen wir Multi-View Oberflächen in denen ich Zentral ein Document erzeuge und dann mit den Views eingentlich mache wasich will.
DocTemplates haben eher was mit der UI zu tun, wenn andere Views aktiviert werden, dann müssen ja evtl. auch andere Menüpunkte eingeblendet werden.
Es ist übrigends sehr einfach in Deiner Methode OnInitialUpdate weitere Argumente mitzugeben. Wenn Du schon CCreateContext selbst befüllst, dann leite eine Klasse davon ab und pack weitere Argumente da hinein...
Ein cast genügt. Das wäre zwar nicht unbedingt typsicher, aber extrem einfach.
-
Okay, also ich habe etwas rumdebugged und habe somit herausgefunden, dass man mit GetRelatedTabGroup an die Tab-Gruppe für das Dokument herankommt und da dann die Schließen-Buttons abstellen kann, soweit so gut!
Das ganze mache ich jetzt im WM_MDIACTIVATE-Handler, schade nur, dass wenn ich das Frame mit WM_CLOSE schließe, ich zwar noch ein MDIACTIVATE mit Activate=FALSE bekomme, dort aber schon die Assoziation zur Related-Tab-Group aufgehoben wurde (Also GetRelatedGroup gibt dann schon nullptr zurück). Jetzt muss ich also in WM_CLOSE und in WM_MDIACTIVATE behandeln...
Lustig ist auch irgendwie, dass ich beim Erzeugen eines Frames dreimal WM_MDIACTIVATE bekomme... Ist das so im Sinne des Erfinders?Also irgendwie glaube ich, dass ich durch die MFC nie so recht durchsteigen werde. Der Grobaufbau ist halbwegs klar, aber hinter den Kulissen geht dann alles so kreuz und quer und wenn dann zwischen den Klassen noch Nachrichten ausgetauscht werden, wird Verstehen-durch-Debuggen zur Qual... Kann man denn mit dem VC++-Debugger irgendwie komfortabel durch SendMessage durchdebuggen, zumindest wenn die Nachricht den Thread nicht verlässt?
Edit: Und noch eine Frage, kann man IntelliSense dazu bekommen, die Definitionen der MFC-Funktionen im MFC-Quellcode zu finden, so dass ich nicht erst in die Funktionen reindebuggen muss?
-
Das Problem ist, dass das MDI Interface eigentlich schon immer gemacht hat was es will. Alks es in Windows 3.1 rauskam hatte ich damit meine "helle Freude" und einen Bug nach dem anderen gefunden. Schnee von gestern, der bis in die Neuzeit reicht scheint es mir...
Ich benutze VA-X Ich gehe auf die Funktion und sage "Go". Da ich die MFC in den Source Dateien im VA-X angegeben habe findet er eigentlich immer die Implementierung.
-
Ja, also VA-X hatte ich auch mal in der Demo-Version hier, war schon ziemlich schön, vor allem dass es direkt aus dem Header die Definitionen für die Funktionen basteln kann usw.. Leider hatte ich in dieser Phase aber auch meine ersten IDE- und nicht nur Compiler-Crashes... Und für meine Verhältnisse kostet es natürlich auch eine Stange. Klar, wenn man als Unternehmer damit mehr Aufträge wegen gesparter Zeit umsetzen kann, ist es das natürlich wert
Aber ich darf feststellen: Ich bin heute recht schnell und ohne größere Probleme auf der anderen Seite angelangt, danke für Deine Hilfe!
-
Herrje, einige Stunden habe ich daran rumdebugged...
Wenn man an den Tab-Schließen-Buttons nach Programmerzeugung noch etwas dreht, scheint erst einmal alles gut zu funktionieren, bis man auf die Idee kommt, ein zweites Dokument aufzumachen und zu schließen, während das erste Dokument in einem Nicht-Schließen-Modus ist. Denn dann werden zwar noch Paint und Timer-Nachrichten zugestellt, aber für alles andere ist das ganze Programm dann nicht mehr erreichbar (Maus und Tastatur...). Als würde es abschmieren... Einmal Minimieren und Wiederherstellen reicht dann aus, um auch den Rest der Nachrichten wieder zu bearbeiten... Herrje... Ich lasse das glaube ich lieber.
-
Wo steht die Kiste demm, wnen Du den Debugger aktivierst.
Sicher in der normalen Mesage Loop?
Wer hat den Focus?
Was ist das aktive Fenster?
-
Hey!
Nun, der Messageloop wird weiterhin angefahren (ich hatte einfach mal auf Pause gedrückt und dort hing er dann wie nicht anders zu erwarten im GetMessage), aber da sind auch viele Timer bzw. APCs unterwegs und dann war ich zu müde da noch irgendwie einen passenden bedingten Breakpunkt bei AfxMessagePump anzubringen (irgendwie auf WM_LBUTTONDOWN oder so)... Das werde ich dann aber noch nachholen. Ein weiteres Problem ist auch, dass sobald der Debugger anspringt der Fokus wechselt und wenn ich die Anwendung dann weiterfahre ist das Problem dadurch schon behoben, das heißt ich muss wirklich genau durch den einen Debugger-Ansprung alles klarmachen... Und eigentlich hatte ich im CMFCTabCtrl-Code auch überhaupt nix "angestoßenes" gesehen (DestroyWindow, Repaint,...), wenn man die EnableMDITabGroups aufruft und da mal etwas mitdebugged.
Ich werde heute Abend nochmal schauen, im Moment bin ich erstmal riesig erschöpft.
-
Das wird heute nix mehr -.-
Fällt Dir eine einfachere Variante ein, als in thrdcore einen Breakpunkt für msg=WM_MBUTTONDOWN zu setzen, und dann per Code nach den fehlerverursachenden Zeilen eine solche Nachricht zu posten?
-
Remote Debugging ist bei so etwass ein Muss!
Das geht auch in einer virtuellen Maschine!
-
Oha, das ist eine Idee! Danke dafür, werde mich dann mit den Resultaten melden!
-
Ich kam immer noch nicht dazu, eine VM zu installieren, für das beschriebene Problem. Ich denke nun aber, dass es wesentlich simpler ist, als zunächst angenommen.
Nachdem mein View irgendwann einmal den Fokus verloren hat (zB. indem man auf einen Button in einer anderen Pane clickt, Menüs funktionieren weiterhin hervorragend), bekommt der View den Fokus nicht mehr, wenn man auf ihn draufclickt. Wenn man auf den Tab des MdiChildFrames clickt, bekommt der View den Fokus wieder und alles ist gut.
Wie könnte es kommen, dass ein Click auf den View keinen Fokus auf ihn setzt? Ich dachte, diese Fokusgeschichte wäre mehr oder weniger eine Win32-Sache. Die WM_LBUTTONDOWN-Nachrichten kommen auf jeden Fall beim View an, bloß bekommt er eigenständig einfach keinen Tastaturfokus.
-
Tja, wenn man OnActivateView überschreibt, aber vergisst, dabei an die Basisklasse zu delegieren, dann passiert sowas halt!