Programm für Fenster / Desktopwechsel Windows

Du hast eine Idee für ein Projekt?
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

Hallo zusammen,

ich bin ganz neu hier in diesem Forum und auch noch ein kompletter Anfänger, was das Programmieren allgemein angeht. Dennoch habe ich Lust und auch schon die ersten Versuche in der Python Programmierung durchgeführt.

Ich habe einen Bedarf an einen Programm, welches mit einer GUI Oberfläche arbeiten soll und hier habe ich die Idee, dass der User dann Eingabefelder zur Verfügung hat, um hier Eingaben für eine Zeitsteuerung machen kann.

Das Tool soll hier einen Start und Beenden / Exit Button haben.

Ziel: Wechsel bei Starten des Programms (Betätigung Start-Button) von ausgewählten Fenstern oder kompletten Desktop (automatischer Wechsel mit variablen Zeitintervall)


Ablauf:

Wenn der User in der GUI Oberfläche in den Eingabefelder (Zeitintervall Fenster1 / Zeitintervall Fenster 2) eingegeben hat, sollte hier diese Zeit dann im Programm übernommen werden. Ideal wäre hier auch eine Speicherung der Zeit, wenn das Programm dann geschlossen wird, damit diese Eingabe nicht jedesmal beim Start der Anwendung eingegeben werden muss (Anzeige der gespeicherten Werte ist hier hilfreich).

Nach dem dann der Start-Button bestätigt wird, soll dann der Wechsel zwischen den Desktops (Windows 10 kann mehrere Desktops darstellen -> Tastenkombination für hin- und herschalten STRG + WIN_links + Pfeiltaste_rechts bzw. STRG + WIN_links + Pfeiltaste_links) mit dem eingetragenen Zeitintervall durchgeführt werden. Hier habe ich bereits herausgefunden, dass bei tkinter after() hilfreich ist.

Diese Aktion soll dann solange in einer Dauerschleife laufen, bis der User auf der Tastatur die "ESC-Taste" drückt oder das Programm direkt auf der GUI Oberfläche / Taskleiste beendet. Ideal wäre dann hier beim Beenden der Schleife, durch das Betätigen der "ESC-Taste", dass hier das komplette Programm nicht beendet wird, sondern nur die Schleife und der letzte Wechsel wieder zurück zum Startfenster / Startdesktop durchgeführt wird.



Wie gesagt ich habe hier schon etaws herumprobiert, aber ich komme leider nicht auf das Ergebnis was ich mir gehofft habe.

Ich wäre sehr erleichtert, wenn mir hier jemand helfen kann, dieses Programm gemeinsam zu erstellen, sodass es auch vernünftig programmiert und voll funktionsfähig ist.

Vielen Dank an euch alle und hoffe auf eure Unterstützung.

Gruß
René
Benutzeravatar
__blackjack__
User
Beiträge: 13068
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Rene1303: Also GUI-Programmierung. Da braucht man die Grundlagen bis einschliesslich objektorientierter Programmierung (OOP), also eigene Klassen schreiben.

In der Python-Dokumentation gibt es ein Grundlagentutorial, welches man mal durchgearbeitet haben sollte.

Zum Speichern/Laden von strukturierten Daten eignet sich das JSON-Format. Dafür gibt es in der Python-Standardbibliothek das `json`-Modul. Daran denken, dass man bei Textdateien immer explizit die Kodierung angeben sollte und in diesem Fall auch *muss*, denn JSON wird als UTF-8 erwartet. Alternativ kann man die Datei im Binärmodus öffnen, dann kümmert sich das `json`-Modul selbst um die korrekte Kodierung und kann beim Lesen auch andere UTF-Kodierungen erkennen.

Dann braucht man neben der GUI noch andere Bibliotheken. Wenn Du Tastendrücke programmatisch erzeugen willst, beispielsweise `pyautogui`, und um global Tastendrücke wie die Escape-Taste abgreifen zu können beispielsweise das `keyboard`-Modul.

