Kivy - Anzeigelemente updaten

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
newb
User
Beiträge: 1
Registriert: Montag 13. Mai 2024, 23:09

Ich mache ein kleines GUI für ein Uniprojekt: Es enthält verschiedene Anzeigeelemente, die laufend upgedatet werden müssen.

Mein Problem:
Nach aufrufen der ersten Funktionssquenz "Homing", werden keine Anzeigeelement mehr upgedatet. Im Debug.Log sehe ich, dass die Anzeigeelemente korrekt upgedatet werden - dennoch zeigt mir das GUI interface diese Updates nicht an. Das Seltsame: Homing funktioniert problemlos - sowohl Labels, wie Fortschrittsbalken werden problemlos upgedatet.

Kann mir jemand weiterhelfen?
Da ich mit dem Screenmanager arbeite, bin ich nicht sicher, ob der mir irgendwie dazwischen funkt. Meine Testapp, die mit nur einem einzigen Screen auskam, hatte keine Probleme beim Anzeigen der Anzeige-Elemente.

Hier der relevante Python - Code - bis und mit home_stop(self) läuft alles wunderbar. Ab start(self), resp. clear_progress_bar(self) funzt nix mehr.

Create custom class Cube
class Cube(Widget):
#initialize StringProperty
status : StringProperty = StringProperty(None)

def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(status=self.on_status_change)

def on_status_change(self, instance, value:str):
#Check if status changed
Logger.info(f"Statusänderung erkannt: {value}")
return value

#Set Window-size
Window.size = (1024, 600)

class WindowManager(ScreenManager):
pass

#Define Screens
class StartWindow(Screen):
pass


class GuiWindow(Screen):
"""#Start Pathing and CubeDetector
pathing: Pathing | None = None
pathing_thread : Process = None
cube_detection_thread: Process = None

init_semaphore: int = 0"""

#Queues
queues: SystemQueues = SystemQueues()
bilderkennung_to_gui = queues.gui_bilderkennung_rx
gui_to_bilderkennung = queues.gui_bilderkennung_tx
pathing_to_gui = queues.gui_pathing_rx
gui_to_pathing = queues.gui_pathing_tx

home_started = False
progress_count = 1

def __init__(self, **kwargs):
super().__init__(**kwargs)

#initialize variables
self.time_started = False
self.time_seconds = 0
self.progress_started = False

self.energy_started = False

#Reset counter

#Schedule Queue Handler
Clock.schedule_interval(self.queue_handler, 1)

"""if GuiWindow.pathing is None:
GuiWindow.pathing = PathingFactory.setup_pathing(self.queues)

#initialize threads
GuiWindow.pathing_thread = Process(target=self.pathing.run)
GuiWindow.cube_detection_thread = Process(target=init_cube_detection, args=(self.queues, ))"""


def queue_handler(self, get_pathing):
try:
# check for object in pathing queue
if not GuiWindow.pathing_to_gui.empty():
get_pathing = GuiWindow.pathing_to_gui.get_nowait()
else:
get_pathing = None

prev_get_pathing = None

#Debug Logger entry, if Pathing-Queue updated
if get_pathing == prev_get_pathing:
pass
else:
prev_get_pathing = get_pathing
Logger.info(f"Pathing to gui = {get_pathing}")

# Check if object in cubedetector queue
if not GuiWindow.bilderkennung_to_gui.empty():
get_bildanalyse = GuiWindow.bilderkennung_to_gui.get_nowait()
else:
get_bildanalyse = None

# Debug Logger entry, if CubeDetector Queue updated
prev_get_bildanalyse = None
if get_bildanalyse == prev_get_bildanalyse:
pass
else:
prev_get_bildanalyse = get_bildanalyse
Logger.info(f"Bildanalyse to gui = {get_bildanalyse}")

if isinstance(get_pathing, Setup):
self.home_update(get_pathing)
elif isinstance(get_pathing, MeasureResults):
self.energy_update(get_pathing)
elif isinstance(get_pathing, Build) or isinstance(get_bildanalyse, Analyse):
self.start_update(get_pathing, get_bildanalyse)
elif isinstance(get_pathing, Reset):
self.reset_update(get_pathing)
elif isinstance(get_pathing, Emergency):
self.emergency(get_pathing)

except Empty:
Logger.debug("Die Queue ist leer")
except Exception as e:
Logger.debug(f"Fehler aufgetreten {e}")

#automatic homing when starting 3drebuilder the first time
def home_rebuilder(self):
GuiWindow.home_started = True
Logger.info(f"Homing started {self.home_started}")
command = Setup("home")
GuiWindow.gui_to_pathing.put(command)

Clock.schedule_once(self.fake_homing,1)

#Fake homing_answer
def fake_homing(self, dt):
command = Setup(None, "ack")
GuiWindow.pathing_to_gui.put(command)

Clock.schedule_once(self.fake_homing_two, 9)

def fake_homing_two(self, dt):
command = Setup(None, "done")
GuiWindow.pathing_to_gui.put(command)

