tkinter, How can you drag across buttons? - python

So I have a simple program where when you click a button from a grid it will be filled a colour. I wish to be able to drag across over the buttons and they get filled, unlike at the moment where you have to click every single button. Can this be done?
Here's my code that probably isn't the best:
from tkinter import *
root=Tk()
grid= Frame(root)
grid.pack()
img0=PhotoImage(file="0.png")
img1=PhotoImage(file="1.png")
img2=PhotoImage(file="2.png")
fill = 1
class button:
def __init__(self, x, y):
self.type=0
self.but=Button(grid,command=self.change, image=img0, borderwidth=0)
self.but.grid(row=y, column=x)
def change(self):
if self.type==fill:
self.but.config(image=img0)
self.type=0
else:
self.but.config(image=eval("img"+str(fill)))
self.type=fill
def create(x,y):
grid_buttons = []
for Y in range(y):
grid_buttons.append([])
for X in range(x):
grid_buttons[Y].append(button(X, Y))
create(15,15)
root.mainloop()

Here's one way:
from tkinter import *
root=Tk()
grid= Frame(root)
grid.pack()
img0=PhotoImage(file="0.png")
img1=PhotoImage(file="1.png")
img2=PhotoImage(file="2.png")
fill = 1
class button:
def __init__(self, x, y):
self.type=0
self.but=Button(grid,command=self.change, image=img0, borderwidth=0)
self.but.grid(row=y, column=x)
#Changed
self.already_changed = False
def change(self):
if self.type==fill:
self.but.config(image=img0)
self.type=0
else:
self.but.config(image=eval("img"+str(fill))) #I left this in here, but you should NEVER use eval(). It's unsafe.
self.type=fill
#Changed
def mouse_entered(self):
if not self.already_changed:
self.change()
self.already_changed = True
def mouse_up(self):
self.already_changed = False
#Changed
class Container:
def __init__(self, x, y):
grid_buttons = []
for Y in range(y):
grid_buttons.append([])
for X in range(x):
grid_buttons[Y].append(button(X, Y))
self.buttons = grid_buttons
grid.bind_all("<Button-1>", self.mouse_down)
grid.bind_all("<ButtonRelease-1>", self.mouse_up)
grid.bind_all("<B1-Motion>", self.mouse_motion)
self.mouse_pressed = False
def mouse_down(self, e):
self.mouse_pressed = True
def mouse_up(self, e):
self.mouse_pressed = False
for row in self.buttons:
for but in row:
but.mouse_up()
def mouse_motion(self, e):
for row in self.buttons:
for but in row:
if grid.winfo_containing(e.x_root, e.y_root) is but.but:
but.mouse_entered()
container = Container(15,15)
root.mainloop()
Now, I noticed that some of the things you did aren't quite Python style. So here's a version that more closely follows Python convention. Be warned that it's quite different.
from tkinter import *
root = Tk()
images = {0: PhotoImage(file="0.png"),
1: PhotoImage(file="1.png"),
2: PhotoImage(file="2.png")}
fill = 1
class MyButton(Button): #Convention is for class names to start with uppercase letters
def __init__(self, master):
super(MyButton, self).__init__(master, image = images[0], borderwidth = 0)
self.type = 0
self.already_changed = False
def change(self):
if self.type == fill:
self.type = 0
else:
self.type = fill
self.config(image=images[self.type])
def mouse_entered(self):
if not self.already_changed:
self.change()
self.already_changed = True
def mouse_up(self):
self.already_changed = False
class Container(Frame):
def __init__(self, master, width, height):
super(Container, self).__init__(master)
buttons = []
for y in range(height):
buttons.append([])
for x in range(width):
button = MyButton(self)
button.grid(row = x, column = y)
buttons[y].append(button)
self.buttons = buttons
self.bind_all("<Button-1>", self.mouse_down)
self.bind_all("<ButtonRelease-1>", self.mouse_up)
self.bind_all("<B1-Motion>", self.mouse_motion)
self.mouse_pressed = False
def mouse_down(self, e):
self.update_containing_button(e)
self.mouse_pressed = True
def mouse_up(self, e):
self.mouse_pressed = False
for row in self.buttons:
for button in row:
button.mouse_up()
def mouse_motion(self, e):
self.update_containing_button(e)
def update_containing_button(self, e):
for row in self.buttons:
for button in row:
if self.winfo_containing(e.x_root, e.y_root) is button:
button.mouse_entered()
grid = Container(root, 15, 15)
grid.pack()
root.mainloop()
Why post both? Because it looks like you have more code in the actual application (that's good, it's a minimal example). I didn't want to force you to rewrite my code to make it work with the rest of your code, or vice versa.
Functionality differences between the two versions:
The second version has been modified so it uses object-oriented features instead of global variables, making it more flexible and easier to change.
The second version removes the binding on the buttons themselves, instead having the container handle everything.

