after() Funktion nur einmal möglich?

Fragen zu Tkinter.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@jake-the-snake: Du hast eine falsche Vorstellung davon, wie ein Programm abläuft. Was passiert alles, bevor die Methode write_precoding aufgerufen wird, Du also zu Zeile 32 kommst? Zum Glück weißt Du nicht, wie man globale Variablen benutzt, sonst wüßtest Du wahrscheinlich nur, wie man sie falsch benutzt; codeprocess kann so nicht verwendet werden, wie Du es Dir denkst und wenn Du es richtig löst, wirst Du codeprocess nicht brauchen. Hast Du einen Debugger, wo Du mal Schritt für Schritt Dein Programm durchgehen kannst?
jake-the-snake

Hallo Sirius3

Ich habe hier von Ubuntu den gedit und den auf python3-Modus eingestellt.

Zum Ablauf:
Das Programm läuft von oben nach unten durch und startet dann mit der Mainloop.
Nur Ereignisse können den Programmzeiger aus der Mainloop holen. Es läuft ja auch alles bis Zeile 32, wenn man den Button drückt.
Nur, nachdem der Block write_precoding(self) abgearbeitet wurde, müsste doch der Programmzeiger wieder in die Mainloop springen.
Tut er aber nicht. Habe auch schon ein "return" unter den write_precoding(self)-Block gesetzt. Geht auch nicht, sowie gedacht.

Aber nach deiner Antwort gibt es eine Lösung, wo die codeprocess-Variable nicht benötigt wird? Hör ich doch so richtig heraus? :)

Gruss jts
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi jake-the-snake

Habe mich heute Morgen noch ein bisschen deinem Problem gewidmet. Hoffe mein Skript hat eine klärende Wirkung:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import textwrap
import tkinter as tk

APP_TITLE = "Verschlüsselung"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 300
APP_HEIGHT = 200

        
class Application(tk.Frame):

    def __init__(self, master):
        self.master = master
        
        self.codeprocess = "no"
        self.label_var = tk.StringVar()
        
        # Pre-Code-Button
        self.button_precoding = tk.Button(master, text="Verschlüsseln ►", fg="red",
            command=self.write_precoding)
        self.button_precoding.pack(expand=True)

    ####################
    # Codierungs-Block #
    ####################
    def write_precoding(self):
        self.button_precoding.config(state='disabled')
        # Prozess-Meldung
        self.label_var.set("Bitte warten...\nprecoding Ansprung hat geklappt!")
        self.four_label = tk.Label(self.master, textvariable=self.label_var,
            font=("TimesNewRoman", 18), relief="groove", bg='#FFA5A5',padx=5,
            pady=5)
        self.four_label.pack(expand=True)
        self.master.after(3000, self.write_coding)
 
    def write_coding(self):
        self.codeprocess = "yes"
        self.label_var.set("Beginn des Codierungsblock")
        self.master.after(3000, self.end_coding)
        
    def end_coding(self):
        message = '''Der Codierungsblock ist beendet!
        Nun kann der Verschlüsselungsprozess neu gestartet werden.
        WICHTIG: Das Programm kehr nicht zum Mainloop-Block zurück!!!
        Der im Hintergrund laufenden 'mainloop'-Thread wartet nun auf ein neues
        Ereigniss. In unserem Fall wäre dies eine Neuaktivierung des Buttons
        "Verschlüsseln ►"'''
        template = textwrap.dedent(message).rstrip()
        self.four_label.config(bg='#BADA6A')
        self.label_var.set(template)
        self.master.after(8000, self.master.destroy)

                  
####################
# Globale Funktion #
####################
def center_window(width=300, height=200):
    # get screen width and height
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()
 
    # calculate position x and y coordinates
    x = (screen_width/2) - (width/2)
    y = (screen_height/2) - (height/2)
    root.geometry('%dx%d+%d+%d' % (width, height, x, y))


