How to make pop-up window with force attention in Tkinter - python

I want to create a window which doesn't allow the user to access other windows until you give an input.
I tried win.attribute("ontop", True) but it allows the user to access other windows.
or is there any function like a force_focus_lock() in Tkinter python 3.8 which doesn't allow other window to
get focus until you give a input or the close present window.

I believe the below is what you are trying to do. Explanation is given in comments.
method #1: (PopOut1)
you can still move the main window
the new window assumes focus if there is a mouse release on main window
method #2: (PopOut2)
the main window is locked in place
the new window will assume focus, blink and "ding" if there is a mouse release on main window
import tkinter as tk
#first method
class PopOut1(tk.Toplevel):
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
self.geometry('400x300')
#set focus to this window
self.focus_set()
#releasing on any other tkinter window, within this process, forces focus back to this window
self.grab_set()
#second method
class PopOut2(tk.Toplevel):
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
self.geometry('400x300')
#set focus to this window
self.focus_set()
#disable the main window
master.attributes('-disabled', True)
#so this window can't end up behind the disabled window
#only necessary if this window is not transient
#self.attributes('-topmost', True)
#capture close event
self.protocol("WM_DELETE_WINDOW", self.close)
#event=None ~ in case you also want to bind this to something
def close(self, event=None):
#re-enable the main window
self.master.attributes('-disabled', False)
#destroy this window
self.destroy()
class App(tk.Tk):
TITLE = 'Application'
WIDTH, HEIGHT, X, Y = 800, 600, 50, 50
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="open popout 1", command=self.open1).grid()
tk.Button(self, text="open popout 2", command=self.open2).grid()
def open1(self):
PopOut1(self)
def open2(self):
#.transient(self) ~
# flash PopOut if focus is attempted on main
# automatically drawn above parent
# will not appear in taskbar
PopOut2(self).transient(self)
if __name__ == '__main__':
app = App()
app.title(App.TITLE)
app.geometry(f'{App.WIDTH}x{App.HEIGHT}+{App.X}+{App.Y}')
#app.resizable(width=False, height=False)
app.mainloop()

Related

Tkinter: Keep from moving focus to window

I am trying to make an on screen keyboard. It works for widgets in the window but when I press a button it moves focus from the window I am trying to type in to the window that has the buttons. How do I prevent python from moving?
from tkinter import *
from pynput.keyboard import Key, Controller
keyboard = Controller()
class App:
def __init__(self, master):
self.entry = Entry()
self.buttonOne = Button(text='1')
self.buttonTwo = Button(text='2')
self.buttonThree = Button(text='3')
self.buttonOne.bind("<Button-1>", lambda event, keyPressed='1': self.pressed(event, keyPressed))
self.buttonTwo.bind("<Button-1>", lambda event, keyPressed='2': self.pressed(event, keyPressed))
self.buttonThree.bind("<Button-1>", lambda event, keyPressed='3': self.pressed(event, keyPressed))
self.entry.grid(row=0, column=0, columnspan=3)
self.buttonOne.grid(row=1, column=0)
self.buttonTwo.grid(row=1, column=1)
self.buttonThree.grid(row=1, column=2)
def pressed(self, event, keyPressed):
keyboard.press(keyPressed)
keyboard.release(keyPressed)
root = Tk()
app = App(root)
root.mainloop()
I would suggest using withdraw() and deiconify(). This will make it so the window with the button is invisible once you call it on that window. Once you use deiconify() it reverses this and makes it visible again.
More information can be found here.
Question: Keep from moving focus to window
On X11, you can set the -type attribute:
self.wm_attributes("-type", 'dock')
'dock' will working for me not to grab the focus, but are not supported by all window managers.
Reference:
wm attributes
Communicate with window manager
A list of types
-type
Requests that the window should be interpreted by the window manager as being of the specified type(s). This may cause the window to be decorated in a different way or otherwise managed differently, though exactly what happens is entirely up to the window manager.
'dock'
indicates a dock/panel feature,
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.wm_attributes("-type", 'dock')
for n in range(64, 68):
btn = tk.Button(self, text=chr(n), takefocus=0)
btn.bind("<Button-1>", self.on_click)
btn.pack()
def on_click(self, event):
w = event.widget
print(w['text'])
if __name__ == '__main__':
App().mainloop()

Make Toplevel window pop up to the top when root window clicked