Und dann musst Du das Problem in kleinere Teilprobleme zerlegen, und die Teilprobleme wieder in kleinere Teilprobleme, solange bis die einzelnen Teilprobleme so einfach/klein sind, dass sie sich durch jeweils eine Funktion mit wenigen Zeilen Code lösen lassen. Das macht man dann und testet die Teillösung ob sie funktioniert, bevor man zur nächsten Funktion oder Methode weitergeht. Die Teillösungen kann man dann auch in anderen Teillösungen verwenden. Und wenn man dann so Teillösungen aus anderen Teillösungen zusammensetzt, hat man am Ende eine Gesamtlösung.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

Hallo _blackjack_,

danke für die Rückmeldung. Ich werde das Grundlagentutorial mal durcharbeiten.

Für dieses Programm ist nach dem Motto "learning by doing" ja schon viel dabei, aber ich bin auch auf der Suche nach einem Mentor, der mir hier bei der Erstellung helfen kann. Dies würde mich auch beim Verständnis von Python deutlich weiterbringen und hoffe hier eine Person oder Gruppe zu finden, um mich dabei zu unterstützen.
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

Mein bisheriges Probieren in dem Thema ist folgender Code, jedoch ist dies aus meiner Sicht nicht sonderlich schön und das wichtigste, es funktioniert noch nicht alles. Problem ist hier zum einen die Übersichtlichkeit und auch zum anderen die Eingabe mit den Zeitintervallen bzw. auch die Umsetzung der richtigen Loop Funktion.



Code: Alles auswählen

from tkinter import*

import keyboard

import pyautogui

class StartButton(Button):
    def aktion (self):
        print("Start")

class Button_Eingabe_Bestätigung(Button):
    def Übernehmen (self):

        ausgabe1.config(state=NORMAL)
        ausgabe2.config(state=NORMAL)

        Zeit1 = eingabe1.get()
        Zeit2 = eingabe2.get()

        Zeit1 = "03:00"
            
        print(Zeit1)
        
        def convert(t):
            
            m,s = t.strip().spilt(":")
            
            return int(m) * 60 + int (s)
        print(convert(Zeit1))

        
        print(Zeit1)
        
        convert(Zeit1)
        
        eingabe1.delete(0,'end')
        eingabe2.delete(0,'end')

##        Zeit1["command"] = Zeit1.convert
        
        ausgabe1.insert(0,Zeit1)
        ausgabe2.insert(0,Zeit2)

        ausgabe1.config(state=DISABLED)
        ausgabe2.config(state=DISABLED)

# Informationen für Prorgammfenster       
fenster = Tk()
fenster.geometry("350x300")
fenster.title("Auto-SWITCH")
rahmen = Frame(fenster, relief="ridge", borderwidth=5)
rahmen.pack(fill="both", expand = 1)


# Beschriftung Programmfenster
label = Label(rahmen, text="Programm für automatische Fensterwechsel")
label.grid(row = 1, column = 1, columnspan = 2, pady = 15, padx = 25)

label = Label(rahmen, text = "Eingabe Zeitinterval Fenster 1")
label.grid(row = 2, column = 1)
label = Label(rahmen, text = "Sek.")
label.grid(row = 2, column = 3)


eingabe1 = Entry(rahmen, bd = 2, width = 22)
eingabe1.grid(row = 2, column = 2)

label = Label(rahmen, text = "Eingabe Zeitinterval Fenster 2")
label.grid(row = 3, column = 1)
label = Label(rahmen, text = "Sek.")
label.grid(row = 3, column = 3)


eingabe2 = Entry(rahmen, bd = 2, width = 22)
eingabe2.grid(row = 3, column = 2)


button0 = Button_Eingabe_Bestätigung(rahmen, width = 10, text="Übernehmen")
button0["command"] = button0.Übernehmen
button0.grid(row = 4, column = 1, columnspan = 3, pady = 10)


label = Label(rahmen, text = "Zeiten übernommen:")
label.grid(row = 5, column = 1, columnspan = 2)

