我写了一个 Tkinter 应用程序,我想添加屏幕截图,所以我从 GitHub (screen-snip) 找到了一个用 PyQt 编写的单独程序,我正在 在我的 Tkinter 应用程序中导入并使用它。然后我决定将这些程序结合起来,以便提出一个关于为什么它们不能完全协同工作的问题。我学会了不要将 Tk 和 Qt 结合起来。
所以现在我的问题是,我应该用 Qt 还是 Tk 重写我的程序?
对于这种情况,哪个更好?当您选择图像文件时,我当前的混合 Tk/Qt 程序可以工作,但现在 Qt 的屏幕截图部分
class MyWidget(QtWidgets.QWidget):
会导致它冻结然后崩溃。我认为问题可能是 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()
您所需要的只是一个全屏顶层,其中包含带有可拖动矩形的画布,如
在 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()
我有
一个完整的示例,展示了如何使用 tkinter 和 PyQt6 将 tk 小部件嵌入到 QWidget 中。它还演示了 tkwidget.mainloop()
或
qApp.exec()
均可用于“旋转”事件循环。不过,最好使用
qApp.exec()
,否则 Qt 的
deleteLater
对象将不会在不采取额外步骤的情况下被删除。