tkinter Multilistbox custom event binding problem - python

So my project is about showing information from a database in conjunction with images.
The idea is the following:
I have a database that describes images. One table has general information, like which color is contained in which image and another table has more granular information, like where that color can be found, etc.
When I launch a search, I want two windows to open (ClassTwo and ClassThree).
Instead of the first Window that only contains a Button to launch the search, my full application contains fields that are used to filter a database request. When I launch that search I want e.g. all images with the color green in them, so ClassTwo would list all images with that color, along with additional metadata.
ClassThree would then list all areas that contain the same color, but with a bit more detail, like position in the image and size, etc.
Upon clicking on either of the MultiListbox, I want to open an ImageViewer that shows the image.
In case of ClassThree, I would also directly highlight the area that I clicked on, so both classes would have functions bound to the MultiListbox.
My problem is with the binding of the Listboxes, that does not work properly. When I use e.g. image_parts_info_lb.bind() the function is not triggered at all. When i use image_parts_info_lb.bind_all() only the function of the last MultiListbox is triggered.
You can find the original Python2 version of the MultiListbox in the comment of the class.
Here is my code
import tkinter as tk
class MultiListbox(tk.Frame):
#original Python2 code here:
#https://www.oreilly.com/library/view/python-cookbook/0596001673/ch09s05.html
def __init__(self, master, lists):
tk.Frame.__init__(self, master)
self.lists = []
print(lists)
for l,w in lists:
frame = tk.Frame(self)
frame.pack(side='left', expand='yes', fill='both')
tk.Label(frame, text=l, borderwidth=1, relief='raised').pack(fill='x')
lb = tk.Listbox(frame, width=w, borderwidth=0, selectborderwidth=0,
relief='flat', exportselection=False, height=16)
lb.pack(expand='yes', fill='both')
self.lists.append(lb)
#commented out functions that were not necessary, as suggested in the comments
#lb.bind('<B1-Motion>', self._select)
lb.bind('<<ListboxSelect>>', self._select)
#lb.bind('<Leave>', lambda e: 'break')
lb.bind('<MouseWheel>', lambda e, s=self: s._scroll_mouse(e))
#lb.bind('<Button-2>', lambda e, s=self: s._button2(e.x, e.y))
frame = tk.Frame(self)
frame.pack(side='left', fill='y')
tk.Label(frame, borderwidth=1, relief='raised').pack(fill='x')
sb = tk.Scrollbar(frame, orient='vertical', command=self._scroll)
sb.pack(expand='yes', fill='y')
self.lists[0]['yscrollcommand']=sb.set
def _select(self, event):
w = event.widget
curselection = w.curselection()
if curselection:
self.selection_clear(0, self.size())
self.selection_set(curselection[0])
def _button2(self, x, y):
for l in self.lists:
l.scan_mark(x, y)
return 'break'
def _b2motion(self, x, y):
for l in self.lists: l.scan_dragto(x, y)
return 'break'
def _scroll(self, *args):
for l in self.lists:
l.yview(*args)
return 'break'
def _scroll_mouse(self, event):
for l in self.lists:
l.yview_scroll(int(-1*(event.delta/120)), 'units')
return 'break'
def curselection(self):
return self.lists[0].curselection()
def delete(self, first, last=None):
for l in self.lists:
l.delete(first, last)
def get(self, first, last=None):
result = []
for l in self.lists:
result.append(l.get(first,last))
if last: return apply(map, [None] + result)
return result
def index(self, index):
self.lists[0].index(index)
def insert(self, index, *elements):
for e in elements:
for i, l in enumerate(self.lists):
l.insert(index, e[i])
def size(self):
return self.lists[0].size()
def see(self, index):
for l in self.lists:
l.see(index)
def selection_anchor(self, index):
for l in self.lists:
l.selection_anchor(index)
def selection_clear(self, first, last=None):
for l in self.lists:
l.selection_clear(first, last)
def selection_includes(self, index):
return self.lists[0].selection_includes(index)
def selection_set(self, first, last=None):
for l in self.lists:
l.selection_set(first, last)
class ClassOne:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.create_elements()
self.frame.pack()
def create_elements(self):
search_button = tk.Button(self.frame, text = "Launch searches", command = \
self.call_other_classes)
search_button.grid(row = 2, column = 0, padx = 20, pady = 10)
exit_button = tk.Button(self.frame, text = "Exit", command = self.master.quit)
exit_button.grid(row = 2, column = 1, padx = 20, pady = 10)
def call_other_classes(self):
self.classes_list = []
self.classes_list.append(ClassTwo(self.master))
self.classes_list.append(ClassThree(self.master))
class ClassTwo:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(tk.Toplevel(self.master))
self.frame.pack()
self.create_elements()
self.fill_image_listbox()
def create_elements(self):
#placement of the custom MultiListbox
self.available_images_lb = MultiListbox(self.frame, (('stuff1', 0), ('stuff1', 0), \
('stuff1', 0), ('stuff1', 0), ('stuff1', 0) ))
self.available_images_lb.grid(row = 1, column = 1)
self.available_images_lb.bind('<<ListboxSelect>>', self.print_stuff_two)
#Button
exit_button = tk.Button(self.frame, text = "Quit", command = self.frame.quit)
exit_button.grid(row = 2, column = 1, padx = 20, pady = 10)
def fill_image_listbox(self):
image_info = [5*['ABCD'],5*['EFGH'],5*['JKLM'],5*['NOPQ'],5*['RSTU'], 5*['VWXY']]
for item in image_info:
self.available_images_lb.insert('end', item)
def print_stuff_two(self, event):
print('Class Two active')
class ClassThree:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(tk.Toplevel(self.master))
self.create_elements()
self.frame.pack()
self.fill_imageparts_listbox()
def create_elements(self):
self.image_parts_info_lb = MultiListbox(self.frame, (('stuff1', 0), ('stuff1', 0), \
('stuff1', 0), ('stuff1', 0), ('stuff1', 0) ))
self.image_parts_info_lb.grid(row = 1, column = 1)
self.image_parts_info_lb.bind('<<ListboxSelect>>', self.print_stuff_three)
def fill_imageparts_listbox(self):
self.image_parts_info_lb.delete(0, 'end')
image_part_info = [5*['1234'],5*['5678'],5*['91011']]
for item in image_part_info:
self.image_parts_info_lb.insert('end', item)
def print_stuff_three(self, event):
print('Class Three active')
def main():
root = tk.Tk()
root.title('Image Viewer')
root.geometry('500x150+300+300')
my_class_one = ClassOne(root)
root.mainloop()
if __name__ == "__main__":
main()
Instead of the printing functions I would launch a simple Image Viewer and use Pillow to highlight areas in the image. That functionality is implemented and works well, only the function binding to the custom Listbox is not working as it should.
I am open to any input. Also, if you have any recommendations for coding patterns, feel free to let me know.