I have a UI root window where two other Toplevel windows get created on separate button clicks. These Toplevel windows are anchored to the root window and drag along the screen with the root window.
My problem is if i have another window open and my UI is hiding behind it, if i click on my UI from the taskbar or the little i can see on the screen, only the root Tk window pops up and the other Toplevel windows are still hiding behind the other window.
I tried toplevel.lift() and toplevel.wm_attributes("-topmost", 1) but neither give me what i want.
How can I tie the Toplevel windows so that if they are open and I click on the root window the Toplevel window also pops to the top?
Here is a simple example that will open 2 windows and disable everything on the root window while also binding any interaction with that root window to lift all the top windows above it.
I have also bound the top level close event to first remove the root binding and then destroy the top levels then re-enable all the widgets in the root window. this should serve to be an antiquity example of what you are trying to do.
Let me know if you have any questions.
import tkinter as tk
class ExampleApp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.master.geometry("400x150")
self.main_frame = tk.Frame(self.master)
self.main_frame.pack(expand=tk.YES, fill=tk.BOTH)
self.master.protocol('<WM_LBUTTONDBLCLK>', self.motion)
tk.Label(self.main_frame, text = "This is the main window").pack()
tk.Button(self.main_frame, text = "Open 2 top level windows!", command = self.open_windows).pack()
def motion(self, event):
x, y = event.x, event.y
print('{}, {}'.format(x, y))
def open_windows(self):
self.top1 = tk.Toplevel(self.master)
self.top2 = tk.Toplevel(self.master)
self.top1.geometry("100x100")
self.top2.geometry("100x100")
# ties the window close event to our customer close method for toplevel
self.top1.protocol("WM_DELETE_WINDOW", self.close_toplevels)
self.top2.protocol("WM_DELETE_WINDOW", self.close_toplevels)
self.master.bind("<Unmap>", self.icon_all)
self.top1.bind("<Unmap>", self.icon_all)
self.top2.bind("<Unmap>", self.icon_all)
self.master.bind("<Map>", self.de_icon_all)
self.top1.bind("<Map>", self.de_icon_all)
self.top2.bind("<Map>", self.de_icon_all)
for child in self.main_frame.winfo_children():
child.configure(state='disable')
tk.Label(self.top1, text ="Topwindow 1").pack()
tk.Label(self.top2, text ="Topwindow 2").pack()
# sets the top windows to their initial locations
self.lock_top_to_root()
#keeps the top windows in the specified locations compared to root window
self.master.bind("<Configure>", self.lock_top_to_root)
def withdraw_tops(self, event=None):
self.top1.withdraw()
self.top2.withdraw()
def de_icon_tops(self, event=None):
self.top1.deiconify()
self.top2.deiconify()
def icon_all(self, event=None):
self.withdraw_tops()
self.master.iconify()
def de_icon_all(self, event=None):
self.de_icon_tops()
self.master.deiconify()
self.lock_top_to_root()
def lock_top_to_root(self, event=None):
self.top1.lift() # lift both toplevel windows about root
self.top2.lift()
# places each top level at each side
# this is not set up to compensate for the root being resized but can be if you need it to.
self.top1.geometry('+{}+{}'.format(self.master.winfo_x()+10, self.master.winfo_y()+30))
self.top2.geometry('+{}+{}'.format(self.master.winfo_x()+275, self.master.winfo_y()+30))
def close_toplevels(self):
# customer close method to reset everything
self.master.unbind('<Configure>')
self.master.unbind("<Unmap>")
self.master.unbind("<Map>")
self.top1.destroy()
self.top2.destroy()
for child in self.main_frame.winfo_children():
child.configure(state='active')
root = tk.Tk()
my_example = ExampleApp(root)
root.mainloop()

Tkinter Toplevel always in front

