1

Is there a way to tell Tkinter that I want some widget to always remain focused? I've created a minimal example that can be run to show my issue , here's an example window with small toplevel windows also overlayed:

enter image description here

Now if I click the upper title tk, the main window comes into focus and suddenly the small windows are behind the main window

enter image description here

I want to treat these smaller windows as if they are always in focus until the user specifically closes them. Of course this is a minimal example that is an idea behind a small subsection of my large application , is there any easy setting I can use for the toplevel that guarantees it will always remain in focus regardless of other windows? Here's the actual code that can be run to replicate this:

from Tkinter import *

class PropertyDialog(Toplevel):
    def __init__(self, root, string):
        Toplevel.__init__(self)
        self.wm_overrideredirect(1)
        self.root = root 
        self.\
             geometry('+%d+%d' %
                      (root.winfo_pointerx(),
                       root.winfo_pointery()))
        try:
            self.tk.call('::Tk::unsupported::MacWindowStyle',
                                         'style', self._w,
                                         'help', 'noActivates')
        except TclError:
            pass
        window_frame = Frame(self)
        window_frame.pack(side=TOP, fill=BOTH, expand=True)
        exit_frame = Frame(window_frame, background='#ffffe0')
        exit_frame.pack(side=TOP, fill=X, expand=True)
        button = Button(exit_frame, text='x', width=3, command=self.free,
               background='#ffffe0', highlightthickness=0, relief=FLAT)
        button.pack(side=RIGHT)
        text_frame = Frame(window_frame)
        text_frame.pack(side=TOP, fill=BOTH, expand=True)
        label = Label(text_frame, text=string, justify=LEFT,
                      background='#ffffe0',
                      font=('tahoma', '8', 'normal'))
        label.pack(ipadx=1)

    def free(self):
        self.destroy() # first we destroy this one
        for val,widget in enumerate(dialogs): # go through the dialogs list
            if widget is self: # when we find this widget
                dialogs.pop(val) # pop it out
                break # and stop searching
        if dialogs: # if there are any dialogs left:
            for widget in dialogs: # go through each widget
                widget.lift(aboveThis=self.root) # and lift it above the root

def bind():
    """
    toggle property window creation mode
    """
    root.bind('<ButtonPress-1>', create)


def create(event):
    """
    Create actual window upon mouse click
    """
    dialogs.append(PropertyDialog(root, 'help me'))

root = Tk()
dialogs = []
root.geometry('%dx%d' % (300,400))

Button(root, text='create', command=bind).pack()

root.mainloop()
Syntactic Fructose
  • 18,936
  • 23
  • 91
  • 177
  • You could [build a custom `Frame` to replace the default title bar](http://stackoverflow.com/questions/23836000/can-i-change-the-title-bar-in-tkinter), and then bind it to a function that put the popups back on top. – TigerhawkT3 Aug 10 '15 at 19:02
  • I got it to keep the popups on top by using a recurring call to `after()` to `lift()` them all on a timer, but at this point I'm wondering if it might be better to use ordinary widgets for the popups and `place()` them in a `Canvas` or something instead of having a bunch of separate windows. Then they would certainly stay put in the main window. – TigerhawkT3 Aug 10 '15 at 19:13

2 Answers2

0

change this:

    if dialogs: # if there are any dialogs left:
        for widget in dialogs: # go through each widget
            widget.lift(aboveThis=self.root) # and lift it above the root

to this:

    if dialogs: # if there are any dialogs left:
        for widget in dialogs: # go through each widget
            widget.lift() # and lift it above the root

the widgets will stay above the main window.

EDIT:

Sorry that only half worked... the widows will stay above sometimes with that code

:-X

It was keeping the widgets on top until you closed one of them.... this code does keep the widgets on top

it uses the self.attributes("-topmost", True) when you spawn the windows.

Sorry again.

from Tkinter import *

class PropertyDialog(Toplevel):
    def __init__(self, root, string):
        Toplevel.__init__(self)
        self.wm_overrideredirect(1)
        self.root = root 
        self.\
             geometry('+%d+%d' %
                      (root.winfo_pointerx(),
                       root.winfo_pointery()))
        try:
            self.tk.call('::Tk::unsupported::MacWindowStyle',
                                         'style', self._w,
                                         'help', 'noActivates')
        except TclError:
            pass
        window_frame = Frame(self)
        window_frame.pack(side=TOP, fill=BOTH, expand=True)
        exit_frame = Frame(window_frame, background='#ffffe0')
        exit_frame.pack(side=TOP, fill=X, expand=True)
        button = Button(exit_frame, text='x', width=3, command=self.free,
               background='#ffffe0', highlightthickness=0, relief=FLAT)
        button.pack(side=RIGHT)
        text_frame = Frame(window_frame)
        text_frame.pack(side=TOP, fill=BOTH, expand=True)
        label = Label(text_frame, text=string, justify=LEFT,
                      background='#ffffe0',
                      font=('tahoma', '8', 'normal'))
        label.pack(ipadx=1)
        self.attributes("-topmost", True)

    def free(self):
        self.destroy() # first we destroy this one

def bind():
    """
    toggle property window creation mode
    """
    root.bind('<ButtonPress-1>', create)


def create(event):
    """
    Create actual window upon mouse click
    """
    dialogs.append(PropertyDialog(root, 'help me'))

root = Tk()
dialogs = []
root.geometry('%dx%d' % (300,400))

Button(root, text='create', command=bind).pack()

root.mainloop()
Alex Jadczak
  • 550
  • 3
  • 11
0

I recommend moving away from Toplevel widgets, since those are separate windows and you're suppressing their window-like behavior. This version makes PropertyDialog inherit from Frame instead of Toplevel, using the place() geometry manager. When you click the main window, it first checks whether the widget clicked was the main window or a popup window to prevent a new popup from appearing when you close an existing one. Changed areas are marked with #CHANGED#.

from Tkinter import *

class PropertyDialog(Frame): #CHANGED#
    def __init__(self, root, string, event): #CHANGED#
        Frame.__init__(self) #CHANGED#
        self.root = root 
        try:
            self.tk.call('::Tk::unsupported::MacWindowStyle',
                                         'style', self._w,
                                         'help', 'noActivates')
        except TclError:
            pass
        exit_frame = Frame(self, background='#ffffe0') #CHANGED#
        exit_frame.pack(side=TOP, fill=X, expand=True)
        button = Button(exit_frame, text='x', width=3, command=self.free,
               background='#ffffe0', highlightthickness=0, relief=FLAT)
        button.pack(side=RIGHT)
        text_frame = Frame(self) #CHANGED#
        text_frame.pack(side=TOP, fill=BOTH, expand=True)
        label = Label(text_frame, text=string, justify=LEFT,
                      background='#ffffe0',
                      font=('tahoma', '8', 'normal'))
        label.pack(ipadx=1)
        self.place(x=event.x, y=event.y, anchor=NW) #CHANGED#

    def free(self):
        self.destroy()
        # other things you want to do - if there's nothing else,
        # just bind the close button to self.destroy

def bind():
    """
    toggle property window creation mode
    """
    root.bind('<ButtonPress-1>', create)


def create(event):
    """
    Create actual window upon mouse click
    """
    if event.widget is root: #CHANGED#
        dialogs.append(PropertyDialog(root, 'help me', event))

root = Tk()
dialogs = []
root.geometry('%dx%d' % (300,400))

Button(root, text='create', command=bind).pack()

root.mainloop()
TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97