####################
# Mainloop - Block #
####################
root = tk.Tk()
root.title(APP_TITLE)
root.resizable(False, False)
root.attributes("-topmost", 1)
root.wm_attributes('-type', 'splash')
center_window(800, 480)
    
app = Application(root)

print("Dieser Mainloop-Block wird nur einmal abgearbeitet!!!")
print("Ab Jetzt läuft ein Thread 'mainloop' im Hintergrund weiter")

root.mainloop()
Gruss wuf :wink:
Take it easy Mates!
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@wuf: der Kommentar ist ein bißchen irreführend. Es gibt keinen Thread 'mainloop', sondern der Main-Loop wurstelt im Vordergrund herum und ruft je nach Event im Vordergrund verschiedene Methoden auf.

@jake-the-snake: wenn eine Methode aus dem Mainloop heraus aufgerufen wurde, dann springt nach Ende der Funktion das Programm wieder in den Mainloop, also in die Funktion, die Du in der letzten Zeile aufgerufen hast.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Sirius3 hat geschrieben:@wuf: der Kommentar ist ein bißchen irreführend.
Du hast recht der ganze Thread hier ist es ein bisschen. :) Habe noch weitere Versuche angestellt. @jake-the-snacke: Habe ein weiteres Skript für Forschungszwecke erstellt. Als Zeitschinderprogramm habe ich die Funktion 'do_maraton_job()' als Zähler integriert. Neu dabei ist auch die tk-Methode 'root.update_idletasks()'.In der Messagebox Funktion 'show_messagebox()'ist eine Eigenbau- und Tk-Variante der Messagebox enthalten. Die Tk-Variante ist aus dokumentiert. Die Eigenbau-Variante wird angezeigt während dem das Zeitschinderprogramm am laufen ist. Wenn man aber die Eigenbau-Variante aus- und die Tk-Variante eindokumentiert wird bei einem Neustart des Skripts das Hauptfenster abgedeckt durch die Tk-Messagebox angezeigt aber das Zeitschinderprogramm fängt erst an zu laufen sobald die Tk-Messsagebox geschlossen wird. In beiden Fällen wird erst nach Ablauf des Zeitschinderprogramm die Methode 'root.mainloop()' ausgeführt.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import messagebox

APP_TITLE = "Tk Research"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 300
APP_HEIGHT = 200

def show_messagebox():
    # Custom-Messagebox
    message_box = tk.Toplevel(root)
    message_box.title("Messagebox")
    tk.Label(message_box, text="Programm aktiv - Bitte warten...",
        padx=20, pady=20).pack(expand=True)
    message_box.transient(root)
    message_box.grab_set()
    return message_box
    # Tk-Messagebox
    #messagebox.askyesno("System-Meldung", "Programm aktiv - Bitte warten...")
    print ("Funktion short_message ausgeführt!")

########################
# Applications - Block #
########################
def do_maraton_job(delay_count):
    while delay_count > 0:
        print(delay_count)
        delay_count -=1
    return 'yes'
 

####################
# Mainloop - Block #
####################
root = tk.Tk()
root.title(APP_TITLE)
root.resizable(False, False)
root.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))

codeprocess = "no"
delay_count = 100000

my_messagebox = show_messagebox()
root.update_idletasks()

codeprocess = do_maraton_job(delay_count)

if codeprocess == "yes":
    my_messagebox.destroy()
    print("if-Block mainloop erreicht")

root.mainloop()
Gruss wuf :wink:
Take it easy Mates!
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Uuuuuund da sind wir dann im "soll-man-nicht-machen-Land". update und update_idletasks sollte man nicht selbst aufrufen. Sondern den mainloop betreten, und after verwenden.

https://stackoverflow.com/questions/291 ... g-mainloop

"""
What's wrong with update? There are several answers. First, it tends to complicate the code of the surrounding GUI. If you work the exercises in the Countdown program, you'll get a feel for how much easier it can be when each event is processed on its own callback. Second, it's a source of insidious bugs. The general problem is that executing [update] has nearly unconstrained side effects; on return from [update], a script can easily discover that the rug has been pulled out from under it. There's further discussion of this phenomenon over at Update considered harmful.
"""

