Tkinter Zähler

Fragen zu Tkinter.
Antworten
hallo
User
Beiträge: 4
Registriert: Freitag 24. Januar 2014, 19:34

Hallo,
ich habe gerade mit der tkinter Programmierung angefangen und will einen Zähler programmieren:
immer wenn ich auf einen Button drücke soll eine variable "zahl" um 1 erhöht werden und ausgegeben werden.
Ich komm einfach nicht weiter :?: :?:
es wäre nett wenn mir einer mit einem kleinem Quelltext helfen würde. :)
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@hallo: Wenn Du nicht weiter kommst, wie weit bist Du denn bisher gekommen? Kannst Du Dein bisheriges Programm posten und schreiben, was daran nicht funktioniert?
hallo
User
Beiträge: 4
Registriert: Freitag 24. Januar 2014, 19:34

Code: Alles auswählen



from tkinter import*
count=0#anfangs counter bei 0


def druck(count):#counter Funktion
    print(count)
    return(count+1)




meinfenster = Tk()
Label(meinfenster,text="drücke den Knopf").pack()
Button(meinfenster,text="drück mich",command=druck(count)).pack()
count=druck(count)
print(count)
meinfenster.mainloop()


Problem gibt 1 aus ende
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@hallo: an welcher Stelle weist Du der Variable »count« etwas anderes zu als 0?
BlackJack

@hallo: Das `command`-Argument muss ein aufrufbares Objekt sein, also zum Beispiel eine Funktion oder Methode. Du übergibst dort aber eine 1, denn das ist an der Stelle der Rückgabewert vom ``druck(count)``-Aufruf. Und eine 1 ist offensichtlich nicht aufrufbar:

Code: Alles auswählen

In [1]: 1()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-769e123a5ede> in <module>()
----> 1 1()

TypeError: 'int' object is not callable
``return`` ist übrigens keine Funktion, darum sollte man das auch nicht schreiben als wäre es eine. Die Klammern gehören dort nicht hin weil sie völlig überflüssig sind.

Kommentare sollten dem Leser einen Mehrwert gegenüber dem Code bieten. Beide Kommentare tun das nicht. Das `count` am Anfang 0 ist und das ``def druck(…)`` eine Funktion definiert ist trivial aus dem Code ersichtlich. Der Name der Funktion könnte besser sein und beschreiben was die Funktion tut, zum Beispiel `update_counter()`.

Eine saubere Lösung würde hier wohl eine Klasse enthalten. IMHO sollte man objektorientierte Programmierung (OOP) halbwegs drauf haben *bevor* man sich an die GUI-Programmierung wagt.
hallo
User
Beiträge: 4
Registriert: Freitag 24. Januar 2014, 19:34

ok danke
BlackJack

Mal so als Beispiel:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import print_function, unicode_literals
try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk


class CounterUI(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.counter = 0
        self.counter_label = tk.Label(self)
        self.counter_label.pack()
        tk.Button(self, text='drück mich', command=self.count_up).pack()
        self._update_counter()

    def _update_counter(self):
        self.counter_label['text'] = str(self.counter)
        
    def count_up(self):
        self.counter += 1
        self._update_counter()


def main():
    root = tk.Tk()
    CounterUI(root).pack()
    root.mainloop()


if __name__ == '__main__':
    main()
hallo
User
Beiträge: 4
Registriert: Freitag 24. Januar 2014, 19:34

Meine Lösung:

Code: Alles auswählen

from tkinter import*
klicks=0
root=Tk()
label=Label(root,text="")
label.pack()
def counter():
    global label
    global klicks
    label.configure(text=klicks)
    label.update()
    klicks+=1
Button(root,text="Klick mich",command=counter).pack()
mainloop()

BlackJack

``global`` ist keine Lösung sondern ein Problem. :-)
jqz4n
User
Beiträge: 21
Registriert: Sonntag 2. Februar 2014, 19:26

Eine Variante, die ohne "global" auskommt, sie basiert auf der letzten Version von hallo:

ACHTUNG! Dieses Programm funktioniert zwar, die Anwendung wird jedoch nicht empfohlen!
Die zuvor von BlackJack vorgestellte Anwendung ist auf jeden Fall die empfehlenswertere, da leichter erweiterbare!