label = Label(rahmen, text = "Zeitintervall Fenster 1:")
label.grid(row = 6, column = 1)

Zeit1 = StringVar()
Zeit1.set(eingabe1.get())

ausgabe1 = Entry(rahmen, state=NORMAL, bd = 2, width = 10)
ausgabe1.config(state=DISABLED)
ausgabe1.grid(row = 6, column = 2)

label = Label (rahmen, text = "Zeitintervall Fenster 2:")
label.grid(row = 7, column = 1)

ausgabe2 = Entry(rahmen, bd = 2, width = 10)
ausgabe2.config(state=DISABLED)
ausgabe2.grid(row = 7, column = 2)

button1 = StartButton(rahmen, width = 10, text="Start")
button1["command"] = button1.aktion
button1.grid(row = 8, column = 1, pady = 10)

button2 = Button(rahmen, width = 10, text="Exit")
button2["command"] = fenster.destroy
button2.grid(row = 8, column = 2, pady = 10)


fenster.mainloop()


def fenster_destroy(event=None):
    if event.keysym == 'Escape':
        fenster.destroy()
        
fenster.bind_all('<KeyPress-Escape>', fenster_destroy)
Benutzeravatar
__blackjack__
User
Beiträge: 13068
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Rene1303: Sternchen-Importe sind Böse™. Da holt man sich gerade bei `tkinter` fast 200 Namen ins Modul von denen nur ein kleiner Bruchteil verwendet wird. Auch Namen die gar nicht in `tkinter` definiert werden, sondern ihrerseits von woanders importiert werden. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen.

Der `mainloop()`-Aufruf kehrt erst zurück wenn die GUI geschlossen/beendet ist, das heisst alles was danach steht wird erst ausgeführt wenn alles schon vorbei ist, was die GUI betrifft.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Die Fenstergrösse gibt man nicht vor, die ergibt sich aus der Grösse des Inhalts automatisch. Je nach Auflösung des Rechners auf dem das am Ende läuft kann es sonst passieren, das die Pixelgrösse die man dort vorgegeben hat, gar nicht ausreicht um den Inhalt anzuzugeigen.

Wenn es im `tkinter`-Modul Konstanten für bestimmte Optionen gibt, dann sollte man die auch nutzen, statt literale Zeichenketten mit dem gleichen Inhalt zu schreiben. Bei `DISABLED` und `NORMAL` für `state` wurde das ja auch gemacht, aber an anderen Stellen für `relief` oder `fill` nicht.

Ich würde die Erstellung der Widgets weniger nach dem Widget-Typ sortieren, sondern mehr nach der Reihenfolge wie die in der Anzeige angeordnet werden.

Die beiden Klassen sind keine Klassen sondern einfach nur Container um da jeweils *eine* *Funktion* rein zu stecken. Das macht man nicht. Funktionen sind ausserhalb von Klassen. Und es wird von `Button` geerbt, aber dann nichts gemacht um tatsächlich einen Button zu beschreiben der sich anders verhält als der normale `Button`. Damit ist es sinnfrei davon zu erben. Wenn man statt dieser beiden ”Klassen” einfach normale Funktionen mit dem `command` eines normalen `Button` verbindet, verhält sich das Programm immer noch gleich. Das zeigt, dass die Klassen hier keinen Zweck erfüllen.

Ein Warnzeichen für Funktionen die als Methoden ”verkleidet” sind, ist wenn `self` gar nicht verwendet wird. Wenn eine ”Methode” das Objekt gar nicht braucht/verwendet, dann ist es keine Methode und man muss sich fragen warum das auf der Klasse definiert wurde, wenn es das gar nicht braucht.

Namen sollten nicht durchnummeriert werden. Entweder will man dort bessere Namen verwenden, oder gar keine einzelnen Werte, sondern eine Datenstruktur. Oft eine Liste. Oder im Falle von den `button0` bis `button2` einfach gar nicht an einen Namen binden wenn man auf die Objekte gar nicht mehr zugreift.

