How do I make a button resize the window? - python

I was working on a trigonometry calculator and I wanted to make this button resize the window based on the resolution of the frame:
res_btn = Button(frame2,
text='⮞',
command= resolution)
res_btn.pack(side= LEFT, padx=2)
This calls the function:
def resolution():
frame.update()
print(f"{frame.winfo_reqwidth()} x {frame.winfo_reqheight()}")
customRes = f"{frame.winfo_reqwidth() + 15}x{frame.winfo_reqheight() + 15}"
However, when I set the original window geometry function to the variable customRes it does not change:
global customRes
customRes = '500x500'
root = Tk()
root.title('Sin Rule Calculator - Manpreet Singh')
root.geometry(customRes)
root.configure(bg='#686e70')
(Let me know if you need the whole source code)

Related

Way to avoid having multiple windows when using Messagebox (or alternative to it)?

I'm trying to write my first GUI based python program using Tkinter. I have created a base window which holds the menu bar with a couple of options.
One of the options is for a standard 'About' box. When I call the about section with
helpMainMenu.add_command(label="About", command=self.aboutProgram)
It opens the message box, but in a fresh window so there are now two windows showing on the taskbar.
Is there any way to stop it opening a new window and use the main one instead, or is there a better way to do it?
The full code is below
#! /usr/bin/python3
from tkinter import *
from tkinter import messagebox
import datetime
timeNow = datetime.datetime.now()
writeYear = 2020 # Enter the year you started writing the program
lineFeed = "\n"
programTitle = "Basic Menu"
programVersion = "Version 1.0.0"
programmerName = " Name (email#gmail.com)"
if timeNow.year > writeYear:
programAuthor = "©" + str(writeYear) + "-" + str(timeNow.year) + programmerName
else:
programAuthor = "©" + str(writeYear) + programmerName
aboutMessage = programTitle + lineFeed + programVersion + lineFeed + programAuthor
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("{} ({})".format(programTitle, programVersion))
self.pack(fill=BOTH, expand=1)
menu = Menu(self.master)
self.master.config(menu=menu)
fileMainMenu = Menu(menu, tearoff=0) #Create the File menu container
fileMainMenu.add_command(label="Exit", command=self.programExit) # File menu option
menu.add_cascade(label="File", menu=fileMainMenu)
helpMainMenu = Menu(menu, tearoff=0) #Create the Help menu container
helpMainMenu.add_command(label="About", command=self.aboutProgram)
menu.add_cascade(label="Help", menu=helpMainMenu)
def programExit(self):
exitMsgBox = messagebox.askquestion ("Exit Application","Are you sure you want to exit the application",icon = "warning")
if exitMsgBox == "yes":
root.destroy()
exit()
def aboutProgram(self):
messagebox.showinfo("About","About the application", icon = "info")
root = Tk() # root window created. Here, that would be the only window, but
windowHeight = int(root.winfo_screenheight()/100*75) # Set the main window height to 75% of the screen height
windowWidth = int(root.winfo_screenwidth()/100*75) # Set the main window width to 75% of the screen width
screenWidth = int(root.winfo_screenwidth())
screenHeight = int(root.winfo_screenheight())
positionRight = int(root.winfo_screenwidth()/2 - windowWidth/2) # Get the screen width and divide by 2, then minus the result of 'windowWidth' divided by 2
positionDown = int(root.winfo_screenheight()/2 - windowHeight/2) # Get the screen height and divide by 2, then minus the result of 'windowHeight' divided by 2
root.geometry("{}x{}+{}+{}".format(windowWidth, windowHeight, positionRight, positionDown)) # Positions the window in the center of the page.
app = Window(root)
root.mainloop()
Python Version 3.7.3
tkinter.TkVersion 8.6
The simplest way would be to create a new frame for the "about" page, and then overlay it on top of the main window with place -- one of the few times when place is superior to grid and pack.
You should also do a "grab" on the frame so that all events are funneled to the frame and its children. With a grab, while the popup is visible you can't interact with the widgets on the main window.
Here's a quick example:
def aboutProgram(self):
# create the frame with a message and a button
# which destroys the window
aboutFrame = Frame(self.master, bd=2, relief="groove")
label = Label(aboutFrame, text="About the application...")
button = Button(aboutFrame, text="Ok", command=aboutFrame.destroy)
label.pack(side="top", padx=20, pady=20)
button.pack(side="bottom", pady=20)
# overlay the "about" page on top of the root window
aboutFrame.place(relx=.5, rely=.5, anchor="c")
# force all events to go to the popup
aboutFrame.grab_set()
If you want to completely hide the contents of the main window, you can change the place arguments to fill the window:
aboutFrame.place(x=0, y=0, anchor="nw", relwidth=1.0, relheight=1.0)

