Tkinter grouping buttons and lables - python

I want to create a GUI where you specify a bunch of files for later processing (commandline parsing a dozen files is just a PITA).
So now I want to group a lable and a button for better readability.
I've tried something like that:
DC1_path_label = Label(window_fine_DC, text = "Please specify the path of DC 1:", relief=RAISED)
DC1_B = Tkinter.Button(window_fine_DC, textvariable = DC_path1, command = setDC1)
DC2_path_label = Label(window_fine_DC, text="Please specify the path of DC 2:", relief=RAISED)
DC2_B = Tkinter.Button(window_fine_DC, textvariable=DC_path2, command=setDC2)
DC1_path_label.grid(row = 0, column = 1)
DC1_B.grid(row = 1, column = 1)
DC2_path_label.grid(row = 3, column = 1)
DC2_B.grid(row = 4, column = 1)
but it seems to have no effect, also paddings dont work that great either, since those go in all directions equally.
And while we are at it, is there a more elegant way to change a window than closing the old one, and opening a new?
Sorry if this is a stupid question, I'm fairly new with python/gui stuff...

I believe what you need is to provide a tuple to your padx and/or pady.
You can specify padding for each side this way.
I use pady as an example here to show how you can provide padding on only one side. Notice how the top Label is padded at its top but the bottom button is not padded on the bottom side.
from tkinter import *
root = Tk()
DC1_path_label = Label(root, text = "Please specify the path of DC 1:", relief=RAISED)
DC1_B = Button(root)
DC2_path_label = Label(root, text="Please specify the path of DC 2:", relief=RAISED)
DC2_B = Button(root)
DC1_path_label.grid(row = 0, column = 1, pady = (10, 0))
DC1_B.grid(row = 1, column = 1, pady = (10, 0))
DC2_path_label.grid(row = 3, column = 1, pady = (10, 0))
DC2_B.grid(row = 4, column = 1, pady = (10, 0))
root.mainloop()
Result:
Update:
As far as changing your window there are several options for manipulating what is there and replacing the content.
You could destroy() the widgets and make new ones. Though this does not strike me as more elegant. It will allow you to keep the window open though.
There are some config options you can use to change the text of current widgets or rearrange the grid through a series of configs. Though this does not strike me as elegant either.

Related

Python how to use .pack for an other monitor resolution?