Damit zu arbeiten kann also zu subtilen Fehlern fuehren, bis hin zu abstuerzen.

Wir haben hier und and 1000 anderen stellen gesagt, wie es geht. Das Problem von jks ist nix besonderes, das da ein anderes als die vorgestellten Loesungen verlangt - nur sein Verstaendnis ist halt nicht da.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und noch ein Nachtrag: ausser ein Fenster schon darzustellen, bevor der Mainloop betreten wurde, tut dein Programm nichts, was im Kontext hier hilfreicher waere, als der stinknormale Callback eines Buttons. Der ja schlussendlich eh der ausloesende Faktor sein wird.

Jakes Problem ist abstrakt das eines Fortschrittbalkens oder Spinners. Dazu braucht man aber after, und natuerlich einen Status, der abfragbar ist, und zB von einem Thread oder Prozess herruehrt.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi __deets__

Danke für deine Hinweise. Habe meine Informationen von:

Tkinter 8.5 reference: a GUI for Python
John W. Shipman
2013-12-31 17:59

Der schreibt Betreffs:

w.update()
This method forces the updating of the display. It should be used only if you know what you're
doing, since it can lead to unpredictable behavior or looping. It should never be called from an event
callback or a function that is called from an event callback.


w.update_idletasks()
Some tasks in updating the display, such as resizing and redrawing widgets, are called idle tasks
because they are usually deferred until the application has finished handling events and has gone
back to the main loop to wait for new events.
If you want to force the display to be updated before the application next idles, call the w.update_id-
letasks() method on any widget.


Dass die erste Variante mit Vorsicht anzuwenden ist aus seiner Beschreibung klar. Die zweite beschreibt er aber wesentlich weniger dramatisch. Scheinbar wurden diese Methoden kreiert um für bestimmte zeit verzögernde TCL Abläufe beschleunigen. Dachte 'jake-the-snake' möchte seine Messagebox mit der Meldung "Programm aktiv - Bitte warten..." anzeigen bevor seine Stundenlange Verschlüsselung erledigt ist, welche er aufruft bevor die 'root.mainloop()' Methode ausgeführt wir. Dies macht mein Skript aber genau. (Eventuell läuft dies unter Windows anders ab. -> Bei mir läuft Ubuntu 16.04)

Gruss wuf :wink:
Take it easy Mates!
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

@wuf in der Oberfläche (er hat eine Screenshot auf seiner Webseite) gehts klar um das verschlüsseln eines Textfeldes. Hat also nichts mit Anfang des Programms zu tun. Und das Update idletasks spart man sich, wenn man einfach per Button command ein Fenster aufmacht. Oder per After. Einen echten Mehrwert sehe ich da null in deinem Beispiel. Milde anders wäre das, wenn man eine echte Aufgabe erledigen würde, und zwischendurch updated. Nur ist das einer echten nebenläufigkeit unterlegen aus schon genannten Gründen .
jake-the-snake

Abend die Herren

So, nach viel Schweiß und Blut, welches transpiriert wurde, habe ich jetzt eine Lösung, die für meine Software 1.5 passt und in der nächsten Version 1.6 eingebaut wird. Das kann ich dann sowohl für das Verschlüsseln, wie auch für des Entschlüsseln nutzen. Aber zunächst bin ich froh, dass es läuft!

Ich habe das nun so gelöst, dass ich nach dem Klicken des roten Knopfes erst einmal ein Label öffne, mit dem Hinweis "Bitte warten..". Dieses Label lege ich in der finalen Version so geschickt über die Schaltelemente (kann man auch am unterem Beispiel sehen), dass man dort, für die Dauer der Label- Anzeige nicht klicken kann. Habe es ausprobiert - das geht tatsächlich. Danach springe ich nach der after- Methode in den Codierungs-Block und für die Dauer der Codierung bleibt die Anzeige "Bitte warten..." stehen. Am Ende der Codierung wird das Label entfernt. Und das Programm ist wieder betriebsbereit.

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from tkinter import *

