본문으로 바로가기

출처: https://stackoverflow.com/questions/11044348/integrating-tkinter-into-pyqt-or-painting-in-pyqt/79081176#79081176

 

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