`zeit1` im Hauptprogramm macht keinen Sinn. Das wird nicht wirklich verwendet, weil die nach der Definition folgende Zeile auch sinnfrei ist, denn das `Entry` dessen Inhalt dort abgefragt und dann als Wert für das `StringVar`-Objekt gesetzt wird, ist zu diesem Zeitpunkt *garantiert* leer, also enthält immer die leere Zeichenkette, weil an diesem Zeitpunkt die GUI ja gar nicht aktiv ist, der Benutzer da also noch gar keine Zeit hatte irgendetwas einzutragen in das Eingabefeld.

Die Ausgabe-`Entry`\s könnten den DISABLED-Zustand gleich beim Erstellen bekommen, statt da einen extra `config()`-Aufruf in der nächsten Anweisung zu machen.

In `Übernehmen()` steht einiges was nicht wirklich Sinn macht, weil es keinen Effekt hat. Und die Funktion braucht die Ein- und Ausgaben als Argumente, denn alles was eine Funktion oder Methode ausser Konstanten benötigt wird als Argument(e) übergeben. Womit wir dann bei GUIs wären und warum man dort entweder Closures oder Objekte braucht. Man kann das hier noch mit `functools.partial()` lösen, aber das ist schon unschön, weil man dem Button hier einen Namen geben muss, weil `command` erst mit einem Wert versehen werden kann, wenn alle Werte die `Übernehmen()` braucht erzeugt sind. Wenn das nur das Objekt wäre auf dem `Übernehmen()` als Methode definiert ist, dann gäbe es das schon und es müssten aber noch nicht alle Attribute definiert sein wenn `command` festgelegt werden muss.

Funktionen sollten nicht in anderen Funktionen definiert werden, wenn sie nicht entweder so trivial sind, dass es keinen Sinn macht sie einzeln testen kann oder nie in ihnen nach Fehlern suchen muss, oder wenn man an der Stelle ein Closures erzeugen will/muss. Das ist bei `convert()` beides nicht der Fall. Entweder schreibt man die Rechnung direkt in den Quelltext — die Funktion wird ja nur an einer Stelle aufgerufen — oder das ist eine eigenständige Funktion.

Das es `spilt()` nicht als Methode gibt, hätte Dir eigentlich beim testen/ausführen auffallen sollen.

`destroy()` geht nur wenn man das auf dem Hauptfenster aufruft. Besser ist es auf im Grunde egal welchem Widget die `quit()`-Methode aufzurufen um die GUI-Hauptschleife zu verlassen.

`fenster_destroy()` ist ein bisschen sinnfrei, weil dort nur gestestet wird ob das Ereignis die Escape-Taste war, die Funktion aber auch nur für diese Taste registriert wird, also dieser Test *immer* wahr ist.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from functools import partial


def convert(zeit_text):
    minutes_text, seconds_text = zeit_text.split(":")
    return int(minutes_text) * 60 + int(seconds_text)


def uebernehmen(eingaben, ausgaben):
    for eingabe, ausgabe in zip(eingaben, ausgaben):
        zeit = eingabe.get()
        print(zeit)
        print(convert(zeit))
        eingabe.delete(0, tk.END)
        ausgabe["state"] = tk.NORMAL
        ausgabe.delete(0, tk.END)
        ausgabe.insert(0, zeit)
        ausgabe["state"] = tk.DISABLED


