Checkbutton im Menü

Fragen zu Tkinter.
Antworten
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Hallo,

ich möchte im Menü den Cascadeneintrag "Programmsprache wählen" einfügen. Darunter sollen vorerst deutsch und englisch auswählbar sein. Ich denke dazu an Checkbuttons im Menü. Jedoch kann ich nachträglich nicht mehr darauf zugreifen, so dass die möglicherweise vorher gewählten Menüeinträge wieder nicht selektiert werden.
Hier mein Auszug aus der GUI:

Code: Alles auswählen

		self.languagepack = Menu(top, tearoff=0)
		self.optionen.add_cascade(menu=self.languagepack,
				activebackground="#d9d9d9",
				activeforeground="#000000",
				background="#d9d9d9",
				font="TkMenuFont",
				foreground="#000000",
				label="Programmsprache wählen")
		self.languagepack.add_checkbutton(
				variable=self.LangA,
				activebackground="#d9d9d9",
				activeforeground="#000000",
				background="#d9d9d9",
				command=self.LangChoose,
				font="TkMenuFont",
				foreground="#000000",
				label="Deutsch",
				onvalue=1,
				offvalue=0)
		self.languagepack.add_checkbutton(
				variable=self.LangB,
				activebackground="#d9d9d9",
				activeforeground="#000000",
				background="#d9d9d9",
				command=self.LangChoose,
				font="TkMenuFont",
				foreground="#000000",
				label="Englisch",
				onvalue=1,
				offvalue=0)
Kann mir jemand helfen? Es soll nur ein einziger Menü-Eintrag angeählt angezeigt werden.

Grüße von Stefan an einem sonnigen Sonntag nachmittag.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

stefanxfg hat geschrieben:Hallo,

ich möchte im Menü den Cascadeneintrag "Programmsprache wählen" einfügen. Darunter sollen vorerst deutsch und englisch auswählbar sein. Ich denke dazu an Checkbuttons im Menü.
Da denkst Du falsch! Checkbuttons signalisieren dem Anwender, daß mehrfaches Ankreuzen möglich ist. Soll nur eine Auswahl möglich sein, nimmt man Radiobuttons!
stefanxfg hat geschrieben:Jedoch kann ich nachträglich nicht mehr darauf zugreifen, so dass die möglicherweise vorher gewählten Menüeinträge wieder nicht selektiert werden.
Nachträglich darauf zugreifen sollst Du nicht. Das wäre auch kompliziert. Wenn Du Radiobuttons nimmst, dann verbinde sie mit ein und derselben Variablen, die muss aber eine tkinter Variable sein, nämlich tk.IntVar oder tk.StringVar. Strings sind aussagekräftiger.

Außerdem sollte man die Methode LangChoose in zwei Methoden unterteilen, nämlich eine, welche die Auswahl des Benutzer im Menü behandelt und eine, welche die Sprache umstellt und vom Menü eventuell nichts zu wissen braucht. Hier ist, was mir dazu einfiel:

Code: Alles auswählen

import tkinter as tk

DEFAULT_LANGUAGE = 'Deutsch'

class AppMenu(tk.Menu):
    def __init__(self,master,LangChoose,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)

        self.LangChoose = LangChoose
        self.language = tk.StringVar()
        self.language.set(DEFAULT_LANGUAGE)
 
        self.languagepack = tk.Menu(self,tearoff=0)

        self.add_cascade(menu=self.languagepack,
            activebackground="#d9d9d9",
            activeforeground="#000000",
            background="#d9d9d9",
            font="TkMenuFont",
            foreground="#000000",
            label="Programmsprache wählen")

        self.languagepack.add_radiobutton(
            variable=self.language,
            activebackground="#d9d9d9",
            activeforeground="#000000",
            background="#d9d9d9",
            command=self.select_language,
            font="TkMenuFont",
            foreground="#000000",
            label="Deutsch",
            value = 'Deutsch')

        self.languagepack.add_radiobutton(
            variable=self.language,
            activebackground="#d9d9d9",
            activeforeground="#000000",
            background="#d9d9d9",
            command=self.select_language,
            font="TkMenuFont",
            foreground="#000000",
            label="Englisch",
            value = 'Englisch')

    def select_language(self):
        self.LangChoose(self.language.get())
        

class Application(tk.Tk):
    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)

        self['menu'] = AppMenu(self,self.LangChoose)
        self.LangChoose(DEFAULT_LANGUAGE)

    def LangChoose(self,language):
        print(language)

Application().mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Noch einmal klarer geordnet mit Kommentar # radiobutton Logik, damit man besser sieht, worauf es ankommt:

Code: Alles auswählen

import tkinter as tk

DEFAULT_LANGUAGE = 'Deutsch'

