Variable von anderer Datei aus verändern

Fragen zu Tkinter.
Antworten
Life4Gaming
User
Beiträge: 9
Registriert: Mittwoch 8. April 2020, 19:57

Hallo,

ich versuche mich gerade daran eine eigene Diashow zu programmieren und komme da gerade nicht weiter.

Ich habe eine Funktion welche immer wieder den Status meiner Variable "pause" überprüft.
Wenn diese wahr ist soll eben die Diashow pausiert werden und wenn nicht wird immer weiter überprüft.

Code: Alles auswählen

    def check_variables(self):
        global pause
        if pause:
            self.stop()

        root.after(timer, self.check_variables)
Später möchte ich dazu in der Lage sein, diese und andere Variablen, von einer anderen Python Datei aus zu verändern um so eben auch eine Pause zu ermöglichen

In einer anderen Datei ändere ich so die Variable ab:

Code: Alles auswählen

import slideshow
slideshow.pause = True
Allerdings hat diese keinerlei Auswirkung auf die Variable.
Die check_variables Funktion wird definitiv aufgerufen aber der Wert von pause ändert sich nicht.

Liegt das an dem mainloop der ausgeführt oder verstehe ich da etwas grundlegend falsch?
Wie kann ich das umgehen?

Vielen Dank schonmal für die Hilfe.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Globale Variablen zu benutzen ist schonmal grundlegend falsch.
Ansonsten zeigst Du zu wenig Code, um sagen zu können, was Du da machst.
Wenn Du von verschiedenen Modulen redest, wo was geändert wird, ist das nicht richtig. Die GUI wird am besten aus der GUI-Klasse heraus geändert.
Life4Gaming
User
Beiträge: 9
Registriert: Mittwoch 8. April 2020, 19:57

Sorry ich dachte das würde ausreichen.
Das ist bis jetzt mein gesamter Code:

Code: Alles auswählen

import tkinter as tk
from PIL import Image, ImageTk, ImageOps, ImageFilter
import random
import glob


timer = 2000
debug = True
pause = False

root = tk.Tk()
SCREEN_WIDTH = root.winfo_screenwidth()
SCREEN_HEIGHT = root.winfo_screenheight()

pic_list = []
for name in glob.glob(r'C:\Coding\PythonSlideshow\images\*'):
    pic_list.append(name)


class SlideGui:
    def __init__(self, main_window):
        self.new_pic = None
        self.mainWindow = main_window
        self.mainWindow.title("Python Slideshow")
        self.mainWindow.geometry('%dx%d' % (SCREEN_WIDTH, SCREEN_HEIGHT))

        if not debug:
            root.attributes('-fullscreen', True)

        self.mainWindow.configure(bg="black")
        self.img = tk.Label(self.mainWindow)
        self.img.pack()

        self.check_variables()
        self.pic()

    def stop(self):
        root.after_cancel(self.new_pic)

    def resume(self):
        self.pic()

    def pic(self):
        load = Image.open(random.choice(pic_list))
        load = ImageOps.exif_transpose(load)
        pic_width, pic_height = load.size
        real_aspect = pic_width / pic_height

        cal_width = int(real_aspect * SCREEN_HEIGHT)
        load2 = load.resize((cal_width, SCREEN_HEIGHT))

        render = ImageTk.PhotoImage(load2)

        self.img.config(image=render, borderwidth=0, highlightthickness=0)
        self.img.image = render

        self.new_pic = root.after(timer, self.pic)

    def check_variables(self):
        global pause
        if pause:
            self.stop()

        root.after(timer, self.check_variables)


if __name__ == "__main__":
    slideshow = SlideGui(root)
    root.mainloop()
Wenn ich also mit mehreren Modulen arbeiten will muss ich also aus der GUI-Klasse raus die Variable in dem anderen Modul überprüfen?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Wer soll denn die Variable `pause` ändern?
Das macht doch der Nutzer über einen Knopf in der GUI?
Life4Gaming
User
Beiträge: 9
Registriert: Mittwoch 8. April 2020, 19:57

Ich habe mich da glaube ich etwas umständliche ausgedrückt deshalb erkläre ich mal mein Vorhaben genauer:
Das ganze soll später auf einem Raspberry Pi laufen.
Momentan habe ich da eigentlich schon eine Lösung mit der ich allerdings nicht zu 100% zufrieden bin und möchte daher meine eigene entwickeln.

Da ich am Pi keine Möglichkeiten habe Buttons zu drücken möchte ich das ganze über HTTP Requests laufen lassen.
Damit steuere ich momentan z.B. das ein und ausschalten des Bildschirms.

Das gleiche möchte ich jetzt eigentlich auch mit dieser Pause Funktion machen.
Sobald die Request aufgerufen wird soll diese Variable geändert werden (oder wie auch immer das jetzt funktioniert).

Ich dachte eigentlich das wäre einfach gemacht mit der globalen Variable aber es ist ja scheinbar doch etwas aufwendiger.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du kannst in einem extra Thread in der GUI einen HTTP Server laufen lassen. Der kann dann den geteilten Zustand modifizieren. Einfach ein anderes Skript laufen lassen bringt nix. Das ist, als ob du das gleiche Word Dokument zweimal öffnest - da siehst du auch keine Änderung des einen im anderen.
Life4Gaming
User
Beiträge: 9
Registriert: Mittwoch 8. April 2020, 19:57

