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

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.

Related

how to draw rectangle over Tkinter scale?

I have the following GUI as shown in the Figure below. Instead of the black box, I wanted to draw a transparent rectangle on top of all the scales covering values 1, 0, and -1 of the scale.
Is there a way to make the Tkinter canvas transparent? If it is not the correct way to do it, what are the alternatives that I could try? I shared the sample code I use. That can be used to reproduce this GUI.
from tkinter import *
import itertools
root = Tk()
root.geometry('840x420')
root.title('Test Window')
variables = {"var1", "var2", "var3", "var4"}
pair_list = list(itertools.combinations(list(variables), 2))
pair_result_dictionary = dict.fromkeys(pair_list)
my_canvas = Canvas()
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_scrollbar = Scrollbar(root, orient=tk.VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox("all")))
second_frame = Frame(my_canvas)
my_canvas.create_window((0, 0), window=second_frame, anchor="nw")
i = 0
heading_label = Label(second_frame, text="Test Window", font=('Arial',16))
heading_label.grid(column=0, row=0, sticky=tk.NW, columnspan=2, padx=(52, 0), pady=(20, 10))
for pair in pair_list:
sample_scale = tk.Scale(second_frame, from_=9, to=-9, length=600, orient=tk.HORIZONTAL, font=('Arial', 15),
tickinterval=1,resolution=1)
label_left = tk.Label(second_frame, text=pair[0], font=('Arial', 15))
label_left.grid(column=0, row=2 + i, sticky=tk.W, padx=(52, 0), pady=5)
sample_scale.set(((sample_scale['from'] - sample_scale['to']) / 2) + sample_scale['to'])
sample_scale.grid(column=1, row=2 + i, sticky=tk.W, padx=(5, 0), pady=5)
label_right = tk.Label(second_frame, text=pair[1], font=('Arial', 15))
label_right.grid(column=2, row=2 + i, sticky=tk.W, padx=(5, 5), pady=5)
i = i + 100
rectangle_holder_canvas = tk.Canvas(second_frame, width=100, height=70, bd=0, background='#000000')
rectangle_holder_canvas.grid(column=1, row=2, sticky=tk.S, padx=(0, 0), pady=0)
rec = rectangle_holder_canvas.create_rectangle(3, 3, 100, 70, outline='blue', fill='')
rectangle_holder_canvas.tag_raise(rec, 'all')
root.mainloop()
If I use the following code lines, it makes the entire square transparent and will see what's underneath the main window which is not what I want sadly.
root.wm_attributes("-transparentcolor", 'grey')
rectangle_holder_canvas = tk.Canvas(second_frame, width=100, height=70, bd=0, bg="grey")
Appreciate your thoughts and time on how to achieve this.
I read that, "tag_raise" method does not affect canvas window items. To change a window item's stacking order, use a lower or lift method on the window.. So I tried to draw a rectangle on my_canvas by setting second_frame.lower() as shown below code. Then I only see the rectangle but not the scales or labels on the second frame.
my_canvas.create_window((0, 0), window=second_frame.lower(), anchor=CENTER)
rec = my_canvas.create_rectangle(50, 50, 200, 200, outline='blue', fill='blue')
Your question has no easy answer.
The organisation of canvas objects is divided in two groups.
Ordinary graphical objects like rectangle, polygon etc are stacked in the order
of their creation, with the first object in the background and the last object
in the foreground. The stacking order of such objects can be changed via
canvas.lift, canvas.lower, canvas.tag_raise and canvas.tag_lower.
The second group are the canvas window objects, these are created using a
fixed stacking order that ALLWAYS sit above graphical objects.
Therefore it is not possible to place ordinary graphical objects above window
objects.
This would seem to make your objective impossible, however there is (at least)
one solution.
The following code achieves your objective by using a Toplevel window that is
positioned above the canvas. This window uses wm_attributes("-transparentcolor", color)
and overrideredirect(1), it also sets wm_attributes("-topmost", True).
The main window and top window are then bound together via the "Configure"
binding so that moving main window will automatically move top window.
This creates the effect you are looking for, although it may not suit your needs.
I've used one of your Scale objects placed in a canvas window object to simulate
a fragment of your code.
import tkinter as tk
color = "red"
class transParent(tk.Tk):
def __init__(self):
super().__init__()
self.withdraw()
self.columnconfigure(0, weight = 1)
wide, high = 606, 106
self.geometry(f"{wide}x{high}+20+20")
self.canvas = tk.Canvas(
self, background = color, highlightthickness = 0, borderwidth = 0)
self.canvas.grid(sticky = tk.NSEW)
self.s_scale = tk.Scale(
self.canvas, from_ = 9, to = -9, length = 600,
orient = tk.HORIZONTAL, font = "Arial 15",
takefocus = 1, tickinterval = 1, resolution = 1)
self.s_scale.set(0)
self.s_scale.grid(
column = 0, row = 0, sticky = tk.W, padx = 5, pady = 5)
self.sample_window = self.canvas.create_window(
0, 0, window = self.s_scale, anchor = tk.NW)
self.deiconify()
self.wait_visibility()
# build transparency toplevel : width, height, borderwidth
w, h, bw = 100, 100, 4
self.top = tk.Toplevel(
self, padx = 0, pady = 0, background = color,
highlightthickness = 0, relief = "solid", borderwidth = bw)
self.X, self.Y = self.winfo_x, self.winfo_y
self.top.geometry(f"{w}x{h}+{self.X()}+{self.Y()}")
self.top.wm_attributes("-transparentcolor", color)
self.top.overrideredirect(1)
self.top.wm_attributes("-topmost", True)
# estimate top position > trial & error?
self.xx, self.yy = int(wide / 2 - w / 2.4), int(high / 3.4)
# primary bindings
self.bind("<Configure>", self.moveit)
self.top.event_add(
"<<HOOD>>", "<Button-1>", "<Button-2>", "<Button-3>")
self.top.bind("<<HOOD>>", self.focal)
self.focus_force()
def moveit(self, ev):
self.top.geometry(f"+{self.X()+self.xx}+{self.Y()+self.yy}")
self.top.lift()
def focal(self, ev):
self.s_scale.focus_set()
if __name__ == "__main__":
app = transParent()
app.mainloop()

