Forcing tkinter window to render below everything else with native Python

Viewed 270

I want to create a tkinter window that sits beneath all other windows and can't be interacted with using Window's SetWindowPos() function. I would expect the following code to do the trick, but the window that appears is resizable, can accept focus, receives mouse clicks, and is not below everything else.

import time
import ctypes.wintypes
from ctypes import c_bool, c_int
from ctypes.wintypes import HWND, UINT
import tkinter

# Create a window and get its HWND
root = tkinter.Tk()
hwnd = root.winfo_id()

# Flags to move the window to the bottom and keep it from being touched
SWP_NOSIZE          = 0x0001
SWP_NOMOVE          = 0x0002
SWP_NOACTIVATE      = 0x0010
SWP_NOOWNERZORDER   = 0x0200
SWP_NOSENDCHANGING  = 0x0400

swpFlags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING

SetWindowPos = ctypes.windll.user32.SetWindowPos
SetWindowPos.argtypes = [HWND, HWND, c_int, c_int, c_int, c_int, UINT]
SetWindowPos.restype = c_bool

HWND_BOTTOM = 1

root.update()
SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 400, 600, swpFlags)

while True:
    time.sleep(0.1)
    root.update_idletasks()
    root.update()
2 Answers

Based on your comments, I believe you have misunderstood the use of SetWindowPos. You want to make your window be not resizable and set the zorder to the bottom plus a few other things.

This api call does 3 things, set zorder, size, and position. However, should you want to only set 1 or 2 of these, you use the flags to say, "don't do that item". It does not make the window no longer do those things. I believe you need to use a different api call.

In your code above, you are actually saying don't do any of the 3 things the call is to do, which would make the call into a NOP. Using all 3 together would seem to be a programming error.

I guess the windows api doesn't have any other way to have optional arguments. They probably should have broken that call into 3 separate ones.

For what you want, I think you are looking for a different api call, likely SetWindowLongPtr is what you are looking for.

In the tcl twapi code, there are 2 small procedures that are used to set the extra style bits. You can download twapi source in a zip at

``https://sourceforge.net/projects/twapi/files/Development%20Releases/

Then in the file ui.tcl there is some code for setting the styles.

# Update the frame - needs to be called after setting certain style bits
proc twapi::redraw_window_frame {hwin} {
    # 0x4037 -> SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE |
    #    SWP_NOMOVE | SWP_NOSIZE |
    #    SWP_NOZORDER | SWP_FRAMECHANGED
    SetWindowPos $hwin 0 0 0 0 0 0x4037
}

proc twapi::set_window_style {hwin style exstyle} {
    # GWL_STYLE -> -16, GWL_EXSTYLE -20
    set style [SetWindowLongPtr $hwin -16 $style]
    set exstyle [SetWindowLongPtr $hwin -20 $exstyle]

    redraw_window_frame $hwin
    return
}

Thinking laterally, would grab_* be a solution for your use case? You would create a window, set it up to be in the background and the right size, then make a new window on top of it and do grab_set, forcing interaction in the app to occur only on the new window and its children. This modality is usually used for dialog windows and popup boxes to force the user to interact with the specific window, but it may work here too, depending on what you are really trying to do.

Related