Tkinter Button bleibt gedrückt

Fragen zu Tkinter.
Antworten
sedi
User
Beiträge: 104
Registriert: Sonntag 9. Dezember 2007, 19:22

Hallo zusammen,

in einem Teil meines Programms muss ein *save* - Button gedrückt werden, um einen neuen Datensatz zu speichern. Vor dem Speichern wird ein Duplikattest durchgeführt. Existiert der Datensatz schon, dann wird eine Messagebox ausgegeben und die Speicherungsmethode verlassen durch

Code: Alles auswählen

return 'break'
. So weit der gewollte Ablauf...

ABER:
1) Wenn ich auf den Button klicke dann senkt er sich nicht wie gewohnt. Ich klicke drauf und optisch verändert sich gar nichts. Die mit *bind* verbundene Methode wird aber ausgeführt.
2) Manchmal - ich kann den Fehler leider nicht reproduzieren - bleibt der Button nach der Aktion dann aber gedrückt??? :K

Leider weiß ich nicht wie ich den Code hier übermitteln soll, da das Projekt ganz schön groß ist und ich keine blasse Ahnung habe, wo der Fehler liegt. Außerdem könnte man es auch nicht testen, da es eine Datenbank benutzt.

Vielleicht hatte ja von euch jemand das gleiche Problem...
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Der Fehler wird wohl irgendwo in der mittels bind gebundenen Methode liegen, vielleicht zeigst du die hier. Reagiert dein Programm denn noch, nachdem du auf den Button geklickt hast? Wird die Methode korrekt verlassen, oder bleibt das Programm darin hängen?
Das Leben ist wie ein Tennisball.
BlackJack

Ich würde den Fehler eher darin sehen das überhaupt `bind()` verwendet wird. Das ist nicht der vorgesehene Weg um Aktionen bei Buttons zu hinterlegen. Dafür gibt es die `command`-Option.
sedi
User
Beiträge: 104
Registriert: Sonntag 9. Dezember 2007, 19:22

Danke @EyDu: Das Programm reagiert vollkommen wie erwartet, nur optisch nicht.

Hier die *setup* - Methode im *Presenter* (MVP):

Code: Alles auswählen

    def setup(self, *eventargs, **presets):
        '''
        ... hier code ausgelassen, der nur die Widgets erzeugt (Views)
        '''
        self.view.bind_all("<Control-x>", self.presenter.close)
        self.view.btn_close.bind("<Button-1>", self.presenter.close)

        self.view.bind_all("<Control-s>", self.presenter.create)
        self.view.btn_save.bind("<Button-1>", self.presenter.create)
        self.view.btn_save.bind("<Return>", self.presenter.create)

        '''
        ... 
        '''

###
# Hier die *create* - Methode

    def create(self, *eventargs):
        """Der uebergeordnete Presenter/Controller ruft diese
        Methode auf.

        Tests:
        
            1) alle noetigen Felder vorhanden?
            2) handelt es sich um ein Duplikat?
        
        Test ok => _create_dataset
        
        :param eventargs:
        :return:
        """
        feld = self.view.get_values()

        if not (feld["nachname"] or \
                feld["vorname"] or \
                feld["geschlecht"] or \
                feld["klasse"]):
            tkMessageBox.showerror("ERROR",
                u"Nachname, Vorname, Klasse und Geschlecht nötig")
            return 'break'

        try:
            schueler = Schueler.select().where(
                Schueler.nachname==feld["nachname"],
                Schueler.vorname==feld["vorname"],
                Schueler.geschlecht==feld["geschlecht"],
                Schueler.spitzname==feld["spitzname"],
                Schueler.klasse==feld["klasse"],
                Schueler.zweig==feld["zweig"],
                Schueler.gebdat==feld["geburtstag"]
            ).get()

            # Warnung, der Schueler existiert bereits
            self.app.view.write_statusinfo(
                2,
                u"Schüler existiert bereits [ID=%s]!" % (schueler.id,))

        except peewee.DoesNotExist:
            self._create_dataset(feld)
            self.listview.remove_filter()

###
# eigentliche Speicherungsmethode

    def _create_dataset(self, werte):
        self.presenter.schueler = Schueler.create(
            nachname=werte["nachname"],
            vorname=werte["vorname"],
            geschlecht=werte["geschlecht"],
            klasse=werte["klasse"],
            heimantrag=werte["heimantrag"],
            zweig=werte["zweig"],
            gebdat=werte["geburtstag"],
            eintritt=werte["eintritt"],
            austritt=werte["austritt"],
            sonstiges=werte["sonstiges"])

        # View anpassen

        if werte["geschlecht"] in ["W", "w"]:
            self.app.view.write_statusinfo(
                1, u"Schülerin eingetragen:")
            self.app.view.write_statusinfo(
                2, u"%s, %s [id: %s]" % (werte["nachname"],
                                         werte["vorname"],
                                         self.presenter.schueler.id))
        else:
            self.app.view.write_statusinfo(
                1, u"Schüler eingetragen:")
            self.app.view.write_statusinfo(
                2, u"%s, %s [id: %s]" % (werte["nachname"],
                                         werte["vorname"],
                                         self.presenter.schueler.id))

        self.view.ent_nachname.focus_set()
