tk.OptionMenu - als Farbauswahl - und Auswahlikone ändern oder löschen

Fragen zu Tkinter.
Antworten
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Hallo zusammen,

ich habe zwei Fragen zum Widget tk.OptionMenue, dass ich aktuell nutze um C64-Sprites eine von 8 Farben zuzuodnen:

1. Auch in der schmalsten Breite ist mir das Widget noch zu breit.
Lässt sich das Symbol (der horizontale Balken) auf der evtl. Schaltfläche "löschen" oder durch ein smaleres ersetzen?


2. Ich möchte das tk.OptionMenue nutzen um 8 fest definierte Farben auswählen zu können ( also: 0 = schwarz, 1 = weiß, 2 = rot, 3=grün, ...).
Ist es möglich, jeden Eintrag der 8 Farben mit der ausgewählten Farbe zu hinterlegen? Das wäre bei der Auswahl viel intuitiver.
Wenn ich die Auswahlmöglichkeiten aufreiße sehe ich dann also nicht nur die Ziffern 0-7 sondern Ihr Hintergrund ist auch (schwarz, weiß, rot, ...)

Leider läßt sich kein Bild hier anhängen. Damit könnte ich klarer aufzeigen was ich meine.

Würde mich sehr freuen, wenn ihr mir helfen könnt.

Viele Grüße

aitsch
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo aitsch,

ich bezweifle, dass dein Vorhaben mit dem Widget OptionMenu machbar ist.
Aber Du kannst das mit Radiobuttons nachbauen.

Code: Alles auswählen

import tkinter as tk

class Farbwahl():
    def __init__(self, root):
        self.var = tk.StringVar()
        self.root = root        
        self.taste = tk.Button(self.root, width=0, relief='raised', bd=3,
                               bg='gray', command=self.zeige_farben)
        self.taste.pack()
        
        self.rb_rot = tk.Radiobutton(self.root, indicatoron=0, bg='red',
                                     value='red', variable=self.var,
                                     selectcolor='red', bd=3, width=2,
                                     command=self.auswerte)
        self.rb_blau = tk.Radiobutton(self.root, indicatoron=0, bg='blue',
                                     value='blue', variable=self.var,
                                     selectcolor='blue', bd=3, width=2,
                                     command=self.auswerte)
        self.rb_schwarz = tk.Radiobutton(self.root, indicatoron=0, bg='black',
                                         value='black', variable=self.var,
                                         selectcolor='black', bd=3, width=2,
                                         command=self.auswerte)      
        
    def zeige_farben(self):
        self.rb_rot.pack()
        self.rb_blau.pack()
        self.rb_schwarz.pack()
   
    def auswerte(self):        
        auswahl = self.var.get()
        print(auswahl)
        self.taste.config(bg=auswahl)           

