Python PyQt5-撤消实现

Python PyQt5-撤消实现,python,user-interface,pyqt,pyqt5,Python,User Interface,Pyqt,Pyqt5,我需要在这个小部件中实现撤销功能,用组合键Ctrl+Z激活。我可以在输入到构造函数的图像上画线。因此,我的想法是从行列表中删除最后一项。每次我绘制一行时,我都会在此列表中添加一行,并在按Ctrl+Z时重新绘制所有其他行。如何实现此刷新?有没有更有效的方法来做这样的事情 代码: 从PyQt5导入QtWidgets,Qt 从PyQt5.QtCore导入QSize、QPoint 从PyQt5.QtGui导入QImage 将numpy作为np导入 导入系统 从PyQt5.QtCore导入Qt,QPoin

我需要在这个小部件中实现撤销功能,用组合键Ctrl+Z激活。我可以在输入到构造函数的图像上画线。因此,我的想法是从行列表中删除最后一项。每次我绘制一行时,我都会在此列表中添加一行,并在按Ctrl+Z时重新绘制所有其他行。如何实现此刷新?有没有更有效的方法来做这样的事情

代码:

从PyQt5导入QtWidgets,Qt 从PyQt5.QtCore导入QSize、QPoint 从PyQt5.QtGui导入QImage 将numpy作为np导入 导入系统 从PyQt5.QtCore导入Qt,QPoint 从PyQt5.QtWidgets导入QApplication 从PyQt5.QtGui导入QPixmap、QPainter、QPen 类距离窗口QTWidgets.QMainWindow: def u_init__self,父项=无: superDistanceWindow,self.\u init\u父 self.axial=np.random.rand512,512 打印轴形 self.axial=QPixmapQImageself.axial,self.axial.shape[1],self.axial.shape[0],QImage.Format_Indexed8 self.axialWidget=DrawWidgetself.axial 类DrawWidgetGetQTWidgets.QWidget: 定义初始自我,图像: 超级__ self.drawing=False self.startPoint=None self.endPoint=None self.image=image self.setgeometry100500300 self.resizeself.image.width、self.image.height 自我表现 self.lines=[] def鼠标presseventself,事件: 如果event.button==Qt.LeftButton: self.startPoint=event.pos def mouseMoveEventself,事件: 如果self.startPoint: self.endPoint=event.pos 自我更新 def mouseReleaseEventself,事件: 如果self.startPoint和self.endPoint: 自我更新图像 def paintEventself,事件: 油漆工 dirtyRect=event.rect painter.drawImagedirtyRect,QImageself.image,dirtyRect 如果self.startPoint和self.endPoint: painter.drawLineself.startPoint,self.endPoint def updateImageself: 如果self.startPoint和self.endPoint: painter=QPainterself.image painter.setPenQPenQt.red,2,Qt.SolidLine,Qt.RoundCap,Qt.RoundJoin painter.drawLineself.startPoint,self.endPoint firstPoint=np.array[self.startPoint.x,self.startPoint.y] secondPoint=np.array[self.endPoint.x,self.endPoint.y] 距离=np.sqrtsecondPoint[0]-firstPoint[0]**2+secondPoint[1]-firstPoint[1]**2 painter.setPenQPenQt.yellow painter.drawTextsecondPoint[0],secondPoint[1]+10,strdistance+'mm' 线路信息 行={} 行['points']=[self.startPoint,self.endPoint] 直线['distance']=距离 self.line.appendline 结束 self.startPoint=self.endPoint=None 自我更新 def按键按EventSelf,事件: 如果event.key==Qt.key_控件和Qt.key_Z: 自我撤销 def undoself: 从self.lines中删除最后一行并绘制所有其他行 如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu': app=QApplicationsys.argv 主=距离窗口 sys.exitapp.exec_ 更新

我修理了一只虫子

如果多次绘制直线,并多次按Ctrl+Z组合键,然后重新执行

你会得到最新的图像

