【问题标题】:Forcing tkinter window to render below everything else with native Python使用本机 Python 强制 tkinter 窗口在其他所有内容下方呈现
【发布时间】:2021-08-15 04:32:53
【问题描述】:

我想创建一个位于所有其他窗口下方的 tkinter 窗口,并且无法使用 Window 的 SetWindowPos() 函数进行交互。我希望下面的代码可以解决问题,但是出现的窗口可以调整大小,可以接受焦点,接收鼠标点击,并且不在其他所有内容之下。

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()

【问题讨论】:

  • 我现在不在windows机器上,所以我不能运行代码,但同时这里有一些建议:Tkinter应用程序开发应该遵循(1)创建的一般流程使用tk.Tk() 的根小部件,(2) 在根上创建小部件,(3) 使用packgridplace 将小部件放置在根上,以及(4) 运行root.mainloop()。以这种方式重构您的代码可能会神奇地解决您的所有问题。我稍后会回到这个问题来添加更多细节!看这里:docs.python.org/3/library/…
  • 使用mainloop() 并在.after() 回调中安排SetWindowPos() 调用不能解决问题。
  • @boof:按照你的建议,没有什么神奇的方法。
  • 我认为root.update_idletasks() 调用是不必要的/多余的,因为root.update() 应该处理 Tk 事件循环的完整迭代。
  • 没有理由打电话给update_idle_tasksupdate()。您没有实际的 tkinter 应用程序正在执行其mainloop()SetWindowPos() 调用似乎没有任何效果,无论您调用一次还是多次。

标签: python windows tkinter tk user32


【解决方案1】:

根据您的cmets,我相信您误解了SetWindowPos的使用。你想让你的窗口不能调整大小,并将 zorder 设置为底部加上一些其他的东西。

这个 api 调用做了 3 件事,设置 zorder、大小和位置。但是,如果您只想设置其中的 1 或 2 个,您可以使用标志来表示“不要执行该项目”。它不会使窗口不再做那些事情。我相信您需要使用不同的 api 调用。

在您上面的代码中,您实际上是在说不要执行调用要执行的 3 件事中的任何一个,这会使调用变为 NOP。将所有 3 个一起使用似乎是一个编程错误。

我猜windows api 没有任何其他方式来获得可选参数。他们可能应该将该调用分成 3 个单独的调用。

对于您想要的,我认为您正在寻找不同的 api 调用,可能 SetWindowLongPtr 就是您正在寻找的。​​p>

在 tcl twapi 代码中,有 2 个小程序用于设置额外的样式位。您可以在 zip 中下载 twapi 源代码

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

然后在文件 ui.tcl 中有一些设置样式的代码。

# 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
}

【讨论】:

  • 是的,似乎是这样。不幸的是,没有可以满足我需要的窗口样式(或扩展样式),为此需要一些黑魔法:codeproject.com/Articles/856020/…
  • 现在我必须将这些调用转换为 ctypes,并弄清楚如何让 Tkinter 生成一个以 WorkerW 作为父窗口的窗口。
  • 抱歉,从 Tk 邮件列表中获得了一些帮助,但没有更新帖子。
  • 是的,样式很复杂,但我编辑了我的帖子以包含 2 个处理样式的 tcl/twapi 程序,也许它会给你一个关于如何做这些事情的提示。我没有找到这些样式位是在哪里定义的。
  • 在 SetWindowLongPtr 上的文档中,偏移量的描述中有 2 个指向样式和扩展样式位设置的超链接
【解决方案2】:

横向思考,grab_* 会成为您的用例的解决方案吗?您将创建一个窗口,将其设置为背景和正确的大小,然后在其上创建一个新窗口并执行grab_set,强制应用程序中的交互仅在新窗口及其子窗口上发生。这种模式通常用于对话框窗口和弹出框,以强制用户与特定窗口进行交互,但它也可以在这里工作,具体取决于您真正想要做什么。

【讨论】:

  • 抱歉,这根本不是我想要的。我正在尝试模拟 Rainmeter 等应用的行为。
猜你喜欢
  • 2021-08-08
  • 2010-12-27
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-15
  • 1970-01-01
相关资源
最近更新 更多