How can I make a self-generating button? - python-3.x

I'm attempting to create a function that will create a button named after a variable every time the submit to database function is used (1 button for every dataset submitted to my database). Currently I can get the button to name itself, but I'm having trouble offsetting the line for each time it's used. I could just add a simple counter and set row=n, but at this point I'm not sure my approach to the problem is the best. For every dataset there needs to be a button generated for it. Would it be best to approach this by trying to run a program that makes buttons based on my dataset (not exactly sure how I'd do this) or by having my submit return a button with row=counter? Or is there another solution I haven't thought of?
**Note: Looking back on this my buttons don't stay after the program restarts which makes sense. If I'm going to use the submit/counter solution I'll also have to make the data store itself in the code.
**Minimal reproducible code:
from tkinter import *
root = Tk()
root.title('Button')
root.geometry('400x400')
f_name = Entry(root, width=30)
f_name.grid(row=0, column=1)
f_name_lbl = Label(root, text="First Name:")
f_name_lbl.grid(row=0, column=0)
def gen_button():
auto_button = Button(root, text=f_name.get())
auto_button.grid(row=2, column=0, columnspan=2)
submit_btn = Button(root, text="Submit:", command=gen_button).grid(row=1, column=0, columnspan=2, ipadx=100)
I believe I did that right, I apologize I'm new to python and stack overflow

If you do not specify a row, grid will automatically pick the next row after the last occupied row.
auto_button.grid(column=0, columnspan=2)
Though, if you create a separate frame for these buttons you can use pack which is a bit easier since pack aligns widgets along a side. In your case you want them aligned to the top of the frame, just below any other widgets.
def gen_button():
auto_button = Button(button_frame, text=f_name.get())
auto_button.pack(side="top")
submit_btn = Button(root, text="Submit:", command=gen_button)
submit_btn.grid(row=1, column=0, columnspan=2, ipadx=100)
button_frame = Frame(root)
button_frame.grid(row=2, column=0, columnspan=2, sticky="nsew")

Related

Why is Python Tkinter grid method separating the buttons

I am trying to make a game using python 3.8, where I have 1 canvas/frame and 2 buttons. I want the buttons to be right next to each other but whenever I run the code, there is always white space in between the 2 buttons. I tried using pack() but when you click on the buttons, it makes the buttons go above the canvas/frame instead of to the right of it.
My code:
from tkinter import *
# windows, canvas, and frames
root = Tk()
WatchRun = Canvas(root, bg="green", width=600, height=500)
WatchRun.grid(row=0, column=0, rowspan=25)
Upgrade = Frame(root, bg="yellow", width=600, height=500)
Upgrade.grid_forget()
# button functions
def show_upgrade(widget, widget2):
global upgradeBtn
global WatchRunBtn
widget.grid_forget()
widget2.grid(row=0, column=0, rowspan=25)
def show_watchrun(widget, widget2):
global upgradeBtn
global WatchRunBtn
widget.grid_forget()
widget2.grid(row=0, column=0, rowspan=25)
# variables and buttons
distance = 0
started = 0
money = 0
startImage = PhotoImage(file='start.png')
stopImage = PhotoImage(file='stop.png')
upgradeBtn = Button(root, text="Upgrades", width=9, command=lambda: show_upgrade(WatchRun, Upgrade))
upgradeBtn.grid(row=0, column=1)
WatchRunBtn = Button(root, text="Watch run", width=9, command=lambda: show_watchrun(Upgrade, WatchRun))
WatchRunBtn.grid(row=1, column=1)
#loop
root.mainloop()
It appears you're trying to use rowspan to try to force the widgets apart. That's not a good solution.
While there are multiple ways to solve the problem of the buttons not being together, the one I recommend in this specific case is to treat your GUI as if it had three rows (or maybe four, depending on what you want to happen when you resize the window).
Row 0 holds the first button, row 1 holds the second, and row 2 takes up the rest of the GUI. Then, the canvas can span all three rows.
So, start by adding a non-zero weight to the third row so that all unallocated space goes to it.
root.grid_rowconfigure(2, weight=1)
Next, add your buttons to rows 0 and 1:
upgradeBtn.grid(row=0, column=1)
WatchRunBtn.grid(row=1, column=1)
And finally, have your canvas span all three rows. It will force the window to grow larger, with all extra space being given to the third row. This allows the first two rows to retain their natural small height.
WatchRun.grid(row=0, column=0, rowspan=3)
An arguably better solution involves using pack and an extra frame or two. The UI seems to clearly have two logical sections to it: a canvas on the left and a column of buttons on the right. So, you can create one frame for the left and one for the right. Then, put the buttons in the right frame and the canvas in the left frame.
See my comments that indicates the changes in the code. If you have questions left on it, let me know. You may find this Q&A helpfull as well. PEP 8
import tkinter as tk #no wildcard imports
#free functions under imports to ensure function is defined
def show_frame(widget, widget2):
'''common function for upgrade_btn and watchrun_btn
- packs the appropiated widget in the left_frame'''
widget.pack_forget()
widget2.pack()
distance = 0
started = 0
money = 0
#variable names lowercase
root = tk.Tk()
#split window in two containers/master/frames
left_frame = tk.Frame(root)
right_frame= tk.Frame(root)
#leftframe content
watchrun = tk.Canvas(left_frame, bg="green", width=600, height=500)
upgrade = tk.Frame(left_frame, bg="yellow", width=600, height=500)
#rightframe content
upgrade_btn = tk.Button(right_frame, text="Upgrades", width=9, command=lambda: show_frame(watchrun, upgrade))
watchrun_btn = tk.Button(right_frame, text="Watch run", width=9, command=lambda: show_frame(upgrade, watchrun))
#geometry management
left_frame.pack(side=tk.LEFT)
right_frame.pack(side=tk.RIGHT,fill=tk.Y)
watchrun.pack()
upgrade_btn.pack()
watchrun_btn.pack()
root.mainloop()

"Sticky" paramter doesnt change the window at all

I am just building my first tkinter window and I am a little puzzled. I created a label and a Button and set them in them same row but different columns.
Now the placement of the button changes depending on the length of the text in the label. To fix this I wanted to "stick" the label and the button to east/west respectively and add a small amount of padding.
Now the issue, no matter what variant of "sticky" I add it doesn't affect the placement of anything at all.
Below are the two code variants that all lead to the same output window:
from tkinter import *
def Btt_ShowAll_clicked():
print("klicked")
#create the window
Main_Window = Tk()
#Modify the Window
Main_Window.title("Ressourcen Verwaltung")
Lbl_Descr_a = Label(Main_Window, text = "Einträge einsehen")#Create Label
Lbl_Descr_a.grid(column=0, row=0, padx=10) #Show Label
Btt_ShowAll_a = Button(Main_Window, text="Einträge anzeigen")
Btt_ShowAll_a.bind("<Button-1>",Btt_ShowAll_clicked)#Button click starts function
Btt_ShowAll_a.grid(column=1, row=0, padx=10, pady=10, sticky=W)
#In the line above Change "W" to "E" ord delet the "sticky = W" alltogether and nothing changes in the window
Main_Window.geometry('350x200') #Window size
#Make the windows stay (loop)
Main_Window.mainloop()
What can I do to get the desired output?
Shouldn't "sticky" stick it to the given side and then with padx I should be able to choose how close it is said side?
As far as I know, you have to type sticky='w' instead of sticky=W, should work then.

Can you fit multiple buttons in one grid cell in tkinter?

I'm making a table, and the grid of the table is going to be filled with buttons, Is it possible to fit more than one button in a grid space?
Yes you can. Put a frame inside the cell, and then you can put whatever you want inside the frame. Inside the frame you can use pack, place or grid since it is independent from the rest of the widgets.
For example:
import Tkinter as tk
root = tk.Tk()
l1 = tk.Label(root, text="hello")
l2 = tk.Label(root, text="world")
f1 = tk.Frame(root)
b1 = tk.Button(f1, text="One button")
b2 = tk.Button(f1, text="Another button")
l1.grid(row=0, column=0)
l2.grid(row=0, column=1)
f1.grid(row=1, column=1, sticky="nsew")
b1.pack(side="top")
b2.pack(side="top")
root.mainloop()
#jasonharper already provided the answer, but here's some code to go along with it.
This is just a random example with a bunch of buttons / frames using grid / pack. The pack for the buttons was arbitrary you could have used grid instead Each grid section has a random padx to show that it's in a different column and each different column within the grid contains multiple buttons
import tkinter as tk
root = tk.Tk()
#Now you want another frame
for i in range(5):
gridframe = tk.Frame(root)
for j in range(3):
tk.Button(gridframe, text="%d%d" % (i, j)).pack(side=tk.LEFT)
gridframe.grid(row=0, column=i, padx=20)
root.mainloop()

Raise Frame on top in PanedWindow in tkinter python 3.5

I have a frame in PanedWindow which i need on every tkinter GUI (in this case it's topFrame). Below it are many frames and I want to switch between those frame on button click (just like in any software where the top portion of screen is fixed and clicking on buttons the lower portion of GUI changes).
I know i need grid layout for it. But, it is not happening and i am not getting a solution anywhere.I have researched a lot on this topic everywhere but this solution is nowhere. Here is my code... i have written in comments those code which i feel are not working fine.
#python 3.5
from tkinter import *
#function to raise the frame on button click
def raiseFrame(frame):
frame.tkraise()
m = PanedWindow(height=500, width=1000, orient=VERTICAL)
m.pack(fill=BOTH, expand=1)
#to expand the column and row to fill the extra space
m.grid_columnconfigure(index=0, weight=1) #this code is not working as it should
m.grid_rowconfigure(index=0, weight=1) #this code is not working as it should
#top frame has two buttons which switches the bottom frames
topFrame = Frame(m, bg="blue")
m.add(topFrame)
button1 = Button(topFrame, text="Raise Frame 2", command=lambda: raiseFrame(frame2)) #raises frame 2 on clicking it
button1.pack(side=LEFT)
button2 = Button(topFrame, text="Raise Frame 1", command=lambda: raiseFrame(frame1)) #raises frame 1 on clicking it
button2.pack(side=LEFT)
#bottomframe acts as container for two other frames which i need to switch
bottomframe = Frame(m, bg="orange")
m.add(bottomframe)
frame1 = Frame(bottomframe, bg="yellow")
frame1.grid(row=0, column=0, sticky="news") ## sticky is not working ##
frame2 = Frame(bottomframe, bg="green")
frame2.grid(row=0, column=0, sticky="news") ## sticky is not working ##
label1 = Label(frame1, text="i should change")
label1.pack(padx=10, pady=10)
label2 = Label(frame2, text="i am changed !!")
label2.pack(padx=10, pady=10)
mainloop()
1)Please correct my code.
2)Explain me why in the "topFrame" even though i have not written
topFrame.pack(fill=BOTH, expand=True)
my code is showing the above property and it's expanding as well as filling both X and Y.
Same goes for the bottomFrame, it's orange colour is filling the entire space which does not happen in normal frames. So, is it some special feature of PanedWindow ?
You don't want to call topFrame.pack() and m.add(topFrame). You either pack/place/grid the window, or you add it to a paned window, you don't do both.
Also, if the frame is going to be in the paned window it needs to be a child of the paned window rather than a child of the root window.