Code: Alles auswählen

#!/usr/bin/python
import sys
if sys.version_info.major < 3:
    import Tkinter as tkinter
else:
    import tkinter
root=tkinter.Tk()
label=tkinter.Label(root,text="")
label.pack()
def counter(klicks=[]):
    label.configure(text=len(klicks))
    label.update()
    klicks.append(None)
    
tkinter.Button(root,text="Klick mich",command=counter).pack()
tkinter.mainloop()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@jqz4n: Naja, funktionieren ist relativ. Das funktioniert genau dann, wenn man genau einen Button für den Zähler hat, sonst müsste man für jeden Zähler eine neue Funktion schreiben. Der default-Wert für alle ``klicks`` ist nämlich immer der selbe (darauf baut deine Implementierung sogar auf). Wenn man das in Kauf nehmen will, dann ist die Implementierung über das Verlängern der Liste natürlich ganz unsinnig. Genau so gut könntest du für klicks eine Liste verwenden, welche genau ein Element als Inhalt hat: die Anzahl der Klicks:

Code: Alles auswählen

def counter(klicks=[0]):
    ...
    klicks[0] += 1
Das ist natürlich noch immer ein grausamer Hack. Ein wenig schöner wird es, wenn du die Funktionen verpackst:

Code: Alles auswählen

def make_counter(initial=0):
    value = initial
    
    def counter():
        ...
        value += 1
    
    return counter

...

tkinter.Button(root,text="Klick mich",command=make_counter(0)).pack()
Das ganze ist übrigens noch immer total unpraktisch, da von außen nicht auf den Zähler zugegriffen werden kann. Daher bietet sich, wie von BlackJack schon vorgeschlagen, der Ansatz über eine eigene Klasse an.

Bei der Gelegenheit solltest du auch gleich noch einen weiteren Blick auf seinen Code werfen. Dein Vorgehen beim Importieren von Tkinter ist nicht gerade ideomatisch. Der Weg über die Ausnahme ist deutlich schöner.
Das Leben ist wie ein Tennisball.
jqz4n
User
Beiträge: 21
Registriert: Sonntag 2. Februar 2014, 19:26

EyDu hat geschrieben:Das ist natürlich noch immer ein grausamer Hack.
So gut wie sämtliche Lösungen für das Problem, die nicht auf Klassen bassieren, sind grausame Hacks. Gut, die Variante mit dem einen Wert in der Liste geht, wie auch die Variante des Tkinter-Imports "ein wenig" schonender mit dem Arbeitsspeicher um ;)

Die zweite Variante des Codes funktioniert meines Erachtens nach nicht, weil die counter-Funktion nicht auf die value-Variable zugreifen kann. (Da value nicht in den locals der counter-Funktion ist, sondern nur in denen der make_counter-Funktion)

Aber schlechte Beispiele sind auch Beispiele ;)
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

`couter` kann auf `value` zugreifen (-> Closure) ... nur aendern kann man `value` nicht. Da es aber beides gleichzeitig tun will fuehrt es zu einem `UnboundLocalError`.

Globale Variablen, direkt als Name mit `global` oder als mutable Datenstruktur wir Dictionaries, sind allerdings weit weniger grausam als eine Funktion die Parameter hat, allerdings nur dann korrekt funktioniert, wenn man sie nicht benutzt.
jqz4n
User
Beiträge: 21
Registriert: Sonntag 2. Februar 2014, 19:26

cofi hat geschrieben:`couter` kann auf `value` zugreifen (-> Closure) ... nur aendern kann man `value` nicht. Da es aber beides gleichzeitig tun will fuehrt es zu einem `UnboundLocalError`.
Der Error wird allerdings bereits geworfen, wenn ich in der counter-Subfunktion

Code: Alles auswählen

...
def make_counter(initial=0):
    ...
    def counter():
        label.configure(text=value)
        ...

schreibe. – Obgleich zu diesem Zeitpunkt "value" noch gar nicht geändert wurde. Warum?
BlackJack

@jqz4n: Weil in den ``...`` in der inneren Funktion die Zeile steht die `value` in der Funktion neu bindet und damit zu einem lokalen Namen macht, der in der ersten Zeile halt noch nicht gebunden ist.
Antworten