def main():
    fenster = tk.Tk()
    fenster.title("Auto-SWITCH")

    rahmen = tk.Frame(fenster, relief=tk.RIDGE, borderwidth=5)
    rahmen.pack(fill=tk.BOTH, expand=True)

    tk.Label(rahmen, text="Programm für automatische Fensterwechsel").grid(
        row=1, column=1, columnspan=3, pady=15, padx=25
    )

    eingaben = []

    tk.Label(rahmen, text="Eingabe Zeitinterval Fenster 1").grid(
        row=2, column=1
    )
    eingabe = tk.Entry(rahmen, bd=2, width=22)
    eingabe.grid(row=2, column=2)
    eingaben.append(eingabe)
    tk.Label(rahmen, text="Sek.").grid(row=2, column=3)

    tk.Label(rahmen, text="Eingabe Zeitinterval Fenster 2").grid(
        row=3, column=1
    )
    eingabe = tk.Entry(rahmen, bd=2, width=22)
    eingabe.grid(row=3, column=2)
    eingaben.append(eingabe)
    tk.Label(rahmen, text="Sek.").grid(row=3, column=3)

    uebernehmen_button = tk.Button(rahmen, text="Übernehmen")
    uebernehmen_button.grid(row=4, column=1, columnspan=3, pady=10)

    tk.Label(rahmen, text="Zeiten übernommen:").grid(
        row=5, column=1, columnspan=2
    )

    ausgaben = []

    tk.Label(rahmen, text="Zeitintervall Fenster 1:").grid(row=6, column=1)
    ausgabe = tk.Entry(rahmen, state=tk.DISABLED, bd=2, width=10)
    ausgabe.grid(row=6, column=2)
    ausgaben.append(ausgabe)

    tk.Label(rahmen, text="Zeitintervall Fenster 2:").grid(row=7, column=1)
    ausgabe = tk.Entry(rahmen, state=tk.DISABLED, bd=2, width=10)
    ausgabe.grid(row=7, column=2)
    ausgaben.append(ausgabe)

    assert len(eingaben) == len(ausgaben)

    uebernehmen_button["command"] = partial(uebernehmen, eingaben, ausgaben)

    tk.Button(
        rahmen, width=10, text="Start", command=partial(print, "start")
    ).grid(row=8, column=1, pady=10)
    
    tk.Button(rahmen, width=10, text="Exit", command=fenster.destroy).grid(
        row=8, column=2, pady=10
    )

    fenster.bind_all("<KeyPress-Escape>", lambda _event: fenster.quit())

    fenster.mainloop()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

@_blackjack_ vielen Dank für die ausführliche Beschreibung und auch beim umstellen oder richtigstellen des Codes.

Ich möchte nun als nächstes eine after()- Methode verwenden und dies sollte erst aktiv werden, wenn der "Start-Button" angeklickt wurde.

Hierfür habe ich folgendes probiert:

Code: Alles auswählen

def start(wechsel):
    
        wechsel.after(3000, wechsel(fenster1))
        
        
        #pyautogui.hotkey('ctrl', 'winleft', 'left')
        #print('wechsel links')

def wechsel(fenster1):

    pyautogui.hotkey('ctrl', 'winleft', 'right')
    print('wechsel rechts')
Das Problem ist nur, dass ich nicht genau weiß, wie ich die "3000" Millisekunden (bei der after-Methode wird Millisekunden anstatt Sekunden verwendet, habe ich nun erfahren), aus den ausgaben erhalte. Hier kann dies dann für den Wechsel Fenster 1 ja auch unterschiedlich zu Wechsel Fenster 2 sein.

Damit ich überhaupt beim Anklicken in die Funktion komme habe ich noch einen Eintrag bei dem tk.Button eingefügt:

Code: Alles auswählen

tk.Button(
        rahmen, width=10, text="Start", command=partial(print, "start")
    ).grid(row=8, column=1, pady=10)
    tk.Button (command=partial(start(wechsel)))
Nur da ich wie gesagt hier nicht weiß, wie ich auf die ausgaben komme, bin ich mir auch nicht sicher, was ich hier als Attribut übergeben muss.

Von der Idee her habe ich folgendes vor:

Ich benötige zuerst die Funktion Start, welche dann die Zeitverzögerung für den jeweiligen Wechsel ausführen soll. Hier dachte ich dann eben an das after(delay, function). Es sollte dann so sein, dass pyautogui.hotkey für den ersten Wechsel nach rechts ausgeführt wird (nach Ablauf der umgerechneten Zeit in Ausgabe für Fenster 1) und dann gleich die nächste after()-Methode greift mit der anderen Zeiteingabe / Ausgabe.