__deets__ hat geschrieben: Mittwoch 20. April 2022, 16:38 Du kannst in einem extra Thread in der GUI einen HTTP Server laufen lassen. Der kann dann den geteilten Zustand modifizieren. Einfach ein anderes Skript laufen lassen bringt nix. Das ist, als ob du das gleiche Word Dokument zweimal öffnest - da siehst du auch keine Änderung des einen im anderen.
Vielen Dank für den Hinweis!
Da ich mich noch nie wirklich mit Threads beschäftigt habe, hat es sehr lange gedauert bis ich etwas funktionierendes hinbekommen habe...

Allerdings bin ich mit dieser Lösung nicht wirklich zufrieden.


slideshow.py:

Code: Alles auswählen

timer = 5000
debug = True

root = tk.Tk()
SCREEN_WIDTH = root.winfo_screenwidth()
SCREEN_HEIGHT = root.winfo_screenheight()

pic_list = []
for name in glob.glob(r'C:\Coding\PythonSlideshow\images\*'):
    pic_list.append(name)


class SlideGui:
    def __init__(self, main_window, pause):
        self.new_pic = None
        self.pause = pause
        self.mainWindow = main_window
        self.mainWindow.title("Python Slideshow")
        self.mainWindow.geometry('%dx%d' % (SCREEN_WIDTH, SCREEN_HEIGHT))

        if not debug:
            root.attributes('-fullscreen', True)

        self.mainWindow.configure(bg="black")
        self.img = tk.Label(self.mainWindow)
        self.img.pack()

        self.label = tk.Label(self.mainWindow)
        self.label.pack(side="top", fill="both", expand=True)

        self.check_pause()
        self.pic()

    def stop(self):
        root.after_cancel(self.new_pic)

    def resume(self):
        self.pic()

    def pic(self):
        load = Image.open(random.choice(pic_list))
        load = ImageOps.exif_transpose(load)
        pic_width, pic_height = load.size
        real_aspect = pic_width / pic_height

        cal_width = int(real_aspect * SCREEN_HEIGHT)
        load2 = load.resize((cal_width, SCREEN_HEIGHT))

        render = ImageTk.PhotoImage(load2)

        self.img.config(image=render, borderwidth=0, highlightthickness=0)
        self.img.image = render

        self.new_pic = root.after(timer, self.pic)

    def check_pause(self):
        if self.pause.value == 1:
            self.stop()
            root.after(timer, self.check_not_pause)
        else:
            root.after(timer, self.check_pause)

    def check_not_pause(self):
        if self.pause.value == 0:
            self.resume()
            root.after(timer, self.check_pause)
        else:
            root.after(timer, self.check_not_pause)


if __name__ == "__main__":
    pause = multiprocessing.Value('i', 0)
    slideshow = SlideGui(root, pause)
    x = threading.Thread(target=webview.startWebview, args=(pause,))
    x.start()
    y = threading.Thread(target=root.mainloop(), args=(pause,))
    y.start()

webview.py:

Code: Alles auswählen

app = Flask(__name__)


class WebView(FlaskView):

    def start(self, pause):
        self.pause = pause
        app.run(debug=False, host='0.0.0.0')

    def index(self):
        return 'Hello world'

    @route("/screen", methods=["GET"])
    def screenOn(self):
        if "state" in request.args:
            state = request.args["state"]
            if state == "on":
                # run("vcgencmd display_power 1", shell=True)

                self.pause.value = 1
                print("Web: " + str(self.pause.value))
                return "Bildschirm an"

            elif state == "off":
                # run("vcgencmd display_power 0", shell=True)
                self.pause.value = 0
                return "Bildschirm wird ausgeschaltet"
            else:
                return "Unbekannter Status"

        else:
            return "Error: Kein Status angegeben"


WebView.register(app, route_base="/")


def startWebview(pause):
    WebView.start(FlaskView, pause)

Die HTTP Requests werden momentan genutzt zum ein und ausschalten des Bildschirms. Ich hab die jetzt einfach unverändert reingeschmissen also bitte nicht über /screen etc. wundern.

An sich funktioniert soweit alles, allerdings wirkt das für mich sehr aufwendig wenn ich noch andere Requests wie z.B. skip, timer, debug, etc. einbauen will.
Ich müsste dann für jeden Request eine eigene Value übergeben oder? Gibt es da geeignetere Wege?

Gibt es hier sonst noch Punkte die man verbessern kann?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich verstehe nicht, was genau du mit auf wenig meinst. Wenn du x verschiedene Dinge via HTTP steuern willst, müssen eben x verschiedene Requests verschickt werden. Wie genau die sich unterscheiden - also zb alle zum gleichen Händler, aber mit einem Parameter, der verschiedene Werte annimmt, oder zu 11 Handlern, das ist eine Frage des Geschmacks. Bzw von Konvention.