def main():
    root = tk.Tk()
    Farbwahl(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Gruss Peter
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Moin Peter,

das ist eine ziemlich coole Lösung aber leider nicht das Richtige für mein Problem.

Wahrscheinlich muss ich mir was anderes einfallen lassen als die Farben über das OptionMenue auswählen zu lassen :( .

Gruß
aitsch
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo aitsch,
weshalb ist das nicht die richtige Lösung?
ich hab das Script soweit erweitert, dass es dem OptionMenu entspricht.
Also nach der Farbwahl ist nur noch ein kleines Quadrat vorhanden.
Die Icons hab ich auch entfernt und eine Taste zum Beenden angefügt.

Code: Alles auswählen

import tkinter as tk

class Farbwahl():
    def __init__(self, root):
        self.var = tk.StringVar()
        self.root = root
        self.root.wm_attributes('-type', 'dock')
        self.taste = tk.Button(self.root, width=0, relief='raised', bd=3,
                               bg='gray', command=self.zeige_farben)
        self.taste.pack()

        self.rahmen = tk.Frame(self.root)
        
        self.rb_rot = tk.Radiobutton(self.rahmen, indicatoron=0, bg='red',
                                     value='red', variable=self.var,
                                     selectcolor='red', bd=3, width=2,
                                     command=self.auswerte)
        self.rb_blau = tk.Radiobutton(self.rahmen, indicatoron=0, bg='blue',
                                     value='blue', variable=self.var,
                                     selectcolor='blue', bd=3, width=2,
                                     command=self.auswerte)
        self.rb_schwarz = tk.Radiobutton(self.rahmen, indicatoron=0, bg='black',
                                         value='black', variable=self.var,
                                         selectcolor='black', bd=3, width=2,
                                         command=self.auswerte)
        self.rb_quit = tk.Radiobutton(self.rahmen, bg='white', text='q', bd=3,
                                      indicatoron=0,
                                      width=2, command=self.beende, value='q')
        
    def zeige_farben(self):
        self.rahmen.pack()
        self.rb_rot.pack()
        self.rb_blau.pack()
        self.rb_schwarz.pack()
        self.rb_quit.pack()
           
    def auswerte(self):        
        auswahl = self.var.get()
        self.taste.config(bg=auswahl)
        self.rahmen.pack_forget()
        print(auswahl)        

    def beende(self):
        self.root.destroy()
        
def main():
    root = tk.Tk()
    Farbwahl(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Gruss Peter
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Du hast das schon sehr clever gelöst.

Aber:
Während das ausgeklappte OptionMenue sich über andere Widgets legt bzw. sich über das Fenster hinaus erstreckt,
erweitert deine Lösung dynamisch das Fenster.
Dadurch verschieben sich alle angrenzenden Widgets bzw. vergrößert sich das Fenster.

siehe hier:

Code: Alles auswählen

import tkinter as tk

class Farbwahl():
    def __init__(self, root):
        self.var = tk.StringVar()
        self.root = root
        #self.root.wm_attributes('-type', 'dock')

        
        # Hier das OptionMenu
        __values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15']
        self.__tkvar = tk.StringVar(value='1')
        self.sprite_color = tk.OptionMenu(self.root, self.__tkvar, *__values)
        self.sprite_color.configure(relief='sunken', state='normal', width='0')
        self.sprite_color.pack(side='right')

        # peterpy's colorchooser
        self.taste = tk.Button(self.root, width=0, relief='raised', bd=3,
                               bg='gray', command=self.zeige_farben)
        self.taste.pack(side='left')

        self.rahmen = tk.Frame(self.root)
        
        self.rb_rot = tk.Radiobutton(self.rahmen, indicatoron=0, bg='red',
                                     value='red', variable=self.var,
                                     selectcolor='red', bd=3, width=2,
                                     command=self.auswerte)
        self.rb_blau = tk.Radiobutton(self.rahmen, indicatoron=0, bg='blue',
                                     value='blue', variable=self.var,
                                     selectcolor='blue', bd=3, width=2,
                                     command=self.auswerte)
        self.rb_schwarz = tk.Radiobutton(self.rahmen, indicatoron=0, bg='black',
                                         value='black', variable=self.var,
                                         selectcolor='black', bd=3, width=2,
                                         command=self.auswerte)
        self.rb_quit = tk.Radiobutton(self.rahmen, bg='white', text='q', bd=3,
                                      indicatoron=0,
                                      width=2, command=self.beende, value='q')
        

    def zeige_farben(self):
        self.rahmen.pack()
        self.rb_rot.pack()
        self.rb_blau.pack()
        self.rb_schwarz.pack()
        self.rb_quit.pack()
           
    def auswerte(self):        
        auswahl = self.var.get()
        self.taste.config(bg=auswahl)
        self.rahmen.pack_forget()
        print(auswahl)        

    def beende(self):
        self.rahmen.destroy()
        
def main():
    root = tk.Tk()
    Farbwahl(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Trotzdem Danke, für deine Idee.

aitsch
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Das Thema läßt mich nicht los.

Meine Idee ist, farbige Labels zu erzeugen und in eine Liste (__values) zu packen.
Anschließend ein OptionMenu zu erzeugen und die Objektliste (__values) als Wertselektion zu hinterlegen.
(self.sprite_color = tk.OptionMenu(self.root, *__values))

Es wird zwar etwas als Inhalt des OptionMenu angezeigt aber es ist nicht unterschiedlich farbig und die Auswahl erzeugt .

hier mal als Code:

Code: Alles auswählen

import tkinter as tk

class Coose_Color():
    def __init__(self, root):
        self.var = tk.StringVar()
        self.root = root
        color=['#ff0000', '#0080ff', '#00ff40', '#ffff00']

        # Hier werden Labels mit versch. Farben angelegt un in der Liste __values abgelegt
        __values = []
        for i in range(4):
            __values.append(tk.Label(name=str(i), background=color[i], height='2', width='2'))

        # Hier wird das OptionMenu erzeugt
        # Idee: Die Labelobjekte aus __values bilden den Inhalt des OptionMenu -> klappt aber nicht :(
        self.sprite_color = tk.OptionMenu(self.root, *__values)
        self.sprite_color.configure(relief='sunken', state='normal', width='0')
        self.sprite_color.pack(side='right')
       
def main():
    root = tk.Tk()
    Coose_Color(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Ist sowas denkbar oder kann ich das abbrechen?

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

@aitsch: Lass mal diese komischen doppelten führenden Unterstriche sein. Bei lokalen Namen machen die überhaupt gar keinen Sinn und bei Attributen verwendet man *einen* führenden Unterstrich um dem Leser zu sagen, dass es sich um ein Implementierungsdetail handelt.

Doppelte führende Unterstriche verändern den tatsächlichen Namen um Namenskollisionen bei Mehrfachvererbung oder sehr tiefen Vererbungshierarchien zu vermeiden. Beides kommt in Python selten bis gar nicht vor.

Man müsste sich da wohl ein eigenes `OptionMenu` basteln wo man die Werte selber hinzufügt, um die entsprechenden Werte für die Darstellung der einzelnen Menüpunkte zu setzen oder man geht nach dem erstellen noch mal über die Einträge von dem Menu und ändert das entsprechend.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo aitsch,

ich werd aus deinem Vorhaben nicht ganz schlau.
Zuerst farbige Labels mit Index erzeugen.
Dann den Index mittels OptionMenu abrufen und dem Index entsprechende Farbe setzen
Wenn ich das richtig verstanden habe, dann willst Du so etwas?

Code: Alles auswählen

import tkinter as tk
from functools import partial

class Faebwaehler():
    def __init__(self, root):
        self.root = root
        farben = {1:'aqua', 2:'gold', 3:'olive', 4:'brown', 5:'coral',
                  6:'cyan', 7:'magenta', 8:'lime', 9:'maroon', 10:'moccasin',
                  11:'orange', 12:'orchid'}
        
        for zeile, (feld, farbe) in enumerate(farben.items()):
            feld = tk.Label(self.root, text=feld, width=3, background=farbe)
            feld.grid(row=zeile, column=0)

        schluessel = list(farben.keys())        
        
        self.tkvar = tk.StringVar()
        self.tkvar.set(schluessel[0])
        self.sprite_color = tk.OptionMenu(self.root, self.tkvar, *schluessel)
        self.sprite_color.configure(relief='sunken', state='normal', width='0')
        self.sprite_color.grid(row=0, column=1)
        
        self.tkvar.trace('w', partial(self.auswerte, farben))     
        

    def auswerte(self, farben, *args):        
        self.sprite_color['background'] = farben[int(self.tkvar.get())]   
        

def main():
    root = tk.Tk()
    Faebwaehler(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Gruss Peter
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@peterpy: Die Farben sollen im Option-Menü angezeigt werden, nicht ausserhalb Platz weg nehmen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Die Farben sollen im Option-Menü angezeigt werden
genau das hab ich vermutet.
Einen Weg hab ich schon mit den Radiobuttons aufgezeigt, aber das will aitsch nicht.
Dabei gäbe es noch die Möglichkeit, den Farbwähler in einem Toplevel Widget zu erstellen.
Gruss Peter
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

was ich suche, sieht so aus:

Bild.

Ein OptionMenu, wo man neben der Zahl (oder irgendwie anders) auch die entsprechenden Farbe sieht.
Aber halt innerhalb des OptionMenu.
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@aitsch: Wie schon gesagt, da wirst Du `OptionMenu` nachbauen/verändern müssen, oder die einzelnen Menüpunkte nachträglich verändern.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Ja, ich weiß.
Ich wollte nur Peters Frage aufklären.

Zum selbst Bauen reichen meine Kenntnisse nicht aus.
Falls jemand hier die Muße hat, gerne.

Ansonsten hätte sich dieses Thema erledigt.
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo aitsch,
ich hab die Klasse OptionMenu kopiert und angepasst.
So kannst Du images als Option darstellen.
Die images musst Du selbst erstellen.
Ich hab einfach ein paar images aus meinem System genommen.

Code: Alles auswählen

#!/usr/bin/env python3 -u

# image_option_menu.py

import tkinter as tk
from functools import partial
from PIL import ImageTk
from pathlib import Path

class Bildwaehler():
    def __init__(self, root):
        self.root = root
        self.tkvar = tk.StringVar()
        pfad = Path('/usr/share/icons')        

        images = [ImageTk.PhotoImage(file=f'{pfad}/firefox.png'),
                  ImageTk.PhotoImage(file=f'{pfad}/chinese_section.png'),
                  ImageTk.PhotoImage(file=f'{pfad}/file_tools_section.png'),
                  ImageTk.PhotoImage(file=f'{pfad}/databases_section.png'),
                  ImageTk.PhotoImage(file=f'{pfad}/gnuplot.png'),
                  ImageTk.PhotoImage(file=f'{pfad}/harddrake.png'),
                  ImageTk.PhotoImage(file=f'{pfad}/userdrake.png')]        
        
        self.sprite_color = Image_Option_Menu(self.root, self.tkvar, *images)
        self.sprite_color.configure(relief='sunken', state='normal')
        self.sprite_color.grid(row=0, column=1)
        self.sprite_color['indicatoron'] = False
        self.sprite_color['width'] = 50
        self.tkvar.trace('w', partial(self.auswerte, images))     
        self.sprite_color['image'] = images[0]

    def auswerte(self, *args):        
        self.sprite_color['image'] = self.tkvar.get()
        print(self.tkvar.get())

class Image_Option_Menu(tk.Menubutton):
    """OptionMenu which allows the user to select a image from a menu."""
    def __init__(self, master, variable, value, *values, **kwargs):
        """Construct an optionmenu widget with the parent MASTER, with
        the resource textvariable set to VARIABLE, the initially selected
        value VALUE, the other menu values VALUES and an additional
        keyword argument command."""
        kw = {"borderwidth": 2, "textvariable": variable,
              "indicatoron": 1, "relief": 'raised', "anchor": "c",
              "highlightthickness": 2}
        
        tk.Menubutton.__init__(self, master, kw)
        self.widgetName = 'tk_optionMenu'
        menu = self.__menu = tk.Menu(self, name="menu", tearoff=0)        
        self.menuname = menu._w
        # 'command' is the only supported keyword
        callback = kwargs.get('command')
        if 'command' in kwargs:            
            del kwargs['command']
        if kwargs:
            raise TclError('unknown option -'+kwargs.keys()[0])
        menu.add_command(image=value, 
                 command=tk._setit(variable, value, callback))
        for i, v in enumerate(values):            
            m = menu.add_command(image=values[i], command=tk._setit(variable,
                                                v, callback))           
        self["menu"] = menu

    def __getitem__(self, name):
        if name == 'menu':
            return self.__menu
        return tk.Widget.__getitem__(self, name)

    def destroy(self):
        """Destroy this widget and the associated menu."""
        tk.Menubutton.destroy(self)
        self.__menu = None
        

def main():
    root = tk.Tk()
    Bildwaehler(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Gruss Peter
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo aitsch,
noch einfacher, ein Bau mit Buttons

Code: Alles auswählen

#!/usr/bin/env python3 -u

import tkinter as tk
from functools import partial

class Farbwaehler():
    def __init__(self, root):
        farben = {1:'aqua', 2:'gold', 3:'olive', 4:'brown', 5:'coral',
                  6:'cyan', 7:'magenta', 8:'lime', 9:'maroon', 10:'moccasin',
                  11:'orange', 12:'orchid'}
        self.root = root
        self.haupttaste = tk.Button(self.root, text=0, width=3, bd=3,
                               background=farben[1], activebackground=farben[1],
                               command=self.zeige_optionen)
        self.haupttaste.pack()
        self.rahmen = tk.Frame(self.root)
        
        farben = {1:'aqua', 2:'gold', 3:'olive', 4:'brown', 5:'coral',
                  6:'cyan', 7:'magenta', 8:'lime', 9:'maroon', 10:'moccasin',
                  11:'orange', 12:'orchid'}
        
        for zeile, (option, farbe) in enumerate(farben.items()):
            option = tk.Button(self.rahmen, text=zeile, width=3, bd=3,
                               background=farbe, activebackground=farbe,
                               command=partial(self.auswerte, zeile, farbe))
            option.pack()

    def zeige_optionen(self):
        self.rahmen.pack()

    def auswerte(self, zeile, farbe):
        self.haupttaste.config(text=zeile)
        self.haupttaste.config(bg=farbe)
        self.rahmen.pack_forget()
        print(farbe)


def main():
    root = tk.Tk()
    Farbwaehler(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Gruss Peter
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@peterpy: Bei Lösung Nummer 2 verändert sich ja der Fensterinhalt durch `pack()`/`pack_forget()`.

Bei der ersten Lösung wurde viel Quelltext kopiert für eine recht kleine Änderung.

Falls man unbekannte Optionen übergibt, läuft man dort übrigens in einen `NameError` weil `TclError` undefiniert ist.

`pfad` ist ein `Path`-Objekt, wird dann aber in eine Zeichenkette umgewandelt um in einer Zeichenkettenoperation einen Dateinamen mit einem hart kodierten Pfadtrenner dran zu pappen.

Hier mal ein Ansatz von `OptionMenu` zu erben und die Menüeinträge zu verändern:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk

COLORS = [
    "#000000",
    "#FFFFFF",
    "#96282e",
    "#5bd6ce",
    "#9f2dad",
    "#41b936",
    "#2724c4",
    "#eff347",
    "#9f4815",
    "#5e3500",
    "#da5f66",
    "#474747",
    "#787878",
    "#91ff84",
    "#6864ff",
    "#aeaeae",
]


class ColorOptionMenu(tk.OptionMenu):
    def __init__(self, master, variable, value, *values, **kwargs):
        tk.OptionMenu.__init__(
            self, master, variable, value, *values, **kwargs
        )
        menu = self["menu"]
        for index, color in enumerate((value,) + values):
            menu.entryconfig(index=index, label=index, background=color)


def main():
    root = tk.Tk()
    color_var = tk.StringVar(value=COLORS[0])
    ColorOptionMenu(root, color_var, *COLORS, command=print).pack()
    root.mainloop()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Hallo Zusammen,

erstmal: @peterpy Vielen Dank für dein Durchhaltevermögen und deine vielen Tipps!

Implementiert habe ich dann aber doch den Code von __blackjack__. Auch Dir: Danke dafür!!

Der Code hatte das Problem, dass er nach der Auswahl nicht den Inhalt 0 - 15 sondern den Colorstring z.Bsp. #FFFFFF in das Feld eingetragen hatte.
Hier wusste ich mir nicht besser zu helfen, als eine eigene Funktion zu basteln, die aus dem Colorstring wieder einen Wert von 0-15 macht, ihn in das OptionMenu einträgt
und die Hintergrundfarbe des Widgets auf die ausgewählte Farbe anpasst.

Das sieht so aus:

Code: Alles auswählen

def choose_color(self, *args):
        selected_color = args[0]				# Farbwert
        index_color=self.COLORS.index(selected_color)		# passenden Index zum Farbwert
        for key in self.__tkvar:				# Alle 8 Auswahlbuttons durchsuchen
            if self.__tkvar[key].get() == selected_color:	# Wenn Inhalt zum Wert passt, ...
                self.__tkvar[key].set(index_color) 		# ... ersetze Farbwert durch 0-15
                self.sprite_color[key].config(bg=selected_color)	# Hintergrundfarbe ersetzen
 


Das geht bestimmt eleganter aber es macht was es soll.

So sieht jetzt das Ergebnis aus:

Bild

Danke nochmals für eure Hilfe.

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

Ich würde da noch die Speicherbereiche speziell kennzeichnen wo der Videochip Zeichensatz-ROM sieht und nicht auf RAM zugreifen kann. Und vielleicht unter den Screens, noch eine Zeile wo man die Sprites sieht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
aitsch
User
Beiträge: 18
Registriert: Freitag 18. März 2022, 14:39

Hey cool, du gehörst zu der seltenen Spezies, die sich noch auf dem C64auskennt. :ugeek:

Aktuell habe ich das mit den Speicherbereichen über den generierten Code gelöst.

so:

Bild

Allerdings: Das ganze ist noch sehr *WIP*

Es ist aktuell als kleine Programmierhilfe gedacht, damit ich nicht immer die Übersicht verliere.

Mal sehen, was noch draus wird. :D

P.S.: und ich sehe gerade, da fehlt auch noch ein Semikolon im Code :wink:
Antworten