我应该用 PyQt 重写我的 Tkinter,还是反之亦然?

问题描述 投票:0回答:2

我写了一个 Tkinter 应用程序,我想添加屏幕截图,所以我从 GitHub (screen-snip) 找到了一个用 PyQt 编写的单独程序,我正在 在我的 Tkinter 应用程序中导入并使用它。然后我决定将这些程序结合起来,以便提出一个关于为什么它们不能完全协同工作的问题。我学会了不要将 Tk 和 Qt 结合起来。

所以现在我的问题是,我应该用 Qt 还是 Tk 重写我的程序?

对于这种情况,哪个更好?

当您选择图像文件时,我当前的混合 Tk/Qt 程序可以工作,但现在 Qt 的屏幕截图部分

class MyWidget(QtWidgets.QWidget):

 会导致它冻结然后崩溃。

enter image description here

我认为问题可能是 Qt 与 Tk 混合的结果,但我不确定。 我最初运行了两个 tkinter 实例,这使我能够使用新窗口获取屏幕,但导致

button

 窗口出现问题,因此我尝试使用 
tk.Toplevel
 来替换它

class MyWidget(QtWidgets.QWidget): def __init__(self, master): super().__init__() self.master = master self.window = tk.Toplevel(self.master)
就在那时我遇到了麻烦。该小部件根本不再工作,程序崩溃,没有任何线索或错误。知道为什么吗?

简化代码:

import tkinter as tk from tkinter import filedialog from PIL import ImageTk, Image, ImageGrab import sys from PyQt5 import QtWidgets, QtCore, QtGui import numpy as np import cv2 class ButtonImg: def __init__(self, master): self.newWindow = None self.master = master self.fontA = ("arial", 20, "bold") self.canvas = tk.Canvas(height = 5) self.canvas.pack() self.button = tk.Button(bg="#61B5DA", height = 5, text = "Select Image", font = self.fontA, command = self.changeImage) self.button.pack(fill="both") def changeImage(self): print('open second window') self.newWindow = tk.Toplevel(self.master) img = AcquireImage(self.newWindow) self.master.wait_window(self.newWindow) print('close second window') if img.image_selected: # check if image was selected self.image = img.image_selected self.button.configure(image=self.image, height=self.image.height()) class AcquireImage: def __init__(self, master): self.master = master self.fontA = ("arial", 20, "bold") self.frame = tk.Frame(master, bg="#96beed") self.frame.pack(fill="both", expand=True) self.button1 = tk.Button(self.frame, text="Select Image File", padx=5, pady=5, bg="#6179DA", font = self.fontA, command =lambda: self.show_dialogs(1)) self.button1.grid(row=0, column=0, sticky="nsew") self.button2 = tk.Button(self.frame, text="Get Screen Snip", padx=5, pady=5, bg="#6179DA", font = self.fontA, command=lambda: self.show_dialogs(2)) self.button2.grid(row=0, column=1, sticky="nsew") self.image_selected = None def show_dialogs(self, method): if method == 1: ret = filedialog.askopenfilename() #filedialog.askopenfilename(initialdir='/home/user/images/') if ret: self.image_selected = ImageTk.PhotoImage(file = ret) self.master.destroy() elif method == 2: newWin = MyWidget(self.master) newWin.show() ret = newWin.img if ret: self.image_selected = ImageTk.PhotoImage(file = ret) class MyWidget(QtWidgets.QWidget): def __init__(self, master): super().__init__() self.master = master self.window = tk.Toplevel(self.master) screen_width = self.thirdWin.winfo_screenwidth() screen_height = self.thirdWin.winfo_screenheight() self.setGeometry(0, 0, screen_width, screen_height) self.setWindowTitle(' ') self.begin = QtCore.QPoint() self.end = QtCore.QPoint() self.img = None self.setWindowOpacity(0.3) QtWidgets.QApplication.setOverrideCursor( QtGui.QCursor(QtCore.Qt.CrossCursor) ) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) print('Capture the screen...') self.show() def getRect(self): # a commodity function that always return a correctly sized # rectangle, with normalized coordinates width = self.end.x() - self.begin.x() height = abs(width * 2 / 3) if self.end.y() < self.begin.y(): height *= -1 return QtCore.QRect(self.begin.x(), self.begin.y(), width, height).normalized() def paintEvent(self, event): qp = QtGui.QPainter(self) qp.setPen(QtGui.QPen(QtGui.QColor('black'), 3)) qp.setBrush(QtGui.QColor(128, 128, 255, 128)) qp.drawRect(self.getRect()) def mousePressEvent(self, event): self.begin = event.pos() self.end = self.begin self.update() def mouseMoveEvent(self, event): self.end = event.pos() self.update() def mouseReleaseEvent(self, event): self.close() rect = self.getRect() self.img = ImageGrab.grab(bbox=( rect.topLeft().x(), rect.topLeft().y(), rect.bottomRight().x(), rect.bottomRight().y() )) #self.img.save('capture.png') self.img = cv2.cvtColor(np.array(self.img), cv2.COLOR_BGR2RGB) cv2.imshow('Captured Image', self.img) cv2.waitKey(0) #cv2.destroyAllWindows() if __name__ == '__main__': root = tk.Tk() app = ButtonImg(root) root.mainloop()
    
