I have two list boxes. When one listbox is selected, it triggers the end to update using the output from a function. This works fine when I click each option individually using the <<ListboxSelect>> event, however I don't know now to get it to work with a select all button. The select all button works in terms of highlighting items, but I can not get it to update the second list.
Comments are from a previous question.
from Tkinter import *
# dummy list so that the code does not relay on actually drives and files
rdrive = ['drive1','drive2','drive3']
sel_files = {'drive1': ['file1','file2'],
'drive2': ['file3','file4'],
'drive3': ['file6','file5']}
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Listbox")
self.pack(fill=BOTH, expand=1)
# Drive Select List Box
# global rdrive
# rdrive = drive_ctypes.find_rmdrv()
# use dummy rdrive instead of physical drives. Otherwise,
# cant reproduce the problem.
self.lb = Listbox(self, height=10, selectmode=MULTIPLE)
for i in rdrive:
self.lb.insert(END, i)
self.lb.bind("<<ListboxSelect>>", self.onSelect)
self.lb.grid(row =3, column =2)
self.drives_select_b = Button(self, text = "Select All", command = self.select_all_drives)
#self.drives_select_b.bind("<Button-1>", PLACE HOLDER)
self.drives_select_b.grid(row =4, column =3)
## File Select List Box
self.flb = Listbox(self, height=10, selectmode=MULTIPLE)
self.flb.grid(row =3, column =4)
def onSelect(self, event):
# most changes are here. GUI programming is event driven, so you need
# to get the list of files for selected drive (i.e. when selection even occurs).
# Also here you respond the the even, so that the right list is populated.
# get widget (i.e. right listbox) and currently selected item(s)
widget = event.widget
selection=widget.curselection()
files_avalibe = []
# if something was selected, than get drives for which it was selected
# and retrieve files for each drive
if selection:
for drive_i in selection:
selected_drive = rdrive[drive_i]
files_avalibe += sel_files[selected_drive]
print(files_avalibe)
# once we have files from the selected drive, list them
# in the right list box
self.update_file_list(files_avalibe)
def update_file_list(self, file_list):
# updates right listbox
self.flb.delete(0, END)
for i in file_list:
self.flb.insert(END, i)
def select_all_drives(self):
self.lb.select_set(0, END)
root = Tk()
f = Example(root)
root.mainloop()
Your select_all_drives function can trigger the event:
def select_all_drives(self):
self.lb.select_set(0, END)
self.lb.event_generate("<<ListboxSelect>>")
You can reuse the code that you have in the onSelect method. All you need to do is replace event.widget with self.lb:
def select_all_drives(self):
self.lb.select_set(0, END)
selection=self.lb.curselection()
files_avalibe = []
if selection:
for drive_i in selection:
selected_drive = rdrive[drive_i]
files_avalibe += sel_files[selected_drive]
self.update_file_list(files_avalibe)
Of course, this is somewhat repetitive (both methods have identical code). It might be better to factor this out into a separate method:
def get_selected_files(self):
selection=self.lb.curselection()
files_avalibe = []
if selection:
for drive_i in selection:
selected_drive = rdrive[drive_i]
files_avalibe += sel_files[selected_drive]
return files_avalibe
and then call get_selected_files in the onSelect and select_all_drives methods:
def onSelect(self, event):
self.update_file_list(self.get_selected_files())
...
def select_all_drives(self):
self.lb.select_set(0, END)
self.update_file_list(self.get_selected_files())
Related
So, I have 5 listboxes in which I need to control at the same time, almost as if they were one listbox with columns.
I am trying to find a way in which when I select an item from any one of the listboxes and delete them, it will highlight and delete the other items in the corresponding index.
so far I am only able to delete the other indexed items only when I invoke curselection() on Listbox1, but if a user selects an item on listbox2 and calls the same, it'll throw an error because the variable is looking for listbox1.
I can't seem to find any documentation or examples of how to control multiple listboxes simultaneously anywhere.
Is it possible to have a self.listbox[0, 1, 2, 3].curselection() type of thing? or even an if statement that allows me to check if self.listbox1.curselection() == True: and then execute according.
This is the function anyway:
def removeSeq(self, event=None):
index = self.listbox1.curselection()[0]
print(index)
## self.listbox1.selection_set(1)
## selectedItem = self.listbox2.curselection()
## print(selectedItem)
## self.listbox1.delete(selectedItem)
## self.listbox2.delete(selectedItem)
## self.listbox3.delete(selectedItem)
## self.listbox4.delete(selectedItem)
## self.listbox5.delete(selectedItem)
pass
I've commented most of it out for test purposes, any help would be massively appreciated.
In your binding you can use event.widget to know which widget was clicked on. Then it's just a matter of getting the selection from that widget and applying it to the other listboxes.
Here's a simple example. To delete a row, double-click in any listbox:
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()
I've looked around SO and tried the solutions offered, but I can't seem to change the text of any button I've made using a double for loop.
The loops are there so I can add them to a list of lists of buttons so I can (supposedly) access them conveniently through the nested list by calling board_button[2][3] or something. They are also dynamically created after the user inputs the board_size such that it generates a grid of nxn buttons so there's that. These are all done inside a class method, and there's another class method that should change the button's text when it's called by the button.
I've tried using the solutions offered here, but none of them actually worked for my problem.
Pardon the long block of code, but I honestly think the way I've made it may have contributed to the problem, and give more insight as result.
from tkinter import filedialog
from tkinter import *
class MainWindow(Frame):
board_size = None
file_input = None
board_buttons = None
board_strvars = None
row = []
def __init__ (self, parent):
Frame.__init__(self, parent)
# initialize widgets but don't show them yet
self.initWidgets()
# show the starting window
self.startWindow()
def generateBoard(self, to_forget=None):
# hides the groups of the method that called it
if to_forget != None:
for i in to_forget:
self.row[i].forget()
# get the board_size from user
self.board_size = int(self.size_entry.get())
# initialize text variables for each button
self.board_strvars = []
for i in range(self.board_size):
self.row_strvars=[]
for j in range(self.board_size):
var = StringVar()
var.set(" ")
self.row_strvars.append(var)
self.board_strvars.append(self.row_strvars)
# insert list of lists of buttons here
self.row[1].pack(fill=X)
self.board_buttons = []
for i in range(self.board_size):
self.row_buttons=[]
for j in range(self.board_size):
self.row_buttons.append(Button(self.row[1], textvariable=self.board_strvars[i][j], command=lambda:self.place(i, j)))
self.row_buttons[j].grid(row=i, column=j)
self.board_buttons.append(self.row_buttons)
# for solve and back button
self.row[2].pack(fill=X)
def initWidgets(self):
# create the rows or groups of widgets
for i in range(3):
self.row.append(Frame())
# row 0; startWindow
self.size_entry = Entry(self.row[0])
self.size_entry.pack(fill=X, side=LEFT)
self.size_button = Button(self.row[0], text="Enter", command=lambda:self.generateBoard([0]))
self.size_button.pack(fill=X, side=LEFT)
self.load_button = Button(self.row[0], text="Load", command=self.loadFile)
self.load_button.pack(fill=X, side=LEFT)
# row 2; generateBoard
self.solve_button = Button(self.row[2], text="Solve", command=self.showSolutions)
self.solve_button.pack(fill=X, side=LEFT)
self.back_button = Button(self.row[2], text="Back", command=lambda:self.startWindow(to_forget=[0,2], to_destroy=[1]))
self.back_button.pack(fill=X, side=RIGHT)
def loadFile(self):
print("file loaded!")
def place(self, i, j):
if self.board_strvars[i][j].get() == " ":
self.board_strvars[i][j].set("C")
else:
self.board_strvars[i][j].set(" ")
def showSolutions(self):
print("solutions shown!")
def startWindow(self, to_forget=None, to_destroy=None):
# hides the groups of the method that called it
if to_forget != None:
for i in to_forget:
self.row[i].forget()
# destroys the groups' child widgets and hides the group
if to_destroy != None:
for i in to_destroy:
for child in self.row[i].winfo_children():
child.destroy()
self.row[i].forget()
self.row[0].pack(fill=X)
if __name__ == "__main__":
root=Tk()
root.title("test")
app = MainWindow(root)
root.mainloop()
I originally wanted to define a function that will change the text of the button that called it. But so far I've found no way to do so.
Doing the solutions offered in the post I linked changes nothing to the buttons. In the code I provided though, I used the StringVar() to be assigned as textvariable of the button. However, it only changes the last row, last column button element no matter which button you click. It's supposed to work in a way that the button that was clicked, will get its text changed.
Thanks!
Change your lambda function to force a closure:
def generateBoard(self, to_forget=None):
...
for i in range(self.board_size):
self.row_buttons=[]
for j in range(self.board_size):
self.row_buttons.append(Button(self.row[1], textvariable=self.board_strvars[i][j], command=lambda i=i, j=j:self.place(i, j)))
self.row_buttons[j].grid(row=i, column=j)
self.board_buttons.append(self.row_buttons)
Also note that its better to not call your own method place since there is already a place method in Tk.
I am creating a simple program using Tkinter. I want a function to be called every time xview property of entry changes. But there doesn't seem to be an event like this, at least not one that I can find.
The <Configure> event fires only on resize, which I already handled, but it doesn't fire when actual value I'm tracking changes in a different way, such as the user dragging his mouse to see the end of the entry.
Here is the code:
import Tkinter as Tk
import tkFileDialog
root = Tk.Tk()
class RepositoryFolderFrame(Tk.Frame):
def __init__(self, root):
Tk.Frame.__init__(self, root)
self.build_gui()
self.set_entry_text("Searching...")
#root.after(0, self.find_repo)
self.prev_entry_index = len(self.entry.get())
root.bind("<Configure>", self.on_entry_resize)
#self.entry.bind(???, self.on_entry_change)
#self.entry.bind("<Configure>", self.on_entry_change)
def on_entry_resize(self, event):
cur_entry_index = self.entry.xview()[1]
if cur_entry_index != self.prev_entry_index:
self.entry.xview(self.prev_entry_index)
def on_entry_change(self, event):
# This should be called when xview changes...
cur_entry_index = self.entry.xview()[1]
self.prev_entry_index = cur_entry_index
def set_entry_text(self, text):
self.entry_text.set(text)
self.entry.xview("end")
def build_gui(self):
label = Tk.Label(self, text = "Repository folder:")
label.pack(side = Tk.LEFT)
self.label = label
entry_text = Tk.StringVar()
self.entry_text = entry_text
entry = Tk.Entry(self, width = 50, textvariable = entry_text)
entry.configure(state = 'readonly')
entry.pack(side = Tk.LEFT, fill = Tk.X, expand = 1)
self.entry = entry
button = Tk.Button(self, text = "Browse...")
button.pack(side = Tk.LEFT)
self.button = button
repo_frame = RepositoryFolderFrame(root)
repo_frame.pack(fill = Tk.X, expand = 1)
root.mainloop()
There is no mechanism for getting notified when the xview changes. There are ways to do it by modifying the underlying tcl code, but it's much more difficult than it's worth.
A simple solution is to write a function that polls the xview every few hundred milliseconds. It can keep track of the most recent xview, compare it to the current, and if it has changed it can fire off a custom event (eg: <<XviewChanged>>) which you can bind to.
It would look something like this:
class RepositoryFolderFrame(Tk.Frame):
def __init__(self, root):
...
self.entry.bind("<<XviewChanged>>", self.on_entry_change)
# keep a cache of previous xviews. A dictionary is
# used in case you want to do this for more than
self._xview = {}
self.watch_xview(self.entry)
def watch_xview(self, widget):
xview = widget.xview()
prev_xview = self._xview.get(widget, "")
self._xview[widget] = xview
if xview != prev_xview:
widget.event_generate("<<XviewChanged>>")
widget.after(100, self.watch_xview, widget)
You'll need to modify that for the edge case that the entry widget is destroyed, though you can handle that with a simple try around the code. This should be suitably performant, though you might need to verify that if you have literally hundreds of entry widgets.
I've been building an app to track stock prices. The user should see a window with an entry widget and a button that creates a new frame with a label and a button. The label is the stock price and symbol, the button is a delete button, and should hide that frame if clicked.
I've re-written this program 4 times now, and it's been a great learning experience, but what I've learned is that I can't have the "mini-frames" being called from methods part of the main GUI class - this funks up the delete buttons, and updates the value behind frame.pack_forget() so it only deletes the last item ever.
I've moved my mini-frame widgets down into the class for the actual stock values. I've packed them (what I assume to be correct) but they don't show up. They also don't error out, which isn't very helpful. Here's my code, although I've omitted a lot of the functional parts to show what is happening with my frames. Keep in mind I need to keep it so that I can call my updater (self.update_stock_value) with a .after method against myapp.myContainer.
Is there a better way to do this?? Thanks in advance, my head hurts.
import re
import time
import urllib
from Tkinter import *
import threading
from thread import *
runningThreads = 0
# each object will be added to the gui parent frame
class MyApp(object):
def __init__(self, parent):
self.myParent = parent
self.myContainer = Canvas(parent)
self.myContainer.pack()
self.create_widgets()
# METHOD initiates basic GUI widgets
def create_widgets(self):
root.title("Stocker")
self.widgetFrame = Frame(self.myContainer)
self.widgetFrame.pack()
self.input = Entry(self.widgetFrame)
self.input.focus_set()
self.input.pack()
self.submitButton = Button(self.widgetFrame, command = self.onButtonClick)
self.submitButton.configure(text = "Add new stock")
self.submitButton.pack(fill = "x")
# METHOD called by each stock object
# returns the "symbol" in the entry widget
# clears the entry widget
def get_input_value(self):
var = self.input.get()
self.input.delete(0, END)
return var
# METHOD called when button is clicked
# starts new thread with instance of "Stock" class
def onButtonClick(self):
global runningThreads # shhhhhh im sorry just let it happen
runningThreads += 1 # count the threads open
threading.Thread(target = self.init_stock,).start() # force a tuple
if runningThreads == 1:
print runningThreads, "thread alive"
else:
print runningThreads, "threads alive"
def init_stock(self):
new = Stock()
class Stock(object):
def __init__(self):
# variable for the stock symbol
symb = self.stock_symbol()
# lets make a GUI
self.frame = Frame(myapp.myContainer)
self.frame.pack
# give the frame a label to update
self.testLabel = Label(self.frame)
self.testLabel.configure(text = self.update_stock_label(symb))
self.testLabel.pack(side = LEFT)
# create delete button to kill entire thread
self.killButton = Button(self.frame, command = self.kill_thread)
self.killButton.configure(text = "Delete")
self.killButton.pack(side = RIGHT)
# create stock label
# call updater
def kill_thread(self):
global runningThreads
runningThreads -= 1
self.stockFrame.pack_forget() # hide the frame
self.thread.exit() # kill the thread
def update_stock_label(self, symb):
self.testLabel.configure(text = str(symb) + str(get_quote(symb)))
myapp.myContainer.after(10000, self.update_stock_label(symb))
def stock_symbol(self):
symb = myapp.get_input_value()
print symb
# The most important part!
def get_quote(symbol):
try:
# go to google
base_url = "http://finance.google.com/finance?q="
# read the source code
content = urllib.urlopen(base_url + str(symbol)).read()
# set regex target
target = re.search('id="ref_\d*_l".*?>(.*?)<', content)
# if found, return.
if target:
print "found target"
quote = target.group(1)
print quote
else:
quote = "Not Found: "
return quote
# handling if no network connection
except IOError:
print "no network detected"
root = Tk()
root.geometry("280x200")
myapp = MyApp(root)
root.mainloop()
Your code won't run because of numerous errors, but this line is definitely not doing what you think it is doing:
self.frame.pack
For you to call the pack function you must include (), eg:
self.frame.pack()
You ask if your code is the best way to do this. I think you're on the right track, but I would change a few things. Here's how I would structure the code. This just creates the "miniframes", it doesn't do anything else:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.entry = tk.Entry(self)
self.submit = tk.Button(self, text="Submit", command=self.on_submit)
self.entry.pack(side="top", fill="x")
self.submit.pack(side="top")
def on_submit(self):
symbol = self.entry.get()
stock = Stock(self, symbol)
stock.pack(side="top", fill="x")
class Stock(tk.Frame):
def __init__(self, parent, symbol):
tk.Frame.__init__(self, parent)
self.symbol = tk.Label(self, text=symbol + ":")
self.value = tk.Label(self, text="123.45")
self.symbol.pack(side="left", fill="both")
self.value.pack(side="left", fill="both")
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
i am going to create an tkinter gui app, and i know how i want it to look. but after playing around with tkinter, i found no way to toggle between screens when you press buttons down at the bottom. i know it does nothing but below is the simple layout i want to have, and switch between "myframe1" and "myframe2" kind of like the Apple App Store layout. is this possible?
from tkinter import *
tk = Tk()
tk.geometry("300x300")
myframe1 = Frame(tk,background="green",width=300,height=275)
myframe1.pack()
myframe2 = Frame(tk,background="cyan",width=300,height=275)
myframe2.pack()
btnframe = Frame(tk)
btn1 = Button(btnframe,text="screen1",width=9)
btn1.pack(side=LEFT)
btn2 = Button(btnframe,text="screen2",width=9)
btn2.pack(side=LEFT)
btn3 = Button(btnframe,text="screen3",width=9)
btn3.pack(side=LEFT)
btn4 = Button(btnframe,text="screen4",width=9)
btn4.pack(side=LEFT)
myframe1.pack()
btnframe.pack()
tk.mainloop()
something for you to get started with:
def toggle(fshow,fhide):
fhide.pack_forget()
fshow.pack()
btn1 = Button(btnframe,text="screen1", command=lambda:toggle(myframe1,myframe2),width=9)
btn1.pack(side=LEFT)
btn2 = Button(btnframe,text="screen2",command=lambda:toggle(myframe2,myframe1),width=9)
btn2.pack(side=LEFT)
Are you looking for something like a tabbed widget? You could use forget and pack as suggested here
Here is a class that I use in my code that works:
class MultiPanel():
"""We want to setup a pseudo tabbed widget with three treeviews. One showing the disk, one the pile and
the third the search results. All three treeviews should be hooked up to exactly the same event handlers
but only one of them should be visible at any time.
Based off http://code.activestate.com/recipes/188537/
"""
def __init__(self, parent):
#This is the frame that we display
self.fr = tki.Frame(parent, bg='black')
self.fr.pack(side='top', expand=True, fill='both')
self.widget_list = []
self.active_widget = None #Is an integer
def __call__(self):
"""This returns a reference to the frame, which can be used as a parent for the widgets you push in."""
return self.fr
def add_widget(self, wd):
if wd not in self.widget_list:
self.widget_list.append(wd)
if self.active_widget is None:
self.set_active_widget(0)
return len(self.widget_list) - 1 #Return the index of this widget
def set_active_widget(self, wdn):
if wdn >= len(self.widget_list) or wdn < 0:
logger.error('Widget index out of range')
return
if self.widget_list[wdn] == self.active_widget: return
if self.active_widget is not None: self.active_widget.forget()
self.widget_list[wdn].pack(fill='both', expand=True)
self.active_widget = self.widget_list[wdn]