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)
Related
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,
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
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 make it so that a user inputs a number, and that number of buttons are created Using TKinter, I have tried doing it by using the following, Where the Buttons are successfully created, however i am struggling with calling them in order to place them / display them on the grid (Added randint to simulate user input (User Input not limited to 9 and may be as high as 40))
from tkinter import *
from random import randint
inputValue = randint(3,9)
print(inputValue)
root = Tk()
while inputValue > 0: # for every number in inputted value
inputValue = int(inputValue) - 1 # take one
globals()['Sailor%s' % inputValue] = Button(root, text="Lap :" + str(inputValue), command=lambda: retrieve_input()) # Create the button function in the format 'Sailors{Inputnumber}'
('Sailors%s' % inputValue).grid(row=inputValue, column=1, columnspan=2) # Place the button (Doesn't work)
root.mainloop() # Does work (required)
Howerver the following does not work (It is meant to place the button),
('Sailors%s' % inputValue).grid(row=inputValue, column=1, columnspan=2) # Place the button (Doesn't work)
Can you think of a method i can use in order to create and place Amount of buttons?
Thanks in advance
You should never create dynamic variable names like you are attempting to do. It adds a lot of complexity, reduces clarity, and provides no real benefit.
Instead, use a dictionary or list to keep track of the buttons. In your case, however, since you're never using the buttons anywhere but in the loop you can just use a local variable.
Example using a local variable, in case you never need to access the button in code after you create it:
for count in range(inputValue):
button = Button(...)
button.grid(...)
Here's how you do it if you need to access the buttons later in your code:
buttons = []
for count in range(inputValue):
button = Button(...)
button.grid(...)
buttons.append(button)
With the above you can iterate over all of the buttons in buttons:
for button in buttons:
button.configure(state='disabled')
If you need to configure a single button, use its index:
button[0].configure(...)
You can calling grid on a string at the moment, which throws your error.
Your need to replace ('Sailors%s' % inputValue) with globals()['Sailor%s' % inputValue], with orders your buttons on individual rows, labeled 0-8.
So, your current code is:
from tkinter import *
from random import randint
inputValue = randint(3,9)
print(inputValue)
root = Tk()
while inputValue > 0: # for every number in inputted value
inputValue = int(inputValue) - 1 # take one
globals()['Sailor%s' % inputValue] = Button(root, text="Lap :" + str(inputValue), command=lambda: retrieve_input()) # Create the button function in the format 'Sailors{Inputnumber}'
globals()['Sailor%s' % inputValue].grid(row=inputValue, column=1, columnspan=2)
root.mainloop() # Does work (required)
When retrieve_input is defined, the code will work fine.
Just to point out, instead of inputValue = int(inputValue) - 1 you can use inputValue -= 1.
I've been trying to create a piece of code that would take a integer as a argument and create that number of tkinter entry fields. With a submit button at the end that would retrieve the data from the fields add these data to a list then close the window.
I have been able to get it working however I cant find a way to convert this to a callable function; a requirement to use it with the rest of my program.
This is the code I have produced so far, thanks:
import tkinter as tk
b = input("Enter: ")
b = int(b)
root = tk.Tk()
newdict = dict()
outputs = list()
for i in range(b):
newdict["entry" + str(i)] = tk.Entry(root)
newdict["entry" + str(i)].pack()
button1 = tk.Button(root, text="Submit", command=lambda: Get(newdict))
button1.pack()
def Get(newdict):
for j in range(b):
outputs.append(newdict["entry" + str(j)].get())
root.quit()
root.mainloop()
print(outputs)
The basic idea is to create a window, then use the wait_window method to wait for the window to be destroyed. Once it has been destroyed you can return some value.
The problem is that the values you want to fetch must not be attributes of the window, since it will have been destroyed by the time you are ready to fetch them. You need to set up your code to save the values before the window is destroyed.
A simple way is to provide an "OK" button which gets the values and then destroys the window. Another way would be to put a trace on variables associated with each entry, and save the values immediately as they are edited.
Which method you choose depends on what behavior you want when the user clicks the window control to close the window (eg: the red circle on OSX, the [x] button on windows, etc). Do you want to return what they had input, or do you treat that as a cancel action and return nothing?
Here's a simple example using an OK button. This example assumes that you aren't already running a GUI, and that this is to be run as part of a non-GUI application.
import tkinter as tk
class Dialog(object):
def show(self, num_fields):
self.num_fields = num_fields
self.root = tk.Tk()
self.entries = []
for i in range(num_fields):
entry = tk.Entry(self.root)
entry.pack(fill="x")
self.entries.append(entry)
ok = tk.Button(self.root, text="OK", command=self.ok)
ok.pack(side="bottom", anchor="e", pady=(10,0), padx=10)
# wait for the window to be destroyed, then
# return the values. If the user clicks the OK button
# the values will be set; if they cancel the dialog
# this will return None.
self.values = None
self.root.wait_window()
return self.values
def ok(self):
# save all the values, then destroy the window
self.values = []
for i in range(self.num_fields):
self.values.append(self.entries[i].get())
self.root.destroy()
Assuming you're running a non-gui program, here's an example of how you would use this class:
b = input("Enter: ")
b = int(b)
result = Dialog().show(b)
print("result:", result)