Mehrmaliges Buttondrücken unterdrücken



  • 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 per PeekMessage 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



  • 1. http://lmgtfy.com/?q=RAII

    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
    Leichtfertig Application->ProcessMessages() ist nie eine gute Idee, da bekommst du ruckzuck selstames Verhalten, dass sich nur schwer erklären und debuggen lässt. Wenn man Application->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.


Anmelden zum Antworten