Tkinter set geometry of Toplevel before displaying - python

I'm trying to create a Toplevel window that appears beside one of my other windows, but i'm having trouble figuring out how to set the location of the window before it is displayed. It's a simple couple lines:
histogram_window = Toplevel(self)
histogram_window.geometry('+%d+%d' % (self.__root.winfo_rootx() + self.winfo_x()*2,
self.__root.winfo_rooty()))
histogram_window.transient(self.__root)
But the window will first appear, then quickly move to the location I specified. How can I simply make it appear in the location I specified?

I've found the issue thanks to a reminder from Bryan Oakley, the problem was that upon the creation of the class I bound a matplotlib canvas to the screen and packed the widget, then tried to create a Toplevel window at the same time. I fixed this by calling the update() function for my window
Class some(Toplevel):
def __init__(self, root):
Toplevel(self, root)
# created widget
# pack widget
self.read()
def read(self):
hist = Toplevel(self)
hist.geometry(...)
hist.update() #fixed the issue

Related

How can I add custom Frame objects to custom ttk Notebook in Tkinter/python3?

I am creating a Tkinter/Python3 application where the main window inherits from Notebook (i need tabs), and each tab should be a custom class inheriting from Frame (I would then dynamically use matplotlib to create custom graphs).
Unfortunately I don't seem to be able to have Notebook accept my custom Frames.
Following very reduced snippet of code:
#!/usr/bin/env python3
from tkinter import *
from tkinter.ttk import Notebook
class MyFrame1(Frame):
def __init__(self, master=None, mytext=""):
super().__init__(master)
self.create_widgets(mytext)
def create_widgets(self, mytext):
self.label = Label(self.master, text=mytext, anchor=W)
# this is not placed relative to the Frame, but to the
# master
# 1. How I get the relative coordinates inside the frame
# to be 10, 10 of the frame area?
self.label.place(x=10, y=10, width=128, height=24)
class MyNotebook(Notebook):
def __init__(self, master=None):
super().__init__(master)
self.create_widgets()
def create_widgets(self):
self.f1 = MyFrame1(self, "abc")
# once the UI is drawn, the label "def" seems to overlay
# "abc" even when "f1" is selected
# 2. Why is self.f2 always shown even when self.f1 is
# selected?
self.f2 = MyFrame1(self, "def")
self.add(self.f1, text="f1")
self.add(self.f2, text="f2")
# Without this command nothing gets drawn
# 3. Why is this? Is this equivalent of 'pack' but for
# pixel driven layout?
self.place(width=640, height=480)
def main():
root = Tk()
root.minsize(640, 480)
root.geometry("640x480")
app = MyNotebook(master=root)
# this works as intended the label is indeed placed
# in the frame at 10, 10
#app = MyFrame1(master=root, mytext="123abc")
app.mainloop()
return None
if __name__ == "__main__":
main()
As per comments I have the following main question: why aren't my custom instances of MyFrame1 properly displayed inside MyNotebook?
Sub questions:
How can I get relative coordinate areas of where the frame is located when place my elements (in this case a Label)?
Why even when self.f1 tab is selected in the UI, I can still see the content of self.f2 tab?
Is self.place required in order to show all sub-elements when not using pack?
If I dynamically create Tkinter elements after the MyNotebook is initialized, will those be bound to respective tabs?
Not sure what I'm doing wrong?
Thanks!
Not sure what I'm doing wrong?
Your create_widgets method needs to add widgets to self, not self.master.
How can I get relative coordinate areas of where the frame is located when place my elements (in this case a Label)?
I don't understand what you mean by this. When you use place, coordinates will be interpreted relative to the frame. However, I strongly advise against using place. Both pack and grid will trigger the frame to resize to fit its children which almost always results in a more responsive UI
Why even when self.f1 tab is selected in the UI, I can still see the content of self.f2 tab?
Because you added internal widgets to self.master instead of self.
Is self.place required in order to show all sub-elements when not using pack?
No. It is required to use a geometry manager but it doesn't have to be place. Usually, place is the least desirable geometry manager to use. pack and grid are almost always better choices except for some very specific situations.
If I dynamically create Tkinter elements after the MyNotebook is initialized, will those be bound to respective tabs?
They will be in whatever tab you put them in.
Finally, I would suggest that you remove self.place in create_widgets. Instead, call pack, place, or grid in the same block of code that creates an instance of that class.
It's a bad practice for a widget to add itself to another widget's layout. The code that creates the widget should be the code that adds the widget to the layout.

Disable window move in Tkinter Python

