Mehrmaliges Buttondrücken unterdrücken
-
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....
-
klar, ich zahle jeden Monat 150 Tausend an Alle Beteiligten...
Im Ernst: Solange ich das Spiel ohne Originalnamen/Wappen anbiete und es die Möglichkeit gibt, diese in Eigenregie runterzuladen und ins SPiel zu integrieren, ist alles in Butter...
So hat es damals schon Anstoss gehandhabt...
-
Mit Problem meine ich, dass die Lösung, die von mehreren Leuten hier unabhängig voneinander hier vorgestellt wurde, nicht funktioniert. Ich habe nicht behauptet, dass deine Software abstürzt. Wie dem auch sein, der Programmablauf ist schon eigenartig und in meinen Augen Frickelei, die durch ein unsauberes Design entstanden ist. Und dadurch hast du dir jetzt ein anderes Problem eingehandelt, dass du nicht hättest, wenn das Design sauber gewesen wäre. Aufgrund des Entwicklungsstandes scheint ein Redesign wohl unmöglich oder sehr aufwändig, also muss man sehen, wie man das Problem anders in den Griff bekommt.
Reicht es eigentlich nicht aus, den Button in seinem Eventhandler zu deaktivieren? Er muss ja gar nicht reaktiviert werden, da er im Laufe der Nachrichtenbehandlung gelöscht und später neu erzeugt wird.
@Audacia
Ich kann mir schon vorstellen, dass die Komponente, in deren Ereignishandler man sich gerade befindet, nicht im Ereignishandler gelöscht werden darf. Schließlich könnten nach dem Aufruf des Ereignishandlers weitere Methoden der Komponente aufgerufen oder auf Attribute zugegriffen werden , was dann zu UB führt. Hab jetzt gerade keine Zeit und Lust in den Delphi Source zu gucken, wie der Ereignishandler aussieht und ob nach dem Handler Aufruf noch irgendwas mit dem Objekt gemacht wird. Würde fast drauf wetten, dass da noch was passiert.Edit 1:
Gut, auf den zweiten Blick ist die Verwendung von PostMessage doch die einzige Möglichkeit, Funktionalität vom Click Event zu entkoppeln. Allerdings würde ich dazu nciht die Dispatch() Methode überschreiben, sondern Message Handler für benutzerdefinierte Nachrichten registrieren.Edit 2:
Hab gerade doch mal in den Delphi Source geschaut und hätte die Wette verloren ;). Der OnClick Handler wird tatsächlich am Ende der Nachrichtenverarbeitungskette aufgerufen und greift damit nicht mehr auf Elemente des auslösenden Objekts zu. Allerdings wird nach dem Klicken noch die WM_LMOUSEUP Nachricht durch dieDefaultHandler
Methode bearbeitet, was jetzt zu UB führt.