how to implement dynamic tkinter listboxes? - python

So I was given some example code of how to essentially control multiple Listboxes within one function, it seems to work during the example code but after implementing i am struggling to see what I've missed out.
Example code:
import tkinter as tk
class MultiListbox(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
for i in range(5):
lb = tk.Listbox(self, height=10, exportselection=False)
lb.pack(side="left", fill="y")
for j in range(10):
lb.insert("end", f"Listbox {i+1} value {j+1}")
lb.bind("<Double-1>", self.removeSeq)
def removeSeq(self, event):
lb = event.widget
curselection = lb.curselection()
index = curselection[0] if curselection else None
for listbox in self.winfo_children():
listbox.delete(index)
root = tk.Tk()
mlb = MultiListbox(root)
mlb.pack(side="top", fill="both", expand=True)
root.mainloop()
Where I am trying to implement logic:
Imports
import tkinter as tk,tkinter.ttk as ttk, pyautogui, numpy, easygui, cv2, os, time, _thread, re, math, subprocess
from tkinter import BOTH, END, LEFT
pyautogui.FAILSAFE = True
Class
class Acgm003App:
def __init__(self, master=None):
for i in range(5):
self.lb = tk.Listbox(self.modeSelect)
self.lb.configure(background='#2f2a2d', exportselection='false', font='{Arial} 12 {}', foreground='#feffff', height='23')
self.lb.configure(relief='flat', width='12')
self.lb.pack(side='left')
for j in range(10):
self.lb.insert("end", f"Listbox {i+1},{j+1}")
self.lb.bind("<Double-1>", self.getIndexLB)
Function
def getIndexLB(self, event):
print('hello')
self.lb = event.widget
curselection = self.lb.curselection()
index = curselection[0] if curselection else None
for listbox in self.lb.winfo_children():
print(index)
listbox.delete(index)
pass
I am just not getting anything back at all, I put print('hello') there just to make sure it was binded correctly, it prints just fine, but no result.
The code is intended to delete listbox items in other listboxes by taking the corresponding index of the curselection, sort of a work around to a tk.treeview.
Let me know if you can help!

I didn't test it but I think you use it with wrong object.
Original code use
lb = tk.Listbox(self, ...)
to add listbox to self and later it searchs children in self
for listbox in self.winfo_children():
You add listbox to self.modeSelect
tk.Listbox(self.modeSelect, ...)
so you should search children in self.modeSelect
for listbox in self.modeSelect.winfo_children():
But this method can make problem if you add other widgets in self.modeSelect because it will try to use .delete(index) also on other widgets. And then you should check if you get tk.Listbox
for child in self.modeSelect.winfo_children():
if isinstance(child, tk.Listbox):
child.delete(index)
EDIT:
import tkinter as tk
class Acgm003App:
def __init__(self, master=None):
self.modeSelect = tk.Frame(master)
self.modeSelect.pack()
# other child in `self.modeSelect`
self.label = tk.Label(self.modeSelect, text="Hello World")
self.label.pack(side='top')
for i in range(5):
self.lb = tk.Listbox(self.modeSelect)
self.lb.pack(side='left')
for j in range(10):
self.lb.insert("end", f"Listbox {i+1},{j+1}")
self.lb.bind("<Double-1>", self.getIndexLB)
def getIndexLB(self, event):
self.lb = event.widget
curselection = self.lb.curselection()
index = curselection[0] if curselection else None
for child in self.modeSelect.winfo_children():
# check if child is `tk.Listbox` or other widget
if isinstance(child, tk.Listbox):
child.delete(index)
root = tk.Tk()
app = Acgm003App(root)
root.mainloop()

Related

Adding Bind Event To Entry Fields Created Using Loops

The main aim here is too bind events to entry fields which are created dynamically using loops and then fetch the values from those fields, how ever i am having issues here to create a function that grabs the text from the entry field as soon as the user starts typing in the box.
from tkinter import Tk, LEFT, BOTH, StringVar
from tkinter.ttk import Entry, Frame
import functools
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Entry")
self.pack(fill=BOTH, expand=1)
self.contents = []
self.ent = []
for i in range(0,5):
self.contents.append(StringVar())
# give the StringVar a default value
for i in range(0,5):
self.entry = Entry(self)
self.entry.grid(row=0,column=i)
self.entry["textvariable"] = self.contents[i]
self.entry.bind('<KeyRelease>', self.on_changed)
self.ent.append(self.entry)
def on_changed(self, event):
print('contents: {}'.format(self.contents.get()))
return True
def main():
root = Tk()
ex = Example(root)
root.geometry("800x400")
root.mainloop()
if __name__ == '__main__':
main()
When you use bind, the event object that is passed to the callback includes a reference to the widget, which you can use to get the value of the entry.
Example
def on_changed(self, event):
entry = event.widget
print('contents: {}'.format(entry.get()))
return True

Replace python tkinter label widget with entry widget

I want to enable double click to edit a label. Is there a way to replace the label, which user double-clicks, with entry widget without destroying it?
This example lists label widgets and after double-click destroys it and sets entry widget at the end:
from tkinter import *
class MainWindow:
def __init__(self, root):
self.root = root
self.tasks = ['text1', 'text2', 'text3']
self.list_tasks()
def list_tasks(self):
self.tasks_frame = Frame(self.root)
for task in self.tasks:
task_label = Label(self.tasks_frame, text=task)
task_label.bind('<Double-Button-1>', self.replace_with_entry)
task_label.pack()
self.tasks_frame.pack()
def replace_with_entry(self, event):
widget = event.widget
widget.destroy()
entry_widget = Entry(self.tasks_frame)
entry_widget.pack()
root = Tk()
main_window = MainWindow(root)
root.mainloop()
I want entry widget in exactly the same place, where label was. I think it is possible with grid, but maybe there is a better way?
The simplest solution is not to replace it, but to instead overlay it. This is one circumstance where place is very useful.
For example:
def replace_with_entry(self, event):
widget = event.widget
entry_widget = Entry(widget)
entry_widget.place(x=0, y=0, anchor="nw", relwidth=1.0, relheight=1.0)
entry_widget.bind("<Return>", self.remove_entry)
entry_widget.focus_set()
def remove_entry(self, event):
entry = event.widget
label = entry.place_info()["in"]
label.configure(text=entry.get())
entry.destroy()
Here is the grid solution. I think it is acceptable, but you can wait to see if someone else provides a better solution.
from tkinter import *
from functools import partial
class MainWindow:
def __init__(self, root):
self.root = root
self.tasks = ['text1', 'text2', 'text3']
self.list_tasks()
def list_tasks(self):
self.tasks_frame = Frame(self.root)
for i, task in enumerate(self.tasks):
task_label = Label(self.tasks_frame, text=task)
task_label.bind('<Double-Button-1>', partial(self.replace_with_entry, i))
task_label.grid(row=i, column=0)
self.tasks_frame.pack()
def replace_with_entry(self, i, event):
widget = event.widget
widget.destroy()
entry_widget = Entry(self.tasks_frame)
entry_widget.grid(row=i, column=0)
root = Tk()
main_window = MainWindow(root)
root.mainloop()
I also made another version that uses tkinter variables to include the text from the label in the entry. It could be modified to allow switching back to the label with the modified text.
from tkinter import *
from functools import partial
class MainWindow:
def __init__(self, root):
self.root = root
self.tasks = ['text1', 'text2', 'text3']
self.list_tasks()
def list_tasks(self):
self.tasks_frame = Frame(self.root)
self.task_widgets = []
for i, task in enumerate(self.tasks):
textvar = StringVar()
textvar.set(task)
task_label = Label(self.tasks_frame, textvariable=textvar)
task_label.bind('<Double-Button-1>', partial(self.replace_with_entry, i))
task_label.grid(row=i, column=0)
task_entry = Entry(self.tasks_frame, textvariable=textvar)
self.task_widgets.append((task_label, task_entry, textvar))
self.tasks_frame.pack()
def replace_with_entry(self, i, event):
widget = event.widget
widget.grid_forget()
self.task_widgets[i][1].grid(row=i, column=0)
root = Tk()
main_window = MainWindow(root)
root.mainloop()

Clearing specific widgets in tkinter

I am attempting to have a function in python that clears the screen upon a button being pressed. I am aware of grid_remove but am unsure of how to use it. Also is there a way to clear everything from a specific function, ie both "hi" and "clear"?
from tkinter import *
class Movies:
def __init__(self, master):
hi = Label(text = "Hello")
hi.grid(row = 0, column = 0)
clear = Button(text = "Click", command=self.clear)
clear.grid(row = 1, column = 0)
def clear(self):
hi.grid_remove()
root = Tk()
gui = Movies(root)
root.geometry("100x200+0+0")
root.mainloop()
You could use the built in winfo_children method if you're just wanting to toggle hiding / showing all of the widgets in whatever parent holds the widgets. Small example:
from tkinter import *
class Movies:
def __init__(self, master):
self.master = master
self.state = 1
for i in range(5):
Label(self.master, text='Label %d' % i).grid(row=0, column=i)
self.magic_btn = Button(self.master, text='Make the Magic!',
command=self.magic)
self.magic_btn.grid(columnspan=5)
def magic(self):
self.state = not self.state
for widget in self.master.winfo_children(): #iterate over all child widgets in the parent
#Comment out to clear the button too, or leave to toggle widget states
if widget != self.magic_btn: #or some other widget you want to stay shown
if self.state:
widget.grid()
else:
widget.grid_remove()
print(self.state)
root = Tk()
gui = Movies(root)
root.mainloop()

access properties of widget inside canvas.create_window

I am having the following problem. I am making a tkinter GUI, and I need to access an object that is inside a canvas, inside a Canvas.create_window widget, packed with some other objects. For example:
import Tkinter as tk
class Demo:
def __init__(self, master):
self.canvas = tk.Canvas()
self.canvas.pack(fill="both", expand=True)
f = tk.Frame(self.canvas)
f.pack()
self.container = self.canvas.create_window(50,50, window = f)
l = tk.Label(f, text='abc')
e = tk.Entry(f, width = 5)
l.pack()
e.pack()
if __name__ == '__main__':
root = tk.Tk()
app = Demo(root)
root.mainloop()
I am trying to edit the text of the l label (which is currently 'abc'), when some other event is triggered. I suppose I need to use canvas.itemconfig, but I can't find a way to pass to this function the correct reference to the label. Any Ideas?
Thank you
You don't need to use itemconfigure -- that is only for configuring canvas items. Your label is not a canvas item, it's just a normal tkinter widget that you access like any other widget. Save a reference, and then use the reference to call a method.
For example:
class Demo:
def __init__(...):
...
self.l = tk.Label(f, text='abc')
...
def some_event_handler(event):
self.l.configure(text="xyz")

python tkinter listbox event binding

I'm having trouble getting an event binding to work with python/tkinter. I'm simply trying to click and have the location printed, but every time I do this, "-1" is the result.
Here's my code
from Tkinter import *
import Tkinter
class make_list(Tkinter.Listbox):
def __init__(self,master, **kw):
frame = Frame(master)
frame.pack()
self.build_main_window(frame)
kw['selectmode'] = Tkinter.SINGLE
Tkinter.Listbox.__init__(self, master, kw)
master.bind('<Button-1>', self.click_button)
master.curIndex = None
#display the clicked location
def click_button(self, event):
self.curIndex = self.nearest(event.x)
print self.curIndex
#display the window, calls the listbox
def build_main_window(self, frame):
self.build_listbox(frame)
#listbox
def build_listbox(self, frame):
listbox = Listbox(frame)
for item in ["one", "two", "three", "four"]:
listbox.insert(END, item)
listbox.insert(END, "a list entry")
listbox.pack()
return
if __name__ == '__main__':
tk = Tkinter.Tk()
make_list(tk)
tk.mainloop()
updated code - I got rid of frame but I can't seem to figure out why I'm getting -1 for the first print statement in the function click_button
from Tkinter import *
import Tkinter
class make_list(Tkinter.Listbox):
#display the clicked location
def click_button(self, event):
##this block works
w = event.widget
index = int(w.curselection()[0])
value = w.get(index)
print value
##this doesn't
self.curIndex = self.nearest(event.y)
print self.curIndex
self.curIndex = event.widget.nearest(event.y)
print self.curIndex
#display the window, calls the listbox
def build_main_window(self):
self.build_listbox()
#listbox
def build_listbox(self):
listbox = Listbox()
listbox.bind('<<ListboxSelect>>', self.click_button)
for item in ["one", "two", "three", "four"]:
listbox.insert(END, item)
listbox.insert(END, "a list entry")
listbox.pack()
return
if __name__ == '__main__':
tk = Tkinter.Tk()
start = make_list(tk)
start.build_main_window()
start.mainloop()
In the comments of an answer you ask for the best practice. The best practice is to bind to <<ListboxSelect>> which will fire immediately after the item is selected in the listbox.
This answer to a similar question has an example.
listbox nearest item is found by y, not x.
self.nearest(event.x) # wrong
self.nearest(event.y) # right
Update: I didn't notice the real problem first:
listbox = Listbox(frame)
It's not the same listbox which you subclassed, it's another unrelated listbox. Your listbox (which is make_list) is empty, that's why it always returns -1 for nearest.
Perhaps subclassing a frame is a good idea (anyway, better than subclassing listbox and adding a frame with another listbox into it). Then you'll have to bind event on that real listbox which is not empty.
Quick way to see how it will work when fixed is to call nearest of a real listbox with event.widget:
self.curIndex = event.widget.nearest(event.y)

Categories