class AppMenu(tk.Menu):
    def __init__(self,master,LangChoose,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)

        self.LangChoose = LangChoose
        self.language = tk.StringVar()
        self.language.set(DEFAULT_LANGUAGE)

        self.languagepack = tk.Menu(self,tearoff=0)

        self.add_cascade(menu=self.languagepack,
            activebackground="#d9d9d9",
            activeforeground="#000000",
            background="#d9d9d9",
            font="TkMenuFont",
            foreground="#000000",
            label="Programmsprache wählen")

        self.languagepack.add_radiobutton(
            activebackground="#d9d9d9",
            activeforeground="#000000",
            background="#d9d9d9",
            font="TkMenuFont",
            foreground="#000000",
            label="Deutsch",
            # radiobutton Logik
            variable=self.language,
            value = 'Deutsch'
            command=self.select_language)

        self.languagepack.add_radiobutton(
            activebackground="#d9d9d9",
            activeforeground="#000000",
            background="#d9d9d9",
            font="TkMenuFont",
            foreground="#000000",
            label="Englisch",
            # radiobutton Logik
            variable=self.language,
            value = 'Englisch'
            command=self.select_language)

    def select_language(self):
        self.LangChoose(self.language.get())
        
    
class Application(tk.Tk):
    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)

        self['menu'] = AppMenu(self,self.LangChoose)
        self.LangChoose(DEFAULT_LANGUAGE)

    def LangChoose(self,language):
        print(language)

Application().mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@stefanxfg: so etwas sollte man nicht für Menüs oder Menü Itemn angeben:

Code: Alles auswählen

    activeforeground="#000000",
    background="#d9d9d9",
    font="TkMenuFont",
    foreground="#000000",
Das sind nämlich Default Einstellungen für Menus und deren Items, wie sie bereits gesetzt sind. Dreimal angegeben ergibt das zwölf unnötige Zeilen. Nicht unnötig ist allerdings, falls Du dieses so wünscht:

Code: Alles auswählen

    activebackground="#d9d9d9",
Hier setzt Du den Hintergrund, wenn die Maus darüber liegt, so, wie auch der normale Hintergrund ist. Abweichende Settings gibt man natürlich an. Aber wo?

Wenn man jedes Menüitem so haben will, genügt es, so etwas lediglich für das Menü anzugeben, denn dann gilt es auch für alle Items in diesem Menü - sofern man diese nicht individuell noch zuzusätzlich verändert - allerdings nicht für dessen Submenüs.

Ich habe das jetzt geändert und außerdem eine generelle Lösung gemacht, bei er man einfach durch die Angabe der vorgesehenen Auswahlen in einem Tuple und der Defaultauswahl automatisch die Radiobuttons bekommt, die dann auch funktionieren.

Code: Alles auswählen

# -*- coding: utf-8 -*-
import tkinter as tk
 
DEFAULT_SELECTION = 'Deutsch'
PROVIDED_SELECTIONS = ('Deutsch','Englisch')
MENU_ACTIVEBACKGROUND = '#d9d9d9'
CASCADE_LABEL = "Programmsprache wählen"