Button not showing up in Tkinter, even w/ geometry manager

I'm designing an app in tkinter, but there is a button that won't show up. The button is crucial to the program's operation.
import tkinter as tk
global field
root = tk.Tk()
root.resizable(0,0)
root.geometry('368x200')
header = tk.Label(root, text = 'Header Text', pady=20)
header.config(font = ('Tahoma', 24))
header.grid(row = 0, columnspan=2)
enter_here = tk.Label(root, text = 'Question: ')
enter_here.grid(row = 1, column = 0, pady = 50)
field = tk.Entry(root, width = 50)
field.grid(row = 1, column = 1, pady = 50)
answer = tk.Button(root, text = 'Answer', command = answerf, width=10)
answer.grid(row=2, column=2)
root.mainloop()
Title, header text and the letters are all placeholders. I just need to figure out how to use the button. I've looked around and couldn't find any answers; most people had just forgot a geometry manager.
You have to be careful what values you are passing to various parameters of tkinter widgets. In above case, this is the reason why you are not able to see button.
Change
field = tk.Entry(root, width = 50)
field.grid(row = 1, column = 1, pady = 50)
to
field = tk.Entry(root, width = 25)
field.grid(row = 1, column = 1, pady = 30)
And,
answer = tk.Button(root, text = 'Answer', command = answerf, width=10)
answer.grid(row=2, column=2)
to
answer = tk.Button(root, text = 'Answer', command = answerf, width=10)
answer.grid(row=1, column=2)
output:
The problem is simply that you're forcing the window to be a size that is too small for the objects inside it.
A simple fix is to remove this line:
root.geometry('368x200')
If you insist on keeping this line, then you need to adjust the parameters of the other widgets so that they fit in the constrained space. For example, you could reduce the size of the widgets or reduce the padding.

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()

How to place widgets next to each other?

I am building a simple GUI program to manage priorities. I am having trouble placing button widgets next to each other. It is somehow logical to me that if I want three buttons (Add, Remove, Edit) to place next to each other, I should use column = 0 for Add, column = 1 for Remove and column = 2 for Edit. Anyway, this is what I get:
Here is the createWidgets function:
def createWidgets(self):
listBox = Listbox(width = 30).grid(row=1, column = 0)
Button(self.root,
text = "Add",
command = self.addItem).grid(row = 2, column = 1, sticky = W)
Button(self.root,
text="Remove",
command = self.removeItem).grid(row = 2, column = 2, sticky = W)
Button(self.root,
text="Edit",
command = self.editItem).grid(row = 2, column = 3, sticky = W)
textBox = Text(height = 10, width = 30).grid(row = 3)
Use the columnspan option for:
textBox = Text(height = 10, width = 30).grid(row = 3, column=0, columnspan=3) # specify the column also
and
listBox = Listbox(width = 30).grid(row=1, column = 0, columnspan=3)
My advice is to break your UI down into regions, and manage each region independently. It's much easier than trying to cram everything into one big grid, especially when you have widgets with very different sizes.
Create three frames: one for the top, one for the group of buttons, and one for the bottom. You can then use pack to place them top-to-bottom:
top_frame.pack(side="top", fill="both", expand=True)
button_frame.pack(side="top", fill="x")
bottom_frame.pack(side="bottom", fill="both", expand=True)
(the use of expand depends on if you want that frame to expand when the window is made larger)
Once you've done that, you can tackle each region separately. For example, you can make the buttons children of the button frame, and use .pack(side='left') to get them to be left-aligned. The other widgets can also use pack if they are the only widgets in each of the other frames, or you can use grid.
You will find that just a few minutes organizing your UI before starting to code will make a big difference in how easy it is to create and maintain.
Example:
def createWidgets(self):
topFrame = tk.Frame(self)
buttonFrame = tk.Frame(self)
bottomFrame = tk.Frame(self)
topFrame.pack(side="top", fill="both", expand=True)
buttonFrame.pack(side="top", fill="x")
bottomFrame.pack(side="bottom", fill="both", expand=True)
listBox = tk.Listbox(topFrame, width=30)
listBox.pack(side="top", fill="both", expand=True)
tk.Button(buttonFrame, text="Add").pack(side="left")
tk.Button(buttonFrame, text="Remove").pack(side="left")
tk.Button(buttonFrame, text="Edit").pack(side="left")
textBox = tk.Text(bottomFrame, height=10, width=30)
textBox.pack(fill="both", expand=True)
When you do
textBox = Text(height = 10, width = 30).grid(row = 3)
tkinter automatically sets column = 0 and because textBox has a width of 30 the first column is stretched to a width of 30. It is possible to place textBox so that it occupies all columns by using the columnspan parameter:
textBox = Text(height = 10, width = 30).grid(row = 3, column = 0, columnspan = 3)
Because listBox also has a width of 30, you should also use columnspan here:
listBox = Listbox(width = 30).grid(row=1, column = 0, columnspan = 3)
A comprehensive guide for the grid method is here.

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