Bildschirmausgabe aktualisieren aus einer Schleife heraus

Fragen zu Tkinter.
Antworten
Telepython
User
Beiträge: 2
Registriert: Donnerstag 21. Mai 2015, 18:30

Hallo zusammen,
ich bin neu hier im Forum und möchte mich kurz vorstellen. Ich bin nicht mehr der Jüngste aber die Programmierung hat mich mal wieder gepackt.
Ich möchte mich in Python einarbeiten, weil ich es später auf dem Raspberry nutzen will. Grundlegende Programmierkenntnisse habe ich und auch Datenbankerfahrung.

Um bei den ersten Programmierschritten nicht wie immer bei "Hello World!" zu beginnen hatte ich mir gedacht ein Anwendung in Richtung "Game of Live" umzusetzen.
Wie gesagt meine Programmierkünste sind nicht allzu gut und damit ist der Code sicherlich noch verbesserbar.

Meine technischen Voraussetzungen sehen wie folgt aus:
- Win7 64bit
- Python3
- IDLE

Prinzip von "Game of Live" ist soweit klar und ich wollte die Ausgabe auf dem Bildschirm mit "Tkinter" bewerkstelligen. Da war schon die erste Hürde - unter Win7 heißt "Tkinter" "tkinter" :(

Die Ausgabe erstelle ich im Prinzip mit

Code: Alles auswählen

l = Label(root,bg = f, relief=SUNKEN, text = ' ',width = 2).grid(row = r,column = j)

Um das Label wirklich darzustellen muss ich es noch mit

Code: Alles auswählen

root.update() 
aktivieren. Unter Win7 geht irgendwie der Befehl "root.pack()" nicht :(

Soweit so gut.

Wenn ich jetzt aber in einer Schleife die Ausgabe erstellen will geht dieses nicht. Die Schleife läuft (das kann ich an der Print-Ausgabe erkenne, aber auich die Variable "step" bleibt immer bei "0") => Die Schleife läuft habe ich festgestellt , wenn ich im Code die Zeile "step += 1" schreibe. Aber die Ausgabe wird nicht aktualisiert.

Hier beispielhaft die Schleife

Code: Alles auswählen

while summenwert3 != summenwert21:
    print('Listen ungleich - Zähler= ', step)
    Liste2_erneut_berechnen() # Funktion um das Liste2 erneut zu berechnen
    step += step  
    Liste2_darstellen()     # Matrix 2 grafisch ausgeben
    root.update()  
und hier die Funktion

Code: Alles auswählen

def Liste2_darstellen():
    i = 0   # Zähler
    r = 0   # row - Zeile
    j = 0   # colum - Spalte
    c1 = 'orange'
    c2 = 'white'
    for i in range(0,n*n):
        r = i//n    # Zeilennummer ermitteln
        j = i%n     # Spaltennummer ermitteln
        if liste2[i] == 0:
            f = c2
        else:
            f = c1

        # Relief ['SUNKEN','FLAT','RAISED','GROOVE','RIDGE']
        l=Label(root,bg=f, relief=SUNKEN, text=' ',width=2).grid(row=r,column=j)        

Ich hoffe ich habe mich verständlich ausgedrückt. Es wäre schön, wenn ihr mir ein paar Tipps geben könntet. Danke im Voraus!

Hier der gesamte Code zum besseren Verständnis

Code: Alles auswählen

#!Test
# Programm zur Simulation des "Game of Live"
#
#
#
#

from tkinter import *
from random import randint
import time
import copy

# Funktionen definieren Anfang
# vvvvvvvvvvvvvvvvvvvvvvvvvvvv

def Liste1_berechnen():
    global summenwert1
    i = 0   # Zähler
    r = 0   # row - Zeile
    j = 0   # colum - Spalte
    c1 = 'orange'
    c2 = 'white'
    summenwert1 = 0
    
    for i in range(0,n*n):
        r = i//n    # Zeilennummer ermitteln
        j = i%n     # Spaltennummer ermitteln
        if j == 0:
            f = c2
            liste1.insert(i,0)  # Schreibe "0" wenn Spalte 0 ist
        elif j == (n-1):
            f = c2
            liste1.insert(i,0)  # Schreibe "0" wenn letzte Spalte erreicht ist (abhängig von 'n')
        elif r == 0:
            f = c2
            liste1.insert(i,0)  # Schreibe "0" wenn Zeile 0 ist
        elif r == (n-1):
            f = c2
            liste1.insert(i,0)  # Schreibe "0" wenn letzte Zeile erreicht ist (abhängig von 'n')
        else:
            zz = randint(1, 10)            
            if zz > 8:
                f = c1
                liste1.insert(i,1)
            else:
                f = c2
                liste1.insert(i,0)

        # Relief ['SUNKEN','FLAT','RAISED','GROOVE','RIDGE']
        l = Label(root,bg = f, relief=SUNKEN, text = ' ',width = 2).grid(row = r,column = j)
        summenwert1 += i*liste1[i]
    
# Funktionen Ende

def Liste1_lesen():
    i = 0   # Zähler
    r = 0   # row - Zeile
    j = 0   # colum - Spalte
    for i in range (0,n*n):
        r = i//n    # Zeilennummer ermitteln
        j = i%n     # Spaltennummer ermitteln
        if j == 0:
            print()
            print(liste1[i], end=' ')
        else:
            print(liste1[i], end=' ')
    print()
    print('====> Ende Matrix1 <=====')
# Funktionen Ende


def Liste2_rechnen():
    global summenwert2
    i = 0   # Zähler
    r = 0   # row - Zeile
    j = 0   # colum - Spalte
    summenwert2 = 0

    for i in range (0,n*n):
        r = i//n    # Zeilennummer ermitteln
        j = i%n     # Spaltennummer ermitteln
        if j == 0:
            liste2.insert(i,0)  # Schreibe "0" wenn Spalte 0 ist
        elif j == (n-1):
            liste2.insert(i,0)  # Schreibe "0" wenn letzte Spalte erreicht ist (abhängig von 'n')
        elif r == 0:
            liste2.insert(i,0)  # Schreibe "0" wenn Zeile 0 ist
        elif r == (n-1):
            liste2.insert(i,0)  # Schreibe "0" wenn letzte Zeile erreicht ist (abhängig von 'n')
        else:
            z0 = ((r*n)+j)
            z1 = ((((r-1)*n)+j)-1)
            z2 = (((r-1)*n)+j)
            z3 = ((((r-1)*n)+j)+1)
            z4 = (((r*n)+j)-1)
            z5 = (((r*n)+j)+1)
            z6 = ((((r+1)*n)+j)-1)
            z7 = (((r+1)*n)+j)
            z8 = ((((r+1)*n)+j)+1)    

            zg = liste1[z1]+liste1[z2]+liste1[z3]+liste1[z4]+liste1[z5]+liste1[z6]+liste1[z7]+liste1[z8]
            
            # print ('Summe von z0 = ', z)
            if zg <= 2:
                liste2.insert(z0,0)      # sterben - unterbevölkert
                
            if zg >=3 and zg <= 4:
                liste2.insert(z0,1)      # weiter leben
                
            if zg > 4:
                liste2.insert(z0,0)      # sterben - überbevölkert
        summenwert2 += i*liste2[i]
                
# Funktionen definieren Ende


def Liste2_lesen():
    i = 0   # Zähler
    r = 0   # row - Zeile
    j = 0   # colum - Spalte
    for i in range (0,n*n):
        r = i//n    # Zeilennummer ermitteln
        j = i%n     # Spaltennummer ermitteln
        if j == 0:
            print()
            print(liste2[i], end=' ')
        else:
            print(liste2[i], end=' ')
    print()
    print('====> Ende Matrix2 <=====')            
# Funktionen Ende

def Liste2_erneut_berechnen():
    global summenwert21
    i = 0   # Zähler
    r = 0   # row - Zeile
    j = 0   # colum - Spalte
    summenwert21 = 0

    for i in range (0,n*n):
        r = i//n    # Zeilennummer ermitteln
        j = i%n     # Spaltennummer ermitteln
        if j == 0:
            liste2[i] = 0  # Schreibe "0" wenn Spalte 0 ist
        elif j == (n-1):
            liste2[i] = 0  # Schreibe "0" wenn letzte Spalte erreicht ist (abhängig von 'n')
        elif r == 0:
            liste2[i] = 0  # Schreibe "0" wenn Zeile 0 ist
        elif r == (n-1):
            liste2[i] = 0  # Schreibe "0" wenn letzte Zeile erreicht ist (abhängig von 'n')
        else:
            z0 = ((r*n)+j)
            z1 = ((((r-1)*n)+j)-1)
            z2 = (((r-1)*n)+j)
            z3 = ((((r-1)*n)+j)+1)
            z4 = (((r*n)+j)-1)
            z5 = (((r*n)+j)+1)
            z6 = ((((r+1)*n)+j)-1)
            z7 = (((r+1)*n)+j)
            z8 = ((((r+1)*n)+j)+1)    

            zg = liste3[z1]+liste3[z2]+liste3[z3]+liste3[z4]+liste3[z5]+liste3[z6]+liste3[z7]+liste3[z8]
            
            # print ('Summe von z0 = ', z)
            if zg <= 2:
                liste2[z0] = 0      # sterben - unterbevölkert
                
            if zg >=3 and zg <= 4:
                liste2[z0] = 1       # weiter leben
                
            if zg > 4:
                liste2[z0] = 0       # sterben - überbevölkert
        summenwert21 += i*liste2[i]

                
# Funktionen definieren Ende


def Liste2_darstellen():
    i = 0   # Zähler
    r = 0   # row - Zeile
    j = 0   # colum - Spalte
    c1 = 'orange'
    c2 = 'white'
    for i in range(0,n*n):
        r = i//n    # Zeilennummer ermitteln
        j = i%n     # Spaltennummer ermitteln
        if liste2[i] == 0:
            f = c2
        else:
            f = c1

        # Relief ['SUNKEN','FLAT','RAISED','GROOVE','RIDGE']
        l=Label(root,bg=f, relief=SUNKEN, text=' ',width=2).grid(row=r,column=j)        
# Funktionen Ende


# ^^^^^^^^^^^^^^^^^^^^^^^^^^
# Funktionen definieren Ende
# ==================================================================================

# ==================================================================================
# Programmcode Start
# vvvvvvvvvvvvvvvvvvvvvvvvvvv
root = Tk()
root.title('===> Game of live <===')

n = 30                      # Anzahl von Spalten und Zeilen
liste1 = []                 # Startmatrix
liste2 = []                 # erste berechnete Ergebnis-Matrix
liste3 = []                 # Liste3 erstellen als Zwischenspeicher von Liste2
step = 0
summenwert3 = 0

Liste1_berechnen()          # Start Liste1 berechnen und darstellen
root.update()
summenwert3 = summenwert1
#Liste1_lesen()              # Liste1 mit print() ausgeben

Liste2_rechnen()            # Liste2 aus Liste1 berechnen
#Liste2_lesen()              # Liste2 mit print() ausgeben
Liste2_darstellen()         # Matrix 2 grafisch ausgeben
root.update()


liste3 = copy.copy(liste1)  # Liste1 in Liste3 kopieren
Liste2_erneut_berechnen()
Liste2_darstellen()         # Matrix 2 grafisch ausgeben
root.update()

liste3 = copy.copy(liste2)  # Liste2 in Liste3 kopieren

while summenwert3 != summenwert21:
    print('Listen ungleich - Zähler= ', step)
    Liste2_erneut_berechnen() # Funktion um das Liste2 erneut zu berechnen
    step += step  
    Liste2_darstellen()     # Matrix 2 grafisch ausgeben
    root.update()
     


print('fertig!')
root.mainloop()

# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Programmciode Ende
# ==================================================================================

BlackJack

@Telepython: Du schreibst da zwei Sachen Win 7 zu die absolut nichts mit dem Betriebssystem zu tun haben. Die Tk-Anbindung heisst auf jeder Plattform `tkinter` wenn man Python *3* verwendet, und es gibt auf keiner Plattform einen `root.pack()`-”Befehl” unter der Annahme das `root` ein `Tk`-Exemplar ist. Das macht auch gar keinen Sinn das Hauptfenster layouten zu wollen. Das wird ja nicht in einem anderen Widget dargestellt sondern ist selbst der ”oberste Container” für Widgets die im Hauptfenster angezeigt werden.

Wenn Du `update()` manuell aufrufst ist fast sicher etwas mit dem Programmentwurf nicht in Ordnung.

In der beispielhaften Schleife sehe ich nicht wo `summenwert3` und `summenwert21` je verändert werden. Bekam da aber schon gleich das dumpfe Gefühl ich werde bald ``global`` zu sehen bekommen. Schmeiss alle ``global`` raus und verwende ”echte” Funktionen. Eine Funktion (und auch Methoden) sollte nur Werte verwenden die als Argumente übergeben wurden und wenn es ein Ergebnis gibt, dann wird das per ``return`` an den Aufrufer zurückgegeben. Alles andere führt zu ganz fürchterlich unübersichtlichem Code mit undurchsichtigen Abhängigkeiten die man erst komplett sieht wenn man das gesamte Programm erfasst und im Blick hat und behält.

Die erste Zeile ist falsch, es sei denn Du hast wirklich ein Programm mit dem Namen ``Test`` welches diesen Quelltext ausführe kann.

Sternchen-Importe sollte man vermeiden weil man sich dadurch alle Namen aus anderen Modulen in das aktuelle Modul holt. Da verliert man dann schnell den Überblick was wo definiert wird und es besteht die Gefahr von Namenskollisionen. `time` wird importiert, aber dann gar nicht verwendet.

Verwende aussagekräftige Namen anstelle von einbuchstabigen Namen die dann in einem Kommentar erklärt werden müssen. Auch ist das durchnummerieren von Namen in der Regel ein Zeichen das man entweder andere Namen wählen sollte oder keine Einzelnamen sondern eine Datenstruktur. Oft eine Liste. Bei `liste1`, `liste2`, und `liste3` möchte man aber ganz dringend vernünftige Namen wählen. So dass man überall wo die Namen verwendet werden, weiss was sie bedeuten, und man nicht die Stelle im Quelltext suchen muss wo der Kommentar steht der das erklärt. Gute Namen zu finden und so wenig wie möglich Code kommentieren zu müssen ist manchmal schwierig, aber mit eine der wichtigsten Sachen beim Programmieren!

Einige Funktionen verwenden *eine* Schleife um dann aus dem Laufindex Zeilen- und Spaltennummer zu berechnen. Das ist unnötig kompliziert und indirekt wenn man doch gleich beides erstellen kann in dem man einfach zwei verschachtelte Schleifen schreibt und damit die verschachtelte Datenstruktur auch im verarbeitenden Code abbildet. Wobei man dann natürlich die 2D-Datenstruktur auch tatsächlich in einer verschachtelten Liste speichern sollte. Also eine Datenstruktur die auch tatsächlich der ”Natur” der Daten entspricht.

Da sind einige sehr fragwürdige, weil falsche, Kommentare. Wie oft sind denn die Funktionen laut Kommentierung zu Ende‽ Das sollte man auch überhaupt nicht kommentieren. Wo die Funktionsdefinitionen anfangen sieht man auch ohne Kommentar.

Sämtliche `list.insert()`-Aufrufe sind falsch weil sie verschleiern was da tatsächlich gemacht wird. Wie kommt man an der Stelle auf `insert()`? Es gibt eine viel passendere Methode die eigentlich in jedem Grundlagentutorial vorkommen sollte. `insert()` braucht man nur ganz selten, eigentlich fast nie.

Programmlogik und Benutzerschnittstelle sollten getrennt sein. So kann man die Programmlogik ohne GUI testen, oder beispielsweise erst einmal eine Benutzerschnittstelle mit Text in der Konsole umsetzen, und wenn das dann läuft eine GUI drauf setzen, und muss so nicht Programmlogik *und* GUI gleichzeitig herumschlagen.

Mit diesen durchnummerierten `summenwert*`-Namen werden komische Sachen gemacht. `summenwert1` wird berechnet um dann nur an `summenwert3` gebunden zu werden. Das wurde vorher mal an 0 gebunden, dieser Wert wird aber nirgends verwendet. WTF‽ Da steigt doch keiner mehr durch.

`Liste1_lesen()` liest überhaupt nichts sondern *schreibt* Informationen in die Konsole. Und `Liste2_lesen()` enthält nahezu den gleichen Code.

`Liste1_berechnen()` und `Liste2_rechnen()` enthalten teilweise den gleichen Code der einen Rahmen erstellt. Code- und Datenwiederholungen sind zu vermeiden weil das fehleranfällig ist wenn man an einer Stelle etwas ändert, bei den Wiederholungen aber entweder nichts oder etwas leicht anderes macht. Und `Liste2_rechnen()` und `Liste2_erneut_berechnen()` enthalten ebenfalls wieder fast identischen Code. Womit hast Du denn Deine grundlegende Programmiererfahrung gemacht?

Bei den Darstellungen erstellst Du ständig neue `Label`. Das funktioniert so nicht. Man erstellt die GUI einmal und aktualisiert die `Label` dann entsprechend des Programmzustands.

Auf Modulebene gehören nur Definitionen von Konstanten, Funktionen, und Klassen. Das Hauptprogramm steht üblicherweise in einer `main()`-Funktion. Damit verhindert man dann auch effektiv das man nicht ausversehen in einer Funktion oder Methode eine Variable verwendet die man eigentlich als Argument hätte übergeben müssen.

Das `copy`-Modul wird nicht wirklich benötigt. Eine flache Kopie von einer Liste hätte man auch ganz einfach mit einem `list()`-Aufruf mit der ursprünglichen Liste machen können. Allerdings ist so ein Vorgehen in Python sowieso eher unüblich. Man erstellt eher eine neue Liste mit den neuen Werten, statt eine alte zu kopieren und dann zu verändern.

``print('fertig')`` *vor* dem Aufruf der GUI-Hauptschleife ist inhaltlich so etwas von falsch weil das Programm eigentlich erst mit Eintritt in die GUI-Hauptschleife losgeht. GUI-Programmierung funktioniert etwas anders als Du das da versuchst. Die ist ereignisbasiert, das heisst der Programmierer gibt nicht mehr den Programmablauf komplett fest vor, sondern man initialisiert den Anfangszustand und erstellt die GUI, registriert Rückruffunktionen auf Ereignisse die einen interessieren (Schaltfläche geklickt, Zeit abgelaufen, …) und die Rückruffunktion reagiert dann kurz darauf und gibt so schnell wie möglich die Kontrolle wieder an die GUI-Hauptschleife zurück. Denn solange die Rückruffunktion läuft, friert die GUI ein und wird nicht mehr aktualisiert und reagiert nicht mehr auf den Benutzer.

Das heisst übrigends „Game Of Life“. :-)
Telepython
User
Beiträge: 2
Registriert: Donnerstag 21. Mai 2015, 18:30

Hallo BlackJack,

vielen Dank für deinen ausführlichen Kommentar und deiner Geduld meinen "Code" zu verstehen. Einige Kommentare sind für mich nur als Hilfsmittel gesetzt um zu schauen was der Code tut. Dabei habe ich leider nicht auf die textlichen Inhalt geschaut. Aber klar ist mir, dass Kommentare nur dort hingehören, wo der Code es nicht eindeutig wiedergibt.

Deine Hinweise auf ''globale'' und ''echte'' Funktionen, Rückgabewerten in Funktionen, verschachtelte ''listen'', Verzicht auf ''insert'', ''copy'', definieren der main()-Funktion
sowie die strikte Trennung zwischen GUI und Code genau anschauen und versuchen umzusetzen.

Meine "Programnmierküste" habe ich mir in der Vergangenheit autodidaktisch beigebracht begonnen mit Basic über VB und zum Schluss mit Accessbasic beigebracht.

Also mach ich mich mal wieder ans Werk...

Nochmals vielen Dank BlackJack!
Antworten