Related
I'm working on a little project and made a little on-screen keyboard as a tkinter Toplevel
my application is buildt like this:
Root-Window (Tk-Widget)
input 1 (Entry-Widget)
input 2 (Entry-Widget)
input 3 (Text-Widget)
on_screen-keyboard (Toplevel-Widget)
the Toplevel-Widget contains Buttons, with callbacks that should enter text in the entries (just like keyboard-Buttons)
What I want is a communication between children of the keyboard/the keyboard and the last active input-Widget. My Problem is, that I don't know, how to say the keyboard, to which input-Widget it should send the message.
import tkinter as tk
from tkinter import ttk
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.active_input = tk.Variable(value=None)
ttk.Button(self, text="show Keyboard", command=lambda: Keyboard(self)).pack()
self.text = tk.StringVar(value="")
self.input1 = ttk.Entry(self)
self.input1.bind("<FocusIn>", lambda e: self.active_input.set(self.input1))
self.input2 = ttk.Entry(self)
self.input2.bind("<FocusIn>", lambda e: self.active_input.set(self.input2))
self.input3 = tk.Text(self, height=3, width=15)
self.input3.bind("<FocusIn>", lambda e: self.active_input.set(self.input3))
self.input1.pack()
self.input3.pack()
self.input2.pack()
class Keyboard(tk.Toplevel):
OPENED = False
NAME = "- Keyboard -"
NUM = [{"text":"1", "width":1},
{"text":"2", "width":1},
{"text":"3", "width":2}]
CHAR= [{"text":"A", "width":1},
{"text":"B", "width":1},
{"text":"C", "width":2}]
def __init__(self, master):
if not Keyboard.OPENED:
Keyboard.OPENED = True
print("keyboard opened!")
self.master = master
tk.Toplevel.__init__(self, master)
self.title(self.NAME)
self.protocol("WM_DELETE_WINDOW", self.close)
self.keyb_nb = ttk.Notebook(self)
self.keyb_nb.pack()
self.num_tab = ttk.Frame(self.keyb_nb)
self.createPad(self.num_tab, Keyboard.NUM,2)
self.keyb_nb.add(self.num_tab, text="123")
self.char_tab = ttk.Frame(self.keyb_nb)
self.createPad(self.char_tab, Keyboard.CHAR, 2)
self.keyb_nb.add(self.char_tab, text="ABC")
def createPad(self, master, pad:list, max_col):
self.co_count = 0
self.ro = 1
for button in pad:
button["id"] = ttk.Button(master, width=6*button["width"], text=button["text"], command=self.bclicked(button))
if self.co_count >= max_col:
self.ro = self.ro + 1
self.co_count = 0
button["id"].grid(row=self.ro, columnspan=button["width"], column=self.co_count)
self.co_count = self.co_count+button["width"]
def bclicked(self, button:dict):
"""
reciver = self.master.active_input #I think the Problem here is, that the variable contains a string, not a widget
reciever.focus_force()
reciever.insert(index=tk.INSERT, string=button["text"])
"""
pass
def close(self):
Keyboard.OPENED = False
self.destroy()
print("keyboard closed!")
root = MainWindow()
root.mainloop()
Here the init of the Mainwindow and the bclicked of the Keyboard class are important...
the code is debug-ready
I would prefer a solution, similar to the communication in the internet (sender=button, receiver-id, message), but very welcome every working solution
btw: I'm also looking for a solution, how I don't have to force the input to focus and the Toplevel stays an the highest layer of the screen (that if I focus the Tk-Widget/one of the inputs/the button, the keyboard will stay in front of it)
SUMMARY: how do I find out, which of the 3 input-widgets was active at last, when the keyboard-toplevel has already the focus?
I may made more changes than needed, but mainly focus on the method keyboard_triger() and pass_key_to_master(), this two use the idea that the variable master implements, having access to call methods out of scope.
Olso the method set_focused_object() stores a reference to the last object beeng focused, note than it stores the widget and not the event, it's easyer than searching each time the object
import tkinter as tk
from tkinter import ttk
class MainWindow(tk.Tk):
def keyboard_triger(self, key):
# to identify wath object is just use
# isinstance(self.active_input, ttk.Entry)
self.active_input.insert(tk.END, key)
def new_keyboard(self):
Keyboard(self)
def set_focused_object(self, event):
self.active_input = event.widget
def __init__(self):
tk.Tk.__init__(self)
self.active_input = None
ttk.Button(self, text="Show Keyboard", command=self.new_keyboard).pack()
self.input1 = ttk.Entry(self)
self.input1.bind("<FocusIn>", self.set_focused_object)
self.input1.pack()
self.input2 = ttk.Entry(self)
self.input2.bind("<FocusIn>", self.set_focused_object)
self.input2.pack()
self.input3 = tk.Text(self, height=3, width=15)
self.input3.bind("<FocusIn>", self.set_focused_object)
self.input3.pack()
class Keyboard(tk.Toplevel):
def pass_key_to_master(self, key):
self.master.keyboard_triger(key)
def __init__(self, master):
tk.Toplevel.__init__(self, master)
self.master = master
self.title('Keyboard')
# this way of agruping keys stores the kwags
# of the drawing method
keys = {
'A': {'x': 0, 'y': 0},
'B': {'x': 20, 'y': 20},
'C': {'x': 50, 'y': 50}
}
# expected structure
# {string key: reference to the button}
self.buttons = {}
for i in keys:
self.buttons[i] = tk.Button( # i=i is required to make a instance
self, text=i, command=lambda i=i: self.pass_key_to_master(i)
)
self.buttons[i].place(**keys[i])
if __name__ == '__main__':
root = MainWindow()
root.mainloop()
Your code maybe could have a better construction.(But I didn't revise your code construction.)
Followed by your code,I use a global variable.And fix some errors in your code.And it could work normally in my computer.
import tkinter as tk
from tkinter import ttk
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.active_input = tk.Variable(value=None)
ttk.Button(self, text="show Keyboard", command=lambda: Keyboard(self)).pack()
global focusedWidget
focusedWidget = None
self.text = tk.StringVar(value="")
self.input1 = ttk.Entry(self)
self.input1.bind("<FocusIn>", self.getFocusWidget)
self.input2 = ttk.Entry(self)
self.input2.bind("<FocusIn>", self.getFocusWidget)
self.input3 = tk.Text(self, height=3, width=15)
self.input3.bind("<FocusIn>", self.getFocusWidget)
self.input1.pack()
self.input3.pack()
self.input2.pack()
def getFocusWidget(self,event): # this function could be a static function
global focusedWidget
focusedWidget = event.widget
class Keyboard(tk.Toplevel):
OPENED = False
NAME = "- Keyboard -"
NUM = [{"text":"1", "width":1},
{"text":"2", "width":1},
{"text":"3", "width":2}]
CHAR= [{"text":"A", "width":1},
{"text":"B", "width":1},
{"text":"C", "width":2}]
def __init__(self, master):
if not Keyboard.OPENED:
Keyboard.OPENED = True
print("keyboard opened!")
self.master = master
tk.Toplevel.__init__(self, master)
self.title(self.NAME)
self.protocol("WM_DELETE_WINDOW", self.close)
self.keyb_nb = ttk.Notebook(self)
self.keyb_nb.pack()
self.num_tab = ttk.Frame(self.keyb_nb)
self.createPad(self.num_tab, Keyboard.NUM,2)
self.keyb_nb.add(self.num_tab, text="123")
self.char_tab = ttk.Frame(self.keyb_nb)
self.createPad(self.char_tab, Keyboard.CHAR, 2)
self.keyb_nb.add(self.char_tab, text="ABC")
def createPad(self, master, pad:list, max_col):
self.co_count = 0
self.ro = 1
for button in pad:
button["id"] = ttk.Button(master, width=6*button["width"], text=button["text"], command=lambda button=button:self.bclicked(button)) # this lambda expression has some errors.
if self.co_count >= max_col:
self.ro = self.ro + 1
self.co_count = 0
button["id"].grid(row=self.ro, columnspan=button["width"], column=self.co_count)
self.co_count = self.co_count+button["width"]
def bclicked(self, button:dict):
global focusedWidget
"""
reciver = self.master.active_input #I think the Problem here is, that the variable contains a string, not a widget
reciever.focus_force()
reciever.insert(index=tk.INSERT, string=button["text"])
"""
if not focusedWidget: # If user hasn't click a entry or text widget.
print("Please select a entry or text")
return
if focusedWidget.widgetName=='ttk::entry': # use if statement to check the type of selected entry.
focusedWidget.insert(index=tk.INSERT,string=button["text"])
else:
focusedWidget.insert("end",button["text"])
def close(self):
Keyboard.OPENED = False
self.destroy()
print("keyboard closed!")
root = MainWindow()
root.mainloop()
I created a program that when clicking on one of the buttons, it changes the frame, which contains other functions.
I tried to use ['frame'] .config, a simple function of the same, but nothing works.
from tkinter import *
from Item import Item
from Monstro import Monster
class Ferramentas:
def __init__(self,master,Item,Monster):
self.Item = Item
self.Monster = Monster
self.ferramentas = Frame(master)
self.ferramentas.grid(row=0,column=0)
self.b1 = Button(self.ferramentas,text='CALCULADORA')
self.b2 = Button(self.ferramentas,text='MONSTROS',command=self.Monstros)
self.b3 = Button(self.ferramentas,text='ITENS',command=self.Itens)
self.b1.grid(row=0,column=0)
self.b2.grid(row=0,column=1)
self.b3.grid(row=0,column=2)
self.frame_mestre = Frame(master)
self.frame_mestre.grid(row=1,column=0)
def Itens(self):
self.frame_mestre = self.Item.frame_2
self.Item.frame_2.grid(row=1,column=0,sticky="nsew")
def Monstros(self):
self.frame_mestre = self.Monster.frame_1
self.Monster.frame_1.grid(row=1,column=0,sticky="nsew")
janela = Tk()
c = Monster(janela)
b = Item(janela)
a = Ferramentas(janela,b,c)
janela.mainloop()
class Item:
def __init__(self,master):
self.frame_2 = Frame(master)
self.item_nome = Label(self.frame_2, text='NOME')
self.item_nome.grid(row=0,column=1)
class Monster:
def __init__(self, master):
self.frame_1 = Frame(master)
self.nome = Label(self.frame_1, text='NOME', relief='raised')
self.nome.grid(row=0,column=1, columnspan=2)
I hope that by clicking on the respective button, the frames change, without one overlapping the other
I'm trying to avoid to multiply functions in code by using
def Return_Label(self,number)
with a parameter.
Any Idea how to use string in order to define variable name usable to .set value to StringVar()?
Example code below:
import tkinter as tk
from tkinter import *
class WINDOW():
def __init__(self):
self.Settings_Window()
def Settings_Window(self):
self.settings_window = tk.Tk()
self.settings_window.minsize(200,200)
self.entry = Entry(self.settings_window)
self.entry.pack()
self.entry2 = Entry(self.settings_window)
self.entry2.pack()
self.label1input = StringVar()
self.label = Label(self.settings_window,textvariable=self.label1input, bg='yellow')
self.label.pack(expand='yes',fill='x')
self.label2input = StringVar()
self.label2 = Label(self.settings_window, textvariable=self.label2input, bg='yellow')
self.label2.pack(expand='yes', fill='x')
self.button = Button(self.settings_window,text='SETUP1',command=self.Next)
self.button.pack()
self.button2 = Button(self.settings_window,text='SETUP2',command=self.Next2)
self.button2.pack()
self.settings_window.mainloop()
def Next(self):
self.number=1
self.Return_Label(self.number)
def Next2(self):
self.number=2
self.Return_Label(self.number)
def Return_Label(self,number):
self.entry_field_value = self.entry.get()
print(self.entry_field_value)
#self.label1input.set(self.entry_field_value)
setattr(self,'label'+str(number)+'input.set',self.entry_field_value)
window=WINDOW()
I prefer a list approach to managing multiple entry fields and updating values.
By using list you can use the index value to manage the labels as well :D.
See the below example of how you can use list to deal with all the values and updates.
import tkinter as tk
from tkinter import *
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.minsize(200, 200)
self.entry_list = []
self.label_list = []
entry_count = 2
for i in range(entry_count):
self.entry_list.append(Entry(self))
self.entry_list[i].pack()
for i in range(entry_count):
self.label_list.append(Label(self,bg='yellow'))
self.label_list[i].pack(expand='yes', fill='x')
Button(self, text='SETUP', command=self.Return_Label).pack()
def Return_Label(self):
for ndex, lbl in enumerate(self.label_list):
lbl.config(text=self.entry_list[ndex].get())
if __name__ == '__main__':
Window().mainloop()
Create lists of objects rather than individual attributes for each object. For example,
import tkinter as tk
from tkinter import *
class Window:
def __init__(self):
self.settings_window()
def Settings_Window(self):
self.settings_window = tk.Tk()
self.settings_window.minsize(200,200)
self.entries = [
Entry(self.settings_window),
Entry(self.settings_window)
]
for e in self.entries:
e.pack()
self.labelinputs = [
StringVar(),
StringVar()
]
self.labels = [
Label(self.settings_window, textvariable=label, bg='yellow')
for label in self.labelinputs
]
for l in self.labels:
l.pack(expand='yes', fill='x')
self.buttons = [
Button(self.settings_window,text='SETUP1',command=lambda: self.return_label(0))
Button(self.settings_window,text='SETUP2',command=lambda: self.return_label(1))
]
for b in self.buttons:
b.pack()
self.settings_window.mainloop()
def return_label(self,number):
entry_field_value = self.entry.get()
self.labelsinput[number].set(entry_field_value)
window=WINDOW()
Dynamicly computing variable names should be avoided at all costs. They are difficult to do correctly, and it makes your code hard to understand, hard to maintain, and hard to debug.
Instead, store the widgets in a dictionary or list. For example:
def __init___(self):
...
self.vars = {}
...
self.vars[1] = StringVar()
self.vars[2] = StringVar()
...
def Return_Label(self,number):
self.entry_field_value = self.entry.get()
var = self.vars[number]
var.set(self.entry_field_value)
Though, you really don't need to use StringVar at all -- they usually just add extra overhead without providing any extra value. You can save the labels instead of the variables, and call configure on the labels
self.labels[1] = Label(...)
...
self.labels[number].configure(text=self.entry_field_value)
I just forayed into tkinter for the first time and am running into some issues. I want to display several lists to users, and store their selections for each list in a dictionary (used to filter several columns of a dataframe later). Suppose, for instance, there are two lists: 1) One labeled "Brand", containing 'Brand X' and 'Brand Y' as options, 2) another "Customer Type", containing "New," "Existing," "All."
In sum, when all is said and done, if a user picks "Brand X", "New", and "All", then I'd get a dictionary back of {'Brand':['Brand X'],'Customer Type':['New','All']}. Getting one list is easy... but looping through the lists is presenting problems.
I have the below code so far:
from tkinter import *
from tkinter import ttk
class checkList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.makeHeader()
self.options = options
self.pack(expand=YES, fill=BOTH, side=LEFT)
self.makeWidgets(self.options)
self.selections = []
def makeHeader(self):
header = ttk.Label(self,text='Please select options to limit on.')
header.pack(side=TOP)
self.header = header
def makeWidgets(self, options):
for key in self.options.keys():
lbl = ttk.Label(self, text=key)
lbl.pack(after=self.header)
listbox = Listbox(self, selectmode=MULTIPLE, exportselection=0)
listbox.pack(side=LEFT)
for item in self.options[key]:
listbox.insert(END, item)
listbox.bind('<<ListboxSelect>>', self.onselect)
self.listbox = listbox
def onselect(self, event):
selections = self.listbox.curselection()
selections = [int(x) for x in selections]
self.selections = [self.options[x] for x in selections]
if __name__ == '__main__':
options = {'Brand':['Brand','Brand Y'], 'Customer Type': ['All Buyers','New Buyers','Existing Buyers']}
checkList(options).mainloop()
Needless to say, the [self.options[x] for x in selections] works great with just one list, but since I have a dictionary, I really need [self.options[key][x] for x in selections]. However, I can't figure out how to pass the key at any given point in the loop. Is there a way to achieve what I'm trying to do?
The "magic" you're looking for to pass the key is simple because the tkinter objects are extensible. Here's your code working, I believe, the way you want:
from tkinter import *
from tkinter import ttk
class checkList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.makeHeader()
self.options = options
self.pack(expand=YES, fill=BOTH, side=LEFT)
self.listboxes = [] # New
self.makeWidgets(self.options)
self.selections = {} # Changed
def makeHeader(self):
header = ttk.Label(self,text='Please select options to limit on.')
header.pack(side=TOP)
self.header = header
def makeWidgets(self, options):
for key in self.options.keys():
lbl = ttk.Label(self, text=key)
lbl.pack(after=self.header)
listbox = Listbox(self, selectmode=MULTIPLE, exportselection=0)
listbox.key = key # here's the magic you were asking about...
listbox.pack(side=LEFT)
self.listboxes.append(listbox) # New
for item in self.options[key]:
listbox.insert(END, item)
listbox.bind('<<ListboxSelect>>', self.onselect)
self.listbox = listbox
def onselect(self, event):
for lb in self.listboxes:
selections = lb.curselection()
selections = [int(x) for x in selections]
self.selections[lb.key] = [self.options[lb.key][x] for x in selections]
print(self.selections)
if __name__ == '__main__': # \/
options = {'Brand':['Brand X','Brand Y'], 'Customer Type': ['All Buyers','New Buyers','Existing Buyers']}
checkList(options).mainloop()
With the code you posted, you only have access to the last ListBox created by makeWidgets in onselect.
With minimal changes:
from tkinter import *
from tkinter import ttk
class checkList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.listboxes = []
self.selections = {}
self.makeHeader()
self.options = options
self.pack(expand=YES, fill=BOTH, side=LEFT)
self.makeWidgets(self.options)
def makeHeader(self):
header = ttk.Label(self,text='Please select options to limit on.')
header.pack(side=TOP)
self.header = header
def makeWidgets(self, options):
for key in self.options.keys():
lbl = ttk.Label(self, text=key)
lbl.pack(after=self.header)
listbox = Listbox(self, selectmode=MULTIPLE, exportselection=0)
listbox.pack(side=LEFT)
for item in self.options[key]:
listbox.insert(END, item)
listbox.bind('<<ListboxSelect>>', self.onselect)
self.listboxes.append(listbox)
def onselect(self, event):
for (option, options), listbox in zip(self.options.items(), self.listboxes):
self.selections[option] = [options[x] for x in map(int, listbox.curselection())]
print(self.selections)
if __name__ == '__main__':
options = {'Brand':['Brand','Brand Y'], 'Customer Type': ['All Buyers','New Buyers','Existing Buyers']}
checkList(options).mainloop()
This recreates selections every time either ListBox selection is modified. Alternatively, you could use event to determine which ListBox selection was modified and update the corresponding part of selections. This would require initializing selections, though.
--Edit: my currently attempt, pretty ugly
import tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.labelLists = []
self.labelBLists = []
self.Label1 = tk.Label(self,text=str(1),bg="red")
self.Label1.pack()
self.Label1.bind("<Enter>", self.on_enter1)
self.Label1.bind("<Leave>", self.on_leave1)
self.Labela = tk.Label(self,text="",bg="blue")
self.Labela.pack()
self.Label2 = tk.Label(self,text=str(2),bg="red")
self.Label2.pack()
self.Label2.bind("<Enter>", self.on_enter2)
self.Label2.bind("<Leave>", self.on_leave2)
self.Labelb = tk.Label(self,text="",bg="blue")
self.Labelb.pack()
self.Label3 = tk.Label(self,text=str(3),bg="red")
self.Label3.pack()
self.Label3.bind("<Enter>", self.on_enter3)
self.Label3.bind("<Leave>", self.on_leave3)
self.Labelc = tk.Label(self,text="",bg="blue")
self.Labelc.pack()
def on_enter1(self, event):
self.Labela.config(text=self.Label1.cget("text"))
def on_leave1(self, enter):
self.Labela.config(text="")
def on_enter2(self, event):
self.Labelb.config(text=self.Label2.cget("text"))
def on_leave2(self, enter):
self.Labelb.config(text="")
def on_enter3(self, event):
self.Labelc.config(text=self.Label3.cget("text"))
def on_leave3(self, enter):
self.Labelc.config(text="")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand="true")
root.mainloop()
I want to create a group of label, say L1, L2, L3, and each one has a corresponding label La, Lb, Lc. What I want to do is when I hover over L1, La display the translation of word on L1. Currently I'm looking at Display message when going over something with mouse cursor in Python and Python Tkinter: addressing Label widget created by for loop, but neither of them addresses binding corresponding method. Is there a way that I can achieve this without creating three pairs of different methods?
Thanks!
Store each set of labels in a list. Then you can go through them together, along with a translation dictionary, and connect the secondary labels (that display a translation) to the primary labels (that respond to user input). This allows you to create a single enter method and a single leave method, using event.widget to access the widget that triggered the event.
import tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.translations = {'a':'A', 'b':'B', 'c':'C'}
self.labelLists = [tk.Label(self,text=str(x),bg="red") for x in range(1,4)]
self.labelBLists = [tk.Label(self,text="",bg="blue") for x in range(3)]
for x,y,tr in zip(self.labelLists, self.labelBLists, sorted(self.translations)):
x.bind('<Enter>', self.enter)
x.bind('<Leave>', self.leave)
x.connected = y
x.key = tr
x.pack()
y.pack()
def enter(self, event):
widget = event.widget
widget.connected.config(text=self.translations[widget.key])
def leave(self, event):
widget = event.widget
widget.connected.config(text='')
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand="true")
root.mainloop()