python tkinter pyqt5
2个回答
2
投票
正如评论中所述,最好是使用单个 GUI 工具包,因此您需要重写 Qt 代码或使用 tkinter 重写截图代码。我不太了解 Qt,所以我无法帮助您选择第一个选项。然而,屏幕截图实际上是使用 PIL 获取的,而不是某些 Qt 特定方法,因此可以在 tkinter 中重写截图代码。

您所需要的只是一个全屏顶层,其中包含带有可拖动矩形的画布,如

在 Tkinter 中使用鼠标事件绘制矩形中所示。使顶层全屏:toplevel.attributes('-fullscreen', True)


顶层需要部分透明,这可以通过

toplevel.attributes('-alpha', <value>)

来实现。我使用的是 Linux(带有 XFCE 桌面环境),我需要添加 
toplevel.attributes('-type', 'dock')
 才能使透明度起作用。

所有内容放在一个班级中,这给出:

import sys import tkinter as tk from PIL import ImageGrab import cv2 import numpy as np class MyWidget(tk.Toplevel): def __init__(self, master): super().__init__(master) self.configure(cursor='cross') if sys.platform == 'linux': self.attributes('-type', 'dock') # to make transparency work in Linux self.attributes('-fullscreen', True) self.attributes('-alpha', 0.3) self.canvas = tk.Canvas(self, bg='white') self.canvas.pack(fill='both', expand=True) self.begin_x = 0 self.begin_y = 0 self.end_x = 0 self.end_y = 0 self.canvas.create_rectangle(0, 0, 0, 0, outline='gray', width=3, fill='blue', tags='snip_rect') self.canvas.bind('<ButtonPress-1>', self.mousePressEvent) self.canvas.bind('<B1-Motion>', self.mouseMoveEvent) self.canvas.bind('<ButtonRelease-1>', self.mouseReleaseEvent) print('Capture the screen...') def mousePressEvent(self, event): self.begin_x = event.x self.begin_y = event.y self.end_x = self.begin_x self.end_y = self.begin_y self.canvas.coords('snip_rect', self.begin_x, self.begin_y, self.end_x, self.end_y) def mouseMoveEvent(self, event): self.end_x = event.x self.end_y = event.y self.canvas.coords('snip_rect', self.begin_x, self.begin_y, self.end_x, self.end_y) def mouseReleaseEvent(self, event): self.destroy() self.master.update_idletasks() self.master.after(100) # give time for screen to be refreshed so as not to see the blue box on the screenshot x1 = min(self.begin_x, self.end_x) y1 = min(self.begin_y, self.end_y) x2 = max(self.begin_x, self.end_x) y2 = max(self.begin_y, self.end_y) img = ImageGrab.grab(bbox=(x1, y1, x2, y2)) self.img = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB) cv2.imshow('Captured Image', self.img) cv2.waitKey(0) if __name__ == '__main__': root = tk.Tk() tk.Button(root, text='Snip', command=lambda: MyWidget(root)).pack() root.mainloop()
    

0
投票
对于小程序,最好坚持使用一个工具包。要移植更大的应用程序,不可避免地必须同时使用 tkinter 和 PyQt。 tkinter 的设计考虑了双向嵌入。可以在 Qt 小部件中嵌入 tkinter 小部件,反之亦然。

我有

一个完整的示例,展示了如何使用 tkinter 和 PyQt6 将 tk 小部件嵌入到 QWidget 中。它还演示了 tkwidget.mainloop()

qApp.exec()
 均可用于“旋转”事件循环。不过,最好使用 
qApp.exec()
,否则 Qt 的 
deleteLater
 对象将不会在不采取额外步骤的情况下被删除。

© www.soinside.com 2019 - 2024. All rights reserved.