Related

How to use tkinter protocol("WM_DELETE_WINDOW",function) with a function that runs twice?

This is a function as part of a class to create tkinter Toplevel instances. I am trying to have the X button on each window destroy itself and then create two new windows. Every time I try running this, 'test' is only printed once and only 1 new window will appear. Why is this happening? Thanks!
Here is the class for creating tkinter instances
class App(Toplevel):
nid = 0
def __init__(self, master, title, f, nid):
# Creates Toplevel to allow for sub-windows
Toplevel.__init__(self, master)
self.thread = None
self.f = f
self.nid = nid
self.master = master
self.canvas = None
self.img = None
self.label = None
self.title(title)
self.geometry('300x300')
def window(self):
# Creates play_sound thread for self
self.thread = threading.Thread(target=lambda: play_sound())
# Disables resizing
self.resizable(False, False)
self.img = PhotoImage(file=self.f)
# Creates canvas
self.canvas = Canvas(self, width=300, height=300)
self.canvas.create_image(20, 20, anchor=NW, image=self.img)
self.canvas.pack(fill=BOTH, expand=1)
# Function to move each window to a random spot within the screen bounds
def move(self):
while True:
new_x = random.randrange(0, x)
new_y = random.randrange(0, y)
cur_x = self.winfo_x()
cur_y = self.winfo_y()
dir_x = random.choice(['-', '+'])
dir_y = random.choice(['-', '+'])
# Tests if the chosen position is within the monitor
try:
if (eval(f'{cur_x}{dir_x}{new_x}') in range(0, x)
and eval(f'{cur_y}{dir_y}{new_y}') in range(0, y)):
break
# Prevents crashing if the program happens to exceed the recursion limit
except RecursionError:
pass
# Sets geometry to the new position
self.geometry(f"+{new_x}+{new_y}")
# Repeats every second
self.after(1000, self.move)
# Starts sound thread
def sound(self):
self.thread.start()
# Changes the function of the X button
def new_protocol(self, func):
def run():
#do_twice
def cmd():
print('test')
func()
def both():
self.destroy()
return cmd()
self.protocol('WM_DELETE_WINDOW', both)
run()
Here is the function to create new windows
def create_window():
global num
# Hides root window
root.withdraw()
# Creates a new window with variable name root{num}.
d['root{0}'.format(num)] = App(root, 'HE HE HE HA', r'build\king_image.png', 0)
app_list.append(d['root{0}'.format(num)].winfo_id())
print(d['root{0}'.format(num)])
globals().update(d)
# Starts necessary commands for window
d['root{0}'.format(num)].window()
d['root{0}'.format(num)].move()
d['root{0}'.format(num)].new_protocol(create_window)
d['root{0}'.format(num)].sound()
d['root{0}'.format(num)].mainloop()
num += 1
Here is the decorator function:
def do_twice(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper
If anyone needs any other parts of my code I will gladly edit this to include them.

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)

Tkinter - pass button grid values between classes upon clicking button