Python tkinter Copy Widget to New Master

I wrote a collapsible frame widget and was hoping to give it a dock/undock property. From what I've read, widgets can't be placed "over" other widgets (except on canvases, which I wish to avoid) so I can't just "lift" the frame, and their master cannot be changed so I can't simply place the frame into a new Toplevel. The only other option I can think of is to copy the widget into the new Toplevel. Unfortunately, I don't see any options on the copy or deepcopy operations to change the Master before the new widget is created.
So, the question:
Are these assumptions accurate, or is there a way to do any of these things?
If not, do I have any other options than the solution I put together here:
def copywidget(self, frame1, frame2):
for child in frame1.winfo_children():
newwidget = getattr(tkinter,child.winfo_class())(frame2)
for key in child.keys(): newwidget[key] = child.cget(key)
if child.winfo_manager() == 'pack':
newwidget.pack()
for key in child.pack_info():
newwidget.pack_info()[key] = child.pack_info()[key]
elif child.winfo_manager() == 'grid':
newwidget.grid()
for key in child.grid_info():
newwidget.grid_info()[key] = child.grid_info()[key]
elif child.winfo_manager() == 'place':
newwidget.place()
for key in child.place_info():
newwidget.place_info()[key] = child.place_info()[key]
There is no way to reparent a widget to a different toplevel. The easiest thing is to make a method that recreates the widgets in a new parent.
Widgets can be stacked on top of each other, though it requires care. You can, for instance, use grid to place two widgets in the same cell, and you can use place to put one widget on top of another .
Using this answer you are able to copy one widget onto another master. Then, you simply forget the previous widget, and it will work as a moved widget.
Example code:
root = tk.Tk()
frame1 = tk.Frame(root, bg='blue', width=200, height=100)
frame1.grid(row=0, column=0, pady=(0, 5))
frame_to_clone = tk.Frame(frame1)
frame_to_clone.place(x=10, y= 15)
tk.Label(frame_to_clone, text='test text').grid(row=0, column=0)
frame2 = tk.Frame(root, bg='green', width=80, height=180)
frame2.grid(row=1, column=0, pady=(0, 5))
# Move frame_to_clone from frame1 to frame2
cloned_frame = clone_widget(frame_to_clone, frame2)
cloned_frame.place(x=10, y=15)
# frame_to_clone.destroy() # Optional destroy or forget of the original widget
root.mainloop()
Gives:

Resources