Verbessern lässt sich eine Menge. Ganz zu Anfang steht mal, dass man nur EINEN thread startet, den für flask. Denn der GUI thread muss der main thread sein, und den benutzt du jetzt nicht. Und über globale Variablen hat Sirius3 ja Schon was gesagt.
Life4Gaming
User
Beiträge: 9
Registriert: Mittwoch 8. April 2020, 19:57

__deets__ hat geschrieben: Donnerstag 21. April 2022, 08:00 Verbessern lässt sich eine Menge. Ganz zu Anfang steht mal, dass man nur EINEN thread startet, den für flask. Denn der GUI thread muss der main thread sein, und den benutzt du jetzt nicht. Und über globale Variablen hat Sirius3 ja Schon was gesagt.
Das ich nur den Thread für Flask starte hab ich jetzt eingebaut.

Meinst du mit den globalen Variablen timer und debug?
Die benutze ich ja momentan nur zum ausprobieren und die fliegen somit nachher auch noch raus

Vielen Dank für deine Hilfe nochmal an der Stelle!
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

timer, debug, root, pic_list sind alle global.
Life4Gaming
User
Beiträge: 9
Registriert: Mittwoch 8. April 2020, 19:57

Stimmt daran hab ich irgendwie nicht gedacht.
Hab ich jetzt auch abgeändert ^^
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@__deets__: es stimmt ja nicht, dass für die GUI ein eigener Thread gestartet wird. Es wird ein neuer Thread gestartet, wenn root.mainloop beendet wird, mit Target None.

@Life4Gaming: das was Du da mit WebView.start ist ziemlich kaputt. self ist nämlich keine Instanz sondern die FlaskView-Klasse und somit wird self.pause kein Instanzattribut sondern eine globale Klassenvariable im Namensraum von FlaskView!

Da Flask eh auf globalen Variablen aufbaut, ist es wohl ok, `pause` global zu definieren.
Den Nutzen von FlaskView sehe ich nicht, scheint mir nur alles viel komplizierter zu machen.

Code: Alles auswählen

import tkinter as tk
import threading
from flask import Flask, request

TIMER = 5000
DEBUG = True
IMAGE_PATH = 'C:/Coding/PythonSlideshow/images/*'

pause = threading.Event()
app = Flask(__name__)

@app.route("/screen", methods=["GET"])
def screen(self):
    if "state" in request.args:
        state = request.args["state"]
        if state == "on":
            # run("vcgencmd display_power 1", shell=True)
            pause.set()
            return "Bildschirm an"
        elif state == "off":
            # run("vcgencmd display_power 0", shell=True)
            pause.clear()
            return "Bildschirm wird ausgeschaltet"
        else:
            return "Unbekannter Status"
    else:
        return "Error: Kein Status angegeben"

def start_server():
    app.run(debug=False, host='0.0.0.0')


class SlideGui:
    def __init__(self, pause):
        self.new_pic = None
        self.pause = pause
        self.main_window = tk.Tk()
        self.pictures = glob.glob(IMAGE_PATH)
        self.main_window.title("Python Slideshow")
        if not DEBUG:
            self.main_window.attributes('-fullscreen', True)

        self.main_window.configure(bg="black")
        self.image = None
        self.img = tk.Label(self.main_window)
        self.img.pack()

        self.label = tk.Label(self.main_Window)
        self.label.pack(side="top", fill="both", expand=True)

        self.pic()

    def pic(self):
        if not self.pause.is_set():
            image = Image.open(random.choice(self.pictures))
            image = ImageOps.exif_transpose(image)
            pic_width, pic_height = image.size
            real_aspect = pic_width / pic_height
            screen_height = self.main_window.winfo_screenheight()

            cal_width = int(real_aspect * screen_height)
            resized_image = image.resize((cal_width, screen_height))

            photo_image = ImageTk.PhotoImage(resized_image)
            self.img.config(image=photo_image, borderwidth=0, highlightthickness=0)
            self.image = photo_image
        self.main_window.after(TIMER, self.pic)


def main():
    threading.Thread(target=start_server, daemon=True).start()
    slideshow = SlideGui(pause)
    slideshow.main_window.mainloop()

if __name__ == "__main__":
    main()
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sirius3 hat geschrieben: Donnerstag 21. April 2022, 10:54 @__deets__: es stimmt ja nicht, dass für die GUI ein eigener Thread gestartet wird. Es wird ein neuer Thread gestartet, wenn root.mainloop beendet wird, mit Target None.
Ah, das ist mir auf dem iPhone entgangen. Unnoetig ist es natuerlich trotzdem.
Life4Gaming
User
Beiträge: 9
Registriert: Mittwoch 8. April 2020, 19:57

Vielen Dank das hat mir sehr weitergeholfen!
Sirius3 hat geschrieben: Donnerstag 21. April 2022, 10:54 Den Nutzen von FlaskView sehe ich nicht, scheint mir nur alles viel komplizierter zu machen.
Ja ich wollte einfach (warum auch immer) Flask in einer Klasse haben und hab da auf die schnelle das mit FlaskView gefunden...
Ist ja jetzt aber viel einfacher als vorher.
Antworten