So und dann müsste ich nach dem Durchlauf eine Variable setzen für eine Loop-Funktion, die dann beim beim Ausführen der Funktion wieder auf "0" gesetzt wird, damit eine Endlosschleife entsteht.

Ist der Gedankenweg hier richtig?

Bei den Versuchen von oben ist das Problem, dass ich Fehler für after() erhalte:

Traceback (most recent call last):
File "C:/Users/ch6007/Desktop/Python Test Programm/Testprogramm_Autoswitch.py", line 105, in <module>
main()
File "C:/Users/ch6007/Desktop/Python Test Programm/Testprogramm_Autoswitch.py", line 93, in main
tk.Button (command=partial(start(wechsel)))
File "C:/Users/ch6007/Desktop/Python Test Programm/Testprogramm_Autoswitch.py", line 25, in start
wechsel.after(3000, wechsel(fenster1))
AttributeError: 'function' object has no attribute 'after'

Kannst du mir da nochmals weiterhelfen?
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast partial nicht verstanden. partial bekommt eine Funktion *OHNE* Argumente, und ein oder mehrere Argumente, und liefert eine neue Funktion (genauer ein partial Objekt), das dieses Argument schon automatisch macht. Du rufst aber gleich die Funktion mit dem Argument auf, und damit baust du auch keinen Callback.

Also

Code: Alles auswählen

partial(start, wechsel)
statt

Code: Alles auswählen

partial(start(wechsel)) # Ruft start auf, start gibt etwas zurueck (None im Zweifel), und partial versucht *das* zu benutzen.
Den gleichen Fehler machst du auch in deinem after-aufruf. Und zusaetlich machst du da den Fehler, after auf der Funktion aufzurufen. Eine Funktion hat kein after. Aber fenster1 (sehr schlecht benannt), das hat die.
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

@__deets__ Danke für die Hilfe. Ich habe das mit partial wirklich noch nicht so ganz verstanden gehabt. Habe nun es wie folgt geändert:

Bezeichnung vom Start-Button geändert:

Code: Alles auswählen

start_button = tk.Button(rahmen, width=10, text="Start")
    start_button.grid(row=8, column=1, pady=10)
    start_button["command"] = partial(print, "start")
    start_button["command"] = partial(startstop_func, wechsel)
Zudem wird nun auch richtig nach dem Anklicken des Buttons in die Funktion startstop_func gesprungen.

Was ich noch nicht verstanden habe ist die after()-Methode:

Code: Alles auswählen

def startstop_func(wechsel):
    #if  
    print("def Start")
    wechsel()
    wechsel.after(3000, wechsel)    
        
        
        #pyautogui.hotkey('ctrl', 'winleft', 'left')
        #print('wechsel links')


def wechsel():
    
    pyautogui.hotkey('ctrl', 'winleft', 'right')
    print('wechsel rechts')
    wechsel.after(3000, wechsel)
    
Ich möchte nachdem ich in die Funktion startstop_func gesprungen bin, direkt eine weitere Funktion aufrufen, die mir dann den Wechsel von Fenster / Desktop macht. Hier also wechsel.

Jedoch möchte ich die Funktion wechsel erst nach einer bestimmten Zeit ausführen lassen bzw. genauer gesagt das pyautogui.hotkey. Hier habe ich mal den fixen Wert 3000 ms eingetragen, damit ich nicht zu lange warten muss. Später soll hier dann ein Variabler Wert aus der Ausgabe hinein.

Aktuell wird aber die Funktion wechsel direkt ohne eine Verzögerung ausgeführt, da ich noch Fehler bei der after()-Methode drin habe.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

wechsel ist eine Funktion. wechsel hat kein after. after ist eine Methode eines tkinter-Objektes, und das musst du - eben mit parital - als Argument anfuegen, damit du darauf dann after aufrufen kannst. So, wie das __blackjack__ ja auch vorgemacht hat.

Und zweimal an command zuweisen, so wie du das da machst, ist sinnlos. Die zweite entfernt die erste Zuweisung. Wenn du mehrere Dinge ausfuehren willst, muss du da extra was fuer programmieren.
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