# Einleitung der App-Knoepfe / Hier gibt es jetzt nur einen zum Test
class App:
  
    def __init__(self, master):
        frame = Frame(master)
        frame.place(width=120, height=320, x=340, y=170)
        # Pre-Code-Button
        self.precoding = Button(frame, 
                         text="Verschlüsseln ►", fg="red", pady=5, padx=5,
                         command=self.write_precoding)
        self.precoding.pack()

####################
# Codierungs-Block #
####################

    def write_precoding(self):
        # Prozess-Meldung
        global fourLabel
        fourLabel = Label(root, text="Bitte warten...", font=("TimesNewRoman", 20), borderwidth=2, relief=("groove"), background=("#ff5a5a"), width=36, anchor=("center"))
        fourLabel.place(x=150,y=165)
        # Testausgabe Warte-Label
        print("Ansprung für Warte-Label OK...")
        root.after(1000, self.write_coding)

    def write_coding(self):
        # Testausgabe Beginn Code-Routine
        print("Beginn der Codierung OK...")
        i = 0
        while i <= 30000000:
            i = i + 1
        # Testausgabe Ende Code-Routine
        print("Ende der Codierung OK...")
        fourLabel.destroy()
        
####################
# Mainloop - Block #
####################

# Fenster-Titel
root = Tk(className=" Fenstername Blablabla")
root.resizable(FALSE,FALSE)
root.attributes("-topmost", 1)
root.wm_attributes('-type', 'splash')

# Knoepfe-App-Ende
app = App(root)

def center_window(width=300, height=200):
    # get screen width and height
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()

    # calculate position x and y coordinates
    x = (screen_width/2) - (width/2)
    y = (screen_height/2) - (height/2)
    root.geometry('%dx%d+%d+%d' % (width, height, x, y))
    
center_window(800, 480)

root.mainloop()
An Alle: Einen ganz herzlichen Dank für die Geduld und Ausdauer der hier Mitwirkenden. Auch einen lieben Dank an wuf, der hier gleich mehrfach größere Programme ablieferte. Ohne Eure Hilfe wäre ich nicht auf die eine, oder andere zündende Idee gekommen.
Und für wuf noch den Screenshot, den _deets_ erwähnt hat: https://www.crypto-code-tool.com/software.html.

So eine GUI ist halt doch was ganz anderes. Ich habe früher Frontend als Webinterface gebaut. Browsertechnisch ist man da doch recht schnell graphisch am Ziel. Der Datenaustausch erfolgte via .shtml-Seiten und serverseitigen Skripten via cgi. Kann man sich wie ein Ping-Pong Spiel vorstellen. Solche Anwendungen waren einfach auf Mehrplatzsysteme übertragbar. Ich habe da kleinere ERP's geschrieben. Mit Perl und SQL. Wenn ich sowas mit Python, bzw. Python ist gar nicht so das Problem - ich meine TKinter - umsetzen müsste... Ich bekäme keine Zwillinge, sondern eher Drillinge...

Gruss und Gute Nacht... bis die Tage...

jts
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@jake-the-snake: ich glaub ja kaum, dass bei den Programmierkünsten irgendwer bereit ist, Geld für ein BlackBox auszugeben. Verschlüsselung beruht darauf, dass der Algorithmus nachweisbar sicher ist. Asymmetrische Schlüssel mit 512bit (wenn das mit 512-facher Verschlüsselung gemeint ist), werden schon längst nicht mehr als sicher eingestuft. Also ohne Veröffentlichung des gesamten Source-Codes kauft niemand der bei Verstand ist, so ein Gerät.

Leider kann man ja jeden Tag lesen, wie viel Schrott produziert wird und es immer noch genug Dumme gibt, die den hohlen Versprechungen glauben.
Antworten