it seems that I was pending to continue the bombardment of questions. It this is short, Is it possible to disable the movement of the Tkinter window without deleting the top bar of this?
It would give a minimal and reproducible code, but if it did it would only be two lines, it would be useless.
Bind a event for your window,and set the window .geometry()
But now you can not revise the window size by dragging the window's border(But it can maximize the window.).
Here is an example of the code:
import tkinter
def GetWindowPos():
global X,Y
X = win.winfo_geometry().split("+")[1]
Y = win.winfo_geometry().split("+")[2]
win.bind_all('<Configure>', HoldOn)
def HoldOn(event):
win.geometry("+{}+{}".format(X,Y))
win = tkinter.Tk()
win.geometry("400x400+{}+{}".format(12,12))
tkinter.Label(win,text="Halo!").grid()
win.after(100,GetWindowPos)
win.mainloop()
I have found a method, but as you might know to achieve something, we have to lose something!
You can use:
root.overrideredirect(True) # turns off title bar
by which you wont be able to move the tkinter window and also Tkinter application won't be displayed in taskbar, but you will also lose the title bar.
but if you wish to have the title bar,
then you can create one by this link.
Or use below to make a new title bar and also be able to move it(from this answer)
def move_window(event):
root.geometry('+{0}+{1}'.format(event.x_root, event.y_root)
# bind title bar motion to the move window function
title_bar.bind('<B1-Motion>', move_window)
But still your Tkinter application won't show up in taskbar, here's a solution(from this answer):
root = tkinter.Tk()
top = tkinter.Toplevel(root)
root.attributes("-alpha",0.0) # to make root invisible
#toplevel follows root taskbar events (minimize, restore)
def onRootIconify(event): top.withdraw()
root.bind("<Unmap>", onRootIconify)
def onRootDeiconify(event): top.deiconify()
root.bind("<Map>", onRootDeiconify)
You can add a toplevel window under the root object, make toplevel invisible and then handle the icon events of top level to hide or show the root window on taskbar.

With Tkinter, how to position a dialog box at initiation OR how to auto-resize a window after geometry is set?

Disclaimer: In all likelihood this could very well be an XY problem, I'd appreciate if you would point me to the correct direction.
Goal: I have a file that I'd like tkinter to read and then create widgets dynamically based on the file contents. I want the window to always open centered at my cursor.
For the sake of MCVE let's consider a plain text file that reads:
Madness?
This
IS
T K I N T E R
And tkinter should create 4 label widgets, with the main window resized to fit. That's very simple... until the file is encrypted. Before I can read the encrypted file, I need to askstring() for a password first (MCVE: let's also assume we're only asking for the key here).
My issues:
The askstring Dialog is always at a default position. I know it's supposed to be relative to the parent (root)...
But I can't set geometry() before I create the widgets or else it won't resize to the widgets...
And I can't pre-determine the size required for geometry() since I can't open the file without the password (key)...
Here's a MCVE sample of my most successful attempt:
import tkinter as tk
from tkinter.simpledialog import askstring
from cryptography.fernet import Fernet
class GUI(tk.Tk):
def __init__(self):
super().__init__()
# side note: If I don't withdraw() the root first, the dialog ends up behind the root
# and I couldn't find a way to get around this.
self.withdraw()
self.create_widgets()
# Attempt: I tried doing this instead of self.create_widgets():
# self.reposition()
# self.after(ms=1,func=self.create_widgets)
# The dialog is positioned correctly, but the window size doesn't resize
self.deiconify()
# I have to do the reposition AFTER mainloop or else the window size becomes 1x1
self.after(ms=1, func=self.reposition)
self.mainloop()
def get_key(self):
return askstring('Locked','Enter Key', show='*', parent=self)
def create_widgets(self):
self.lbls = []
with open('test2.txt', 'rb') as file:
encrypted = file.read()
key = self.get_key()
suite = Fernet(key)
self.data = suite.decrypt(encrypted).decode('utf-8')
for i in self.data.split('\n'):
self.lbls.append(tk.Label(self, text=i.strip()))
self.lbls[-1].pack()
def reposition(self):
width, height = self.winfo_width(), self.winfo_height()
self.geometry(f'{width}x{height}+{self.winfo_pointerx()-int(width/2)}+{self.winfo_pointery()-int(height/2)}')
gui = GUI()
Which achieves (that I can live with):
✓ Correct size based on file content
✓ Root positioned at center of cursor
⨯ Prompts for key at center of cursor
My questions are:
Is it possible to perform an auto-resize on the root again based on the widgets function similar to pack_propagate() after geometry() is set? It seems once geometry() is set the root won't propagate no more.
If not, how can I manually resize it in code? I tried retrieving the total heights of the widgets height = sum([i.winfo_reqheight() for i in self.lbls]) but height just becomes 0. But when I print(self.lbls[-1].winfo_reqheight()) in the self.create_widgets() it returns 26 each, and they actually print after my self.reposition() call, which is weird.
Failing that, is it possible to position the askstring() dialog prior to the the widgets being created?
I'm stumped. I feel like I'm going about this the wrong way but I'm not sure what is the correct way to handle this situation to break the cycle of dependency.
To help with reproducing here's the encrypted string as well as the key:
Data:
gAAAAABb2z3y-Kva7bdgMEbvnqGGRRJc9ZMrt8oc092_fuxSK1x4qlP72aVy13xR-jQV1vLD7NQdOTT6YBI17pGYpUZiFpIOQGih9jYsd-u1EPUeV2iqPLG7wYcNxYq-u4Z4phkHllvP
Key:
SecretKeyAnyhowGoWatchDareDevilS3ItsAmazing=
Edit: Based on #BryanOakley's answer here I can invoke self.geometry("") to reset, however it goes back to the native 200x200 size, and still doesn't propragate the widgets.
After a dozens more tries I think I got it working, here are the relevant parts I modified:
def __init__(self):
super().__init__()
self.withdraw()
# reposition the window first...
self.reposition()
# For some reason I can't seem to figure out,
# without the .after(ms=1) the dialog still goes to my top left corner
self.after(ms=1, func=self.do_stuff)
self.mainloop()
# wrap these functions to be called by .after()
def do_stuff(self):
self.create_widgets()
self.deiconify()
def reposition(self):
width, height = self.winfo_width(), self.winfo_height()
self.geometry(f'{width}x{height}+{self.winfo_pointerx()-int(width/2)}+{self.winfo_pointery()-int(height/2)}')
# reset the geometry per #BryanOakley's answer in the linked thread
# Seems it resets the width/height but still retains the x/y
self.geometry("")
It would appear #BryanOakley have answers for all things tkinter.
I'm still waiting to see if there will be others chiming in on the subject and will be happy to accept your answer to provide some clarity on the subject.

How to get the name of a widget Python Tkinter

I am working on a program where I create some widgets in a for loop. So I need to get the name of them dynamically. I have set up is when the mouse enters the frame. Which holds the two text label widgets. I causes a function to run. And I want to change the background color of a widget with the name of noteName. But I seem to have run into a stopping point and I can no figure it out. I have searched online but could not find much. SO does anyone here know how to get the name of a widget?
Code:
def get_children_hover(event):
for widgets in event.widget.winfo_children():
#This is here where I can not seem to figure out how to get the widgets name.
Can someone push me into the right direction.
winfo_children() is the right thing to use but you are using it wrong. It is a method for parent widgets. (i.e. root, frame, canvas etc..)
Also:
If the order doesn’t matter, you can get the same information from the
children widget attribute (it’s a dictionary mapping Tk widget names
to widget instances, so widget.children.values() gives you a list of
instances).
simple example:
import tkinter as tk
def foo():
print ("Frame:", frm.winfo_children())
print ("Root:", root.winfo_children())
print ("children_values:", root.children.values())
root = tk.Tk()
frm = tk.Frame(root)
tk.Label(root,text="foo").pack()
btn = tk.Button(frm,text="FOOO",command=foo)
frm.pack()
btn.pack()
root.mainloop()
about your code:
def get_children_hover(event):
for widgets in root.winfo_children(): #assuming your Tk() instance named root

Tkinter Geometry Return to Normal

I have a tkinter window which I am able to make fullscreen, using geometry(width+height) and overrideredirect(True), but now when I return the window back to a normal size and execute the command overrideredirect(False), I cannot seem to get the window to automatically follow the size of the widgets inside it, as it would do had I not changed the size. Do you know any way which I could return the window to automatically following the size of the widgets again? Thank You in Advance!
Call the geometry with a value of "" to get it to reset itself to its natural size.
Tkinter is based on tk, and the tk docs say this on the matter:
If newGeometry is specified as an empty string then any existing
user-specified geometry for window is cancelled, and the window will
revert to the size requested internally by its widgets.
I believe you're looking for the winfo_reqwidth/reqheight() methods. These return the required width and height for all the widgets that are children of the widget they're called on. Just plug those into the geometry() method the same way you did to go fullscreen on your restore function, like this:
def fullscreen():
root.overrideredirect(True)
root.geometry('{0}x{1}+0+0'.format(root.winfo_screenwidth(), root.winfo_screenheight()))
def restore():
root.overrideredirect(False)
root.geometry('{0}x{1}'.format(root.winfo_reqwidth(), root.winfo_reqheight()))
root = Tk()
Button(root, text='Full Screen', command=fullscreen).pack()
Button(root, text='Restore', command=restore).pack()
root.mainloop()

Categories