Identify button when generated in a for loop [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
I am trying to program a minesweeper game on python using tkinter. I started off by creating a grid of buttons using a two dimensional list, and the generation of the button and everything works. The only issue I have is that I don't know how to determine which button in my grid is clicked. My goal is to be able to click on a button and through that I know the coordinates of that in my grid [row][col].
This is the code I have so far.
from tkinter import *
from functools import partial
from itertools import product
# Here, we are creating our class, Window, and inheriting from the Frame
# class. Frame is a class from the tkinter module. (see Lib/tkinter/__init__)
class Window(Frame):
# Define settings upon initialization. Here you can specify
def __init__(self, master=None):
# parameters that you want to send through the Frame class.
Frame.__init__(self, master)
#reference to the master widget, which is the tk window
self.master = master
#with that, we want to then run init_window, which doesn't yet exist
numRows = int(input("# of Rows: "))
numCols = int(input("# of Cols: "))
self.init_window(numRows, numCols)
#Creation of init_window
def init_window(self, rowNum, colNum):
# print(x, y)
# changing the title of our master widget
self.master.title("GUI")
# allowing the widget to take the full space of the root window
self.pack(fill=BOTH, expand=1)
# creating a button instance
#quitButton = Button(self, text="Exit",command=self.client_exit)
# placing the button on my window
#quitButton.place(x=0, y=0)
but = []
for row in range(0, rowNum):
curRow = []
for col in range(0, colNum):
curRow.append(Button(self, bg="gray", width=2,height=1, command=lambda: self.open_button(row, col)))
curRow[col].grid(row=row,column=col)
but.append(curRow)
#but[1][1].config(state="disabled")
#but[1][1]["text"] = "3"
#but[1][1]["bg"] = "white"
def open_button(self, r, c):
print(r, " : ", c)
# root window created. Here, that would be the only window, but
# you can later have windows within windows.
root = Tk()
root.geometry("600x600")
#creation of an instance
app = Window(root)
#mainloop
root.mainloop()
Whenever I click on the grid, it gives me the very last button...
For example, a 9x9 grid always gives me "9 : 9" whenever I click any button.
Solutions welcomed! I want an easy way to get the coordinates without changing too much of the code (if possible).
Thanks!
The row and col variables are assigned each value in the ranges. At the end of the loop that generates the buttons, the values for those variables are left at the last values in the ranges, e.g. "9 : 9".
Try replacing the line
curRow.append(Button(self, bg="gray", width=2,height=1, command=lambda: self.open_button(row, col)))
with
curRow.append(Button(self, bg="gray", width=2,height=1, command=lambda rw=row, cl=col: self.open_button(rw, cl)))
This assigns the values of row and col at the time the button is created to the variables rw and cl, which remain the same for each button as the for-loop iterates.
See this link:
Tkinter assign button command in loop with lambda

Find a widget in a window by coordinates

I make a window with tkinter and I want to check if a Label is at the coordinates relx = 0.3 and rely = 0.63. Is there any function which I could use?
I already tried to use the nametowidget function, but there I have to give every widget I have a name.
So, the widget I try to get is moving in the window and if it reaches the coordinates I want to move it in another way, but I dont know any function which I could use
You can use winfo_x/y to get the label coordinates and winfo_width/height to get the dimension of the window to compute relative coordinates. I made a little example where the goal is to resize the window to get the right relative coordinates:
import tkinter as tk
def check_label_relpos():
relx = l.winfo_x()/parent.winfo_width()
rely = l.winfo_y()/parent.winfo_height()
if (abs(relx - 0.3) < 0.02) and (abs(rely - 0.63) < 0.02):
print("Ok")
else:
print(relx, rely, "Try again")
parent = tk.Tk()
l = tk.Label(parent, text="Label text", bg="red")
l.place(x=50, y=160)
tk.Button(parent, text="Check label relpos", command=check_label_relpos).place(relx=0.5, rely=1, anchor="s")
parent.mainloop()

How do I align PanedWindows in Tkinter?

I'm new to Tkinter, and finding it a bit rough to get the hang of it. The point of this piece of code is to calculate time using the equation (Time = (Velocity - Initial Velocity) / Acceleration) But I need to take user input for the variables.
Here's what I have so far. It would be great, except for the fact that the labels don't line up with the text widgets. Is there any easy way to do what I need?
def timF():
timPanel = Toplevel()
timPanel.wm_title("Time")
timCont = PanedWindow(timPanel, orient=VERTICAL)
timCont.pack(fill=BOTH, expand=1)
# Top Paned Window and contents #
timTopCont = PanedWindow(timCont, orient=HORIZONTAL)
timCont.add(timTopCont)
# Velocity label
timFVelL = Label(timTopCont, text="Velocity")
timTopCont.add(timFVelL)
# Initial Velocity label
timFiveL = Label(timTopCont, text="Initial Velocity")
timTopCont.add(timFiveL)
# Acceleration label
timFaccL = Label(timTopCont, text="Acceleration")
timTopCont.add(timFaccL)
# Bottom Paned Window and contents #
timBotCont = PanedWindow(timCont, orient=HORIZONTAL)
timCont.add(timBotCont)
# Velocity entry
timFVelE = Entry(timBotCont)
timBotCont.add(timFVelE)
# Initial Velocity entry
timFiveE = Entry(timBotCont)
timBotCont.add(timFiveE)
# Acceleration entry
timFAccE = Entry(timBotCont)
timBotCont.add(timFAccE)
Just use grid() to place the widgets, instead of pack(). It is the easiest way to do so if you know the concrete row and column of the layout you want to place each widget:
timFVelL.grid(row=0, column=0)
timFVelE.grid(row=0, column=1)
timFiveL.grid(row=1, column=0)
timFiveE.grid(row=1, column=1)
# ...

How do I create a Tiling layout / Flow layout in TkInter?

I want to to fill my window with, say, labels and I want them to wrap once the column would be bigger than the current window (or rather parent frame) size.
I've tried using the grid layout, but then I have to calculate the size of the content of each row myself, to know when to put the next element in the next row.
The reason I ask, is because I want to create some sort of tiled file icons.
Or asked differently, is there something like Swing's FlowLayout for TkInter?
What I do when I want something like this is use the text widget for a container. The text widget can have embedded widgets, and they wrap just like text. As long as your widgets are all the same height the effect is pretty nice.
For example (cut and pasted from the question at the author's request):
textwidget = tk.Text(master)
textwidget.pack(side=tk.LEFT, fill=tk.BOTH)
for f in os.listdir('/tmp'):
textwidget.window_create(tk.INSERT, window=tk.Label(textwidget, text=f))
Here is a way to make flow behavior inside a frame.
I wrote a function that will do this. Basically you pass a frame to the function (not root or top level) and the function will look at all the children of the frame, go through them measure their sizes and place them in the frame.
Here is the placement procedure
Place the first widget, and move x over an amount equal to its width.
Measure the next widget.
If placing the next widget would cause it to goes past the frame width, bump its x value to 0 and bump it down a y value equal to the largest widget in the current row (start a new row).
Reset the value of the largest widget since you are starting a new row.
Keep repeating until all widgets are placed.
Bind that procedure to the resizing of the frame event.
I used 3 functions to make this work:
The function that runs the procedure.
The function that binds the resizing of the frame to the function.
The function that unbinds the resizing of the frame.
Here are the functions:
from tkinter import *
def _reorganizeWidgetsWithPlace(frame):
widgetsFrame = frame
widgetDictionary = widgetsFrame.children
widgetKeys = [] # keys in key value pairs of the childwidgets
for key in widgetDictionary:
widgetKeys.append(key)
# initialization/priming loop
width = 0
i = 0
x = 0
y = 0
height = 0
maxheight = 0
# loop/algorithm for sorting
while i < len(widgetDictionary):
height = widgetDictionary[widgetKeys[i]].winfo_height()
if height > maxheight:
maxheight = height
width = width + widgetDictionary[widgetKeys[i]].winfo_width()
# always place first widget at 0,0
if i == 0:
x = 0
y = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
# if after adding width, this exceeds the frame width, bump
# widget down. Use maximimum height so far to bump down
# set x at 0 and start over with new row, reset maxheight
elif width > widgetsFrame.winfo_width():
y = y + maxheight
x = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
maxheight = height
# if after adding width, the widget row length does not exceed
# frame with, add the widget at the start of last widget's
# x value
else:
x = width-widgetDictionary[widgetKeys[i]].winfo_width()
# place the widget at the determined x value
widgetDictionary[widgetKeys[i]].place(x=x, y=y)
i += 1
widgetsFrame.update()
def organizeWidgetsWithPlace(frame):
_reorganizeWidgetsWithPlace(frame)
frame.bind("<Configure>", lambda event: _reorganizeWidgetsWithPlace(frame))
_reorganizeWidgetsWithPlace(frame)
def stopOrganizingWidgetsWithPlace(frame):
frame.unbind("<Configure>")
And here is an example of them in use:
def main():
root = Tk()
root.geometry("250x250")
myframe = Frame(root)
# make sure frame expands to fill parent window
myframe.pack(fill="both", expand=1)
buttonOrganize = Button(myframe, text='start organizing',
command=lambda: organizeWidgetsWithPlace(myframe))
buttonOrganize.pack()
buttonStopOrganize = Button(myframe, text='stop organizing',
command=lambda: stopOrganizingWidgetsWithPlace(myframe))
buttonStopOrganize.pack()
##### a bunch of widgets #####
button = Button(myframe, text="---a random Button---")
canvas = Canvas(myframe, width=80, height=20, bg="orange")
checkbutton = Checkbutton(myframe, text="---checkbutton----")
entry = Entry(myframe, text="entry")
label = Label(myframe, text="Label", height=4, width=20)
listbox = Listbox(myframe, height=3, width=20)
message = Message(myframe, text="hello from Message")
radioButton = Radiobutton(myframe, text="radio button")
scale_widget = Scale(myframe, from_=0, to=100, orient=HORIZONTAL)
scrollbar = Scrollbar(myframe)
textbox = Text(myframe, width=3, height=2)
textbox.insert(END, "Text Widget")
spinbox = Spinbox(myframe, from_=0, to=10)
root.mainloop()
main()
Notice:
That you do not need to grid, pack or place them. As long as you specify the frame, that will all be done at once when the function is called. So that is very convenient. And it can be annoying if you grid a widget, then try to pack another, then try to place another and you get that error that you can only use one geometry manager. I believe this will simply overwrite the previous choices and place them. I believe you can just drop this function in and it will take over management. So far that has always worked for me, but I think you should really not try to mix and match geometry managers.
Notice that initially the buttons are packed, but after pressing the button, they are placed.
I have added the "WithPlace" naming to the functions because I have a similar set of functions that do something very similar with the grid manager.

Categories

Resources