I want to create my own virtual keyboard. However, it always happens that the focus is taken from another window as soon as a button is pressed or the window is opened. How can I work around this problem?
Below I have created a small example with which you can reproduce the problem.
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_())
So far I've tried changing the focus settings of the button button.setFocusPolicy(Qt.NoFocus)
.
As well as to set the window flags or the attribute:
self.setWindowFlags(Qt.WindowDoesNotAcceptFocus|Qt.WindowStaysOnTopHint).
self.setAttribute(Qt.WA_ShowWithoutActivating)
I may also need the User32 dll to solve the problem (https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlonga). How do I get handle to the window (=hwnd)?
What is the best and easiest way to reach my goal? Maybe this is already integrated in PYQT5, but I couldn't find anything about it.
For tkinter I found the following. And I'm trying the exact same thing with 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()
I solved the problem in Windows by disabling the window via User32.dll. To do this, I simply pass the 'HWND' handler to the 'SetWindowLongW' function and then set a new extended window style. All information can be seen on the following link:
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowlongw
Update: Below is the working example:
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_())