def home_update(self, get_pathing: Setup):
Logger.info("Home_update called")
if GuiWindow.home_started:
if get_pathing.ans == "ack":
self.update_progress_label("Bitte warten..")

zeiten = [0.1, 1.5, 2.2, 3.9, 5.2, 6.7, 8, 9.4]
for zeit in zeiten:
Clock.schedule_once(self.update_progress_bar, zeit)

elif get_pathing.ans == "done":
self.update_progress_label("Gerät ist betriebsbereit.")
Clock.schedule_once(self.update_progress_bar, 0)
self.ids.start.disabled = False
self.ids.reset.disabled = False
self.home_stop()

def home_stop(self):
if GuiWindow.home_started:
GuiWindow.home_started = False
Logger.info(f"Home started {GuiWindow.home_started}")

def update_progress_label(self, text):
self.ids.progress_label.text = text
Logger.info(f"Progress_Label zeigt: {self.ids.progress_label.text}")

def update_progress_bar(self, dt):
Logger.info("Update Progressbar called")
if GuiWindow.progress_count <= 9:
cube_id = "p"+str(self.progress_count)
cube = self.ids.get(cube_id, None)
cube.status = "highlight"
GuiWindow.progress_count +=1
else:
pass

def clear_progress_bar(self): #Funzt irgendwie nicht..
Logger.info("Clear Progressbar called")
GuiWindow.progress_count = 1
Logger.info(f"Progress count {GuiWindow.progress_count}")
self.update_gui()
for i in range(1,10):
cube_id = "p"+str(i)
cube = self.ids.get(cube_id, None)
Logger.info(f"Cube status {cube.status}")
if cube:
Cube.on_status_change(self, cube, None)
else:
Logger.error("Cube nicht erkannt")
self.update_gui()


#Start_Button pressed
def start(self):
if not GuiWindow.home_started:
# Starten Sie die gewünschte Funktionalität
self.clear_progress_bar()

else:
Logger.info("Es laufen noch Hintergrundprozesse. Start wird zurückgestellt.")
Benutzeravatar
__blackjack__
User
Beiträge: 13219
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@newb: Bei Typannotationen darf man davon ausgehen das weder der Leser noch die Software, die das überprüft, komplett blöd ist. Wenn da direkt ein Wert zugewiesen wird und man daran sieht welchen Typ der hat, dann macht eine Typannotation überhaupt keinen Sinn. Bei ``status : StringProperty = StringProperty(None)`` hilft da niemanden das als `StringProperty` zu annotieren. Das sieht man auch ohne die Annotation. Der Kommentar ``#initialize StringProperty`` ist genau so überflüssig an der Stelle.

Bei `GuiWindow` die ganzen Klassenvariablen haben da nichts zu suchen, die gehören auf das Objekt.

Literale Zeichenketten sind nicht zum auskommentieren von Code gedacht. Die haben an bestimmten Stellen eine besondere Bedeutung für Python als Sprache und an noch mehr Stellen für Dokumentationswerkzeuge. Das einzige Kommentarzeichen ist „#“.

Bei Queues testet man nicht mit der `empty()`-Methode sondern man fragt einfach das nächste Element ab und behandelt die Ausnahme falls nichts drin war.

Diese verschleierten Vergleiche von `get_pathing` und `get_bildanalyse` mit `None` sind schräg. Die Namen auch, denn etwas das mit `get_*` anfängt weckt beim Leser die starke Erwartung, dass es aufrufbar ist und einen Wert liefert. Und das zuweisen an `prev_get_pathing` und `prev_get_bildanalyse` in den ``else``-Zweigen ist unsinnig, weil diese Werte nie irgendwo verwendet werden.

``if``- oder ``else``-Zweige in denen nur ein ``pass`` stehen sind unsinnig, das macht man nicht.

Kommentare sollten inhaltlich stimmen. Wenn ein Kommentar behauptet es wird eine Debug-Ausgabe protokolliert, dann darf im Code kein `info()`-Aurfuf stehen. Was ist denn an der Stelle falsch? Der Kommentar oder der Code?

Ausnahmen sollten mit `exception()` protokolliert werden und nicht mit `debug()`.

`isinstance()` um zu entscheiden was gemacht werrden soll ist unschön weil nicht wirklich OOP. Das würde man mindestens mal versuchen mit ``match``/``case`` auszudrücken.

Die `start_update()`-Methode riecht komisch. Dort muss ja noch mal Code stehen der rausfindet welches der beiden Argumente nicht `None` ist (oder vielleicht sogar beide).

In `clear_progress_bar()` ist das ``if cube:`` überflüssig denn der Code davor wäre schon auf die Name gefallen wenn das ``None`` gewesen wäre.

