Fensterinhalt, Aufbau über Menu ändern

Fragen zu Tkinter.
Antworten
kiaralle
User
Beiträge: 195
Registriert: Donnerstag 19. August 2021, 19:11

Hi,
ich könnte mir vorstellen, das ich den Fensterinhalt, Frames usw beim aufrufen eines Menus löschen und mit neuen Inhalt bestücken kann.
Könnt ihr mich kurz einweihen wie ich das mache.
Springe ich dann beim Aufruf von Menu Hiperface in def window_hyperface(): oder muss ich mir eine Klasse bauen in der ich dann hin und her springe.
Meine begrenzten Kenntnisse :-)

Aktuell öffne ich ein neues Fenster. Das würde mir aber nicht so gut gefallen.
Es kann aber auch durchaus so der bessere Weg sein.

Hier man erster Aufbau

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Apr 30 20:05:33 2026

@author: ralf
"""

#from tkinter import 
#import tkinter as tk

from tkinter import ttk, Label, Button, Entry, Menu, LabelFrame, Text, Checkbutton, Radiobutton, messagebox
from ttkthemes import ThemedTk

import serial
import pyudev



usbport =""
context = pyudev.Context()

for device in context.list_devices(subsystem='tty', ID_VENDOR_ID='0403', ): 
    print(device.device_node)
    
    if device.device_node != "":
        # initialization and open the port
        serial_interface = serial.Serial()
        serial_interface.port =device.device_node
        serial_interface.baudrate = 9600
        serial_interface.bytesize = serial.EIGHTBITS 
        serial_interface.parity = serial.PARITY_EVEN 
        serial_interface.stopbits = serial.STOPBITS_ONE 
        serial_interface.timeout = 3      
        serial_interface.xonxoff = False     
        serial_interface.rtscts = False     
        serial_interface.dsrdtr = False       
        usbport= device.device_node





hintergrundcolor="#ffffff"
hintergrundcolornotebook="#ffffff"





def window_hyperface():
    df_window = ThemedTk(theme="blue")
    df_window.title("Hiperface")
    df_window.geometry("800x800")
  
    my_menu=Menu(df_window)
    df_window.config(menu=my_menu)
    df_window_menu= Menu(my_menu)
    my_menu.add_cascade(label="Menu", menu=df_window_menu)
    df_window_menu.add_command(label="Exit",command=df_window.destroy)   
    
    info_frame= LabelFrame(df_window, borderwidth=1, relief="sunken")
    info_frame.grid(column=0, row=0, sticky="w",padx=2,pady=2, columnspan=4)
   
    reset_frame= LabelFrame(df_window, borderwidth=1, relief="sunken")
    reset_frame.grid(column=0, row=1, sticky="w",padx=2,pady=2, columnspan=4)
   


def main():

    root = ThemedTk(theme="blue")
    root.title("Servo-Box Version 1" + usbport )
    root.config(bg=hintergrundcolor)
    #root.config(bg="#FFFFFF" )
    # rootgröße ermitteln
    w, h = root.winfo_screenwidth(), root.winfo_screenheight()
    root.geometry("%dx%d+0+0" % (w, h))
    #root.geometry("1024x750")
    #root. minsize(width=1200, height=950)


        
    my_menu=Menu(root )
    root.config(menu=my_menu)
    file_menu= Menu(my_menu)
    geber_menu = Menu(my_menu)
    messung_menu = Menu(my_menu)
    info_menu = Menu(my_menu)
    
    my_menu.add_cascade(label="Menu", menu=file_menu)
    file_menu.add_command(label="Exit",command=root.destroy) 
    

    
    if usbport == "":
        messagebox.showerror('Beenden', 'Keine Schnittstelle, kein Gerät vorhanden. Bitte Anlage überprüfen und Programm neu starten.')
    else:
        my_menu.add_cascade(label="Geber", menu=geber_menu)   
        geber_menu.add_command(label="Hyperface", command=lambda: window_hyperface()) 
        geber_menu.add_command(label="Hyperface DSL")   
        geber_menu.add_command(label="Resolver") 
        geber_menu.add_command(label="Endat analog")   
        geber_menu.add_command(label="Endat digital")
        
        my_menu.add_cascade(label="Data", menu=messung_menu)   
        messung_menu.add_command(label="Analog") 
        messung_menu.add_command(label="RS485")
        messung_menu.add_command(label="RS422")
        
        my_menu.add_cascade(label="Info", menu=info_menu)    
        info_menu.add_command(label="Schnittstelle : " + usbport)
        

        
    root.mainloop()



if __name__ == "__main__":
    main()

Benutzeravatar
__blackjack__
User
Beiträge: 14401
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kiaralle: Bei mehreren möglichen Inhalten im gleichen Fenster, von denen immer nur eine angezeigt werden soll, erstellt man die üblicherweise alle und legt die übereinander, und holt immer das was man aktuell sehen will in den Vordergrund. Viele andere GUI-Rahmenwerke haben da schon was für — in tkinter muss man sich das selber bauen. Üblicherweise `Frame`\s die allen verfügbaren Platz in einer `grid()`-Zelle ausfüllen, und die man alle in die gleiche `grid()`-Zelle steckt und das gewünschte dann mit der `lift()`-Methode nach oben holt. Oder man verwendet ein `ttk.Notebook`.

Bei dem Quelltext habe ich das Gefühl, dass schon ein oder zweimal erwähnt wurde, das auf Modulebene nur Code gehört der Konstanten, Funktionen, und Klassen definiert.

Konstanten werden KOMPLETT_GROSS geschrieben, und man sollte sich auf _eine_ natürliche Sprache festlegen. `background_color` oder `hintergrundfarbe`, aber nicht `hintergrundcolor`. "white" wäre ein bisschen verständlicher als "#ffffff". Ebenso sind gemischte GUI-Texte komisch. "Exit" hat in einer ansonsten deutschsprachigen Oberfläche nix zu suchen.

Der Kommentar ``# initialization and open the port`` stimmt nicht, weil dort der Port gar nicht geöffnet wird. Dazu würde man die Angaben als Argumente gleich beim Erstellen des `Serial`-Objekts übergeben. Und wie schon mal an anderer Stelle erwähnt, nur die, die man auch übergeben muss, weil sie von den Voreinstellungen abweichen.

Das `Serial`-Objekt sollte man auch mit der ``with``-Anweisung verwenden, damit das am Ende deterministisch und sichergestellt geschlossen wird.

`usbport` ist als Variable redundant, weil man diesen Wert ja auch jederzeit vom `Serial`-Objekt abfragen kann.

Vergrösser des Fensters auf Desktopgrösse bastelt man sich nicht mit `geometry()`, denn das funktioniert nicht zuverlässig, weil nicht alle Windowmanager die Fensterdekoration sinnvoll mit einberechnen. Man nutzt dafür die Maximieren-Funktion der Fensterverwaltung die man als Benutzer auch interaktiv verwenden würde: ``window.attributes("-zoomed", True)``.

Der ``lambda``-Ausdruck beim "Hyperface"-Menü ist überflüssig, da wird ja nur eine Funktion aufgerufen, die man auch direkt hätte übergeben können.

Es darf nur ein `Tk`-Objekt zur gleichen Zeit geben, sonst können komische Dinge passieren. Das ist _das_ Hauptfenster. Wo auch der Tk-Interpreter mit dran hängt. Für zusätzliche Fenster gibt es `Toplevel`-Objekte.

Wenn es im `tkinter`-Modul Konstanten für Zeichenketten mit einer festen Bedeutung als Argument gibt, sollte man die verwenden, und nicht die Zeichenkette als Literal.

In der Schleife, die die Schnittstellen durchgeht fehlt ein ``break``, damit die Schleife endet wenn man eine Schnittstelle gefunden hat, die sich öffnen lässt.

Warum werden da `LabelFrame` ohne Text für das Label verwendet? Dann ist das doch einfach nur ein `Frame`!?

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
"""
Created on Thu Apr 30 20:05:33 2026

