GUI-Elemente in Pygame nutzen (Button, Textfelder, ...)

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Hallo Zusammen,

ich bin in Python eher klassisch unterwegs. Meine GUI-Anwendungen schreibe ich mittels Tkinter.
Allerdings benötige ich auch sehr häufig Grafik um div. Dinge im mneinen Programmen zu viusualisieren.
Dazu eignet sich Pygame ja bekanntlich bestens.

Meine Google-Suche hat ergeben, dass es wohl nicht möglich ist ein Pygame-Fenster in eine Tkinter-Anwendung einzubetten.
Scheinbar bauen sich andere ihre Buttons, Checkboxen, ... selbst in Pygame zusammen.

Nun zur eigentlichen Frage:
Wie kann ich die Vorteile von Pygame und einen GUI-Framework (z.Bsp. Tkinter) kombinieren?
Oder gibt es in Pygame auch fertige Klassen für Buttons, etc?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Pygame hat zumindest nichts eingebautes, und was es so an Drittlösungen gibt, ist oft nicht gepflegt.

Auch wenn es natürlich Unterschiede zu pygame gibt, kann der Canvas von tkinter vieles erledigen. Hast du den mal probiert?
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Hallo __deets__,

ja, bislang habe ich alles in einer Canvas erledigt. Natürlich geht da auch.
Pygame bietet einfach viel mehr und es lässt sich deutlich angenehmer programmieren.

Grüße
aitsch
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@aitsch: Arbeite an Deiner Google-Suche oder Google ist schlechter geworden, dann nimm eine andere Suchmaschine. Man kann das Pygame-Fenster in Tk einbetten. Stichwort ist die Umgebungsvariable `SDL_WINDOWID`. Das findet man auch in der PyGame-Dokumentation bei der pygame.display.init()-Funktion.

Kleines Beispiel:

Code: Alles auswählen

#!/usr/bin/env python3
import os
import tkinter as tk
from random import randrange

import pygame

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)


def update_pygame(widget, screen):
    for event in pygame.event.get():
        print(event)
    screen.set_at(
        (randrange(0, screen.get_width()), randrange(0, screen.get_height())),
        WHITE,
    )
    pygame.display.update()
    widget.after(100, update_pygame, widget, screen)


def main():
    size = width, height = 320, 200
    root = tk.Tk()
    frame = tk.Frame(root, width=width, height=height)
    frame.pack()
    root.update_idletasks()
    os.environ["SDL_WINDOWID"] = str(frame.winfo_id())
    screen = pygame.display.set_mode(size)
    tk.Button(root, text="Löschen", command=lambda: screen.fill(BLACK)).pack()
    update_pygame(frame, screen)
    root.mainloop()


if __name__ == "__main__":
    main()
Einerseits muss man sich um die Ereignisse in Pygame kümmern, damit die Queue nicht voll läuft, andererseits werden viele Ereignisse schon von Tk behandelt, wie Maus- und Tastaturereignisse.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Da werde ich wohl nochmal in der Google-Schule nachsitzen müssen.
Man findet leider soviel Trash in den Suchergebnissen, dass mir wohl die korrekte Methode als solche nicht aufgefallen ist.

Aber: Danke für die Lösung !!!

Sie läuft aber ich würde sie auch gerne besser verstehen.

Daher noch ein paar Fragen:
1. Das mit der überlaufenden Queue der Pygame Events verunsichert mich etwas.
Bisher (ohne Einbettung in Tkinter) ist da noch nie etwas übergelaufen.
Ich würde wahrscheinlich nur Maus-Clicks abfragen bzw. behandeln wollen.
Sollte ich danach ein pygamne.event.clear() einfügen oder reicht die Abfrage über die for-Schleife?

2. Läuft die Pygame-Funktion durch:
widget.after(100, update_pygame, widget, screen)
in einem eigenen Thread oder Task?

3. Was genau bewirkt:
...
root.update_idletasks()
os.environ["SDL_WINDOWID"] = str(frame.winfo_id())
...
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@aitsch:

Ad 1.: `pygame.event.clear()` sollte reichen. Ich hatte die Ereignisse nur ausgegeben, damit man sieht, dass da beispielsweise die Mausbewegungen *nicht* ausgegeben werden wenn man mit der Maus über den PyGame-Bereich geht. Andererseits gibt es Ereignisse die dort behandelt werden können wie das Anzeigen des ”Fensters”. Und man sollte da auch andere Sachen verarbeiten können, die nicht von Tk verarbeitet werden wie Joysticks.

Mausklicks müsste man auf dem `Frame` in Tk behandeln. Beispiel um das setzen von roten Kreisen an der Mausposition erweitert:

Code: Alles auswählen

#!/usr/bin/env python3
import os
import tkinter as tk
from random import randrange

import pygame

BLACK = (0, 0, 0)
RED = (255, 0, 0)
WHITE = (255, 255, 255)


def update_pygame(widget, screen):
    for event in pygame.event.get():
        print(event)
    screen.set_at(
        (randrange(0, screen.get_width()), randrange(0, screen.get_height())),
        WHITE,
    )
    pygame.display.update()
    widget.after(100, update_pygame, widget, screen)


def main():
    size = width, height = 320, 200
    root = tk.Tk()
    frame = tk.Frame(root, width=width, height=height)
    frame.pack()
    root.update_idletasks()
    os.environ["SDL_WINDOWID"] = str(frame.winfo_id())
    screen = pygame.display.set_mode(size)
    frame.bind(
        "<Button>",
        lambda event: pygame.draw.circle(screen, RED, (event.x, event.y), 5),
    )
    tk.Button(root, text="Löschen", command=lambda: screen.fill(BLACK)).pack()
    update_pygame(frame, screen)
    root.mainloop()


if __name__ == "__main__":
    main()
Ad 2.: Nein die läuft im Hauptthread und wird von der `mainloop()` aufgerufen.

Ad 3.: Das sorgt für das Einbetten in die Tk-GUI. Wenn die Umgebungsvariable `SDL_WINDOWID` mit der ID eines ”Fensters” existiert, dann erstellt `pygame.init()`/`pygame.set_mode()` kein eigenes Fenster, sondern benutzt den Bereich der durch die ID beschrieben wird. Das `update_idletasks()` braucht man um sicherzustellen, dass der `Frame` tatsächlich auf Systemebene erstellt wurde und dort eine ID bekommen hat, die man abfragen kann.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Antworten