【问题标题】:Keep console focus when calling mainloop with tkinter in Windows在 Windows 中使用 tkinter 调用 mainloop 时保持控制台焦点
【发布时间】:2021-10-10 22:09:32
【问题描述】:

我正在使用 cmd.exe 运行一个打开 tkinter 窗口的程序。当调用 root.mainloop() 函数时,我想将注意力集中在控制台上。下面的示例代码。这是在带有 Python 3.9 的 Window 系统上运行的。

# gui thread
def guiTask():
  print("doing tasks here")
  root.after(50, guiTask)

# main GUI
root = tk.Tk()
root.geometry('%dx%d' % (800, 800))
root.after(50, guiTask)
root.mainloop()

所以这段代码只会在打开 tkinter 窗口后每 50 毫秒打印一次。执行时焦点转到此窗口。我希望焦点保持在控制台上。这可能吗?

【问题讨论】:

  • 在创建tkinter窗口之前可以使用hwnd = ctypes.windll.user32.GetForegroundWindow()获取当前窗口的窗口句柄,在tkinter窗口出现后使用ctypes.windll.user32.SetForegroundWindow(hwnd)

标签: python tkinter command-line-interface


【解决方案1】:

仅限 Windows(据我所知):
好吧,我找到了一种取自Get HWND of each Window?(问题本身)的方法(部分(部分是指函数的大部分)):

import tkinter as tk


def win_focus_set(win_name):
    import ctypes
    enum_windows = ctypes.windll.user32.EnumWindows
    enum_windows_proc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
    get_window_text = ctypes.windll.user32.GetWindowTextW
    get_window_text_length = ctypes.windll.user32.GetWindowTextLengthW
    is_window_visible = ctypes.windll.user32.IsWindowVisible
    windows = []

    def foreach_window(hwnd, _):
        if is_window_visible(hwnd):
            length = get_window_text_length(hwnd)
            buff = ctypes.create_unicode_buffer(length + 1)
            get_window_text(hwnd, buff, length + 1)
            windows.append((hwnd, buff.value))
        return True

    enum_windows(enum_windows_proc(foreach_window), 0)
    for hwnd, name in windows:
        if win_name in name:
            ctypes.windll.user32.BringWindowToTop(hwnd)


def gui_task():
    print("doing tasks here")
    root.after(50, gui_task)


# main GUI
root = tk.Tk()
root.geometry('%dx%d' % (800, 800))
root.after(50, gui_task)
root.after(1, win_focus_set, 'cmd.exe')
root.mainloop()

基本上它就是那个函数,然后尽快使用.after 调用它,这样它就会在主窗口出现后立即执行(同样"cmd.exe" 只需要在窗口的标题中(所以如果您打开了两个 cmd,它可能会切换到另一个(尽管如果它们都具有相同的标题,即使您提供了完整的标题也可能发生这种情况)并且 .after 将其余参数作为参数传递给预定函数)

另一种选择是获取当前焦点所在的窗口,并在tkinter 启动后将其置于顶部:

import tkinter as tk
import ctypes

hwnd = ctypes.windll.user32.GetForegroundWindow()


def gui_task():
    print("doing tasks here")
    root.after(50, gui_task)


# main GUI
root = tk.Tk()
root.geometry('%dx%d' % (800, 800))
root.after(50, gui_task)
root.after(1, ctypes.windll.user32.BringWindowToTop, hwnd)
root.mainloop()

【讨论】:

  • 嗯..首先想到的是多个 cmd.exe 问题。我想知道是否有更便携和防弹的方法来做到这一点,而不是使用 tkinter 的功能?
  • @jliu83 除非您使用 tkinter 创建窗口,否则您无法从 tkinter 真正使用它,但是您可以使用 cmets 中的建议,我将尝试将其包含在答案中太
  • @jliu83 我已经添加了另一种更短的方法,顺便说一句,.after“循环”不是线程,它们不会同时运行而是异步运行
  • 感谢您阐明线程与并发的区别。您的第二个解决方案效果很好。
  • @jliu83 太好了,顺便说一句,线程和并发在线程并发运行的意义上是一回事,这意味着您可以同时执行多个线程,异步意味着该程序有一些事件循环来处理事件,如果一个事件必须处理太长时间,它将阻塞整个程序(您可以通过将print替换为for i in range(10000): print(i)之类的循环来测试它)这不会如果一个线程需要一段时间才能执行,那么另一个线程仍然可以运行,这只是一个技术性问题,但是......
猜你喜欢
  • 1970-01-01
  • 2011-03-14
  • 2014-11-22
  • 2020-02-24
  • 2023-03-09
  • 1970-01-01
  • 2010-11-17
  • 1970-01-01
  • 2011-07-01
相关资源
最近更新 更多