class AppMenu(tk.Menu):
    def __init__(self,master,extern_callback,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
 
        submenu = tk.Menu(self,tearoff=0,activebackground=MENU_ACTIVEBACKGROUND)
        self.add_cascade(menu=submenu, label=CASCADE_LABEL)

        selection_var = tk.StringVar()
        selection_var.set(DEFAULT_SELECTION)
        extern_callback(DEFAULT_SELECTION)

        def options(name): return {
            'label'     : name,
            'value'     : name,
            'variable'  : selection_var,
            'command'   : lambda : extern_callback(selection_var.get()) }

        for name in PROVIDED_SELECTIONS:
            submenu.add_radiobutton(**options(name))
   
class Application(tk.Tk):
    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
 
        self['menu'] = AppMenu(self,self.LangChoose,activebackground=MENU_ACTIVEBACKGROUND)

    def LangChoose(self,language):
        print(language)
 
Application().mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@stefanxfg: das mit activebackground umstellen gefällt mir gar nicht. Das mit einem bestimmten Farbwert zu tun, noch weiniger. Ist es gewährleistet, daß die Farben der Menüs in jedem System ob Linux, Windows oder Mac OS gleich sind?

Wenn es darum ginge, den activebackground auf die Farbe des background umzustellem, solltge man es daher nicht tun mit:

Code: Alles auswählen

submenu = tk.Menu(self,tearoff=0,activebackground='#d9d9d9')
Richtig für diesen Fall wäre:

Code: Alles auswählen

submenu = tk.Menu(self,tearoff=0)
submenu['activebackground'] = submenu['background']
Doch ist dieses Umstellen richtig, was gewinnt man damit? Wohl gar nichts. Die Auswahl im Menü ist nicht in den gewohnten Farben, sonders schlechter zu sehen.

Warum läßt man es nicht einfach so, wie der Benutzer es gewohnt ist und wie es auch besser zu sehen ist?

Wie bist Du auf diese Werte gekommen, und wie bist Du besonders auf den Wert für activebackground gekommen?

Code: Alles auswählen

activebackground='#d9d9d9'
activeforeground="#000000",
background="#d9d9d9",
font="TkMenuFont",
foreground="#000000",
Vielleicht falsch kopiert? Die richtigen Werte zumindest für Linux wären:

Code: Alles auswählen

activebackground='#ececec'
activeforeground="#000000",
background="#d9d9d9",
font="TkMenuFont",
foreground="#000000",
Aber die gibt man nicht an, die sind eh schon so in trkinter vorhanden. Das sind die Defaulwerte.

Also mache das bitte nicht mehr. Wenn Du sie änderst mit gutem Grund, etwa für ein anderes Design mit Komplementärfarben, mit weiß auf schwarz oder gelb auf blau, das wäre ein Grund die Farben zu ändern. Aber ohne Grund die Sichtbarkeit der Auswahl zu verschlechtern ist wohl keine besonders gute Idee.

Also, ich möchte schon sehen, ob etwas nur ein Label ist, weil beim Bewegen der Maus auf ihn sich seine Farbe nicht ändert, oder ob es etwas zum Auswählen ist, weil sich dabei die Farbe ändert.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Weil ich es nicht so stehen lassen kann, eben nochmal:

Code: Alles auswählen

# -*- coding: utf-8 -*-
import tkinter as tk

# === Allgemeiner callback Aufruf fuer Menue Radiobuttons ====================
DEFAULT_SELECTION = 'Deutsch'
PROVIDED_SELECTIONS = ('Deutsch','Englisch')
CASCADE_LABEL = "Programmsprache wählen"

class AppMenu(tk.Menu):
    def __init__(self,master,extern_callback,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
 
        submenu = tk.Menu(self,tearoff=0)
        self.add_cascade(menu=submenu, label=CASCADE_LABEL)

        selection_var = tk.StringVar()
        selection_var.set(DEFAULT_SELECTION)
        extern_callback(DEFAULT_SELECTION)

        def options(name): return {
            'label'     : name,
            'value'     : name,
            'variable'  : selection_var,
            'command'   : lambda : extern_callback(selection_var.get()) }

        for name in PROVIDED_SELECTIONS:
            submenu.add_radiobutton(**options(name))

# === Spezielle Applikation ================================================== 
class Application(tk.Tk):
    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
 
        self['menu'] = AppMenu(self,self.LangChoose)

    def LangChoose(self,language):
        print(language)
 
Application().mainloop()
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Hallo Alfons,

zunächst einmal entschuldige meine späte Rückmeldung. Du hast mich überzeugt. Ich habe zuerst mit "PAGE" die GUI erzeugt und dann nicht mehr darauf geschaut, was der Sinn des Ganzen ist. Ich werde deine Ratschläge beherzigen und die Defaultwerte belassen. Dazu lösche ich die von dir angegebenen Punkte einfach raus.
Auch danke ich dir für die Verbesserung des Codes. Ich hatte bereits mit Radiobuttons gearbeitet. Aber ich überdenke meinen Code nochmal aufgrund deiner Ratschläge.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hallo stefanxfg,

Ja das mit PAGE kann ein wenig problematisch sein. Es gibt einige solche GUI Generatoren, die mit Java und ähnlichem geschrieben sind und daher tkinter nicht gründlich kennen.

Vielleich kannst Du dir auch einmal meinen GuiDesigner ansehen?
Du findest ihn auf: https://github.com/AlfonsMittelmeyer/py ... -messaging

Kannst ja einmal das zip File herunterladen. Der ist in python mit tkinter geschrieben und kann Dir daher die Defaultwerte auch anzeigen. Und wenn man eine erstellte GUI speichert, werden die Defaultwerte nicht mitgespeichert.

Dieser GUI Designer verwendet ein eigenes Format. Aber man kann auch im Menü unter "File -> export (tk) -> without names" eine erstellte GUI in Python Code abspeichern. Das ist sehr nützlich zur Erzeugung von Beispielen.

Eine besondere Spezialität, wie sie andere GUI Generatoren nicht haben, ist die Behandlung des Grid Layouts, das natürlich in original tkinter unproblematisch zu behandeln ist.

Mit diesem GUI Designer kann man sehr gut ausprobieren, wie sich das Grid Layout verhält. Ich denke, mit dem Gridlayout solltest Du Dich noch etwas näher befassen, weil es diverse Vorteile bietet.

Ich hatte auch zuerst ein Fenster mit place Layout gestaltet, aber das Ergebnis war unzufriedenstellend, wenn man einen anderen Computer benutzt hatte. Da waren die Schriften anders und größer und dann paßte es nicht mehr in das Fenster.
Beim Gridlayout aber passen sich Containergrößen dem Gridlayout darin an. Dann paßt es auch für verschiedene Computer.

ttk habe ich aber nicht realisiert, weiß auch nicht ob ich da einmal dazu komme
Antworten