Tkinter Toplevel() positioning without static geometry - python

Im using Toplevel() for popup windows and I want the popup to be displayed to the right of the mouse when it comes up. I found how to do this but only by specifying the geometry of the window. How can I control where the window comes up without specifying the size. I want the window to be the size it needs to be for whatever data is is going to display.
This is what im using right now:
helpwindow = Toplevel()
helpwindow.overrideredirect(1)
helpwindow.geometry("662x390+{0}+{1}".format(event.x_root - 1, event.y_root - 12))
How can I put only the format settings in the window geometry? Or is their a better way?

Use "+{}+{}" without size
helpwindow.geometry("+{}+{}".format(event.x_root - 1, event.y_root - 12))
ie. moving window :)
import tkinter as tk
def move():
global pos_x
helpwindow.geometry("+{}+200".format(pos_x))
pos_x += 10
root.after(100, move)
root = tk.Tk()
pos_x = 0
helpwindow = tk.Toplevel()
move()
root.mainloop()

Related

App's icon is not show in taskbar because of Custom title bar?

I am creating an code editor in which I want to custom title bar to match my app theme and I have created an custom title bar but my app is not showing in taskbar
If any external libraries are for this, Please tell me
What libraries I have to learn to solve my problem please tell me
how to show app icon on taskbar, Actually I have no idea about it
if you can solve it
Please help me to solve my problem
this is my full code(not full code but short version of real one):-
from tkinter import*
def move(e):
xwin = root.winfo_x()
ywin = root.winfo_y()
startx = e.x_root
starty = e.y_root
ywin -= starty
xwin -= startx
def move_(e):
root.geometry(f"+{e.x_root + xwin}+{e.y_root + ywin}")
startx = e.x_root
starty = e.y_root
frame.bind("<B1-Motion>",move_)
def minieme1_(event=None):
root.update_idletasks()
root.overrideredirect(False)
root.state("iconic")
def frame_map(event=None):
root.update_idletasks()
root.overrideredirect(True)
root.state("normal")
root.call()
def minimefunction(event=None):
global size
if size:
root.geometry(f"{screen_width}x{screen_height-40}+0+0")
minimsi.config(text=" \u2752 ")
size = False
else:
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
minimsi.config(text=" \u25a0 ")
size = True
def quitApp():
root.destroy()
def close_blink(event=None):
close_button.config(bg="red")
def close_blink1(event=None):
close_button.config(bg="gray19")
def minimsi_blink(event=None):
minimsi.config(bg="gray29")
def minimsi_blink1(event=None):
minimsi.config(bg="gray19")
def minimsi1_blink(event=None):
minimsi1.config(bg="gray29")
def minimsi1_blink1(event=None):
minimsi1.config(bg="gray19")
root = Tk()
size = True
app_width = 600
app_height = 500
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
print(screen_width,screen_height)
x = (screen_width/2) - (app_width/2)
y = (screen_height/2) - (app_height/2)
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
root.overrideredirect(True)
frame = Frame(root,bg="gray29")
Label(frame,text="My App",font="Consolas 15",bg="gray29",fg="white").pack(side=LEFT,padx=10)
close_button = Button(frame,text=" X ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=quitApp)
close_button.pack(side=RIGHT)
minimsi = Button(frame,text=" \u25a0 ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minimefunction)
minimsi.pack(side=RIGHT)
minimsi1 = Button(frame,text=" - ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minieme1_)
minimsi1.pack(side=RIGHT)
frame.pack(fill=X)
yscroll = Scrollbar(orient=VERTICAL)
yscroll.pack(side=RIGHT,fill=Y)
editor = Text(font="Consolas 15",bg="gray19",fg="white",insertbackground="white",borderwidth=0,yscrollcommand=yscroll.set)
yscroll.config(command=editor.yview)
editor.pack(expand=True,fill=BOTH)
root.config(bg="gray19")
frame.bind("<Button-1>",move)
frame.bind("<B1-Motion>",move)
# minimsi1.bind("<Button-1>",minieme1_)
frame.bind("<Map>",frame_map)
close_button.bind("<Enter>",close_blink)
close_button.bind("<Leave>",close_blink1)
minimsi.bind("<Enter>",minimsi_blink)
minimsi.bind("<Leave>",minimsi_blink1)
minimsi1.bind("<Enter>",minimsi1_blink)
minimsi1.bind("<Leave>",minimsi1_blink1)
root.mainloop()
You can see the problem in this image:-
Disclaimer, this may not be the best approach to achieve OP`s goal, but I'm sticking to this example because:
There are several question with this example on StackOverflow
It shows a basic knowledge that is important for deeper digging.
The improvement of this code to the original is that the style applies again after you iconify it and bring that windows back up.
See the twitch of this example is how the taskbar keeps track of the application. There is a good article of Raymond Chen (Ms-Developer) where he quotes this:
“If you want to dynamically change a window’s style to one that
doesn’t support visible taskbar buttons, you must hide the window
first (by calling ShowWindow with SW_HIDE), change the window style,
and then show the window.”
And also points out a weak point of some programs and why they lose or get a blank taskbar icon:
Window is taskbar-eligible.
Window becomes visible ? taskbar button created.
Window goes taskbar-ineligible.
Window becomes hidden ? since the window is not taskbar-eligible at this point, the taskbar ignores it.
Anyway, the basic idea is the following:
Get the handle of the window. (Parent window of the root in this case for tkinter related reasons)
get the current style in a hexadecimal code
alter the hexadecimal code to your need with a bitwise operation
apply the altered style to the window
I added the following code at the beginning of your script:
from ctypes import windll
GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080
def set_appwindow():
global hasstyle
if not hasstyle:
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
root.withdraw()
root.after(100, lambda:root.wm_deiconify())
hasstyle=True
This code at the end of your script:
hasstyle = False
root.update_idletasks()
root.withdraw()
set_appwindow()
and added the line set_appwindow() in def frame_map(event=None):. Als I had to implement another additional two lines in def minieme1_(event=None): to update the hasstylevariable.
So the overall implementation of this approach was possible to have your window ready and withdrawn for described reason. An additional variable to avoid a infinite loop while you alter the style of your window that is either True while it is shown or False while it is iconyfied, alongside with your overrideredirect method.
Full Code
from tkinter import*
from ctypes import windll
GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080
def set_appwindow():
global hasstyle
if not hasstyle:
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
root.withdraw()
root.after(100, lambda:root.wm_deiconify())
hasstyle=True
def move(e):
xwin = root.winfo_x()
ywin = root.winfo_y()
startx = e.x_root
starty = e.y_root
ywin -= starty
xwin -= startx
def move_(e):
root.geometry(f"+{e.x_root + xwin}+{e.y_root + ywin}")
startx = e.x_root
starty = e.y_root
frame.bind("<B1-Motion>",move_)
def minieme1_(event=None):
global hasstyle
root.update_idletasks()
root.overrideredirect(False)
root.state("iconic")
hasstyle = False
def frame_map(event=None):
root.overrideredirect(True)
root.update_idletasks()
set_appwindow()
root.state("normal")
def minimefunction(event=None):
global size
if size:
root.geometry(f"{screen_width}x{screen_height-40}+0+0")
minimsi.config(text=" \u2752 ")
size = False
else:
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
minimsi.config(text=" \u25a0 ")
size = True
def quitApp():
root.destroy()
def close_blink(event=None):
close_button.config(bg="red")
def close_blink1(event=None):
close_button.config(bg="gray19")
def minimsi_blink(event=None):
minimsi.config(bg="gray29")
def minimsi_blink1(event=None):
minimsi.config(bg="gray19")
def minimsi1_blink(event=None):
minimsi1.config(bg="gray29")
def minimsi1_blink1(event=None):
minimsi1.config(bg="gray19")
root = Tk()
size = True
app_width = 600
app_height = 500
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
print(screen_width,screen_height)
x = (screen_width/2) - (app_width/2)
y = (screen_height/2) - (app_height/2)
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
root.overrideredirect(True)
frame = Frame(root,bg="gray29")
Label(frame,text="My App",font="Consolas 15",bg="gray29",fg="white").pack(side=LEFT,padx=10)
close_button = Button(frame,text=" X ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=quitApp)
close_button.pack(side=RIGHT)
minimsi = Button(frame,text=" \u25a0 ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minimefunction)
minimsi.pack(side=RIGHT)
minimsi1 = Button(frame,text=" - ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minieme1_)
minimsi1.pack(side=RIGHT)
frame.pack(fill=X)
yscroll = Scrollbar(orient=VERTICAL)
yscroll.pack(side=RIGHT,fill=Y)
editor = Text(font="Consolas 15",bg="gray19",fg="white",insertbackground="white",borderwidth=0,yscrollcommand=yscroll.set)
yscroll.config(command=editor.yview)
editor.pack(expand=True,fill=BOTH)
root.config(bg="gray19")
frame.bind("<Button-1>",move)
frame.bind("<B1-Motion>",move)
# minimsi1.bind("<Button-1>",minieme1_)
frame.bind("<Map>",frame_map)
close_button.bind("<Enter>",close_blink)
close_button.bind("<Leave>",close_blink1)
minimsi.bind("<Enter>",minimsi_blink)
minimsi.bind("<Leave>",minimsi_blink1)
minimsi1.bind("<Enter>",minimsi1_blink)
minimsi1.bind("<Leave>",minimsi1_blink1)
hasstyle = False
root.update_idletasks()
root.withdraw()
set_appwindow()
root.mainloop()
Tested with python 3.10 and windows 11
You can use a hidden root window to let the system window manager to show an icon in the taskbar, and make the custom window as a transient child window of the hidden root window in order to simulate those iconify and deiconify effect.
You need to bind <Button> event on the custom window so to bring it to the front when it is clicked. Also need to bind <FocusIn> event on the hidden root window to move the focus to the custom window instead.
Below is the required changes:
...
def minieme1_(event=None):
# iconify hidden root window will iconify the custom window as well
hidden_root.iconify()
...
def quitApp():
# destroy the hidden root window will destroy the custom window as well
hidden_root.destroy()
...
def on_focus(event):
# bring custom window to front
root.lift()
# create a hidden root window
hidden_root = Tk()
hidden_root.attributes('-alpha', 0)
hidden_root.title('My App')
# you can use hidden_root.iconbitmap(...) or hidden_root.iconphoto(...) to set the icon image
# use Toplevel instead of Tk for the custom window
root = Toplevel(hidden_root)
# make it a transient child window of hidden root window
root.transient(hidden_root)
root.bind('<Button>', on_focus)
hidden_root.bind('<FocusIn>', on_focus)
...
#frame.bind("<Map>",frame_map) # it is not necessary and frame_map() is removed as well
...
The short answer is: you can't do this purely with Tkinter. You can set the window's icon via self.iconbitmap(path_to_icon), but in order to have a unique taskbar icon, you'll need to compile your app into a Windows executable.
See here
Edit: Unrelated, but as a matter of practice it's best to avoid star imports, e.g. from tkinter import * - it's much better to use something like import tkinter as tk and then prefix your Tkinter objects with tk. to avoid namespace pollution!

How can I pack up multiple functions in a single widget?

I want to pack some func. in a single widget so that I can interact with that particular widget using bind func.
There's Frame widget which packs up widgets in it and canvas.create_window func. in Canvas widget which also does the same as Frame.
Following program generates sticman after every 3 sec. And when user click the stickman, it disappears.
I tried using Frame to pack functions 🙁...
from Tkinter import *
root = Tk()
hitbox = Frame(root, height = 100, width= 100)
hitbox.pack()
can = Canvas(hitbox, height= 450, width= 1000) # Canvas inside hitbox
can.pack()
def stickman (a,b,c,d):
# Code that makes stickman according to coordinates
def HP(p,q):
# Code that makes Progressbar widget inside hitbox which act as healthbar
counter = 0
def init():
if counter == 10:
pass
else:
counter +=1
stickman(100,100,130,130)
HP(90, 120)
root.after(3000, init) # Stickman respawns after 3 seconds
def kill():
hitbox.destroy()
hitbox.bind('<Button-1>', kill)
root.mainloop()
Stickman respawns after every 3 seconds but bind func. with frame does not seems to be working when running code. Stickman doesn't disappears when clicked.
I think what you are asking for is to destroy the Tkinter frame when Button 1 or the left button on the mouse is clicked. The stickman doesn't disappear because the frame isn't actively listening for the key click unlike a label or an entry field would do.
So, there are 2 easy fixes for this problem:
1. Binding to Root Window
Binding the keybind to the root window would solve the problem mostly because the root window is always actively listening for keybinds unlike the frame or an input field. The only problem with this approach is that the user can click anywhere on the window to destroy the frame.
2. Binding to the Stickman Itself
Binding the keybind to the stickman itself is the cleanest approach because it would be just the same as the first solution but this time the user can only click on the stickman to destroy the frame. This is probably the solution you were looking for. To implement this solution just replace the root.bind('<Button-1>', kill) with stickman.bind('<Button-1>', kill) (or whatever the name is of your stickman is) after defining the stickman but before packing it.
I atatched a modified version of your code down below for the first option:
from tkinter import *
root = Tk()
def kill(event=None):
hitbox.destroy()
root.bind('<Button-1>', kill)
hitbox = Frame(root, height = 100, width= 100)
hitbox.pack()
can = Canvas(hitbox, height= 450, width= 1000) # Canvas inside hitbox
can.pack()
def stickman (a,b,c,d):
pass
# Code that makes stickman according to coordinates
def HP(p,q):
pass
# Code that makes Progressbar widget inside hitbox which act as healthbar
counter = 0
def init():
if counter == 10:
pass
else:
counter +=1
stickman(100,100,130,130)
HP(90, 120)
root.after(3000, init) # Stickman respawns after 3 seconds
root.mainloop()

How do I get mouse position with tkinter and turtle?

I am trying to make a program that lets me draw on a tkinter window using turtle. For some reason I cannot get the absolute mouse coordinates.
I have done root.winfo_pointerx() - root.winfo_rootx() (and vrootx).
I have also tried:
def mousePos(event):
x,y = event.x , event.y
return x,y
My code:
import turtle
import tkinter as tk
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500,height=500)
cv.focus_set()
cv.pack(side = tk.LEFT)
pen = turtle.RawTurtle(cv)
window = pen.getscreen()
def main():
window.setworldcoordinates(-500,-500,500,500)
window.bgcolor("white")
frame = tk.Frame(root)
frame.pack(side = tk.RIGHT,fill=tk.BOTH)
pointLabel = tk.Label(frame,text="Width")
pointLabel.pack()
def getPosition(event):
x = root.winfo_pointerx()-root.winfo_vrootx()
y = root.winfo_pointery()-root.winfo_vrooty()
pen.goto(x,y)
cv.bind("<Motion>", getPosition)
cv.pack
tk.mainloop()
pass
I want the cursor to be on top of the arrow, but instead it is always to the right and down. Also, when I move the mouse up, the arrow moves down, and vice versa.
Think hard about how you set the setworldcoordinate(). -500 - 500 means your world has 1,000 in size and window size is 500. Also, the mouse pointer offset from the window root - both absolute coordinates should be used. You mixed up the absolute coordinates - mouse pointer and vrootx which is in different scale so the distance of two makes no sense. Following code is probably closer to what you intended. Note that, I set the world coordinate to match the absolute coordinates of mouse pointer offset from the top/left corner of window.
import turtle
import tkinter as tk
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500,height=500)
cv.focus_set()
cv.pack(side = tk.LEFT)
pen = turtle.RawTurtle(cv)
window = pen.getscreen()
def main():
window.setworldcoordinates(0,500,500,0)
window.bgcolor("white")
frame = tk.Frame(root)
frame.pack(side = tk.RIGHT,fill=tk.BOTH)
pointLabel = tk.Label(frame,text="Width")
pointLabel.pack()
print(dir(root))
def getPosition(event):
x = root.winfo_pointerx()-root.winfo_rootx()
y = root.winfo_pointery()-root.winfo_rooty()
print(x, y)
pen.goto(x,y)
pass
cv.bind("<Motion>", getPosition)
cv.pack
tk.mainloop()
pass
if __name__ == "__main__":
main()
pass
You've got an issue working against you that isn't of your own making. The general rule is when in a turtle canvas, use turtle methods. But turtle doesn't have an inherent 'Motion' event type, so you were trying to use the raw Canvas one as a substitute. Thus the conflict.
An issue of your own making is that when you're inside a fast moving event handler, you need to disable the event hander as the first thing you do, reenabling on exit. Otherwise, events overlap and bad things happen. (Inadvertant recursions and other wierdness.)
I've rewritten your program below to work as I believe you intended. The fix is adding the missing turtle method so we can stay within the turtle domain:
import tkinter as tk
from turtle import RawTurtle, TurtleScreen
from functools import partial
def onscreenmove(self, fun, add=None): # method missing from turtle.py
if fun is None:
self.cv.unbind('<Motion>')
else:
def eventfun(event):
fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
self.cv.bind('<Motion>', eventfun, add)
def getPosition(x, y):
screen.onscreenmove(None) # disable events inside handler
pen.setheading(pen.towards(x, y))
pen.goto(x, y)
screen.onscreenmove(getPosition) # reenable handler on exit
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500, height=500)
cv.focus_set()
cv.pack(side=tk.LEFT)
screen = TurtleScreen(cv)
screen.onscreenmove = partial(onscreenmove, screen) # install missing method
pen = RawTurtle(screen)
frame = tk.Frame(root)
frame.pack(side=tk.RIGHT, fill=tk.BOTH)
tk.Label(frame, text="Width").pack()
screen.onscreenmove(getPosition)
screen.mainloop()
Mouse position for Tkinter:
import Tkinter as tk
root = tk.Tk()
def motion(event):
x, y = event.x, event.y
print('{}, {}'.format(x, y))
root.bind('<Motion>', motion)
root.mainloop()
Mouse position for turtle:
canvas = turtle.getcanvas()
x, y = canvas.winfo_pointerx(), canvas.winfo_pointery()
Hope this helps.

Why does Tkinter.TopLevel.wm_geometry() return "1x1+0+0"?

I have a Tkinter root window and a tk.Toplevel. When I call .wm_geometry() on the root window and on the Toplevel, they both return the string 1x1+0+0, even when my windows are visible on the screen.
A condensed version of my code is:
root = tk.Tk()
root.withdraw()
window = tk.Toplevel(root)
window.withdraw()
# add things to this window
root.after(0, execute_next_instruction)
root.mainloop()
def execute_next_instruction():
window.wm_geometry() # "1x1+0+0"
It turns out that because I was using tk.Tk.after(0, execute_next_instruction), the calls to get the window geometry information were still executing before the window was ready. The fix was to use tk.Tk.after(1, execute_next_instruction).
Pass in the position using wm_geometry(). When called with no arguments it returns the current geometry (width x height + x-position + y position). You also call it on the window instance. eg:
root = tk.Tk()
root.wm_geometry("800x600+0+0")
# ... other stuff ...
root.mainloop()

odd behavior in tkinter.tk.Sizegrip

I'm currently trying to learn tkinter in Python 3 so I'm not sure if I'm looking at a bug or I'm not doing things correctly.
from tkinter import *
from tkinter import ttk
root = Tk()
grip = ttk.Sizegrip(root).grid(column=0, row=0, sticky=(S,E))
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.mainloop()
When the Sizegrip is grabbed the whole window moves rapidly (faster than my mouse pointer is moving) to the bottom of the screen. The window is being appropriately resized but the movement of the whole window is not what I would expect. [I'm using Ubuntu 10.04 with Python 3.1.2]
I have the same problem on my system (Ubuntu 10.10, Python 2.6) and I think it has to do with the window manager and not Tkinter. When I use Openbox, I don't have the problem, and when I use root.overrideredirect(1), I don't have the problem either.
What you can do is make a ttk.Label themed to look like a sizegrip. Bind it to and resize the window accordingly. Here's a window with a sizegrip made this way:
from tkinter import *
from tkinter import ttk
#
# Callbacks:
#
# Change "bottom_right_corner" to "size_nw_se" and
# "arrow" to "left_ptr" if running on Windows.
#
def button_press(event):
sizegrip["cursor"] = "bottom_right_corner"
def resize(event):
deltax = event.x_root - root.winfo_rootx()
deltay = event.y_root - root.winfo_rooty()
if deltax < 1:
deltax = 1
if deltay < 1:
deltay = 1
root.geometry("%sx%s" % (deltax, deltay))
def button_release(event):
sizegrip["cursor"] = "arrow"
# Widget Creation
root = Tk()
sizegrip = ttk.Label(root, style="Sizer.TLabel")
# Styling
style = ttk.Style()
style.layout("Sizer.TLabel", [("Sizegrip.sizegrip",
{"side": "bottom", "sticky": "se"})])
# Geometry Management
sizegrip.pack(side="bottom", anchor="se")
# Bindings
sizegrip.bind("<ButtonPress-1>", button_press)
sizegrip.bind("<B1-Motion>", resize)
sizegrip.bind("<ButtonRelease-1>", button_release)
root.mainloop()
I am used to Python 2, so sorry if I've messed up a bit on the syntax. I tested it with Python 2 imports ("from Tkinter import *", and "import ttk") and it works. Let's just hope the imports are all that's different in Python 3.

Categories

Resources