My application should look as follows:
If I click in BasicWindow at Button "Left" and click in NewWindow (should be some widget in seperate class) at Button "apply", in a cell of some grid (4 columns) below of Button "Left" the word hello should appear. If I repeat that n times, new words "Hello" should arranged in the grid below of the "Left" button n times.
If I click in BasicWindow at Button "Right" and click in NewWindow at Button "apply", in a cell of some grid (4 columns) below of Button "Left" the word hello should appear. If I repeat that n times, new words "Hello" should arranged in the grid below of the "Right" button n times.
This graphic illustrates, what the program should do:
I just tried to solve that with following code:
from tkinter import *
class NewWindow(Toplevel):
def __init__(self, master = None, apply=None):
super().__init__(master = master)
self.title('NewWindow')
self.master = master
self.words = 'Hello'
self.bt1 = Button(self, text="apply", command=self.bt_press)
self.bt1.grid(column=0, row=0)
self.apply = apply
def bt_press(self):
self.apply(self.words)
self.destroy()
window = Tk()
def new_Editor(key):
def make_label1(lbl_txt):
lbl = Label(window, text=lbl_txt)
lbl.grid(column=0,row=2)
def make_label2(lbl_txt):
lbl = Label(window, text=lbl_txt)
lbl.grid(column=2,row=2)
if key == 1:
a = NewWindow(window, make_label1)
else:
a = NewWindow(window, make_label1)
window.title("BasicWindow")
window.basic_bt_l = Button(window, text="Left", command=lambda: new_Editor(1))
window.basic_bt_l.grid(column=0, row=0, columnspan=2)
window.basic_bt_r = Button(window, text="Right", command=lambda: new_Editor(2))
window.basic_bt_r.grid(column=1, row=0)
window.mainloop()
My Programm looks like this:
For some reason the two buttons are not very good arranged and the format of the output isn't very good. How can I just define some well formatet grid between Left and Right button and two grids below of Left/Right button with the properties I descriped above?
You've specified columnspan=2 when adding root.basic_bt_l to the window, so it will occupy columns 0 and 1. You then try to put root.basic_bt_r in column 1, which will overlap the first button.
Sometimes these mistakes are easier to see if you group your grid statements together. Notice how the columnspan sticks out like a sore thumb here, and makes it easier to visualize the layout
root.basic_bt_l.grid(column=0, row=0, columnspan=2)
root.basic_bt_r.grid(column=1, row=0)
You need to remove columnspan=2 so that the buttons don't overlap.
As for adding new words on new rows, you will need to calculate which row to place the new label. In this specific case, since you are stacking the labels vertically and never remove them from the middle, you can use the grid_slaves method to tell you how many labels there are. You can add one to that number to get the first empty row for that column. You could also just keep a counter of how many labels you've added for each column.
You also need to make sure that you call make_label2 - you are calling make_label1 in both conditions of your if statement.
def new_Editor(key):
def make_label1(lbl_txt):
print("root.grid_slaves(0):", root.grid_slaves(column=0))
row = len(root.grid_slaves(column=0))+1
lbl = Label(root, text=lbl_txt)
lbl.grid(row=row, column=0)
def make_label2(lbl_txt):
print("root.grid_slaves(1):", root.grid_slaves(column=1))
row = len(root.grid_slaves(column=1))+1
lbl = Label(root, text=lbl_txt)
lbl.grid(row=row, column=1)
if key == 1:
a = NewWindow(root, make_label1)
else:
a = NewWindow(root, make_label2)
Related
I'm trying to create a GUI using Tkinter for a Pip-boy from Fallout 3. I'm running into a problem where after I click a button in one of the frames, the spacing between the buttons gets messed up. This spacing change happens for all but one of the buttons in the frame (the Lockpick one).
This is what I want the button spacing to look like (what it looks like before I click a button):
This is what happens after I click a button (in this case the Barter one)
Here is the code I am using:
from tkinter import *
# to read descriptions of each skill from a text file
with open("skills.txt") as f:
lines = f.readlines()
# function that updates the label with a different description when the corresponding button is clicked
def show_skill_desc(index):
desc['text'] = lines[index-1]
# makes the window and frame
root = Tk()
root.geometry("1024x600")
root.title("Skills")
frame = Frame(root)
frame.grid()
# creates the label
Label(root, text="Stats").grid(row=0, column=0)
# list of skills which will each have their separate labels
skills_list = ["Barter", "Big Guns", "Energy Weapons", "Explosives", "Lockpick", "Medicine",
"Melee Weapons", "Repair", "Science", "Small Guns", "Sneak", "Speech", "Unarmed"]
# placing the label in the frame
desc = Label(root, text="", padx=30, wraplength=600, justify=LEFT)
desc.grid(row=2, column=1)
# creates a button for each skill
button_list = []
for i in range(12):
button_list.append(Button(root, text=skills_list[i], width=40,
height=2, command=lambda c=i: show_skill_desc(button_list[c].grid_info()['row']), padx=0, pady=0))
button_list[i].grid(row=i+1, column=0)
root.mainloop()
The purpose of the GUI is to display the description of each skill, when the button for a skill is clicked.
Does anyone know why the spacing change happens? Thank you!
I am new to python and very new to GUI tkinter. I am working on a project for work that is a simple task, but I wanted to try to make it work using a GUI. The task is to scan three barcodes, parse the last nine digits of the barcodes, and compare. If they all match, then display "pass"; if they don't display, "fail". I have written the code using the basic command line and I was able to get everything to work. Now I am trying to implement that code with a graphical user interface, which is where I am struggling.
The issue I am running into is knowing how or what to update/refresh. My logic works as long as I have a string entered in the ENTRY text box before the code is executed. If the strings are different, it fails; if they are the same or empty, it passes.
I wanted to be able to scan three serial numbers and have the bottom frame update pass or fail and then scan a different set of serial numbers and get another result. See my code below. Any help will be greatly appreciated. Thanks everyone!
from tkinter import *
# FUNCTION THAT ALLOWS THE CURSOR TO MOVE TO THE NEXT ENTRY TEXT BOX AUTOMATICALLY.
def go_to_next_entry(event, entry_list, this_index):
next_index = (this_index + 1) % len(entry_list)
entry_list[next_index].focus_set()
# FUNCTION THAT DETERMINES IF THE THREE SERIAL NUMBERS ARE THE SAME OR DIFFERENT.
# DISPLAYS PASS OR FAIL BASED ON RESULT OF CONDITION.
def get_results():
# DECLARING AND INITIALISING VARIABLES THAT ARE EQUAL TO INPUT FROM ENTRY TEXT BOXES.
scan_one = scan_one_entry.get()
scan_two = scan_two_entry.get()
scan_three = scan_three_entry.get()
# PARSING THE LAST NINE DIGITS OF THE ENTERED STRING.
parsed_scan_one = scan_one[-9:]
parsed_scan_two = scan_two[-9:]
parsed_scan_three = scan_three[-9:]
# IF-ELSE CONDITION THAT DISPLAYS PASS IF THREE SERIAL NUMBERS ARE THE SAME AND FAIL IF THEY
ARE NOT THE SAME.
if parsed_scan_one == parsed_scan_two and parsed_scan_two == parsed_scan_three and
parsed_scan_three == parsed_scan_one:
# DELETING DATA THAT IS STORED IN ENTRY TEXT BOXES.
scan_one_entry.delete(0, END)
scan_two_entry.delete(0, END)
scan_three_entry.delete(0, END)
# PLACING THE PASS BOTTOM FRAME IF CONDITION IS MET.
pass_bottom_frame.grid(row=1, sticky="ns, ew")
# CREATING PASS BOTTOM FRAME WIDGETS OF IF CONDITION IS MET.
pass_label = Label(pass_bottom_frame,
text='PASSED SCAN CHECK!',
font=('Helvetica', 100),
justify="center")
# PICKING BACKGROUND COLOR AND FONT COLOR OF LABEL WIDGET
pass_label.config(bg="#63d464", fg="#000000")
# PLACING THE PASS BOTTOM FRAME WIDGET IF CONDITION IS MET
pass_label.place(relx=.5, rely=.5, anchor="center")
else:
# DELETING DATA THAT IS STORED IN ENTRY TEXT BOXES.
scan_one_entry.delete(0, END)
scan_two_entry.delete(0, END)
scan_three_entry.delete(0, END)
# PLACING THE FAILED BOTTOM FRAME.
fail_bottom_frame.grid(row=1, sticky="ns, ew")
# CREATING PASS BOTTOM FRAME WIDGETS.
fail_label = Label(fail_bottom_frame,
text='FAILED SCAN CHECK!',
font=('Helvetica', 100),
justify="center")
# PICKING BACKGROUND COLOR AND FONT COLOR OF LABEL WIDGET.
fail_label.config(bg="#f51023", fg="#000000")
# PLACING THE FAILED BOTTOM FRAME WIDGET.
fail_label.place(relx=.5, rely=.5, anchor="center")
# CREATING MAIN WINDOW
main_window = Tk()
main_window.title('Serial Number Barcode Scan Check')
main_window.state("zoomed")
# CREATING THE MAIN FRAMES THAT WILL BE PLACED IN MAIN WINDOW
top_frame = Frame(main_window, width=1800, height=500)
pass_bottom_frame = Frame(main_window, bg="#63d464", width=1800, height=500)
fail_bottom_frame = Frame(main_window, bg="#f51023", width=1800, height=500)
# LAYOUT MAIN TOP FRAME
main_window.grid_rowconfigure(1, weight=1)
main_window .grid_columnconfigure(0, weight=1)
# PLACING TOP FRAME
top_frame.grid(row=0, sticky="ns, ew")
# CREATING TOP FRAME WIDGETS
# TOP THREE ARE LABELS AND THE LAST THREE ARE ENTRY BOXES
scan_one_label = Label(top_frame,
text='ENTER SCAN ONE: ',
font=('Helvetica', 40))
scan_two_label = Label(top_frame,
text='ENTER SCAN TWO: ',
font=('Helvetica', 40))
scan_three_label = Label(top_frame,
text='ENTER SCAN THREE: ',
font=('Helvetica', 40))
scan_one_entry = Entry(top_frame, font="Helvetica 20", justify="center", border=5)
scan_two_entry = Entry(top_frame, font="Helvetica 20", justify="center", border=5)
scan_three_entry = Entry(top_frame, font="Helvetica 20", justify="center", border=5)
# PLACING THE TOP FRAME WIDGETS INTO THE TOP FRAME
scan_one_label.grid(row=0, column=0, sticky='w')
scan_two_label.grid(row=1, column=0, sticky='w')
scan_three_label.grid(row=2, column=0, sticky='w')
scan_one_entry.grid(row=0, column=1, ipadx=100, ipady=8)
scan_two_entry.grid(row=1, column=1, ipadx=100, ipady=8)
scan_three_entry.grid(row=2, column=1, ipadx=100, ipady=8)
# CODE USED TO MOVE TO EACH ENTRY TEXT BOX AUTOMATICALLY
entries = [child for child in top_frame.winfo_children() if isinstance(child, Entry)]
for index, entry in enumerate(entries):
entry.bind('<Return>', lambda e, index=index: go_to_next_entry(e, entries, index))
# CALLING THE FUNCTION GET RESULTS
get_results()
# MAIN LOOP
main_window.mainloop()
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 3 years ago.
I wanted to create a game called "battleship". For this, I have to allow the user to place his ships.
I wanted to try this by allowing the user to color a field where the ship has to be placed. My problem is, that I have created 15 x 15 variables in a list by using 2 for loops. Is there actually a command possibility to for example get the row and the column of the grid, because this part, for example, will always give me the last i and the last j I have asked for. Otherwise, I would have to create 225 lines,
by mentioning the i and j when using lambda.
self.ship[i][j] = tk.Button(root, text="", padx=30, pady=20, command=lambda:color(i,j))
self.ship[i][j].grid(row=i, column=j)
I hope the following code gives you a good starting point to go ahead with your game.
I have used the same approach of using two for loops to create a 10x10 grid of buttons. Then i have used an click event callback to get the widget that is being clicked upon and then make the selected button blue or perform any operation on that widget.
import tkinter as tk
class Game:
def __init__(self, master):
self.master = master
self.master.resizable(0,0)
# row index
for row in range(10):
# col index
for col in range(10):
self.button = tk.Button(master, width=1, height=1)
self.button.grid(row=row, column=col, padx=8, pady=8, ipadx=10, ipady=4)
self.button.bind("<Button-1>", self.callback)
master.mainloop()
def callback(self, event):
# get the clicked button
clicked_btn = event.widget
btn_at_row_col = clicked_btn.config(bg='blue', state='disabled')
if __name__ == "__main__":
root = tk.Tk()
Game(root)
The output GUI:
This will render a 10x10 grid of buttons.
Then you can click on any button which makes it blue.
I am just in the middle of creating an entry form for a program, and it seems that I have got stuck with the logic on this one.
Basically I wanted to design a dropdwon-list, which adds words to an array and displays these words as little buttons beneath it. When you click the buttons they disappear again and remove themselfs from the array.
Simple enough, I thought. The adding worked fine so far. But removing not so much... There is a logic error with the button array and I can't seem to figure it out!
I extracted the code for reviewing,
any help is greatly appreciated!
Word adding Window
import tkinter as tk
from tkinter import ttk
def rel_add(*args):
rel_array.append(tkvar.get())
print(rel_array)
rel_buttons_update()
def del_button(i):
print(i)
del rel_array[i]
print(rel_array)
rel_buttons[i].grid_remove()
# del rel_buttons[i]
rel_buttons_update()
def rel_buttons_update():
for i, rel in enumerate(rel_array):
rel_buttons.append(tk.Button(rel_grid, text=rel, font="Helvetica 7", command=lambda c=i: del_button(c)))
rel_buttons[i].grid(column=i, row=0, sticky="nw")
rel_array = []
rel_buttons = []
win = tk.Tk()
tkvar = tk.StringVar(win) # Create a Tkinter variable
choices_words = ["oolbath", "pflanze", "haus", "wasser", "brimbambum"] # Create Variable List
tkvar.set('Related Words...') # set the default option
choices_words.sort() # Sort List
tk.Label(win, text="Related Words: ").grid(row=0,column=0, sticky="w")
rel = tk.OptionMenu(win, tkvar, *choices_words)
rel.grid(row=0,column=1, sticky="w")
# Callbuck Function for Dropdwon Menu
tkvar.trace("w", rel_add)
rel_grid = tk.Frame(win)
# Display the Buttons for the related Words
rel_grid.grid(row=1,column=1, sticky="w")
win.mainloop()
The main problem is that you keep recreating the same buttons over and over, so rel_buttons contains many more elements than you expect.
As a simple experiement, add a print statement to rel_buttons_update like this:
def rel_buttons_update():
for i, rel in enumerate(rel_array):
rel_buttons.append(ttk.Button(rel_grid, text=rel, command=lambda c=i: del_button(c)))
rel_buttons[i].grid(column=i, row=0, sticky="nw")
print('in update, rel_buttons is now', rel_buttons)
You'll notice that there is one button the first time you use the option menu, three buttons the second time, six buttons the third time, and so on.
You need to either only create new buttons, or delete all the old buttons before recreating them.
I'm trying to make a Tkinter widget that contains a number of tables, which are currently frames with entries filled using the .grid method, which can be switched between by pressing buttons. My current attempt at a solution uses the following code:
from tkinter import *
def dot(root, num):
root.subframe.destroy()
root.subframe = TFrame(root, num)
root = Tk()
vscrollbar = Scrollbar(root,orient='vertical')
vscrollbar.grid(row=1,column=2,sticky=N+E+W+S)
root.defaultframe = MainFrame(root)
root.canvas = Canvas(root, yscrollcommand=vscrollbar.set)
root.subframe = Frame(root.canvas)
vscrollbar.config(command=root.canvas.yview)
root.canvas.grid(row=1,column=0)
root.subframe.grid(row=0,column=0)
where MainFrame has the following structure:
class MainFrame(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.grid(row=0,column=0)
b1 = Button(self, text='table 1', command=lambda: dot(root, 0))
b2 = Button(self, text='table 2', command=lambda: dot(root, 1))
b1.grid(row=0, column=0, sticky=N+E+W+S)
b2.grid(row=0, column=1, sticky=N+E+W+S)
and TFrame:
class TFrame(Frame):
def __init__(self, foor, num):
Frame.__init__(self, root.canvas)
for i in range(12):
self.grid_columnconfigure(i, minsize=50)
for x in range(12):
for y in range(20):
label = Label(self, text=num)
label.grid(row=y,column=x,sticky=N+E+W+S)
root.canvas.create_window((0,0),window=self,anchor='nw')
root.canvas.configure(scrollregion=root.canvas.bbox('all'))
When I run the code, pressing the buttons loads the tables, which scroll in the vertical as expected. But only the first 8 columns or so are visible, no matter how the window is resized. Changing the width of the MainFrame by adding empty labels and the like does not affect the size of the TFrame created, even if it is several times wider than the 8 columns the TFrame ends up being. While I could have a somewhat tolerable solution by adding a horizontal scroll bar as well as the vertical, my experiences so far with scrolling in tkinter in general have been negative enough that I hope to avoid using it by any possible means.
Okay, found a solution. It turns out there weren't columns being cut off, the whole canvas was being cut off, and all my test cases just happened to have exactly the right number of columns vs column width that it looked like the columns after the first 8 were being cut off.
Changing:
root.canvas.grid(row=1,column=0)
to
root.canvas.grid(row=1,column=0,sticky=N+E+W+S)
fixed the problem.