I am trying to create a basic Battleship game in Python using Tkinter.
Below is a very simplified version of my code. Essentially I am creating a 10*10 grid of buttons and positioning them using .grid. What I'd like to do is click one of those buttons and pass that buttons grid values (x, y) from the GameBoard class to the Battleship class to position the ship.
I have tried using self.row = row and self.column = column, however when I do this I immediately receive an attribute error, 'GameBoard' object has no attribute 'row'.
import tkinter as tk
class GameBoard:
def __init__(self):
self.mw = tk.Tk()
self.size = 10
def build_grid(self):
for x in range(self.size):
for y in range(self.size):
self.button = tk.Button(self.mw, text = '', width = 2, height = 1,\
command = lambda row = x, column = y: self.clicked(row, column))
self.button.grid(row = x, column = y)
def clicked(self, row, column):
print(row, column)
self.row = row
self.column = column
class Battleship:
def __init__(self, board):
self.gboard = board
def position_ship(self):
x = self.gboard.row
y = self.gboard.column
for i in range (3):
self.submarine = tk.Button(self.gboard.mw, background = "black", text = '',\
width = 2, height = 1)
self.submarine.grid(row = x, column = y)
def main():
gboard = GameBoard()
gboard.build_grid()
bt = Battleship(gboard)
bt.position_ship()
main()
As #acw1668 pointed out in a comment, the problem is the gboard attributes row and column haven't been created yet when you call bt.position_ship() in the main() function.
I don't know your overall game design, but a very simple way to fix that would be to assign a random board position to them in the GameBoard.__init__() method.
I've also modified the code to show how to call bt.position_ship() when a button is clicked. This is done by passing the BattleShip instance bt to the build_grid() function so it can be included in calls to the clicked() method, which can now call it when its called.
from random import randrange
import tkinter as tk
class GameBoard:
def __init__(self):
self.mw = tk.Tk()
self.size = 10
self.row = randrange(self.size)
self.column = randrange(self.size)
def build_grid(self, bt):
for x in range(self.size):
for y in range(self.size):
self.button = tk.Button(self.mw, text='', width=2, height=1,
command=lambda row=x, column=y:
self.clicked(bt, row, column))
self.button.grid(row=x, column=y)
def clicked(self, bt, row, column):
print(row, column)
self.row = row
self.column = column
bt.position_ship()
class Battleship:
def __init__(self, board):
self.gboard = board
def position_ship(self):
x = self.gboard.row
y = self.gboard.column
for i in range (3):
self.submarine = tk.Button(self.gboard.mw, background="black", text='',
width=2, height=1)
self.submarine.grid(row=x, column=y)
def main():
gboard = GameBoard()
bt = Battleship(gboard)
gboard.build_grid(bt)
bt.position_ship()
tk.mainloop()
main()

how do i allow a function to be used anywhere in python tkinter?