Dann kann man auf der *Klasse* `Cube` nicht die Funktion `on_status_change()` mit `self`, was ja so gar kein `Cube`-Objekt ist, als erstem Argument aufrufen. Das müsste man also schon auf dem `cube`-Objekt aufrufen. Das nächste ist die Frage warum man das machen wollen würde‽ Das ist der Rückruf der aufgerufen wird wenn man dem `StringProperty` einen neuen Wert zuweist. Warum will man das aufrufen ohne dem einen neuen Wert zuzuweisen? Und *dann* braucht man das ja auch nicht selber aufrufen wenn man *das* macht.

Ungetestet:

Code: Alles auswählen

class Cube(Widget):
    status = StringProperty(None)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.bind(status=self.on_status_change)

    def on_status_change(self, _instance, value):
        Logger.info("Statusänderung erkannt: %s", value)
        return value


class WindowManager(ScreenManager):
    pass


class StartWindow(Screen):
    pass


class GuiWindow(Screen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        queues = SystemQueues()
        self.bilderkennung_to_gui = queues.gui_bilderkennung_rx
        self.gui_to_bilderkennung = queues.gui_bilderkennung_tx
        self.pathing_to_gui = queues.gui_pathing_rx
        self.gui_to_pathing = queues.gui_pathing_tx

        self.home_started = False
        self.progress_count = 1

        self.time_started = False
        self.time_seconds = 0
        self.progress_started = False

        self.energy_started = False

        Clock.schedule_interval(self.queue_handler, 1)

        # if self.pathing is None:
        #     self.pathing = PathingFactory.setup_pathing(self.queues)
        #     self.pathing_thread = Process(target=self.pathing.run)
        #     self.cube_detection_thread = Process(
        #         target=init_cube_detection, args=(self.queues,)
        #     )

    def queue_handler(self, get_pathing):
        try:
            try:
                pathing_event = self.pathing_to_gui.get_nowait()
            except Empty:
                pathing_event = None
            else:
                Logger.info("Pathing to gui = %s", pathing_event)

            try:
                image_analyse_event = self.bilderkennung_to_gui.get_nowait()
            except Empty:
                image_analyse_event = None
            else:
                Logger.info("Bildanalyse to gui = %s", image_analyse_event)
            #
            # XXX Eventuell lässt sich hier auch mehr matchen und machen als nur
            #     eine Methode aufrufen, oder zumindest Werte aus den
            #     Ereignis-Datenobjekten "entpacken".
            # 
            start_update_called = False
            match pathing_event:
                case Setup():
                    self.home_update(pathing_event)
                case MeasureResults():
                    self.energy_update(pathing_event)
                case Build():
                    self.start_update(pathing_event, image_analyse_event)
                    start_update_called = True
                case Reset():
                    self.reset_update(pathing_event)
                case Emergency():
                    self.emergency(pathing_event)

            if not start_update_called and isinstance(
                image_analyse_event, Analyse
            ):
                self.start_update(pathing_event, image_analyse_event)

        except Exception as error:
            Logger.exception("Fehler aufgetreten.")

    def home_rebuilder(self):
        self.home_started = True
        Logger.info(f"Homing started {self.home_started}")
        self.gui_to_pathing.put(Setup("home"))
        Clock.schedule_once(self.fake_homing, 1)

    def fake_homing(self, _):
        self.pathing_to_gui.put(Setup(None, "ack"))
        Clock.schedule_once(self.fake_homing_two, 9)

    def fake_homing_two(self, _):
        self.pathing_to_gui.put(Setup(None, "done"))

    def home_update(self, setup_event):
        Logger.info("Home_update called")
        if self.home_started:
            if setup_event.ans == "ack":
                self.update_progress_label("Bitte warten..")
                for zeit in [0.1, 1.5, 2.2, 3.9, 5.2, 6.7, 8, 9.4]:
                    Clock.schedule_once(self.update_progress_bar, zeit)

            elif setup_event.ans == "done":
                self.update_progress_label("Gerät ist betriebsbereit.")
                Clock.schedule_once(self.update_progress_bar, 0)
                self.ids.start.disabled = False
                self.ids.reset.disabled = False
                self.home_stop()

    def home_stop(self):
        if self.home_started:
            self.home_started = False
            Logger.info("Home started %s", self.home_started)

    def update_progress_label(self, text):
        self.ids.progress_label.text = text
        Logger.info("Progress_Label zeigt: %s", self.ids.progress_label.text)

    def update_progress_bar(self, _):
        Logger.info("Update Progressbar called")
        if self.progress_count <= 9:
            self.ids.get(f"p{self.progress_count}", None).status = "highlight"
            self.progress_count += 1

    def clear_progress_bar(self):
        Logger.info("Clear Progressbar called")
        self.progress_count = 1
        Logger.info(f"Progress count {self.progress_count}")
        for i in range(1, 10):
            cube = self.ids.get(f"p{i}", None)
            Logger.info("Cube status %s", cube.status)
            cube.status = None

    def start(self):
        if not self.home_started:
            self.clear_progress_bar()
        else:
            Logger.info(
                "Es laufen noch Hintergrundprozesse. Start wird zurückgestellt."
            )
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Antworten