所以,我修复我的代码,你可以一个接一个地执行。对不起

请试试这个

画完线,

然后依次按Ctrl+Z和Ctrl+Y

您可以撤消并重做实现

我想让您比较一下如何实现它们

通常,如果要实现撤消和重做,可以使用QUndoStack。 具体执行是在QUNDO命令中编写的

更新

我修理了一只虫子

如果多次绘制直线,并多次按Ctrl+Z组合键,然后重新执行

你会得到最新的图像

所以,我修复我的代码,你可以一个接一个地执行。对不起

请试试这个

画完线,

然后依次按Ctrl+Z和Ctrl+Y

您可以撤消并重做实现

我想让您比较一下如何实现它们

通常,如果要实现撤消和重做,可以使用QUndoStack。 具体执行是在QUNDO命令中编写的


当要实现撤消支持时,可撤消对象必须能够恢复其以前的状态。对于基于光栅的图像,这显然是不可能的,因为这幅画被认为是破坏性的:一旦像素颜色被改变,就无法知道它以前的状态

一种可能是存储以前的光栅状态,但不建议使用这种方法:如果始终存储完整图像,则可能会使用太多内存,并且实现仅存储已修改图像部分的系统肯定不是适合您的选择 在处理矢量图形时,最简单的方法是将更改存储为绘制例程,并仅在实际需要时保存图像,以便仅使用小部件的paintEvent绘制修改。显然,您需要修改updateImage函数以实际存储图像。 这通常要快得多,允许任意删除绘制功能

在下面的示例中,我使用的是您已经创建的self.line,但是经过一些修改,使事情变得更简单、更清晰

