PYQT5: Window 没有获得焦点

PYQT5: Window does not get the focus

我想创建自己的虚拟键盘。然而,当按下按钮或打开 window 时,总是会发生焦点从另一个 window 转移的情况。 我该如何解决这个问题?

下面我创建了一个小示例,您可以使用它来重现该问题。

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSlot


class App(QWidget):

    def __init__(self):
        super().__init__()
        self.title = 'Keyboard'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 200
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        button = QPushButton('1', self)
        button.move(100, 70)
        button.clicked.connect(self.on_click)

        self.show()

    @pyqtSlot()
    def on_click(self):
        print('PyQt5 button click')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

到目前为止,我已经尝试更改按钮的焦点设置 button.setFocusPolicy(Qt.NoFocus)。 以及设置 window 标志或属性:

self.setWindowFlags(Qt.WindowDoesNotAcceptFocus|Qt.WindowStaysOnTopHint).
self.setAttribute(Qt.WA_ShowWithoutActivating)

我可能还需要User32 dll来解决问题(https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlonga)。如何获取 window (=hwnd) 的句柄?

实现我的目标的最好和最简单的方法是什么?也许这已经集成在 PYQT5 中,但我找不到任何关于它的信息。

对于 tkinter,我发现了以下内容。我正在用 Pyqt5 尝试完全相同的事情!

import tkinter as tk
from ctypes import windll, wintypes

GWL_STYLE = -16
GWL_EXSTYLE = -20
WS_CHILD = 0x40000000
WS_EX_APPWINDOW = 0x00040000
WS_EX_TOOLWINDOW = 0x00000080
WS_EX_NOACTIVATE = 0x08000000

SWP_FRAMECHANGED = 0x0020
SWP_NOACTIVATE = 0x0010
SWP_NOMOVE = 0x0002
SWP_NOSIZE = 0x0001

# write short names for functions and specify argument and return types
GetWindowLong = windll.user32.GetWindowLongW
GetWindowLong.restype = wintypes.ULONG
GetWindowLong.argtpes = (wintypes.HWND, wintypes.INT)

SetWindowLong = windll.user32.SetWindowLongW
SetWindowLong.restype = wintypes.ULONG
SetWindowLong.argtpes = (wintypes.HWND, wintypes.INT, wintypes.ULONG)

SetWindowPos = windll.user32.SetWindowPos

def find_root_window(win): # takes tkinter window ref
    w_id = win.winfo_id() # gets handle
    style = GetWindowLong(w_id, GWL_STYLE) # get existing style
    newstyle = style & ~WS_CHILD # remove child style
    res = SetWindowLong(w_id, GWL_STYLE, newstyle) # set new style
    res = SetWindowPos(w_id, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
    hwnd = int(root.wm_frame(), 16) # find handle of parent
    res = SetWindowLong(w_id, GWL_STYLE, style) # set back to old style
    res = SetWindowPos(w_id, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
    return hwnd # return parents handle

def set_no_focus(hwnd):
    style = GetWindowLong(hwnd, GWL_EXSTYLE) # get existing style
    style = style & ~WS_EX_TOOLWINDOW # remove toolwindow style
    style = style | WS_EX_NOACTIVATE | WS_EX_APPWINDOW
    res = SetWindowLong(hwnd, GWL_EXSTYLE, style)
    res = SetWindowPos(hwnd, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)

def push_me():
    print('you pushed me!')

def focus_me(event):
    root.focus_force()

root = tk.Tk()
root.wm_attributes("-topmost", 1)
tk.Button(root, text="Push me", command=push_me).pack()
e = tk.Entry(root)
e.pack()
e.bind('<Button-1>', focus_me) # if we have a widget that must have focus it needs to be bound
root.update() # for some reason, window style is messed with after window creation, update to get past this
hwnd = find_root_window(root)
if hwnd:
    set_no_focus(hwnd)
root.mainloop()

我通过 User32.dll 禁用 window 解决了 Windows 中的问题。为此,我只需将 'HWND' 处理程序传递给 'SetWindowLongW' 函数,然后设置一个新的扩展 window 样式。 所有资料可见于以下link:

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowlongw

更新: 下面是工作示例:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSlot,Qt
import ctypes
User32 = ctypes.WinDLL('User32.dll')

class App(QWidget):

    def __init__(self):
        super().__init__()
        self.title = 'Keyboard'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 200
        self.initUI()
        self.setWindowFlags(Qt.WindowDoesNotAcceptFocus | Qt.WindowStaysOnTopHint)
        self.setAttribute(Qt.WA_ShowWithoutActivating)
        print(int(self.winId()))
        self.update()
        self.show()
        print(User32.SetWindowLongW(int(self.winId()), -20, 134217728))

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        button = QPushButton('1', self)
        button.move(100, 70)
        button.clicked.connect(self.on_click)

        self.show()

    @pyqtSlot()
    def on_click(self):
        print('PyQt5 button click')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())