I am working on a program that uses a tkinter TopLevel window to display periodically updating log information to the user. My problem is that the main program is fullscreen, so whenever they interact with it after opening the log window, the log window isn't visible since it is now behind the main program.
Is there a way to force a Toplevel window (or actually, any Tkinter window) to remain permanently ontop of all other windows?
Consider this quick setup for example:
import tkinter as tk
from tkinter import ttk
class Example(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.pack()
btn = ttk.Button(self, text = "Press", command = self.openTopLevel)
btn.pack()
def openTopLevel(self):
topLevelWindow = tk.Toplevel(self)
root = tk.Tk()
main = Example(root)
root.mainloop()
When you Press the button and open the Toplevel Window, it is on top. But if you grab the Frame, move it around, etc, the Toplevel goes behind it. How do I stop that? Or is that not something Tkinter allows me to do?
To make a window stay in front of others in a tkinter application, use attributes('-topmost', 'true'). In your code, it is a one-line to add.
import tkinter as tk
from tkinter import ttk
class Example(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.pack()
btn = ttk.Button(self, text = "Press", command = self.openTopLevel)
btn.pack()
def openTopLevel(self):
topLevelWindow = tk.Toplevel(self)
# Make topLevelWindow remain on top until destroyed, or attribute changes.
topLevelWindow.attributes('-topmost', 'true')
root = tk.Tk()
main = Example(root)
root.mainloop()

How do you validate a window in Tkinter?

def createWidgets(self):
self.INSTRUCTIONS = Button(self) #creating button linked to instructions_window
self.INSTRUCTIONS["text"] = "Instructions"
self.INSTRUCTIONS["fg"] = "green"
self.INSTRUCTIONS["command"] = self.instruction_window #command which opens instructions_window
self.INSTRUCTIONS.pack({"side": "left"})
Currently, if I press the button multiple times then the instructions window will open multiple times. How do I ensure that when the button is pressed, if the window is already open then it will flash to show that the same window can't be opened. Is there a command? Or do I need to use a validation of some sort?
Here's a great article dealing with more complicated examples of dialog boxes.
Essentially what you are looking for is almost like a modal dialog window except it seems with the additional ability to still interact with the parent window to a degree. At this point it may be worth considering making it totally modal, but I do not know your use case. If not, you can definitely adapt the scripts given on the tutorial website to fit your needs.
The way I do this is to create a function that will create the window if it doesn't exist, and then display the window. Personally I don't think there's a need to flash the window, but you could do that if you want. Tkinter doesn't know how to flash a window, but you can do something simple like changing the colors briefly.
Here's an example:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.instruction_window = None
self.instructions = tk.Button(self, text="Instructions", foreground="green",
command=self.show_instructions)
self.instructions.pack(side="left")
def show_instructions(self):
'''show the instruction window; create it if it doesn't exist'''
if self.instruction_window is None or not self.instruction_window.winfo_exists():
self.instruction_window = InstructionWindow(self)
else:
self.instruction_window.flash()
class InstructionWindow(tk.Toplevel):
'''A simple instruction window'''
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.text = tk.Text(self, width=40, height=8)
self.text.pack(side="top", fill="both", expand=True)
self.text.insert("end", "these are the instructions")
def flash(self):
'''make the window visible, and make it flash temporarily'''
# make sure the window is visible, in case it got hidden
self.lift()
self.deiconify()
# blink the colors
self.after(100, lambda: self.text.configure(bg="black", fg="white"))
self.after(500, lambda: self.text.configure(bg="white", fg="black"))
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Tkinter color-chooser window focus

I have three windows:
Root window
Toplevel window
Color-chooser window.
The root window has a menu command that opens the toplevel. The toplevel has a button that opens the color-chooser.
When the color-chooser button is pressed and the color-chooser opens, something weird happens. The toplevel window gets sent BEHIND the root window.
The layering of the windows is like this before clicking the button:
ROOT
TOPLEVEL
The layering of the windows is like this after clicking the button:
TOPLEVEL
ROOT
COLORCHOOSER
Why is this happening? How can I stop this from happening? (I'm using Windows 7, and python 2.7)
Here is a simplified working code example:
from Tkinter import *
import ttk
import tkColorChooser
class Root(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
self.menu = Menu(self)
self.menu.add_command(label="Open Toplevel", command=self.create_toplevel)
self.config(menu=self.menu)
def create_toplevel(self):
self.new_toplevel = TopLevelWithButton(self)
class TopLevelWithButton(Toplevel):
def __init__(self, *args, **kwargs):
Toplevel.__init__(self, *args, **kwargs)
self.button = ttk.Button(self, text="Color Chooser", command=self.open_chooser)
self.button.grid(row=0, column=0)
def open_chooser(self):
tkColorChooser.askcolor()
root = Root()
root.mainloop()
You aren't telling the color dialog which window it belongs to, so by default it attaches itself to the root window. With some window managers this will cause the parent window to be raised to the top of the stacking order.
Try passing in the parent attribute, giving it a value of the toplevel window:
tkChooseColor.askcolor(parent=self)

Categories

Resources