Ich habe jetzt noch eine andere Variante. Das mit dem after() gebe ich auf, da es nicht der richtige Weg für mich ist.
Eigentlich möchte ich nur eine Endlosschleife erstellen, die nach der Eingabe der Zeiten (Wechsel Fenster 1 und Wechsel Fenster 2) und Bestätigen vom "Start-Button" losläuft. Der Wechsel Fenster 1 zu Fenster 2 sollte dann mit den Entry-Werten durchgeführt werden. Dies ist ja schon im Skript vorhanden.

Was jedoch noch fehlt ist der Abgleich von der Zeit, d.h. die aktuelle Zeit + Eingabe vom User.

Entweder in der While-Schleife ein time.sleep() und hier dann die Zeit vom Entry nehmen und dies dann nach jedem Wechsel eintragen, nur fehlt mir dann noch ein break, um die Schleife zu beenden.

Ich wollte, um das ganze besser zu verstehen mal ein Beispiel aufbauen und habe hier mit datetime gearbeitet. Jedoch kann ich hier keine if Bedingung erzeugen, dass wenn die Zeit mit Zusatz (10 Sekunden oder ähnliches) erreicht wurde, dass dann die Schleife unterbrochen wird.

Ist das so überhaupt möglich?

Code: Alles auswählen

from datetime import datetime, timedelta
Zeit = datetime.now()
Stopp = (datetime.now() + timedelta(seconds = 5))

while True:
    print ("Start")
    
    if Stopp == Zeit:
        break
    

print ("Schleife beendet")
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist nicht der richtige Ansatz, denn waehrend der while-Schleife laeuft keine GUI. after ist schon die richtige Loesung hier.

Deine Schleife ist deshalb falsch, weil du weder eine neuen Zeitstempel holst (denn Zeit ist *einmal* geholt, nie wieder geupdatet), und zum zweiten darauf setzt, dass du *genau* den Moment triffst, and dem der Zeitstempel gleich ist. Das ist eher unwahrscheinlich.

Eine korrekte (aber immer noch im Kontext falsche!) Implementierung saehe so aus:

Code: Alles auswählen

import time

until = time.monotonic() + 5
while time.monotonic() < until: # Wichtig: der aktuelle Zeitpunkt ist nicht gleich, sondern *groesser* gleich, wenn es zum abbruch kommt.
    print("wasauchimmer")
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

Also dann doch die after-Methode.
Nur wenn ich after nicht in einer Funktion nutzen kann, wie kann ich dann eine Endlosschleife bauen, die nach betätigen des start--Button genau den Wechsel macht bis ich die schleife durch strg +c beende oder auf den Button Stopp drücke?
Das verstehe ich nicht. Daher dachte ich die Loop wäre hier dann eine Alternative.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Indem du die Ausführung des callback abhängig von einer Flagge machst, die durch Stopp auf false gesetzt wird, und damit einfach nicht mehr mit after die nächste Ausführung anstößt. CTRL-C im GUI ist ungewöhnlich bis schlecht, in GUIs ist das copy. Stattdessen nimmt man auch gerne ESC, und dazu brauchst du einen per bind erzeugten Event handler, der eben die gleiche Flagge beeinflusst. Und weil dieser extra Zustand irgendwo leben muss, bietet sich - wie eigentlich immer bei GUIs - eine Klasse an, die das alles beinhaltet. Weil du da mit self.running zb eine solche Flagge darstellen kannst.
Benutzeravatar
Dennis89
User
Beiträge: 1152
Registriert: Freitag 11. Dezember 2020, 15:13

Rene1303 hat geschrieben: Mittwoch 23. Februar 2022, 22:22 Nur wenn ich after nicht in einer Funktion nutzen kann, wie kann ich dann eine Endlosschleife bauen
Wer sagt das denn? Klar nutzt du das in einer Funktion. Aber du musst 'after' auf ein tkinter-Objekt aufrufen und nicht auf eine Funktion, die du erstellt hast.
Du musst das tkinter-Objekt als Argument beim Funktionsaufruf dann natürlich mit übergeben. Aber das hatte __deets__ vor kurzem schon geschrieben. Les dir seinen Post nochmal durch und hinterfrag ihn und/oder frag hier bis du das verstanden hast.

