Mehrmaliges Buttondrücken unterdrücken
-
2. Ein zweiter Thread blockiert nicht die Messagebehandlung. Deshalb werden die Clicks auf dem ausgeschalteten Button bearbeitet, während er ausgeschaltet ist. Du könntest auch interaktiv auf Nutzereingaben reagieren während im Hintergrund die Arbeit ausgeführt wird (Click-Click-Click "bin ja schon dabei..."). Grundlegend wird dein GUI nicht "hängen" wenn du zeitaufwendige Dinge tust. "Gute" Software hängt nicht.
C++ Builder 20006
WOW
-
Morle schrieb:
...
Das ist doch leider einfach nicht wahr. Folgendes gerade ausprobiert:
procedure TtfTestForm.Button1Click(Sender: TObject); begin Button1.Enabled := false; Sleep(5000); Button1.Enabled := true; end;
Der Button wird mit Enabled := false sofort "grau". Die Applikation hängt dann erwartungsgemäß 5 Sekunden. Klickt man dann in dieser Zeit auf den grauen Button, kommt der 2te Klick trotzdem durch, wenn die Sleep Zeit vorrüber ist.
Da scheinen wir einen krassen Unterschied zwischen Delphi und C++ gefunden zu haben, bei mir kann man sich mit folgendem Code ´nen Wolf klicken, ohne dass sich zwei Dialogfenster öffnen:
void __fastcall TForm1::OnClickButton(TObject *Sender) { Button->Enabled = false; ::Sleep( 1500 ); MessageDlg( "Hello World", mtWarning, TMsgDlgButtons() << mbOK, 0 ); Button->Enabled = true; }
Alles andere würde auch keinen Sinn machen, schließlich will man Controls deaktivieren, um die dahinter liegende Funktionalität abzuschalten. Wenn Events unabhängig vom Status des Controls erzeugt würden (was sie übrigens auch mit der MFC und in C# nicht tun) könnte man sich den Firlefanz mit dem Ausgrauen auch sparen, das wäre dann nur Optik. Kannst ja mal ´ne Umfrage aufmachen, unter welcher Programmiersprache und welchem GUI Framework deaktivierte Controls Nachrichten durch Benutzerinteraktion erzeugen (konkret: Draufklicken). Werden wohl nicht viele sein, schätze die Anzahl liegt so ziemlich genau bei 0.
@audacia
Kommt drauf an, wie häufig man das einsetzt. Ich schreibe lieber 25 Zeilen Code, um das Problem an 20 Stellen mit einem Einzeiler zu lösen, statt an 20 Stellen jeweils 7-8 Zeilen identischen Code zu schreiben.@Morris Szyslak
LeichtfertigApplication->ProcessMessages()
ist nie eine gute Idee, da bekommst du ruckzuck selstames Verhalten, dass sich nur schwer erklären und debuggen lässt. Wenn manApplication->ProcessMessages()
sollte man sich wirklich überlegen, ob man die Aufgabe nicht besser durch einen Thread löst.@Mauro77
Wikipedia Link zu RAII
-
DocShoe schrieb:
@audacia
Kommt drauf an, wie häufig man das einsetzt. Ich schreibe lieber 25 Zeilen Code, um das Problem an 20 Stellen mit einem Einzeiler zu lösen, statt an 20 Stellen jeweils 7-8 Zeilen identischen Code zu schreiben.Okay - aber ich finde eine Zeile wie
ScopedVCLComponentDisabler<TButton>( ButtonSave );
außerordentlich unintuitiv zu verstehen, selbst wenn man das Idiom kennt. Obwohl du deinen Scope-Guard so ausführlich betitelt hast, daß eigentlich alle erforderliche Information im Namen steckt - ein try/__finally ist halt doch etwas direkter:
ButtonSave->Enabled = false; try { ... } __finally { ButtonSave->Enabled = true; }
Aber das ist Geschmackssache. Was mir besonders mißfällt, ist die Tatsache, daß du für jede Art von Operation (
BeginUpdate()
/EndUpdate()
,DisableControls()
/EnableControls()
,Enabled = false
/Enabled = true
etc.) einen eigenen Guard brauchst.Aber seit ich
decltype()
zur Verfügung habe, ist das bei mir auch wieder ein Einzeiler:
[```cpp
UCL_SAFEGUARD (ButtonSave, object->Enabled = false, object->Enabled = true);DocShoe schrieb: > Leichtfertig `Application->ProcessMessages()` ist nie eine gute Idee, da bekommst du ruckzuck selstames Verhalten, dass sich nur schwer erklären und debuggen lässt. Von so etwas kann ich auch nur abraten. Das ist fast so schlimm wie ein unkontrolliertes `Sleep(500);` einzubauen, wenn's anders nicht zu gehen scheint.
-
Kann ich aber nicht einfach nach der Save Methode die Messages durchsuchen und alle ausstehenden LinksClicks meiner Applikation aus der Queue löschen?
Das wäre insofern sauber, denn keiner der Buttons soll in dieser Zeit angeclickt werden dürfen.
Leider habe ich nicht rausgefunden, wie man ausstehende Messages einer Applikation rausliest und diese dazu noch löscht...
(Die Message wäre natürlich WM_LBUTTONDOWN).Grüße,
Mauro
-
Die Nachrichten aus der Message Queue zu löschen würde ich schon als Hack ansehen, eine saubere Lösung ist das sicherlich nicht.
Zeig uns doch mal deinen Event Handler, vielleicht stimmt da etwas nicht.
-
void __fastcall THauptformular::WeiterClick(TObject *Sender) { PostMessage(Handle, NAVIGATION_STARTEN, 0, 0); } //--------------------------------------------------------------------------- void __fastcall THauptformular::Dispatch(void *Message) { switch (((PMessage) Message)->Msg) { VCL_MESSAGE_HANDLER(NAVIGATION_STARTEN, TMessage, MessageHandling) ... default: TForm::Dispatch(Message); } } //--------------------------------------------------------------------------- void __fastcall THauptformular::MessageHandling(TMessage &Message) { ... else if (Message.Msg == NAVIGATION_STARTEN) Navigation(); -> Aus dieser Methode werden alle Methoden direkt aufgerufen... } //---------------------------------------------------------------------------
Drückt man auf den Button (WeiterClick), werden ALLE Komponenten (auch der Button selbst) aus der Form dynamisch gelöscht und danach die Methode Navigation() aufgerufen die wiederrum alle KOMPONENTEN dynamisch aufbaut, auch den Button! und anschliessend die Daten speichert.
Mit dieser Vorgehensweise kann ich aber im Button Handler nicht den Knopf ausmachen und später wieder anmachen, da er ja gelöscht und neu aufgebaut wird.
Die Lösung müsste aber dann doch sein, daß man ihm die Ereignisroutine (WeiterClick) erst NACH dem Durchlaufen der Speichermethode (bzw. Navigation()) zuweist. Kann ich sowas irgendwie machen mit PostMessage....?
Mauro
Edit akari : Bitte beim Posten von Code hier Forum die Code-Tags benutzen! sfds
-
Ist das der aktuelle Code?
-
Ja...
Das mit PostMessage aus dem Event Handler war notwendig, damit ich auch den Button löschen kann, sonst würde ich ja aus der Button Event Handler Routine den Buttons selbst löschen was zu Abstürzen führen kann...
-
Das ist, Entschuldigung, großer Käse!
Warum nicht einen einfachen Event Handler für den Button?
-
Warum soll das Käse sein?
Der Button wird wie 100 weitere Komponenten gelöscht, damit 100 andere Komponenten aufgebaut werden, das geschieht natürlich alles dynamisch, um Speicherplatz zu sparen.
Die Methodik mit der PostMessage aus dem Event Handler des zu löschenden Buttons wurde mir seinerzeit von Matt Damon empfohlen - einem C++ Guru.
Ich kann auch nicht hier 20000 Tausend Zeilen Code meines Programms posten, um die Hintergründe zu erklären, warum ich diesen Weg genommen habe, aber es scheint auch plausibel für mich alle Komponenten eines Screens zu löschen und neu aufzubauen, anstatt die 2 oder 3 zu lassen, die sich von Funktion zu Funktion ähneln.
Übrigens, das ganze ist ein einfacher Fußballmanager, der scho nvieln Leuten Spaß bereitet hat.
Mauro
-
Trotzdem ist der Weg unsauber, da PostMessage ja asynchron läuft (und du damit keine Kontrolle über den Programmverlauf hast). Und bei einem ButtonClick sollte man schon warten bis die Aktion ganz ausgeführt wurde (oder wenn sie wirklich langlebig ist, dann Auslagerung in einen eigenen Thread und anschließender Synchronisierung damit).
In meinen eigenen Programmen, wenn ich dynamische Buttons sich selbst löschen will, verstecke ich den Button dann, speichere ihn mir in einer Liste (vector<TControl*>) und zu einem definierten Zeitpunkt lösche ich die Controls und leere die Liste.
-
Solange ich in einer Routine eine PostMessage absetze, wird die Routine IMMER zuerst zu Ende abgearbeitet, bevor das System sich die wartende Message via GetMessage abholt. Somit sollte das sicher sein....
-
Mauro77 schrieb:
Der Button wird wie 100 weitere Komponenten gelöscht, damit 100 andere Komponenten aufgebaut werden, das geschieht natürlich alles dynamisch, um Speicherplatz zu sparen.
Verstehe ich nicht. Wie spart dir das Speicherplatz?
Mauro77 schrieb:
Die Methodik mit der PostMessage aus dem Event Handler des zu löschenden Buttons wurde mir seinerzeit von Matt Damon empfohlen - einem C++ Guru.
Ich wußte nicht, daß Jason Bourne in seiner Freizeit C++ programmiert
Mauro77 schrieb:
Ich kann auch nicht hier 20000 Tausend Zeilen Code meines Programms posten, um die Hintergründe zu erklären, warum ich diesen Weg genommen habe, aber es scheint auch plausibel für mich alle Komponenten eines Screens zu löschen und neu aufzubauen, anstatt die 2 oder 3 zu lassen, die sich von Funktion zu Funktion ähneln.
Warum nimmst du nicht einfach Frames und Komponenten? Und was spricht gegen die saubere Lösung mit Threads?
-
audacia schrieb:
Mauro77 schrieb:
Der Button wird wie 100 weitere Komponenten gelöscht, damit 100 andere Komponenten aufgebaut werden, das geschieht natürlich alles dynamisch, um Speicherplatz zu sparen.
Verstehe ich nicht. Wie spart dir das Speicherplatz?
Mauro77 schrieb:
Die Methodik mit der PostMessage aus dem Event Handler des zu löschenden Buttons wurde mir seinerzeit von Matt Damon empfohlen - einem C++ Guru.
Ich wußte nicht, daß Jason Bourne in seiner Freizeit C++ programmiert
Mauro77 schrieb:
Ich kann auch nicht hier 20000 Tausend Zeilen Code meines Programms posten, um die Hintergründe zu erklären, warum ich diesen Weg genommen habe, aber es scheint auch plausibel für mich alle Komponenten eines Screens zu löschen und neu aufzubauen, anstatt die 2 oder 3 zu lassen, die sich von Funktion zu Funktion ähneln.
Warum nimmst du nicht einfach Frames und Komponenten? Und was spricht gegen die saubere Lösung mit Threads?
Zu Deinen Fragen:
1. Ganz einfach, wenn ich diese Komponenten nur unsichtbar stellen würde, dann
hätte ich dann 1000 Komponenten gleichzeitig im Speicher... Das muss nicht sein... Darum lösche ich diese...2. Tja, er der Mann kann einfach alles, auch schauspielern und programmieren...
Ich glaube, sein Vorname war doch ein Anderer...
3. Es spricht wohl nichts dagegen, bzw. meine Unwissenheit, warum man hier mit aller Gewalt einen Thread bauen soll???
Es handelt sich um ein Spiel und beim Speichern/Laden ist jedes Spiel nicht zugänglich für den User...
Wäre die Lösung bei einem Thread einfach mal im anderen Thread nachzufragen, ob das Speichern abgeschlossen ist, korrekt?? Ein Beispiel für einen Thread hat wohl hier jemand schon gepostet...?
-
Mauro77 schrieb:
1. Ganz einfach, wenn ich diese Komponenten nur unsichtbar stellen würde, dann
hätte ich dann 1000 Komponenten gleichzeitig im Speicher... Das muss nicht sein... Darum lösche ich diese...Das ist klar. Ich dachte, du bezögest dich auf das "dynamische Aufbauen":
das geschieht natürlich alles dynamisch, um Speicherplatz zu sparen
Darunter verstand ich, daß du alle Komponenten im Code erstellst und freigibst ("dynamisch"), anstelle einfach den Formdesigner zu benutzen, was ich nicht verstehen konnte. Aber wenn du nur das Freigeben an sich meinst - okay.
Mauro77 schrieb:
3. Es spricht wohl nichts dagegen, bzw. meine Unwissenheit, warum man hier mit aller Gewalt einen Thread bauen soll???
Wurde schon gesagt. Es ist einfach the right thing to do. Das Windows-Messaging-System rechnet - wie du sehen kannst - nicht mit Unterbrechungen jenseits der menschlichen Reaktionszeit. Dein Benutzer hätte vielleicht gerne die Möglichkeit, eine längere Aktion abzubrechen, oder auch sowas wie eine Fortschrittsmeldung. Und besonders unschön: wenn dein Programm keine Messages mehr verarbeitet, der Benutzer nervös wird und mehrfach darauf herumklickt, dann denkt sich Windows seinen Teil, blendet dein Anwendungsfenster seicht aus und konstatiert trocken "Anwendung XY reagiert nicht mehr". Wenn das nicht Grund genug ist, dann weiß ich auch nicht
Mauro77 schrieb:
Ein Beispiel für einen Thread hat wohl hier jemand schon gepostet...?
Da Multithreading nicht ganz trivial ist, wäre es nicht ungeschickt, hierfür eine existierende Lösung zu benutzen. Du kannst ja mal nach der OmniThreadLibrary oder nach AsyncCalls googlen. (Bei AsyncCalls mußt du allerdings die Unterstützung für Generics deaktivieren, weil der Delphi-Compiler sonst einen ungültigen Header generiert.) Gerade mit AsyncCalls ist die Sache eine Kleinigkeit. In Delphi wäre es etwa so (ungetestet):
procedure TMainForm.EnterBlockedState; begin Button42.Enabled := False; ... end; procedure TMainForm.LeaveBlockedState; begin Button42.Enabled := True; ... end; procedure TMainForm.MyButtonClick(Sender: TObject); begin TAsyncCalls.Invoke (procedure begin TAsyncCalls.VCLInvoke (EnterBlockedState); try // hier Speicherroutine aufrufen finally TAsyncCalls.VCLInvoke (LeaveBlockedState); end; end); eep(1000); end;
In C++ mußt du etwas mehr arbeiten, da es keine anonymen Methoden gibt.
-
@Mauro
Aber du gibst mir schon recht, dass du dir durch deinen Programmablauf Probleme einhandelst, die man normalerweise nicht hat?
Wofür brauchst du denn überhaupt 1.000 Controls? Ich glaube auch zu wissen, warum deine Funktion "Spielstand speichern" 5 Sekunden braucht. Wahrscheinlich dauert das Schreiben nur ein paar Millisekunden, aber 1.000 Controls zu löschen und neu zu erzeugen dauert eine Ewigkeit. Hast du mal getestet, wie lange das Speichern ohne Löschen/Erzeugen der Controls dauert?
-
Also der Reihe nach:
Die Speicherung von 9 MB auf die Festplatte dauert schon seine Zeit, wir sprechen hier von sehr vielen Daten eines Fußball Manager Spiels. Es sind wohl keine 5, sondern 2 - 3 Sekunden.
1000 Controls habe ich natürlich NIE zur gleichen Zeit. Da ich jedoch verschiedene Themenbereiche im Spiel habe, wie Transfermarkt, Training, Aufstellung usw. kommt es schon mal vor, daß ich zig Komponenten habe zur gleichen Zeit auf dem Screen. Da es von Thema abhängig immer andere Komponenten sind, Bilder, Grids, Labels, Buttons uvm. macht es hier durchaus Sinn, diese dynamisch zu löschen und neu zu erzeugen. (Der Speicher wird auch sauber aufgeräumt, habe ich schon öfters nachgeprüft).
Dein Vorschlag, hier einen einfachen Event Handler für den Button würde nur dann funktionieren, wenn ich ihn nicht lösche, das Löschen einer Komponente aus deren Event Handler führt zwangsläufig zu Abstürzen.
Dieses verhindere ich mit PostMessage aus dem EventHandler, dami tist gewährleistet, daß IMMER zuerst der Event Handler zu Ende geführt wird, bevor die Message Abarbeitung einsetzt.Darum meine Frage, was genau meinst Du mit Problemen hier? Ich programmiere das Spiel seit Jahren und habe seit etlichen Jahren keinen undefinierbaren Programmabsturz mehr.
Übrigens, kannst es Dir auf Ebay unter Mauro Manager anschauen, von was für Programmgrößenordnung wir hier sprechen, da sind etliche Bilder zu sehen mit den Komponenten drauf.
Mauro
-
Mauro77 schrieb:
das Löschen einer Komponente aus deren Event Handler führt zwangsläufig zu Abstürzen
Ist das so? Wenn ich mich nicht irre, sollte man innerhalb des Event-Handlers problemlos
delete TheButton;
machen können. In einem einfachen Test funktioniert das auch gut. Kannst du mal näher beschreiben, wie das zu einem Absturz führen kann?Mauro77 schrieb:
Übrigens, kannst es Dir auf Ebay unter Mauro Manager anschauen, von was für Programmgrößenordnung wir hier sprechen, da sind etliche Bilder zu sehen mit den Komponenten drauf.
Kurios - du verkaufst deine Software über eBay?
Sowas ist mir auch noch nie begegnetAber wenn's läuft, warum nicht.
-
Vor ungefähr 8 Jahren hatte ich immer wieder undefinierbare Abstürze.
Daraufhin habe ich mein Coding gepostet, wo ich eben aus dem Event Handler den Button gelöscht habe.
Daraufhin hat Damon Chandler (erfolgreicher C++ Buchautor) mich darauf aufmerksam gemacht. Der Grund ist wohl, daß man beim Löschen des Buttons die Absprungadresse zu der Ereignismethode mitlöscht in der man sich befindet und es nach Verlassen dieser Ereignismethode knallt, die Abstürze sind aber nicht zwingend!!!Ja, ich verkaufe es auf diese Weise, das Spiel macht auch mir selbst Spaß und außerdem die Erweiterungen, die ich immer wieder für dieses Spiel mache.
-
Mal ein wenig offtopic:
Muss man die Verwendung der original Vereinsnamen und Logos nicht lizensieren und hast du das? Sonst wird das u.U. verdammt teuer....