Mehrmaliges Buttondrücken unterdrücken
-
Hi an Alle,
ich habe auf meiner Form einen Button. Bei einem Click drauf werden Daten auf die Festplatte geschrieben, die ganze Aktion dauert um die 5 Sekunden. Wenn ich in dieser Zeit den Button nochmal drücke, dann wird die Speicherung der Daten zum zweiten Mal ausgeführt.
Somit meine Frage, kann ich irgendwie den Button für die Zeit des Speicherns "ausschalten" ??
(Mit OnClick auf NULL setzen und dann später auf die Ereignisroutine funktioniert es nicht, der Rechner "merkt" sich den Click...)
Danke im Voraus,
Mauro
-
Versuch mal den Button auf 'disabled' zu setzen, d.h.
button->Enabled = false; // saving data button->Enabled = true;
-
Hilft leider nicht, der Grund ist, daß, diese Zeilen sich ja in der Methode zum Speichern der Daten befinden und wenn ich einen Click tätige, dan wird diese Methode zu Ende ausgeführt und erst dann der OnClick Ereignis angestossen...
Ich denke, daß ich irgendwie diesen Event abfangen muss, jedoch würde ich gerne die allgemeine Vorgehensweise wissen....
-
Zeig doch bitte mal genau, wie du deine Save- Methode aufrufst, sprich den Quellcode posten.
-
void THauptformular::ManagerAnzeigen(Wettbewerbe wettbewerb, int spieltag, int spielabschnitt, int minute) { ...... // Autosicherung durchführen int wochentag = datum.DayOfWeek(); if (wochentag == 1) { int tage = Manager::OptionAutosaveZeitHolen() * 7; if (Manager::OptionAutosaveHolen() && datum - tage >= aktuell.autosaveDatum) { aktuell.autosaveDatum = datum; String pfad = ExtractFilePath(ParamStr(0)) + "Save\\"; String datei = pfad + "Autosave.emm"; Statustafel.at(1)->Caption = "Autosicherung wird durchgeführt"; // Bildkomponenten vollzeichnen KlareTafel->Update(); for (int i = 0; i < KlareTafel->ComponentCount; i++) { TControl *control = KlareTafel->Controls[i]; control->Update(); } SpielstandSpeichern(datei.c_str()); Statustafel.at(1)->Caption = ""; } } }
In SpielstandSpeichern läuft die Speicherung, wenn ich davor und danach den Button manipuliere, hat es keine Auswirkung auf den Click, da dieser nach Ende der Routine ausgeführt wird...
Mauro
-
und natürlich wird diese ganze Methode (ManagerAnzeigen) aus OnClick Ereignis des Buttons aufgerufen...
-
Das geht genau so, wie Th69 es beschrieben hat. Benutze ich an einigen Stellen in meinen Projekten, um dein Problem zu vermeiden. Jetzt stellt sich die Frage: Wer von uns beiden macht da was falsch?
-
Der VCL Haupthread ist mit dem Speichern beschäftigt. In diese Zeit ruht deswegen auch die Windows-Nachrichtenverarbeitung. Nach dem Speichern wird der Button wieder auf Enabled gestellt, die Onclick Methode wird daraufhin verlassen und Windows liefert die nächste Windows Message aus, die ja so lange gepuffert werden musste. Jetzt ist der Button ja aber schon wieder enabled und das Ereignis OnClick wird erneut ausgelöst.
-
Morle schrieb:
Der VCL Haupthread ist mit dem Speichern beschäftigt. In diese Zeit ruht deswegen auch die Windows-Nachrichtenverarbeitung. Nach dem Speichern wird der Button wieder auf Enabled gestellt, die Onclick Methode wird daraufhin verlassen und Windows liefert die nächste Windows Message aus, die ja so lange gepuffert werden musste. Jetzt ist der Button ja aber schon wieder enabled und das Ereignis OnClick wird erneut ausgelöst.
Wenn
Enabled
auf false gesetzt ist wird für das deaktivierte Element kein Event ausgelöst/gepuffert. Für den Fall, dass der Benutzer schneller klickt als die Message Queue das Click Event zustellt und damit 2 Click Events in der Queue stehen (was ich mir nicht vorstellen kann) muss man perPeekMessage
die Message Queue durchlaufen und alle Nachrichten entfernen, die diesen Button betreffen.
-
Mauro77 schrieb:
Hilft leider nicht, der Grund ist, daß, diese Zeilen sich ja in der Methode zum Speichern der Daten befinden und wenn ich einen Click tätige, dan wird diese Methode zu Ende ausgeführt und erst dann der OnClick Ereignis angestossen...
Ich denke, daß ich irgendwie diesen Event abfangen muss, jedoch würde ich gerne die allgemeine Vorgehensweise wissen....
Du musst diese Zeilen in den Event Handler des Buttons packen:
TForm1::OnClickButtonSave( TObject* Sender ) { ButtonSave->Enabled = false; save_data(); ButtonSave->Enabled = true; }
-
DocShoe schrieb:
Du musst diese Zeilen in den Event Handler des Buttons packen:
Aber mit try/__finally, wenn's geht.
Übrigens sollte man alle Operationen, die viel länger als eine Zehntelsekunde dauern, in einen anderen Thread auslagern. Es ist einfach nicht die Aufgabe des UI-Threads, zehn Sekunden lang irgendeine Datei auf die Festplatte zu speichern. Wenn du das machst, hast du auch das Problem mit der verzögerten Message-Queue-Bearbeitung nicht.
-
audacia schrieb:
DocShoe schrieb:
Du musst diese Zeilen in den Event Handler des Buttons packen:
Aber mit try/__finally, wenn's geht.
Oder per RAII:
#include <boost/noncopyable.hpp> template<typename ControlType> class ScopedVCLComponentDisabler: boost::noncopyable { ControlType* Control_; bool SavedState_; public: ScopedVCLComponentDisabler( ControlType* Control ) : Control_( Control ), SavedState_( true ) { if( Control ) { SavedState_ = Control->Enabled; Control->Enabled = false; } } ~ScopedVCLComponentDisabler() { if( Control_ ) Control_->Enabled = SavedState_; } };
und
void TForm1::OnClickButtonSave( TObject* Sender ) { ScopedVCLComponentDisabler<TButton>( ButtonSave ); save_data(); }
-
DocShoe schrieb:
Wenn
Enabled
auf false gesetzt ist wird für das deaktivierte Element kein Event ausgelöst/gepuffert. Für den Fall, dass der Benutzer schneller klickt als die Message Queue das Click Event zustellt und damit 2 Click Events in der Queue stehen (was ich mir nicht vorstellen kann) muss man perPeekMessage
die Message Queue durchlaufen und alle Nachrichten entfernen, die diesen Button betreffen.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.
-
DocShoe schrieb:
Oder per RAII: [...]
Klar, das geht auch. Warum einfach, wenn's auch umständlich geht
-
Rufe einfach Application->ProcessMessages() auf, bevor du den Button wieder anschaltest.
-
Danke erstmals für die ganzen Antworten, ich werde die Vorschläge heute ausprobieren.
1. Zwei Fragen habe ich aber noch, was ist RAII? Habe ich das in C++ Builder 20006? (Von Boost habe ich schon mal gehört...)
2. Was bringt mir der zweite Thread? Die Problematik mit dem Button bleibt die gleiche, der Thread würde ja immer bei einem Click ausgelöst werden oder bin ich hier total falsch? Habe noch nie die Notwendigkeit gehabt, mich mit Threads auseinanderzusetzen.
Danke,
Mauro
-
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