class DrawWidget(QtWidgets.QWidget):
    # ...

    def mouseMoveEvent(self, event):
        if self.startPoint:
            self.endPoint = event.pos()
            self.update()

    def mouseReleaseEvent(self, event):
        if self.startPoint and self.endPoint:
            line = QLineF(self.startPoint, self.endPoint)
            self.lines.append({
                'points': line, 
                'distance': line.length() * self.pixelSpacing, 
            })
            self.startPoint = self.endPoint = None
            self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHints(painter.Antialiasing)
        dirtyRect = event.rect()
        painter.drawImage(dirtyRect, QImage(self.image), dirtyRect)
        if self.startPoint and self.endPoint:
            painter.drawLine(self.startPoint, self.endPoint)
        linePen = QPen(Qt.red, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        for lineData in self.lines:
            line = lineData['points']            
            painter.setPen(linePen)
            painter.drawLine(line.p1(), line.p2())
            painter.setPen(Qt.yellow)
            painter.drawText(line.p2() + QPoint(0, 10), 
                '{}mm'.format(lineData['distance']))

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Z and event.modifiers() == Qt.ControlModifier:
            self.undo()

    def undo(self):
        if self.lines:
            self.lines.pop(-1)
            self.update()
关于修改的一些注释。 你做的事绝对不需要NumPy。正如您所看到的,您只需使用Qt的类和函数就可以解决所有需要的问题;最重要的是,在本例中,我使用的是a,这是两点之间浮点精度向量的抽象表示。两点之间的距离可以通过QLineFp1,p2.length获得。虽然这显然比python的math或numpy的函数慢一点,但在这种情况下使用QLine肯定会更好,原因如下:无论如何,您都需要一条线,您不需要30-40mb的python模块来计算勾股距离,它是一个表示单个对象的单个对象,它使代码更简单。 键事件不能用于二进制运算符,因为它们是整数,而不是二进制标志:事实上,即使只按Z键或使用其他修饰符,代码也会调用undo;Ctrl键是一个组合键,因此在查找键盘组合时不能与标准键组合,因此需要进行检查。 这显然是一个非常基本的实现,您可以通过存储当前命令的索引来添加重做支持


最后,对于更复杂的用户案例,还有一个,它比您可能需要的要复杂一点,但了解它并理解何时真正需要它仍然很重要。

当要实现撤销支持时,可撤销对象必须能够恢复其以前的状态。对于基于光栅的图像,这显然是不可能的,因为这幅画被认为是破坏性的:一旦像素颜色被改变,就无法知道它以前的状态

一种可能是存储以前的光栅状态,但不建议使用这种方法:如果始终存储完整图像,则可能会使用太多内存,并且实现仅存储已修改图像部分的系统肯定不是适合您的选择

在处理矢量图形时,最简单的方法是将更改存储为绘制例程,并仅在实际需要时保存图像,以便仅使用小部件的paintEvent绘制修改。显然,您需要修改updateImage函数以实际存储图像。 这通常要快得多,允许任意删除绘制功能

在下面的示例中,我使用的是您已经创建的self.line,但是经过一些修改,使事情变得更简单、更清晰

class DrawWidget(QtWidgets.QWidget):
    # ...

    def mouseMoveEvent(self, event):
        if self.startPoint:
            self.endPoint = event.pos()
            self.update()

    def mouseReleaseEvent(self, event):
        if self.startPoint and self.endPoint:
            line = QLineF(self.startPoint, self.endPoint)
            self.lines.append({
                'points': line, 
                'distance': line.length() * self.pixelSpacing, 
            })
            self.startPoint = self.endPoint = None
            self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHints(painter.Antialiasing)
        dirtyRect = event.rect()
        painter.drawImage(dirtyRect, QImage(self.image), dirtyRect)
        if self.startPoint and self.endPoint:
            painter.drawLine(self.startPoint, self.endPoint)
        linePen = QPen(Qt.red, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        for lineData in self.lines:
            line = lineData['points']            
            painter.setPen(linePen)
            painter.drawLine(line.p1(), line.p2())
            painter.setPen(Qt.yellow)
            painter.drawText(line.p2() + QPoint(0, 10), 
                '{}mm'.format(lineData['distance']))

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Z and event.modifiers() == Qt.ControlModifier:
            self.undo()

    def undo(self):
        if self.lines:
            self.lines.pop(-1)
            self.update()
关于修改的一些注释。 你做的事绝对不需要NumPy。正如您所看到的,您只需使用Qt的类和函数就可以解决所有需要的问题;最重要的是,在本例中,我使用的是a,这是两点之间浮点精度向量的抽象表示。两点之间的距离可以通过QLineFp1,p2.length获得。虽然这显然比python的math或numpy的函数慢一点,但在这种情况下使用QLine肯定会更好,原因如下:无论如何,您都需要一条线,您不需要30-40mb的python模块来计算勾股距离,它是一个表示单个对象的单个对象,它使代码更简单。 键事件不能用于二进制运算符,因为它们是整数,而不是二进制标志:事实上,即使只按Z键或使用其他修饰符,代码也会调用undo;Ctrl键是一个组合键,因此在查找键盘组合时不能与标准键组合,因此需要进行检查。 这显然是一个非常基本的实现,您可以通过存储当前命令的索引来添加重做支持


最后,对于更复杂的用户案例,还有一个例子,它比您可能需要的有点复杂,但了解它并理解何时真正需要它仍然很重要。

这需要一个最小的可重复的例子。请让人们可以直接运行代码并测试它。@JussiNurminen谢谢,我添加代码是为了复制示例。这需要一个最小的可复制示例。请让人们可以直接运行代码并测试它。@JussiNurminen谢谢,我添加代码是为了复制示例非常感谢,这似乎是一个非常合理的解决方案,适合我的情况。我最近开始使用PyQt5,所以我还是很缺乏经验。谢谢你的帮助!谢谢你
欧非常,这似乎是一个非常合理的解决方案,适合我的情况。我最近开始使用PyQt5,所以我还是很缺乏经验。谢谢你的帮助!非常感谢您提供此替代解决方案,重做功能的想法非常有趣非常感谢您提供此替代解决方案,重做功能的想法非常有趣