Question: MultiListbox custom event binding problem, click on any of the list elements, trigger the print_stuff_x function
Implement a custom event '<<MultiListboxSelect>>' which get fired on processed a '<<ListboxSelect>>' event and .bind(... at it.
Reference:
Tkinter.Widget.event_generate-method
event generate
Generates a window event and arranges for it to be processed just as if it had come from the window system. If the -when option is specified then it determines when the event is processed.
Crore Point:
self.event_generate('<<MultiListboxSelect>>', when='tail')
class MultiListbox(tk.Frame):
def __init__(self, master, lists):
...
for l,w in lists:
...
lb = tk.Listbox(frame, ...
lb.bind('<<ListboxSelect>>', self._select)
def _select(self, event):
w = event.widget
curselection = w.curselection()
if curselection:
self.event_generate('<<MultiListboxSelect>>', when='tail')
...
class ClassTwo:
...
def create_elements(self):
self.available_images_lb = MultiListbox(self.frame, ...
...
self.available_images_lb.bind('<<MultiListboxSelect>>', self.print_stuff_two)
...
class ClassThree:
...
def create_elements(self):
self.image_parts_info_lb = MultiListbox(self.frame, ...
...
self.image_parts_info_lb.bind('<<MultiListboxSelect>>', self.print_stuff_three)

Related

Changing one scale widget will change the secone one

I have two scale widgets in my code that are supposed to display values from a list. I want to be able to set individual values for both sliders. For example, for file 1 "2.4" and for file 2 "5.025". But currently I can only update the second (lower) slider without it triggering the first (upper) one. If I use the upper one, the value of the second one is also changed.
I'm stuck implementing the lambda function, so it would be create if some could give me a hint.
Thanks!
import tkinter as tk
class File_Selection():
def __init__(self, frame, text):
self.frame = frame
self.text = text
self.label_file = tk.Label(self.frame, text=text)
self.label_file.pack(side="left", anchor="s")
self.label_test = tk.Label(self.frame, text="| Select value: ")
self.label_test.pack(side="left", anchor="s")
self.scale = tk.Scale(self.frame, orient=tk.HORIZONTAL, from_=0)
self.scale.pack(side="left", anchor="s")
class View:
def __init__(self, view):
self.view = view
self.frame = tk.Frame(self.view)
self.frame.pack()
self.frame_row1 = tk.Frame(self.frame)
self.frame_row1.pack(side="top")
self.frame_row2 = tk.Frame(self.frame)
self.frame_row2.pack(side="top")
self.file_one = File_Selection(self.frame_row1, "File 1")
self.file_two = File_Selection(self.frame_row2, "File 2")
class Controller:
def __init__(self):
self.root = tk.Tk()
self.view = View(self.root)
self.values = [1.01,2.4,3.6,4.89,5.025,6.547]
self.view.file_one.scale.bind('<Enter>', self.update_scale)
self.view.file_two.scale.bind('<Enter>', self.update_scale)
def run(self):
self.root.mainloop()
def update_scale(self, event):
self.active_scales = [self.view.file_one.scale, self.view.file_two.scale]
for scale in self.active_scales:
scale.config(from_=min(self.values), to=max(self.values), resolution=0.001, command=lambda value=scale: self.set_scale(value, scale))
def set_scale(self, value, scale):
self.newvalue = min(self.values, key=lambda x: abs(x-float(value)))
scale.set(self.newvalue)
if __name__ == "__main__":
c = Controller()
c.run()
The problem is that you have both scales set to active_scales. You can take advantage of the event and do away with active_scales.
You can get the widget associated with an event with event.widget. So, you can change the update_scales function from
def update_scale(self, event):
self.active_scales = [self.view.file_one.scale, self.view.file_two.scale]
for scale in self.active_scales:
scale.config(from_=min(self.values), to=max(self.values), resolution=0.001, command=lambda value=scale: self.set_scale(value, scale))
to
def update_scale(self, event):
event.widget.config(from_=min(self.values), to=max(self.values), resolution=0.001, command=lambda value=event.widget: self.set_scale(value, event.widget))
Additionally, since self.values don't appear to change, it does not seem necessary to config each scale to change its bounds and resolution on each event. You can instead pull that out of the event and make you Controller class as follows.
class Controller:
def __init__(self):
self.root = tk.Tk()
self.view = View(self.root)
self.values = [1.01,2.4,3.6,4.89,5.025,6.547]
self.view.file_one.scale.bind('<Enter>', self.update_scale)
self.view.file_two.scale.bind('<Enter>', self.update_scale)
self.view.file_one.scale.config(from_=min(self.values), to=max(self.values), resolution=0.001)
self.view.file_two.scale.config(from_=min(self.values), to=max(self.values), resolution=0.001)
def run(self):
self.root.mainloop()
def update_scale(self, event):
event.widget.config(command=lambda value=event.widget: self.set_scale(value, event.widget))
def set_scale(self, value, scale):
self.newvalue = min(self.values, key=lambda x: abs(x-float(value)))
scale.set(self.newvalue)

How to disable specific index/es or item/s in combobox?

I have a combobox and I need to disable some items/indexes. Here is my code.
months = ['january','feruary'.....'december']
cbMonth=ttk.Combobox(frame,values=months)
cbMonth.set(0)
cbMonth.place(relx=xDistance,rely=yDistance,relwidth=xWidth,relheight=yHeight)
cbMonth.update()
Let's say I want to disable or grayed out months of january,march and december or index 5,9,12. How to do it in Python. Something like;
cbMonth.Index[1].Enabled=false or
cbMonth.cget('january').Disable
There isn't a way to disable items in Combobox. But, if you still want to do so you will have to create a custom Combobox.
Here is a demo on how to create a custom Combobox. (you may add custom functionality if you want to).
import tkinter as tk
class CustomComboBox(tk.Frame):
def __init__(self, parent, values=[], validate='none', validatecommand='none',*args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.disabled = []
self.values = values
self.listbox = None
self.scrollbar = None
self.entry = tk.Entry(self, borderwidth=0, background="white",relief="groove")
self.entry.configure(validate=validate, validatecommand=validatecommand)
self.entry.pack(expand=True, fill='both', side='left')
self.entry.bind('<FocusIn>', self._removeListView)
self.arrowButton = tk.Label(self, text='↓', background="white", width=2)
self.arrowButton.pack(side='right', fill='y')
self.arrowButton.bind('<1>', self._listView)
self.previousIndex = 0
self.arrowButton.bind('<Enter>', self._changeBackground)
self.arrowButton.bind('<Leave>', self._changeBackground)
self.bind('<Configure>', self._updatePos)
def _changeBackground(self, event):
if str(event.type)=='Enter':
self.arrowButton.config(bg='#b3d2ff', border=1)
self.entry.config(border=1)
elif str(event.type) == 'Leave':
self.arrowButton.config(bg='white', border=0)
self.entry.config(border=0)
def _listView(self, event=None):
if self.listbox is None:
self.frame = tk.Frame(background=self['bg'] ,width=self.entry['width']+2)
self.frame.lift()
self.frame.place(x=self.winfo_x(), y=self.winfo_y()+self.winfo_height()-2, width=self.winfo_width())
self.listbox = tk.Listbox(self.frame, width=self.entry['width'], border=1, relief="groove", activestyle='none')
self.listbox.focus()
self._addValues()
self.listbox.bind('<<ListboxSelect>>', self._checkDisabled)
self.listbox.bind("<Motion>", self._highlight)
self.listbox.bind("<Leave>", self._removeHighlight)
self.listbox.pack(side='left', expand=True, fill='both')
self._updateListHeight()
else:
self._removeListView()
def _updatePos(self, event):
if self.listbox:
self.frame.place_configure(x=self.winfo_x(), y=self.winfo_y()+self.winfo_height()-2, width=self.winfo_width())
def _updateListHeight(self):
if self.listbox:
if self.listbox.size()>5:
self.listbox.configure(height=5)
if self.scrollbar is None:
self.scrollbar = tk.Scrollbar(self.frame, command = self.listbox.yview)
self.scrollbar.pack(side='right', fill="y")
self.listbox.config(yscrollcommand = self.scrollbar.set)
else:
self.listbox.configure(height=len(self.values))
if self.scrollbar:
self.scrollbar.destroy()
self.scrollbar = None
def _removeListView(self, event=None):
if self.listbox:
self.frame.destroy()
self.listbox = None
self.scrollbar = None
def _addValues(self, remove=(False, 0)):
if self.listbox:
if remove[0]:
self.listbox.delete(remove[1])
else:
for index, element in enumerate(self.values):
self.listbox.insert(index, element)
self._updateDisabled()
self._updateListHeight()
def _highlight(self, event):
self.listbox.itemconfig(self.previousIndex, background='white')
self.previousIndex = self.listbox.nearest(event.y)
if self.previousIndex not in self.disabled:
self.listbox.itemconfig(self.previousIndex, background='#b3d2ff')
def _removeHighlight(self, event):
self.listbox.itemconfig(self.previousIndex, background='white')
def _checkDisabled(self, event):
selection = self.listbox.curselection()[0]
if selection in self.disabled:
self.listbox.selection_clear(selection)
return
else:
self.entry.delete(0, 'end')
self.entry.insert(0, self.listbox.get(selection))
self.entry.focus_set()
self.event_generate('<<ComboboxSelected>>', when='tail')
def _updateDisabled(self, enable=(False, 0)):
if self.listbox:
for x in self.disabled:
self.listbox.itemconfig(x, fg="gray")
if enable[0]:
self.listbox.itemconfig(enable[1], fg="black")
def setValues(self, values): # adds values pass a list
self.values = values
self._addValues()
def removeValue(self, index): # removes values pass index
try:
self.values.pop(index)
self._addValues(remove=(True, index))
except IndexError:
pass
def disable(self, index:list): # disables the values and given indexes
if index not in self.disabled:
self.disabled.extend(index)
self._updateDisabled()
def enable(self, index:int): # enables the values at the given index
try:
self.disabled.remove(index)
self._updateDisabled((True, index))
except ValueError:
pass
def getCurrent(self): # returns current text in the entrybox
return self.entry.get()
def setDefault(self, index): # sets default value pass index
self.entry.insert(0, self.values[index])
(do note that this will not fully function like a Combobox).
Usage:
import tkinter as tk
from tkinter import ttk
from Combo import CustomComboBox
def truth(event):
return event.isalpha() or event==""
def fun(event):
print(monthchoosen.getCurrent())
monthchoosen.enable(2)
window = tk.Tk()
window.geometry('350x250')
ttk.Label(window, text = "Select the Month :",
font = ("Times New Roman", 10)).pack()
monthchoosen = CustomComboBox(window, validate='key', validatecommand=(window.register(truth), '%P'))
monthchoosen.values = ['January', 'Febuary', 'March', 'April', 'May', 'June', 'July']
monthchoosen.setDefault(1)
monthchoosen.bind('<<ComboboxSelected>>', fun)
monthchoosen.disable([2, 4, 3])
monthchoosen.pack(expand=True, fill='x')
window.mainloop()
You can change the values displayed (but not grayed) with postcommand:
def f():
co['values'] = ['c', 'd']
co = ttk.Combobox(values=['a', 'b'], postcommand=f)

communication between tkinter toplevels

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()

displaying corresponding message on label in tkinter

--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()

Passing Information from a child to a parent on an event

I have created a custom matplotlib toolbar, and I'm working on the functions associated with the custom toolbar's buttons. One of the buttons functions will (eventually) return a list position that best represents a user's selected position from a plot. I was having a bit of difficulty making this work in my mind, so I made a simple example (trying to avoid using globals) where a label not associated with the toolbar is updated when the toolbar button is pressed.
class TrackPlotToolbar(NavigationToolbar2TkAgg):
toolitems = [t for t in NavigationToolbar2TkAgg.toolitems if
t[0] in ('Home', 'Pan', 'Zoom', 'Save')]
toolitems.append(('Trace', 'Trace Track Position', 'Trace', 'Trace_old'))
def __init__(self, plotCanvas, frame):
self.TraceListOld = []
self.TraceListNew = []
NavigationToolbar2TkAgg.__init__(self, plotCanvas, frame)
def set_message(self, msg):
pass
def Trace_old(self):
gui.infoLabel.text = "abrakadabra"
gui.infoLabel.update()
return 1
class gui(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.grid()
self.parent = parent
self.infoLabel = Label(master = self, text = 'magic')
self.initUI()
def initUI(self):
TestPlot = FigureCanvasTkAgg(TestFig, self)
TestPlot.get_tk_widget().grid(column = 0,\
row = 0, columnspan = 3, rowspan = 5)
TestFrame = Frame(self)
TestFrame.grid(column = 2, row =6, columnspan = 3)
shockToolbar = TrackPlotToolbar(TestPlot,TestFrame)
shockToolbar.update()
self.infoLabel.grid(column = 2, row = 7)
def main():
root = Tk()
app = gui(root)
root.mainloop()
if __name__ == '__main__':
main()
Am I taking the wrong approach? Is it possible to inquire for new data on an event associated with a class inside of the parent class?

Categories

Resources