i have 2 classes and when i run the first one, it works fine but when it gets to the second class i get an error saying AttributeError: 'Question2' object has no attribute 'correct'. how do i make it so that the functions work in both of the class? is there something wrong with my indent? please help me fix this code, thanks:
Edit: i have a problem with using self, if i remove self from the functions it wouldnt work, if i indent it to not be a part of the class, it still wont work, the self gets turns white
class Question1:
def __init__ (self, master):
x = random.randint(5, 12)
y = random.randint(5, 12)
self.master = master
self.user_choice = StringVar()
self.user_choice.set("")
self.frame = Frame(master, padx=200, pady=200)
self.frame.grid()
self.q = Label(self.frame, text="What is {} + {} ?".format(x, y))
self.q.grid(row=0)
self.ans = Entry(self.frame, width=50, textvariable=self.user_choice)
self.ans.grid(row=1)
self.answer = x+y
self.sub = Button(self.frame, text="submit", command=self.correct)
self.sub.grid(row=3)
def correct(self):
global p
if int(self.user_choice.get()) == self.answer:
cor = Label(self.frame,text="Correct!")
cor.grid(row=5, pady=20)
p += 1
if p >= 3:
Question2(self.master)
else:
self.sub.destroy()
nex = Button(self.frame, text="Next", command=self.necs)
nex.grid(row=4)
else:
inc = Label(self.frame,text="incorrect")
inc.grid(row=5, pady=20)
self.sub.destroy()
nex = Button(self.frame, text="Next", command=self.necs)
nex.grid(row=4)
self.frame.destroy()
Question1(self.master)
def necs(self):
self.frame.destroy()
Question1(self.master)
class Question2:
def __init__(self, master):
x = random.randint(2, 2)
y = random.randint(2, 3)
self.master = master
self.user_choice = StringVar()
self.user_choice.set("")
self.frame = Frame(master, padx=200, pady=200)
self.frame.grid()
self.q = Label(self.frame, text="What is {} x {} ?".format(x, y))
self.q.grid(row=0)
self.ans = Entry(self.frame, width=50, textvariable=self.user_choice)
self.ans.grid(row=1)
self.answer = x * y
self.sub = Button(self.frame, text="submit", command=self.correct)
self.sub.grid(row=3)
You can do that by inheriting the properties of Question1 to Question2:
That can be:
class Question2(Question1):
#you can access that by:
self.correct()
Other way is you can define a global function outside both the classes and you can easily access it.
Example:
#somewhere globally:
def correct():
#some code
class Question1():
correct()
class Question2():
correct()
I think you can develop more such ideas of using a function which will be required by multiple classes.
As #JenilDave answered, you need to define function outside class, inherit from other class.explicitly call class.
i.e. for last case:
class Question1:
def correct():
<codes>
class Question2:
q1 = Question1()
q1.correct()
or
Question1.correct(<Question1 instance>)
But since your 'correct' function are heavily dependent to Question1, you can't use either way, and reconstruct your codes.
Working example below:
Instead of generating question Class per questions, send lists of questions to one class.
Every time you succeed third time, you'll move on to next questions by poping list you've provided before.
When Lists are empty, pop() causes IndexError and program closes.
...
Since I can't get what variable 'p' stands for, I'm guessing it's number of successes(passes).
Full Code:
import random
from tkinter import Frame, Label, Entry, Button, StringVar, Tk
class QuestionFrame(Frame):
def __init__(self, master, question_answer):
super().__init__(master)
self.x, self.y = 0, 0
self.master = master
self.entries = question_answer
self.question, self.answer = self.entries.pop(0)
self.success = 0
self.user_choice = StringVar()
self.frame = Frame(master)
self.frame.grid()
self.quest_label = Label(self.frame)
self.ans = Entry(self.frame, width=50, textvariable=self.user_choice)
self.sub = Button(self.frame)
self.quest_label.grid(row=0)
self.ans.grid(row=1)
self.sub.grid(row=3)
self.reload_question()
def reload_question(self):
self.x, self.y = random.sample(range(5, 12), 2)
next_quest = f"What is {self.question.format(self.x, self.y)} ?"
self.quest_label.configure(text=next_quest)
self.ans.delete(0, 'end')
self.sub.configure(text="submit", command=self.correct)
def next(self):
print(self.success)
if self.success == 3:
# loads next entry
try:
self.question, self.answer = self.entries.pop(0)
except IndexError:
self.master.destroy()
else:
self.success = 0
self.reload_question()
else:
self.reload_question()
def correct(self):
self.sub.configure(text="Next", command=self.next)
if int(self.user_choice.get()) == self.answer(self.x, self.y):
self.quest_label['text'] = "Correct!"
self.success += 1
else:
self.quest_label['text'] = "Incorrect!"
if __name__ == '__main__':
# Passing questions with list of (question, answer) tuples.
tests = [("{} x {}", lambda x, y: x*y),
("{} - {}", lambda x, y: x-y)]
root = Tk()
root.title(f'Some fancy title')
window = QuestionFrame(root, tests)
window.mainloop()

tkinter Multilistbox custom event binding problem

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)

Categories

Resources