Arparso Geschrieben 14. September 2005 Geschrieben 14. September 2005 Hallo, bin noch Programmieranfänger und hätte da eine Frage. Arbeite zur Zeit an einem kleinen, dialog-basierten Tool, welches ich mit Visual C++ 6.0 erstelle. Beim Start des Tools soll dabei automatisch zunächst ein Dialogfeld mit DoModal() aufgerufen werden, welches automatisch verschiedene Einstellungen initialisiert und das auch entsprechend darstellt. Das klappt auch alles wunderbar, nur kriege ich im Debug-Modus einen "Debug Assertion Error" in der wincore.cpp, Zeile 3486. Ein paar Details. Mein Hauptdialog enthält als Member das aufzurufende Dialogfeld. Dieses wird dann in der OnInitDialog-Funktion mittels DoModal() aufgerufen. In der OnPaint-Funktion des aufgerufenen Dialogs wird dann die auszuführende Funktion gestartet (auch wirklich nur einmal, nicht bei jedem Paint) und läuft problemlos durch. Sobald die aufgerufene Funktion aber versucht, das Dialogfenster wieder mittels Aufruf von OnOK, OnCancel oder EndDialog zu beenden, kommt besagter "Debug Assertion Error". Der Rest des Programms läuft dann normal weiter. Tja, hab keine Idee, wieso und warum. Außerdem wär ich für Tipps dankbar, die mir verraten, wie man sofort nach dem Aufruf eines Dialogs Funktionen ausführen kann. OnInitDialog ist leider keine Option, da die Funktion einige Elemente des Dialogfensters beeinflusst (u.a. eine ProgressBar) und das keinen Sinn machen würde, wenn das Fenster noch nichtmal dargestellt wird. Zitieren
Klotzkopp Geschrieben 15. September 2005 Geschrieben 15. September 2005 Das Problem liegt vermutlich darin, dass du das ganze in OnPaint machst. Außerdem wär ich für Tipps dankbar, die mir verraten, wie man sofort nach dem Aufruf eines Dialogs Funktionen ausführen kann. OnInitDialog ist leider keine Option, da die Funktion einige Elemente des Dialogfensters beeinflusst (u.a. eine ProgressBar) und das keinen Sinn machen würde, wenn das Fenster noch nichtmal dargestellt wird.Üblicherweise benutzt man für so etwas einen Timer mit kurzem Intervall, der in OnInitDialog gestartet wird, und sofort, nachdem er zum ersten Mal ausgelöst hat, wieder zerstört wird. Du musst dir allerdings im Klaren sein, dass du das Fenster hier nur begrenzt beeinflussen kannst, weil die Nachrichtenschleife steht, solange deine Funktion läuft. Das Problem ließe sich nur mit einem weiteren Thread lösen. Zitieren
Narf! Geschrieben 15. September 2005 Geschrieben 15. September 2005 Ich würde die Funktionen immer in OnPaint() zum Ausführen setzen. Da sieht bei mir so aus: bool isPaint=0; //als Member-Variable Dialog::OnPaint() { if(!isPaint) { isPaint=1; //damit wird verhindert, dass jedesmal die Funktion() aufgerufen wird Funktion(); }//end if(!isPaint) ... }//end OnPaint() Mit IsWindowVisible() kannst Du noch abfragen, ob der Dialog sichtbar ist. Zitieren
Arparso Geschrieben 15. September 2005 Autor Geschrieben 15. September 2005 Üblicherweise benutzt man für so etwas einen Timer mit kurzem Intervall, der in OnInitDialog gestartet wird, und sofort, nachdem er zum ersten Mal ausgelöst hat, wieder zerstört wird. Hmm, das würde natürlich wieder die Ausführung etwas verzögern. Wie sähe denn ein brauchbarer Timer aus? Sprich, welche Funktionen gibt es für sowas? Du musst dir allerdings im Klaren sein, dass du das Fenster hier nur begrenzt beeinflussen kannst, weil die Nachrichtenschleife steht, solange deine Funktion läuft. Das Problem ließe sich nur mit einem weiteren Thread lösen. Viel will ich ja gar nicht machen. Die Funktion überprüft im Hintergrund ein paar Einstellungen, liest ein bisschen was aus der Registry, lädt die Konfigurationsdatei, usw. Nebenbei werden im Dialogfenster entsprechende "Static Text" Elemente sichtbar gemacht, die dann eben anzeigen, womit die Funktion grad beschäftigt ist. Gleichzeitig wird die ProgressBar weiter bewegt. In der Regel sollte die ganze Funktion auch relativ schnell abgeschlossen sein, so dass der User kaum was davon mitbekommt... Ich würde die Funktionen immer in OnPaint() zum Ausführen setzen. Genau so mach ich es ja auch. Hier ist meine OnPaint() Funktion: void StartUp::OnPaint() { CPaintDC dc(this); // device context for painting if(firstStart == TRUE && IsWindowVisible()){ IntegrityCheck(); firstStart = FALSE; } } Der if-Teil ist von mir; firstStart ist ein Member des Dialogs, der im Konstruktor mit TRUE initialisiert wird. (Übrigens danke für den IsWindowVisible() Tipp ). Die Funktion IntegrityCheck() sieht dann so aus: void StartUp::IntegrityCheck() { firstStart = FALSE; [..] // irgendwelcher Code EndDialog(IDOK); } Selbst wenn ich den Code vor EndDialog() weglasse und wirklich nur noch EndDialog() drin steht, bekomme ich den Debug Assertion Error. Im Release Modus kommt da nix und alles scheint auch problemlos zu funktionieren... nur eben im Debug Modus nervt das doch ganz schön. Das Dumme daran ist auch, dass ich bei dem Assertion Error zwar auf "Retry" drücken kann, aber danach meine Anwendung nur abschmiert, statt mich irgendwie debuggen zu lassen. Beim Klick auf "Ignore" läuft die Anwendung normal weiter, als wäre nix gewesen... Zitieren
Klotzkopp Geschrieben 15. September 2005 Geschrieben 15. September 2005 Hmm, das würde natürlich wieder die Ausführung etwas verzögern.Nicht so, dass es ein Mensch bemerken würde. welche Funktionen gibt es für sowas?CWnd::SetTimer, CWnd::KillTimer. Viel will ich ja gar nicht machen. [...] Nebenbei werden im Dialogfenster entsprechende "Static Text" Elemente sichtbar gemacht, die dann eben anzeigen, womit die Funktion grad beschäftigt ist. Gleichzeitig wird die ProgressBar weiter bewegt.Das sind aber schon Dinge, die nicht so ohne weiteres ohne eine laufende Nachrichtenschleife funktionieren. Genau so mach ich es ja auch. Hier ist meine OnPaint() Funktion:Du solltest so etwas definitiv nicht in OnPaint machen. Im Release Modus kommt da nix und alles scheint auch problemlos zu funktionieren... nur eben im Debug Modus nervt das doch ganz schön.Das liegt daran, dass Assertions im Release-Modus vom Präprozessor entfernt werden. Der Fehler ist immer noch da, du wirst nur nicht mehr darauf hingewiesen. Das Dumme daran ist auch, dass ich bei dem Assertion Error zwar auf "Retry" drücken kann, aber danach meine Anwendung nur abschmiert, statt mich irgendwie debuggen zu lassen.Was heißt "schmiert ab"? Startest du im Debugger? Wie sieht der Callstack aus? Zitieren
UltimateRuppi Geschrieben 15. September 2005 Geschrieben 15. September 2005 Viel will ich ja gar nicht machen. Die Funktion überprüft im Hintergrund ein paar Einstellungen, liest ein bisschen was aus der Registry, lädt die Konfigurationsdatei, usw. Nebenbei werden im Dialogfenster entsprechende "Static Text" Elemente sichtbar gemacht, die dann eben anzeigen, womit die Funktion grad beschäftigt ist. Gleichzeitig wird die ProgressBar weiter bewegt. In der Regel sollte die ganze Funktion auch relativ schnell abgeschlossen sein, so dass der User kaum was davon mitbekommt... Dieser Initialisierungskram wird eigentlich überlicherweise in OnInitDialog gemacht. Genau dafür ist diese Funktion da um die Dialoginstanz zu initialisieren Zitieren
Arparso Geschrieben 15. September 2005 Autor Geschrieben 15. September 2005 Dieser Initialisierungskram wird eigentlich überlicherweise in OnInitDialog gemacht. Genau dafür ist diese Funktion da um die Dialoginstanz zu initialisieren Das ist mir ja durchaus klar, ABER zum Zeitpunkt von OnInitDialog ist das Dialogfenster noch gar nicht gezeichnet worden... wie sollte ich also irgendwelche graphischen Ausgaben machen (Text Labels sichtbar machen, ProgressBar bewegen), wenn das Fenster noch gar nicht "wirklich" da ist? @KlotzKopp: Danke für die Hilfe Ich rufe jetzt in OnInitDialog() einen Timer auf, der wiederum in OnTimer() zerstört wird (KillTimer()). Sofort danach wird wie gehabt IntegrityCheck() aufgerufen und läuft diesmal ohne Debug Assertion Error durch. Funktioniert jetzt also erstmal alles und ich kann mir diese dubiose OnPaint() Methode sparen (mir war schon klar, dass das nicht grad die eleganteste Lösung war - darum bin ich ja hier ). Zitieren
Bubble Geschrieben 15. September 2005 Geschrieben 15. September 2005 Üblicherweise benutzt man für so etwas einen Timer mit kurzem Intervall, der in OnInitDialog gestartet wird, und sofort, nachdem er zum ersten Mal ausgelöst hat, wieder zerstört wird. Ich rufe jetzt in OnInitDialog() einen Timer auf, der wiederum in OnTimer() zerstört wird (KillTimer()). Das ist aber kein schöner Workaround. So sollte man IMO nicht vorgehen. Eine modale Dialogbox ist für den Dialog mit einem Anwender gedacht (insbesondere werden Eingaben vom Anwender erwartet) und nicht dafür Statusinformationen in eine Richtung auszugeben. Hierfür solltest Du lieber ein normales Fenster nehmen, dieses kannst Du natürlich der Einfachheit halber auch mit CreateDialog aus einem Dialog Template Resource erstellen. Da Du bei dieser Variante die Nachrichtenschleife und das Dispatchen der Nachrichten an die DialogProc selbst übernehmen musst, kannst Du hier (nach erfolgter Darstellung und erstmaligem Zeichnen) Deine umfangreichere Verarbeitung problemlos anstoßen und ggf. Elemente in dem Fenster verändern. Du musst nur immer wieder zwischendurch die angefallenen Nachrichten verarbeiten oder alternativ zwei getrennte Threads verwenden (einen für die Nachrichtenverarbeitung, einen weiteren für Deine sonstige nebenläufige Funktionalität). Zitieren
Klotzkopp Geschrieben 15. September 2005 Geschrieben 15. September 2005 Das ist aber kein schöner Workaround. So sollte man IMO nicht vorgehen. Eine modale Dialogbox ist für den Dialog mit einem Anwender gedacht (insbesondere werden Eingaben vom Anwender erwartet) und nicht dafür Statusinformationen in eine Richtung auszugeben. Hierfür solltest Du lieber ein normales Fenster nehmen, Das sehe ich nicht so. Ich stimme dir zu, dass das kein "Dialog" im engeren Sinne mehr ist. Andererseits hat ein modaler Dialog den großen Vorteil, dass er, so lange er läuft, jegliche Interaktion mit dem Elternfenster verhindert. Damit hat man klare Schnittstellen, gerade für einen Vorgang, den der Benutzer nicht beeinflussen kann, sobald er gestartet ist. Bei einem normalen Fenster müsste man mehr oder weniger umständlich dafür sorgen, dass der Benutzer nicht dazwischenfunkt. Zitieren
Bubble Geschrieben 16. September 2005 Geschrieben 16. September 2005 Andererseits hat ein modaler Dialog den großen Vorteil, dass er, so lange er läuft, jegliche Interaktion mit dem Elternfenster verhindert. Damit hat man klare Schnittstellen, gerade für einen Vorgang, den der Benutzer nicht beeinflussen kann, sobald er gestartet ist. Man kann eigene modale Fenster sauber programmieren, die wie eine modale Dialog-Box die Interaktion mit dem Parent-Window unterbinden. Das Hauptfenster kann man selbst mit EnableWindow() "lahmlegen", da für ein modales Fenster eine eigene Message-Loop verwendet wird, kommt die Message-Loop vom Hauptfenster ohnehin nicht zum Zug. Es muss dabei darauf geachtet werden, dass Parent-Window wieder zu aktivieren, bevor das Anzeige-Fenster geschlossen wird. Man sollte auch eine eintreffende WM_QUIT Nachricht geeignet behandeln (sie sollte in die übergeordnete Message-Loop wieder eingefügt werden). Ich hab Dir noch mehrere Links zu ein paar Beispielen rausgesucht, die eine mögliche Vorgehensweise beschreiben: http://www.mvps.org/user32/modal.html und http://blogs.msdn.com/oldnewthing/archive/2004/02/27/81155.aspx und http://blogs.msdn.com/oldnewthing/archive/2005/02/18/376080.aspx Nichts für ungut, aber Timer, die genau einmal aufgerufen werden, nur mit dem Zweck eine einzige WM_TIMER Nachricht zu erzeugen, halte ich für keine saubere Lösung. Da könnte man doch eher mit PostMessage (eine) eigene Nachricht(en) in die Nachrichten-Warteschlange einfügen, das wäre zumindest etwas schöner. Zitieren
Klotzkopp Geschrieben 16. September 2005 Geschrieben 16. September 2005 Danke für die Links, die sind sehr interessant. Nichts für ungut, aber Timer, die genau einmal aufgerufen werden, nur mit dem Zweck eine einzige WM_TIMER Nachricht zu erzeugen, halte ich für keine saubere Lösung. Da könnte man doch eher mit PostMessage (eine) eigene Nachricht(en) in die Nachrichten-Warteschlange einfügen, das wäre zumindest etwas schöner.Ich halte deine Einwände für grundsätzlich richtig. Allerdings darf man hier nicht außer acht lassen, dass es um MFC und vor allem um Anfänger geht. Benutzerdefinierte Nachricht bedeutet, dass man selbst Hand an die MFC-MessageMap legen muss. Allein dabei kann viel schiefgehen. Oft genug habe ich schon gesehen, dass dabei die falsche Map benutzt wurde. Praktisch alles, was man dabei falsch machen kann, führt zu logischen Fehlern, die man (über ein Forum) nur schwer finden kann. Die Vorgehensweisen, die auf den von dir verlinkten Seiten vorgeschlagen werden, haben sogar noch mehr mögliche Fehlerquellen, wenn sie denn überhaupt auf die MFC anwendbar sind:Es muss dabei darauf geachtet werden, dass Parent-Window wieder zu aktivieren, bevor das Anzeige-Fenster geschlossen wird. Man sollte auch eine eintreffende WM_QUIT Nachricht geeignet behandeln (sie sollte in die übergeordnete Message-Loop wieder eingefügt werden).Ich denke, so etwas kann man Anfängern - und mit solchen haben wir es hier im Forum nun mal meistens zu tun - nicht zumuten. Zitieren
UltimateRuppi Geschrieben 16. September 2005 Geschrieben 16. September 2005 Das ist mir ja durchaus klar, ABER zum Zeitpunkt von OnInitDialog ist das Dialogfenster noch gar nicht gezeichnet worden... wie sollte ich also irgendwelche graphischen Ausgaben machen (Text Labels sichtbar machen, ProgressBar bewegen), wenn das Fenster noch gar nicht "wirklich" da ist? ehh mit Komponente XY.ShowWindow(SW_SHOW) oder zum unsichtbar machen mir SW_HIDE. Zum Progressbarbewegen, die entsprechenden Funktionen von CProgressCtrl aufrufen oder die Membervariable die mit der Progressbar assoziert ist setzen und UpdateData(FALSE) aufrufen. Das Fenster ist schon da, nur noch nicht sichtbar. Es wird erst nach Abschluss von OnInitDialog sichtbar. Zitieren
Bubble Geschrieben 16. September 2005 Geschrieben 16. September 2005 so etwas kann man Anfängern - und mit solchen haben wir es hier im Forum nun mal meistens zu tun - nicht zumuten. Das es für manche Anfänger zu viel ist, dem stimme ich zu. Allerdings: Es ist wie beim Strassenverkehr. Abkürzungen zu nehmen, wie eine Einbahnstrasse in die falsche Richtung zu befahren, und das mit dem Status "Fahranfänger" zu entschuldigen geht nicht. Und mal ehrlich, selbst wenn er es nicht hinbekommt: Dann gibt er seine Statusmeldungen einfach auf einem anderem Weg aus oder lässt sie einfach weg und das Programm funktioniert (hoffentlich) trotzdem noch (und enthält keine Workarounds). Es gibt wichtigeres, als eine hübsche GUI Zitieren
Empfohlene Beiträge
Dein Kommentar
Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.