I have a listbox and I printed some contents with it. I want to put a back and forth button, as I press the button, it goes back and forth in the indexes and this selection appears in the listbox.
For example, 0 index is selected in the photo. When I press Forth, I want it to come to 1 index.
For this, I did research and analyzed the sample codes, but did not have the idea that I could not find the result.
import tkinter as tk
from tkinter import *
root = tk.Tk()
my_frame = Frame(root)
my_scrollbar = Scrollbar(my_frame, orient=VERTICAL)
list = Listbox(my_frame, width=85, height=20, yscrollcommand=my_scrollbar.set)
for i in range(50):
list.insert(END, str(i)+".index")
my_scrollbar.config(command=list.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
my_frame.pack()
list.pack()
So if I understand correctly, you'd like 2 buttons that allows you to cycle forward and backwards through the indexes.
So below is your code modified that allows you to visibly cycle through the listbox by using the GUI.
(EXPLANATION BELOW)
import tkinter as tk
from tkinter import *
root = tk.Tk()
my_frame = Frame(root)
my_frame.pack()
my_scrollbar = Scrollbar(my_frame)
my_scrollbar.pack(side=RIGHT, fill=Y)
listBox_0 = tk.Listbox(my_frame, width=85, height=20, yscrollcommand=my_scrollbar.set, selectmode='single')
listBox_0.pack()
my_scrollbar.config(command=listBox_0.yview)
def backButtonFN(my_frame):
curselection = listBox_0.curselection()
indexLB = curselection[0]
indexLB = indexLB - 1
if indexLB > 0 or indexLB == 0:
listBox_0.selection_clear(0, last='end')
listBox_0.selection_set(indexLB)
else:
None
pass
def forwardButtonFN(my_frame):
curselection = listBox_0.curselection() #current selection method
indexLB = curselection[0] #get value of index
indexLB = indexLB + 1 #add 1 to current value
listBox_0.selection_clear(0, last='end') #delete old selection
listBox_0.selection_set(indexLB) #select new index
pass
backButton = tk.Button(my_frame)
backButton.configure(text='BACK')
backButton.pack(anchor='s', side='left')
backButton.bind('<1>', backButtonFN, add='' )
forwardButton = tk.Button(my_frame)
forwardButton.configure(text='FOWARD')
forwardButton.pack(anchor='s', side='right')
forwardButton.bind('<1>', forwardButtonFN, add='' )
for i in range(50):
listBox_0.insert(END, str(i)+".index")
listBox_0.selection_set(0) #activate first index
Initially you called your listbox list, which is a big no-go as it can cause issues in terms of syntax later on down the line, so I changed the name of the Listbox to listBox_0, so this is your new Listbox as you originally intended.
once I did that, I created 2 functions for each of the forward and backwards buttons, I also had to rearrange the code to allow it to <bind> correctly.
I started off by making the first index active, that essentially will give me a value for curselection(), I then took that value, turned it into an integer I could play with, then each time you press the button, it will turn that extracted index, add one to it, then remove the previous selection and then add the new updated selection with indexLB
Then the same process for backButtonFN except this time, I am deducting 1 instead of adding, and then making sure it doesn't continue to go back once it hits 0.
I've also changed your configuration for listBox_0 to selectmode='single' to allow you to select one item at a row, you can disable this if you'd like to select multiple.
It can definitely use some tidying up as the pro's may immediately see, but I hope this answers your question
Related
I am new to Python and coding and i am trying to make an object detection application. For this i have used a loop which will print out buttons for the objects that was detected. For example if we have detected 3 objects, then 3 buttons will appear on a Tkinter window, where by clicking on each will bring you to an amazon page where you can shop for that product.
I managed to complete work with buttons, however when i select a button for the objects detected, it only opens up an amazon page for the last object detected and i assume its because python loop erases previous data and keeps the last one, (if i am right). The problem is based in the commented part of this code, where i need an application to read the URL for all objects and not only last one. I hope it is clear. Here is the code i have used:
def button_window():
window.geometry("350x600")
# Create a LabelFrame
labelframe = LabelFrame(window)
# Define a canvas in the window
canvas = Canvas(labelframe)
canvas.pack(side=RIGHT, fill=BOTH, expand=1)
labelframe.pack(fill=BOTH, expand=1, padx=30, pady=30)
start = 0
end = length_list
step = 1
for y in range(start, end, step):
x = y
sec = new_lst[x:x+step]
# URL
init_url = "amazon.co.uk/s?k="
x = str(sec)
final_url = init_url + x
cutting = final_url.replace("'", '')
cutting_two = cutting.replace("[", "")
cutting_three = cutting_two.replace("]", "")
print(cutting_three)
# def open():
# for x in range(cutting_three):
# webbrowser.open(cutting_three)
btn = Button(canvas, text=sec, command=open)
btn.pack()
window.mainloop()
I beleive the issue is you are assigning the 3 buttons to the same function: open, which is re-defined every time you loop over.
Create the function outside the loop.
Also open function does not take any input, so for the 3 buttons it will run the same code.
Depending how your program works, try to simplify few things.
for instance:
def open(url):
webbrowser.open(url)
init_url = "amazon.co.uk/s?k="
for item in new_lst:
final_url = init_url + item
Button(canvas, text=item, command=lambda url=finla_url: open(url)).pack()
Lambda is necessary to avoid open(url) be called at creation time.
Lastly, new_lst variable is probably being taken from the outer scope, so you might want to make it an input parameter to the function (as well as init_url, rather than defining this variable inside the window function)
I'm trying to create a series of tkinter buttons with a loop that are .grid'd to their own respective frames. I want every button to have a function that .tkraises the next frame in the list of frames that I create. Any idea how? Here's what I've got. The buttons/ frames are created I think but the .tkraise function doesn't work. Thanks
from tkinter import *
from PIL import ImageTk, Image
## Define root and geometry
root = Tk()
root.geometry('200x200')
# Define Frames
winlist = list()
winlist = Frame(root, bg='red'), Frame(root, bg='green'), Frame(root, bg='blue')
# Configure Rows
root.grid_rowconfigure(0, weight = 1)
root.grid_columnconfigure(0, weight = 1)
# Place Frames
for window in winlist:
window.grid(row=0, column = 0, sticky = 'news')
# Raises first window 'To the top'
winlist[0].tkraise()
# Function to raise 'window' to the top
def raise_frame(window):
window.tkraise()
d = {}
count = 0
for x in range(0, 3):
d["label{0}".format(x)] = Label(winlist[x], text = "label{0}".format(x))
if count <=1:
try:
d["button{0}".format(x)] = Button(winlist[x], text = "button{0}".format(x), command = raise_frame(winlist[x+1]))
d["button{0}".format(x)].pack(side=TOP)
except:
pass
else:
d["label{0}".format(x)].pack(side=TOP)
count += 1
root.mainloop()
The issue is on the command option of the line:
d["button{0}".format(x)] = Button(winlist[x], text = "button{0}".format(x), command = raise_frame(winlist[x+1]))
It will execute raise_frame(winlist[x+1]) immediately and then assign the result (which is None) to command option. Therefore, clicking the button later does nothing.
You need to use lambda instead:
d["button{0}".format(x)] = Button(winlist[x], text="button{0}".format(x),
command=lambda x=x: raise_frame(winlist[x+1]))
I answered my own question. instead of using frames I went back to creating Tk() objects. I made a loop that runs a function that creates Tk() objects and passed in a variable that carried the count of my loop. I used that count to change information on each Tk() object and instead made the 'command =' of each button include a Tk().destroy function. This creates all the windows I wanted all at once and I can perform an action and exit the window. It's progress. Thanks,
Tim,
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 am trying to create a program that allows the user to select any number of check boxes and hit a button to return a random result from those check boxes. Since I am basing my list off the roster of Smash bros ultimate, I am trying to avoid creating 70+ variables just to place check boxes. However, I am unable to figure out how to iterate this. The various values set for rows are just placeholders until I can figure this out. I would also like to have a reset button at the top that allows the user to automatically uncheck every box. This code is what I have so far. Any help would be greatly appreciated.
#!/usr/bin/python3
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
f = [] #check boxes
ft = open("Fighters.txt").readlines() #list of all the character names
fv=[0]*78 #list for tracking what boxes are checked
ff=[] #list to place final character strings
def reset():
for i in fv:
fv[i]=0
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for y in range (0,77):
f[y] = Checkbutton(window, text = ft[y], variable = fv[y])
f[y].grid(column=0, row=4+y)
def done():
for j in fv:
if fv[j] == 1:
ff.append(fv[j])
result = random.choice(ff)
r=Label(window, text=result)
d = Button(window, text="Done", command=done)
d.grid(column=0, row = 80)
window.mainloop()
Unfortunately I'm afraid you are going to have to create variables for each checkbox.
tkinter has special purpose Variable Classes for holding different types of values, and if you specify an instance of one as the variable= option when you create widgets like Checkbutton, it will automatically set or reset its value whenever the user changes it, so all your program has to do is check its current value by calling its get() method.
Here's an example of the modifications to your code needed to create them in a loop (and use them in the done() callback function):
import random
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
with open("Fighters.txt") as fighters:
ft = fighters.read().splitlines() # List of all the character names.
fv = [BooleanVar(value=False) for _ in ft] # List to track which boxes are checked.
ff = [] # List to place final character strings.
def reset():
for var in fv:
var.set(False)
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for i, (name, var) in enumerate(zip(ft, fv)):
chk_btn = Checkbutton(window, text=name, variable=var)
chk_btn.grid(column=0, row=i+4, sticky=W)
def done():
global ff
ff = [name for name, var in zip(ft, fv) if var.get()] # List of checked names.
# Randomly select one of them.
choice.configure(text=random.choice(ff) if ff else "None")
d = Button(window, text="Done", command=done)
d.grid(column=0, row=len(ft)+4)
choice = Label(window, text="None")
choice.grid(column=1, row=3)
window.mainloop()
I wasn't sure where you wanted the Label containing the result to go, so I just put it to the right of the Reset button.
variable = fv[y]
This looks up the value of fv[y] - i.e, the integer 0 - at the time the Checkbutton is created, and uses that for the variable argument.
You need to use an instance of one of the value-tracking classes provided by TKinter, instead. In this case we want BooleanVar since we are tracking a boolean state. We can still create these in a list ahead of time:
text = open("Fighters.txt").readlines()
# Let's not hard-code the number of lines - we'll find it out automatically,
# and just make one for each line.
trackers = [BooleanVar() for line in text]
# And we'll iterate over those pair-wise to make the buttons:
buttons = [
Checkbutton(window, text = line, variable = tracker)
for line, tracker in zip(text, trackers)
]
(but we can not do, for example trackers = [BooleanVar()] * len(text), because that gives us the same tracker 78 times, and thus every checkbox will share that tracker; we need to track each separately.)
When you click the checkbox, TKinter will automatically update the internal state of the corresponding BooleanVar(), which we can check using its .get() method. Also, when we set up our options for random.choice, we want to choose the corresponding text for the button, not the tracker. We can do this with the zip trick again.
So we want something more like:
result_label = Label(window) # create it ahead of time
def done():
result_label.text = random.choice(
label
for label, tracker in zip(text, trackers)
if tracker.get()
)
I am trying to take the selection from a listbox and populate a new list with it, and it will be multiple items. I can't figure this out, here's what i have so far (and I need the actual strings in the list, not the indices). Also, how can I completely get rid of the Tkinter widgets after making the selection - it closes out but it seems like there is a ghost of it still hanging around after it closes.
def execute(*events):
UsrFCList = []
selctd_indices = lbox.curselection()
lst_select = list(selctd_indices)
for i in lst_select:
lbox.get(i)
UsrFCList.append(i)
lbox.quit()
fc_lb = Tk()
scrollbar = Scrollbar(fc_lb)
scrollbar.pack(side=RIGHT, fill=Y)
lbox = AutoSzLB(fc_lb,selectmode=EXTENDED)
for item in lb_list:
lbox.insert(END, *item)
button = Button(fc_lb, text="Analyze selected feature classes", command=execute)
lbox.autowidth(250)
lbox.pack()
button.pack()
lbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=lbox.yview)
mainloop()
I figured it out, instead of
for i in lst_select:
lbox.get(i)
UsrFCList.append(i)
It was
for i in lst_select:
UsrFCList.append(lbox.get(i))