@author: ralf
"""

from tkinter import SUNKEN, Frame, Menu, Toplevel, W, messagebox

import pyudev
import serial
from ttkthemes import ThemedTk

BACKGROUND_COLOR = "white"
NOTEBOOK_BACKGROUND_COLOR = "white"


def create_hyperface_window():
    window = Toplevel()
    window.title("Hyperface")

    menu = Menu(window)
    window.config(menu=menu)
    window_menu = Menu(menu)
    menu.add_cascade(label="Datei", menu=window_menu)
    window_menu.add_command(label="Beenden", command=window.destroy)

    info_frame = Frame(window, borderwidth=1, relief=SUNKEN)
    info_frame.grid(column=0, row=0, sticky=W, padx=2, pady=2)

    reset_frame = Frame(window, borderwidth=1, relief=SUNKEN)
    reset_frame.grid(column=0, row=1, sticky=W, padx=2, pady=2)


def main():

    for device in pyudev.Context().list_devices(
        subsystem="tty", ID_VENDOR_ID="0403"
    ):
        print(device.device_node)
        if device.device_node:
            try:
                with serial.Serial(
                    device.device_node, parity=serial.PARITY_EVEN, timeout=3
                ) as connection:
                    root = ThemedTk(theme="blue")
                    root.title(f"Servo-Box Version 1 {connection.port}")
                    root.config(bg=BACKGROUND_COLOR)
                    root.attributes("-zoomed", True)

                    menu = Menu(root)
                    root.config(menu=menu)
                    file_menu = Menu(menu)
                    geber_menu = Menu(menu)
                    messung_menu = Menu(menu)
                    info_menu = Menu(menu)

                    menu.add_cascade(label="Datei", menu=file_menu)
                    file_menu.add_command(
                        label="Beenden", command=root.destroy
                    )
                    menu.add_cascade(label="Geber", menu=geber_menu)
                    geber_menu.add_command(
                        label="Hyperface", command=create_hyperface_window
                    )
                    geber_menu.add_command(label="Hyperface DSL")
                    geber_menu.add_command(label="Resolver")
                    geber_menu.add_command(label="Endat analog")
                    geber_menu.add_command(label="Endat digital")

                    menu.add_cascade(label="Data", menu=messung_menu)
                    messung_menu.add_command(label="Analog")
                    messung_menu.add_command(label="RS485")
                    messung_menu.add_command(label="RS422")

                    menu.add_cascade(label="Info", menu=info_menu)
                    info_menu.add_command(
                        label=f"Schnittstelle : {connection.port}"
                    )
                    root.mainloop()
                    break
            except serial.SerialException as error:
                messagebox.showerror(
                    "Fehler", f"{device.device_node}:\n{error}."
                )
    else:
        messagebox.showerror(
            "Beenden",
            (
                "Keine Schnittstelle/kein Gerät vorhanden.\n"
                "Bitte Anlage überprüfen und Programm neu starten."
            ),
        )


if __name__ == "__main__":
    main()
Ich finde die Hauptfunktion ja schon zu gross, und letztlich wird man bei GUI-Programmierung nicht um das programmieren von mindestens einer Klasse herum kommen. Entweder für die GUI oder um die Verbindung und die Programmlogik zu kapseln, die man dann immer herum reichen muss, wenn die GUI nicht objektorientiert gelöst ist.
“It is well known that a vital ingredient of success is not knowing that what you're attempting can't be done.”
— Terry Pratchett, Equal Rites
kiaralle
User
Beiträge: 195
Registriert: Donnerstag 19. August 2021, 19:11

@__blackjack__ Danke.
Wie immer was dazu gelernt.
Meine nächste frage wäre nämlich gewesen ,wie man die Sub-Fenster beim beenden des Hauptfensters mit schleiß.

Danke.
Antworten