PyQt 위젯에 Tkinter를 탑재하는 방법...
# A demo of capturing a Tk widget into a Qt Widget
from win32more import cast, POINTER, c_void_p, UInt32
from win32more.Windows.Win32.UI.Input.KeyboardAndMouse import SetFocus
from win32more.Windows.Win32.UI.WindowsAndMessaging import (
MSG, WM_USER,
HWND_TOP, SWP_SHOWWINDOW, SWP_NOMOVE, SWP_NOZORDER, SWP_NOACTIVATE,
SetWindowPos, SetParent,
)
import math
import tkinter as tk
from tkinter import _tkinter
import PyQt6
from PyQt6.QtCore import Qt
from PyQt6 import QtCore as qtc, QtGui as qtg, QtWidgets as qtw
TK_CLAIMFOCUS = (WM_USER) # an embedded window requests to focus
TK_GEOMETRYREQ = (WM_USER+1) # an embedded window requests to change size
TK_ATTACHWINDOW = (WM_USER+2) # an embedded window requests to attach
TK_DETACHWINDOW = (WM_USER+3) # an embedded window requests to detach
TK_MOVEWINDOW = (WM_USER+4) # an embedded window requests to move
TK_RAISEWINDOW = (WM_USER+5) # an embedded window requests to raise
TK_ICONIFY = (WM_USER+6) # an embedded window requests to iconify
TK_DEICONIFY = (WM_USER+7) # an embedded window requests to deiconify
TK_WITHDRAW = (WM_USER+8) # an embedded window requests to withdraw
TK_GETFRAMEWID = (WM_USER+9) # an embedded window requests a frame window id
TK_OVERRIDEREDIRECT = (WM_USER+10) # an embedded window requests to overrideredirect
TK_SETMENU = (WM_USER+11) # an embedded window requests to setup menu
TK_STATE = (WM_USER+12) # an embedded window sets/gets state
TK_INFO = (WM_USER+13) # an embedded window requests a container's info
# The following are sub-messages (wParam) for TK_INFO. An embedded window may
# send a TK_INFO message with one of the sub-messages to query a container
# for verification and availability
TK_CONTAINER_VERIFY = 0x01
TK_CONTAINER_ISAVAILABLE = 0x02
class TkHost(qtw.QWidget):
hwnd = 0
scaling: float = 1.0
tkwin: tk.Tk | None = None
timer: qtc.QBasicTimer = qtc.QBasicTimer()
def __init__(self, parent=None, flags=Qt.WindowType(0), **kwargs):
super().__init__(parent, flags)
if kwargs.get('nopaint', False):
self.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent)
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
def setTkWin(self, tkwin: tk.Tk):
self.tkwin = tkwin
self.scaling = float(tkwin.eval("tk scaling"))
def useId(self):
return hex(self.winIdH())
def winIdH(self):
return int(self.winId())
def _process_tk_events(self):
if self.tkwin:
while self.tkwin.tk.dooneevent(_tkinter.DONT_WAIT):
pass
def event(self, e: qtc.QEvent):
rc = super().event(e)
if e.type() == qtc.QEvent.Type.Timer and e.timerId() == self.timer.timerId():
self.timer.stop()
self._process_tk_events()
return rc
def nativeEvent(self, eventType, message):
if not self.timer.isActive():
self.timer.start(0, self)
msg = MSG.from_address(int(message))
if msg.message < TK_CLAIMFOCUS or msg.message > TK_INFO:
return (False, 0)
if msg.message == TK_INFO:
print(f"TK_INFO {msg.wParam}")
if msg.wParam == TK_CONTAINER_VERIFY:
return (True, self.winIdH())
elif msg.wParam == TK_CONTAINER_ISAVAILABLE:
return (True, 1)
else:
return (True, 0)
elif msg.message == TK_CLAIMFOCUS:
print(f"TK_CLAIMFOCUS {msg.wParam:x}")
if msg.wParam:
self.setFocus(Qt.FocusReason.OtherFocusReason)
return (True, 1)
elif msg.message == TK_ATTACHWINDOW:
print(f"TK_ATTACHWINDOW {msg.wParam:x}")
self.hwnd = msg.wParam
rc = SetParent(self.hwnd, self.winIdH())
if not rc:
raise WindowsError
return (True, 1)
elif msg.message == TK_MOVEWINDOW:
print(f"TK_MOVEWINDOW {msg.wParam:x} {msg.lParam:x}")
if msg.wParam >= 0 and msg.lParam >= 0:
self.move(msg.wParam, msg.lParam)
return (True, 1)
else:
return (True, (self.x() << 16 & 0xffff0000) | (self.y() & 0xffff))
elif msg.message == TK_SETMENU:
print(f"TK_SETMENU {msg.wParam:x} {msg.lParam:x}")
return (True, 1)
elif msg.message == TK_STATE:
print(f"TK_STATE {msg.wParam:x} {msg.lParam:x}")
if msg.wParam < 0 or msg.wParam > 3:
if self.isFullScreen():
return (True, 1+2) # zoon
elif self.isMinimized():
return (True, 1+3) # icon
elif self.isVisible():
return (True, 1+1) # normal
else:
return (True, 1+0) # withdrawn
return (True, 1)
elif msg.message == TK_GEOMETRYREQ:
print(f"TK_GEOMETRYREQ {msg.wParam} {msg.lParam}")
cm = self.contentsMargins()
msg.wParam += cm.left() + cm.right()
msg.lParam += cm.top() + cm.bottom()
self.resize(msg.wParam, msg.lParam)
return (True, 1)
else:
print(msg.message-WM_USER)
return (False, 0)
def focusInEvent(self, e: qtg.QFocusEvent) -> None:
if self.hwnd:
SetFocus(self.hwnd)
def closeEvent(self, e: qtg.QCloseEvent) -> None:
if self.tkwin and self.testAttribute(Qt.WidgetAttribute.WA_QuitOnClose):
self.tkwin.quit()
def showEvent(self, e: qtg.QShowEvent) -> None:
if self.hwnd:
self._setWindowPos(SWP_SHOWWINDOW)
def resizeEvent(self, e: qtg.QResizeEvent) -> None:
if self.hwnd:
self._setWindowPos(SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE)
def _setWindowPos(self, flags: int):
cm = self.contentsMargins()
size = self.windowHandle().geometry().size()
SetWindowPos(self.hwnd, HWND_TOP,
math.ceil(cm.left() * self.scaling),
math.ceil(cm.top() * self.scaling),
math.ceil(size.width() * self.scaling),
math.ceil(size.height() * self.scaling),
flags)
def main():
app = qtw.QApplication([])
app.processEvents()
qwin = TkHost(nopaint=True)
qwin.show()
qwin.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
app.tkwin = tk.Tk(use=qwin.useId())
qwin.setTkWin(app.tkwin)
label = tk.Label(app.tkwin, text="Hello!")
label.grid()
label = tk.Label(app.tkwin, text="Foo")
label.grid()
# app.exec()
app.tkwin.mainloop()
app.processEvents()
main()
'블로그 (Blog) > 개발로그 (Devlogs)' 카테고리의 다른 글
Vector Database (벡터 데이터베이스) (0) | 2025.01.31 |
---|---|
누락된 DLL 검색 (0) | 2025.01.23 |
CEF + MinGW + Qt (0) | 2025.01.12 |
QtAV for Qt 6.x (0) | 2024.12.26 |
FFmpeg 5.1.6 for msys2 (0) | 2024.12.24 |