Eigentlich wollte ich ein Code-Beispiel schreiben, aber ich glaub es ist sinnvoller wenn du von selbst Schritt für Schritt drauf kommst.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13068
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei man, ergänzend zu __deets__, für die Flagge statt eines Wahrheitswertes auch `None` und die ID nehmen könnte die `after()` liefert, weil man mit *der* dann auch ein noch anhängiges Zeitereignis wiederrufen kann (`after_cancel()`).
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

Hallo zusammen,
Ich habe den Wechsel nun mittels einer Endlosschleife realisiert. Damit mir die GUI nicht einfriert habe ich versucht diese Schleife in einen Thread auszulagern bzw. Dort laufen zu lassen. Das hat auch soweit geklappt und auch, dass der Thread beim beenden der Schleife gestoppt wird. Nur kann ich diesen Thread ja nicht erneut laufen lassen, da die nur 1 mal gestartet werden können.
Ich würde gern die Schleife mit einem Start Button an der GUI starten und mit einem Stopp Button an der GUI beenden, ohne, dass mir die GUI einfriert. Daher die Idee mit dem Thread.
Kann mir jemand helfen, wie ich das am besten anstelle, dass ich entweder einen neuen thread starten kann wenn ich den Start Button an der GUI betätige oder mir einen anderen Lösungsweg erklären?

Vielen Dank
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

after ist immer noch die richtige Antwort. Statt da jetzt rumzuwuergen, und mit einem Thread etwas kompliziertes zu bauen, mach es eben mit after. Dann musst du keinen Thread verwalten.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hier ist ein triviales Programm, mit dem man mit after einen timer realisiert, der durch einen Knopf gestartet, und einen anderen gestoppt werden kann. Oder eben auslaeft.

Code: Alles auswählen

import tkinter as tk
import time

class App:

    def __init__(self, root, timeout=2):
        self._root = root
        self._timeout = timeout
        self._start_button = tk.Button(root, text="Start", command=self._start)
        self._start_button.pack()
        self._stop_button = tk.Button(root, text="Stop", command=self._stop)
        self._stop_button.pack()
        # force initial button state
        self.running = False

    @property
    def running(self):
        return self._running

    @running.setter
    def running(self, value):
        self._running = value
        self._stop_button["state"] = tk.DISABLED if not value else tk.ACTIVE
        self._start_button["state"] = tk.DISABLED if value else tk.ACTIVE

    def _start(self):
        self.running = True
        self._then = time.monotonic()
        self._timer_callback()

    def _stop(self):
        self.running = False

    def _timer_callback(self):
        print("timer_callback")
        if self._then + self._timeout <= time.monotonic():
            self.running = False
        if self._running:
            self._root.after(100, self._timer_callback)


def main():
    root = tk.Tk()
    app = App(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Strenggenommen kann man auch den timer canceln, wenn stop gedrueckt wird - das ist bei dem kurzen Interval eh egal.
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

__deets__
Danke für die Antwort, nur das ich das richtig verstehe. Damit wäre das ausführen (starten) der Endlosschleife möglich, wenn ich den Timer weglassen würde und durch den Button auch wieder zu stoppen. Richtig? In der Schleife ist schon ein Timer vorhanden mit sleep. Kann ich die Eingabe innerhalb von sleep in Sekunden auch durch eine Vaiable ersetzen, wenn diese vor dem Ausführen gesetzt wird oder geht hier wirklich nur die Zahl als Sekundenangabe?
Rene1303
User
Beiträge: 22
Registriert: Montag 21. Februar 2022, 12:50

__deets__

Wie sieht es dann aus mit der GUI? Friert diese bei der Variante ein oder kann ich hier noch eine Eingabe vornehmen?
Antworten