@BlackJack: Warum kann man einen Callback nicht mit *bind* anhängen? Wenn man MVP umzusetzen will ist das doch gar nicht anders möglich. Der View erstellt die ganzen Widgets, der Presenter kümmert sich um die Funktionalität, dh. er weist dem Widget nachträglich den Callback zu.
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
BlackJack

@sedi: Das kann man nicht machen weil sich Buttons dann nicht mehr so verhalten wie der Benutzer das von Buttons erwartet. Genau das ist ja gerade Dein Problem. Buttons lösen keine Aktion aus wenn man den Mouse-Button herunterdrückt wenn man sich darüber befindet. Das bewirkt erst einmal nur ein zeichnen eines gedrückten Buttons. Von da an gibt es zwei Möglichkeiten: Die Maustaste wird über dem Button losgelassen, dann wird die Aktion ausgeführt und danach der Button wieder normal gezeichnet, oder der Benutzer verlässt bei gedrückter Maustaste den Button und lässt ausserhalb los. Dann wird die Aktion *nicht* ausgelöst. Diese ganze Logik steckt im `Button` und das alles umgehst Du wenn Du selbst ein `bind()` machst. Der Button implementiert das selbst in dem er auf die verschiedenen Ereignisse reagiert, Du *musst* also die `command`-Option verwenden wenn Du das normale Standardverhalten von Buttons haben möchtest und den Benutzer nicht mit völlig anderem Verhalten irritieren willst.
sedi
User
Beiträge: 104
Registriert: Sonntag 9. Dezember 2007, 19:22

Nach weiteren Tests ist aufgefallen, dass nur der Messagebox-Zweig Probleme macht. Kommentiert man in meinem Code oben die Zeilen 39 bis 41 aus, dann tritt der Fehler nicht auf.

Das ganze hat also mit der Messagebox zu tun!

@BlackJack: Deine Aussage kann ich deshalb nicht bestätigen, denn die Probleme entstehen nur, wenn diese Fehlermeldung über die Messagebox ausgegeben werden sollen. Ich habe dann den Gegentest gemacht und alle Messageboxen aus dem Code entfernt und siehe da, keine Probleme. Zu Deiner Aussage konnte ich auch im Python-Code von Tkinter selbst keinen Hinweis finden, dass das mit bind nicht klappen sollte. Vielleicht habe ich das ja auch nicht richtig verstanden - Deinen Worten entnehme ich folgenden Ablauf:

Maus über Button --> grafische Veränderung des Buttons
--> dann Mausbutton klicken --> grafische Veränderung des Buttons
--> Mausbutton wieder loslassen --> Aktion startet --> grafische Veränderung des Buttons

Das genau tritt aber nicht ein. Ist die MessageBox in den Code eingebaut, dann wird die Aktion gestartet **bevor** die grafische Veränderung des Buttons eintritt:

Maus über Button --> grafische Veränderung des Buttons
--> dann Mausbutton klicken --> **Aktion startet** (keine grafische Veränderung des Buttons!) --> Mausbutton wird im gedrückten Zustand angezeigt.
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
BlackJack

@sedi: Also bei mir verhalten sich Buttons so wie ich das beschrieben. Das tun sie nicht mehr wenn man `bind()` auf den Mausklick setzt statt `command` zu verwenden. Und das sollte man nicht machen wenn man den Benutzer nicht verwirren möchte. Ich verstehe auch nicht warum Du kein `command` verwenden kannst. Das ist der dafür vorgesehene Weg.
sedi
User
Beiträge: 104
Registriert: Sonntag 9. Dezember 2007, 19:22

Hallo @BlackJack!

Ich habe mal einen einfachen Test geschrieben, der Deine Aussage bestätigt: keine grafische Veränderung falls das Ereignis mit *bind* angeworfen wird!

Code: Alles auswählen

import ttk
tk = ttk.Tkinter


class App(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        
        self.btn1 = tk.Button(self, 
                              text="normal",
                              command=self.click_me)
        self.btn1.pack()
        
        # bind button
        
        self.btn2 = tk.Button(self, 
                              text="mit bind")
        self.btn2.pack()
    
    def click_me(self, *eventargs):
        tkMessageBox.showerror("ERROR", u"bla bla")
        return 'break'


def main():
    app = App(tk.Tk(), )
    app.pack()
    app.btn2.bind("<Button-1>", app.click_me)
    app.mainloop()
    return 0


if __name__ == '__main__':
    main()
Dann muss ich jetzt 'ne Menge ändern :(

Warum ich das so gemacht habe? Nach jetzigem Wissensstand ist das eine berechtigte Frage...

Aber Danke
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
Antworten