Hi i can't solve this problem, i created a program in a 1920x1080 resolution monitor and when i run it on purpose on an other pc with 1366x768 resolution the position of the Labels is different, i picked the height that should be 768 in one case and 1080 in the other case with:
larghezza = window.winfo_screenheight()
and used in the pack placements:
bottone_reset.pack(side=BOTTOM,anchor=S,pady=(0,altezza*0.1))
bottone_premi.pack(side=BOTTOM,anchor=S,pady=(0,altezza*0.2))
etichetta_click.pack(side=BOTTOM,anchor=S,pady=(0,altezza*0.1))
etichetta_titolo.pack(side=BOTTOM,anchor=S,pady=(0,altezza*0.1))
Because in this way the formula changes according to the current monitor resolution but the Labels are still in a different position, how to handle this ? Thanks
So, the problem being that the location of the buttons and label changes based on window size. You're trying to modify the positioning manually based on the window size. While ingenious, it's obviously not working as you intend. There are, however, different options other than pack(). These are grid() and place().
(Links go to "geeksforgeeks" website, which is what I'm currently using as reference.)
'grid()' places elements in a grid using columns and rows, and is best used for elements that have the same dimensions (at least in the direction you're placing them, so if you want them all aligned on the X direction, they should have similar length to not look weird).
bottone_reset.grid(column = 0, row = 0, padx = 5, pady = 5)
bottone_premi.grid(column = 1, row = 0, padx = 5, pady = 5)
etichetta_click.grid(column = 2, row = 0, padx = 5, pady = 5)
etichetta_titolo.grid(column = 3, row = 0, padx = 5, pady = 5)
'place()' lets you determine the explicit location of something, which can be useful but in case of your choice of element placement would likely not help, since if the window shortens, place will not change location.
I would suggest creating a frame for the elements listed, then depending on what is most useful to you, use pack() or grid() to place the elements within that frame. In turn, place that frame where you want the buttons to end up.
frame.pack(side=BOTTOM, anchor = S)
bottone_reset = Button(frame,text = "reset")
bottone_premi = Button(frame, text = "premi")
etichetta_click = Label(frame, text = "click")
etichetta_titolo = Label(frame, text = "Title")
Ofc, your definition of the buttons and labels will look different, these are just placeholders.
The following code will always place the buttons in the bottom center of the window, regardless of what size that screen is (unless the total elements no longer fit). It's closest to what I could see you were trying to attempt. of course, you'll have to replace my manual input of window width and height with your screen size detection. I could not immediately get that to work with just tkinter, so I decided to change values manually.
from tkinter import *
window = Tk()
window.title("Pack Problem")
windowWidth = 1000
windowHeight = 768
windowSize = str(windowWidth) + "x" + str(windowHeight)
window.geometry(windowSize)
frame = Frame()
frame.pack(side=BOTTOM, anchor = S)
bottone_reset = Button(frame,text = "reset")
bottone_premi = Button(frame, text = "premi")
etichetta_click = Label(frame, text = "click")
etichetta_titolo = Label(frame, text = "Title")
bottone_reset.grid(column = 0, row = 0, padx = 5, pady = 5)
bottone_premi.grid(column = 1, row = 0, padx = 5, pady = 5)
etichetta_click.grid(column = 2, row = 0, padx = 5, pady = 5)
etichetta_titolo.grid(column = 3, row = 0, padx = 5, pady = 5)
window.mainloop()
The layout and makeup are obviously just placeholders, but this should function as a framework for what you want to do.
EDIT: With a better understanding of the problem, different approach. Leaving the original comment for those that could use it.
In order to change padding size based upon resolution, the fastest way I can think of is to bind a resize function to window configuration.
window.bind('<Configure>', function)
Sadly, attempts to make it a smooth adjustment have not been successful so far. I'll leave that up to you to attempt if you wish.
The solution I've gotten to work is this:
def padsize(e):
if e.height <= 1080 and e.height > 768:
bottone_reset.grid(column=0, row=0, padx=25, pady=25)
bottone_premi.grid(column=1, row=0, padx=25, pady=25)
etichetta_click.grid(column=2, row=0, padx=25, pady=25)
etichetta_titolo.grid(column=3, row=0, padx=25, pady=25)
#print("window height is larger than 768")
elif e.height <768 and e.height > 640:
bottone_reset.grid(column=0, row=0, padx=15, pady=15)
bottone_premi.grid(column=1, row=0, padx=15, pady=15)
etichetta_click.grid(column=2, row=0, padx=15, pady=15)
etichetta_titolo.grid(column=3, row=0, padx=15, pady=15)
#print("window height is larger than 768")
elif e.height <640 and e.height > 250:
bottone_reset.grid(column=0, row=0, padx=5, pady=5)
bottone_premi.grid(column=1, row=0, padx=5, pady=5)
etichetta_click.grid(column=2, row=0, padx=5, pady=5)
etichetta_titolo.grid(column=3, row=0, padx=5, pady=5)
#print("window height is smaller than 768")
window.bind('<Configure>', padsize)
As you can see, I made the pad size differences really drastic, in order to be able to see the change rather than needing a ruler to check. It should be far less noticeable at smaller size differences.

Python Tkinter Number of Button changes based on selected RadioButton

EDIT: I tried to edit it based on one suggestion that it needs to be tied to a function. But still not working properly. Thank you very much!
Hope you can help me. Saw related questions but not exactly the same. Thank you very much!
I have a radiobutton. Based on the selected value, the number of buttons will change. However, the number of buttons dont change. The number just based on the default value of the radiobutton (i tried changing it).
import tkinter
from tkinter import *
root = Tk()
TIME_SUBFRAME = root
#dont mind much this part, this is just the popup window that will show after I click the buttons dependent on the radiobutton
def month():
CAL_WINDOW = Toplevel(TIME_SUBFRAME) #other parts removed
#this is the part that I would want to be dependent on the radiobutton
ONLY_MONTH = Button(TIME_SUBFRAME, text='Month', command=month)
START_MONTH = Button(TIME_SUBFRAME, text='Start Month', command=month)
END_MONTH = Button(TIME_SUBFRAME, text='End Month', command=month)
def sing_month():
START_MONTH.destroy()
END_MONTH.destroy()
ONLY_MONTH.grid(row = 3, column = 2, columnspan = 2)
def mult_month():
ONLY_MONTH.destroy()
START_MONTH.grid(row = 3, column = 2)
END_MONTH.grid(row = 3, column = 3)
#Radiobuttons for timepoint selection type
YRTYPE = IntVar(TIME_SUBFRAME, 1)
SING_MONTH = Radiobutton(TIME_SUBFRAME, text = "Single Month", command = sing_month, variable = YRTYPE, value = 1)
MULT_MONTH = Radiobutton(TIME_SUBFRAME, text = "Multiple Months", command = mult_month, variable = YRTYPE, value = 2)
SING_MONTH.grid(row = 2, column = 2, padx = 10, pady = 10)
MULT_MONTH.grid(row = 2, column = 3, padx = 10, pady = 10)
root.mainloop()
The radiobutton numbers needs to checked their value when a function is called.You have declared checking statement in the current running block, which will return initial value of your radiobuttons.
The values needs to be checked when user checks the radiobutton.So, we need an extra button widget commanded with a function that checks the value of both radiobuttons and performs the related actions.
The radiobutton will not check the values automatically, it needs to be checked by function.
Here's your Solution,
import tkinter
from tkinter import *
root = Tk()
TIME_SUBFRAME = root
#dont mind much this part, this is just the popup window that will show after I click the buttons dependent on the radiobutton
def month():
CAL_WINDOW = Toplevel(TIME_SUBFRAME) #other parts removed
#this is the part that I would want to be dependent on the radiobutton
ONLY_MONTH = Button(TIME_SUBFRAME, text='Month', command=month)
START_MONTH = Button(TIME_SUBFRAME, text='Start Month', command=month)
END_MONTH = Button(TIME_SUBFRAME, text='End Month', command=month)
def sing_month():
START_MONTH.grid_remove()
END_MONTH.grid_remove()
ONLY_MONTH.grid(row = 3, column = 2, columnspan = 2)
def mult_month():
ONLY_MONTH.grid_remove()
START_MONTH.grid(row = 3, column = 2)
END_MONTH.grid(row = 3, column = 3)
#Radiobuttons for timepoint selection type
YRTYPE = IntVar(TIME_SUBFRAME, 1)
SING_MONTH = Radiobutton(TIME_SUBFRAME, text = "Single Month", command = sing_month, variable = YRTYPE, value = 1)
MULT_MONTH = Radiobutton(TIME_SUBFRAME, text = "Multiple Months", command = mult_month, variable = YRTYPE, value = 2)
SING_MONTH.grid(row = 2, column = 2, padx = 10, pady = 10)
MULT_MONTH.grid(row = 2, column = 3, padx = 10, pady = 10)
root.mainloop()

How can I insert a selected file path from a browse button in a GUI?

I am new with Python GUI creation and I am trying to get the file path of the .csv file from a directory and print it on a text box in a GUI. I am using tkinter library for the GUI and I can't seem to make it work. Is there anyone who can help me with this problem?
import tkinter as tk
from tkinter.filedialog import askopenfilename
def browseFile1():
global infile1
infile1=askopenfilename()
txt1.insert(0.0, infile1)
root = tk.Tk()
root.title("CSV Comparison Tool")
Label = tk.Label(root, text="Select CSV files to compare").grid(row = 1, column = 0, columnspan = 30)
browseButton1 = tk.Button(root,text="Browse", command=browseFile1).grid(row = 2, column = 30)
txt1 = tk.Text(root, width = 100, height = 1).grid(row = 2, column = 0, columnspan = 30)
root.mainloop()
The error says:
AttributeError: 'NoneType' object has no attribute 'insert'
I tried 1 button first and applying it on the next one it it works. I am using spyder as a tool.
Thanks!
Your problem is these lines:
Label = tk.Label(root, text="Select CSV files to compare").grid(row = 1, column = 0, columnspan = 30)
browseButton1 = tk.Button(root,text="Browse", command=browseFile1).grid(row = 2, column = 30)
txt1 = tk.Text(root, width = 100, height = 1).grid(row = 2, column = 0, columnspan = 30)
The grid method on a widget—like most methods that mutate objects in Python—returns None. So you're just storing None in Label, and browseButton1, and txt1. So when you later try this:
txt1.insert(0.0, infile1)
That's trying to call None.insert, which obviously doesn't work. Tkinter catches the error, prints it out to the terminal, and keeps going as if your function had never been called.
The solution is to just not do that. Instead, do this:
Label = tk.Label(root, text="Select CSV files to compare")
Label.grid(row = 1, column = 0, columnspan = 30)
browseButton1 = tk.Button(root,text="Browse", command=browseFile1)
browseButton1.grid(row = 2, column = 30)
txt1 = tk.Text(root, width = 100, height = 1)
txt1.grid(row = 2, column = 0, columnspan = 30)
Now, not only does your code work, it even fits in a typical editor window or Stack Overflow page.

Tkinter issue with using wrap length on a Label widget organised within a grid

So I have 2 rows dedicated to a messaged label widget to display any successful/unsuccessful messages to the user while they're using the tkinter GUI.
Some of the messages are to long to fit within the column width, so I have used the wraplength feature for the Label widget to wrap the text to the next line.
The issue that I'm having, is that because of this feature, it shifts the placements of the widgets underneath this widget, by 1 row for every new row it wraps the text onto.
So I was wondering if there's any way to have the text wrap, without moving the lower widgets.
How the message Label looks within the GUI with height = 1:
How the message Label looks when it wrap's the text to a newline with height = 1:
How the message Label looks within the GUI with height = 2:
How the message Label looks when it wrap's the text to a newline with height = 1:
I would like for the message Label in the 2nd image link to display the way it does, but keeping the vertical layout of the widgets as seen in the 1st image link.
The following code is for defining the widgets:
Choice_list = Listbox(window, selectmode=SINGLE, width = 17, height = 9,
justify = CENTER)
image = PhotoImage(file = 'Dummy_Logo.gif')
Image_label = Label(window, image = image)
extract = Button(window, text = "Archive News from Webpage",
command = func1, width = 20)
archive = Button(window, text = "Extract News from Archive",
command = func2, width = 22)
display = Button(window, text = "Display the News",
command = func3, width = 14)
Message_Widget = Label(window, text = '', fg = 'black', justify = CENTER,
height = 2, wraplength = 300)
Log_Event = Checkbutton(window, text = 'Log Event', variable = logState,
command = logEvent)
The following code is the grid alignment for the widgets:
Image_label.grid(row = 1, column = 1)
Choice_list.grid(row = 1, column = 2, rowspan = 9, sticky = W)
Message_Widget.grid(row = 2, column = 1, rowspan = 2)
Log_Event.grid(row = 12, column = 2)
archive.grid(row = 13, column = 1, rowspan = 2, sticky = W)
extract.grid(row = 13, column = 1, rowspan = 2, sticky = E)
display.grid(row = 13, column = 2, rowspan = 2, sticky = W)
Give the label a height of 2 or 3. The problem is simply that it wants to be one character tall by default. Tkinter will allocate only one character worth of height when laying out all the widgets. When the text wraps, the label simply must become taller so that the text will fit.
By giving it a height of 2 or 3 to start out, that extra space is built-in to the GUI. When the text wraps, the label doesn't have to grow to accommodate the new text.
This might not be a proper method of solving this problem, however I have managed to get the outcome I was looking for by only adjusting the rowspan parameter in the Message_Widget to rowspan = 11.
Message_Widget.grid(row = 2, column = 1, rowspan = 11)
This produced the following results:
With a short text:
https://i.stack.imgur.com/XpGLq.png
With a long text:
https://i.stack.imgur.com/iXwlR.png
Have you considered using a text widget for the readout?
For example this below code will wrap the text if it is to long and it will not resize the widgets. This is just an example but should be enough to help you with you problem.
from tkinter import *
import random
root = Tk()
text1 = Text(root, height=1, width=40, wrap="word")
text1.grid(row=0, column=0, sticky="ew")
def update_lbl1():
x = ["Click and drag to see some much longer random text for updating label!!!","some short data"]
text1.delete("1.0", "end")
text1.insert("1.0", random.choice(x))
Button(root, text="Change label from list", command = update_lbl1).grid(row=1, column=0)
root.mainloop()

Positioning widgets on frames with Tkinter

So, I'm fairly new to Python, and Tkinter.
I'm in the process of creating a Battleship game. So far I've succesfully created a grid of buttons. I am now trying to create a sort of a menu to the right of this game pad. I'm trying to do this by having two different Frames in my window - one where my game pad is and one where I have the menu (which is below a Label).
Whatever I put inside cheat_button.grid() I can't seem to change the position of this button. It just stays in the top-left corner of the menu_frame Frame. I also want to add anothern button beneath this one, as well as a message box a bit further down, on the same frame. Any suggestions? I have linked the part of the code I find relevant.
Thanks.
col_tot = 10
row_tot = 10
self.grid_frame = tk.Frame(self)
self.grid_frame.grid(row=0, column=0, rowspan=row_tot, columnspan=col_tot, sticky = "WENS")
self.menu_frame = tk.Frame(self, bg="grey", bd=1, relief="ridge")
self.menu_frame.grid(row=1, column=col_tot, rowspan=row_tot-1, columnspan=4, sticky = "WENS")
button_no = 0
self.button_list = []
for x in range(row_tot):
self.button_list.append([""] * col_tot)
for i in range(row_tot):
for j in range(col_tot):
self.button_list[i][j] = (tk.Button(self.grid_frame, height = 3, width = 6, text = str(button_no + 1),
activebackground = "yellow", relief = "groove"))
self.button_list[i][j]["command"] = lambda i=i, j=j:self.update_colour(i, j)
self.button_list[i][j].grid(row = i, column = j, sticky = "NW")
button_no += 1
self.welcome = tk.Label(self, text = "Welcome to Battleship!",
fg = "red", bg = "grey", font = ("Helvetica", 12), relief="ridge")
self.welcome.grid(row = 0, column = col_tot, columnspan = 4, sticky = "WENS")
self.cheat_button = tk.Button(self.menu_frame, text = "Cheat")
self.cheat_button.grid()
self.cheat_button.config(bg = "grey")
Rows and columns have a size of zero if they are empty and haven't configured to have a minimum size. It's also important to know that each frame has it's own "grid", so even though the frame on the left has 10 rows and ten columns, the frame on the right starts out completely empty.
If you want the button centered in the frame, one solution is to leave an empty row above and below it, and configure those rows to be given any extra space. For example:
self.welcome.grid(row = 0, column = 0, sticky = "WENS")
# notice we've put nothing in rows 1 or 3
self.cheat_button.grid(row=2, column=0)
self.menu_frame.grid_rowconfigure(1, weight=1)
self.menu_frame.grid_rowconfigure(3, weight=1)

Categories