89-事件与事件的处理函数-鼠标事件和键盘事件

本文详细介绍了Qt编程中与鼠标事件、键盘事件、拖放事件和剪贴板操作相关的类和方法,包括QMouseEvent、QWheelEvent、QDrag和QClipboard的使用,以及如何处理拖放和上下文菜单事件,提供了多个应用实例来展示这些功能的实际应用。

鼠标事件和键盘事件

鼠标事件和键盘事件是用得最多的事件,通过鼠标和键盘事件可以拖拽控件、弹出快捷菜单等。

鼠标事件QMouseEvent和滚轮事件QWheelEvent

from PySide6.QtGui import QMouseEvent, QWheelEvent

QMouseEvent(arg__1: PySide6.QtGui.QMouseEvent) -> None
QMouseEvent(type: PySide6.QtCore.QEvent.Type, localPos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], button: PySide6.QtCore.Qt.MouseButton, buttons: PySide6.QtCore.Qt.MouseButton, modifiers: PySide6.QtCore.Qt.KeyboardModifier, device: PySide6.QtGui.QPointingDevice = Default(QPointingDevice.primaryPointingDevice)) -> None
QMouseEvent(type: PySide6.QtCore.QEvent.Type, localPos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], globalPos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], button: PySide6.QtCore.Qt.MouseButton, buttons: PySide6.QtCore.Qt.MouseButton, modifiers: PySide6.QtCore.Qt.KeyboardModifier, device: PySide6.QtGui.QPointingDevice = Default(QPointingDevice.primaryPointingDevice)) -> None
QMouseEvent(type: PySide6.QtCore.QEvent.Type, localPos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], scenePos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], globalPos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], button: PySide6.QtCore.Qt.MouseButton, buttons: PySide6.QtCore.Qt.MouseButton, modifiers: PySide6.QtCore.Qt.KeyboardModifier, device: PySide6.QtGui.QPointingDevice = Default(QPointingDevice.primaryPointingDevice)) -> None
QMouseEvent(type: PySide6.QtCore.QEvent.Type, localPos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], scenePos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], globalPos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], button: PySide6.QtCore.Qt.MouseButton, buttons: PySide6.QtCore.Qt.MouseButton, modifiers: PySide6.QtCore.Qt.KeyboardModifier, source: PySide6.QtCore.Qt.MouseEventSource, device: PySide6.QtGui.QPointingDevice = Default(QPointingDevice.primaryPointingDevice)) -> None

QWheelEvent(arg__1: PySide6.QtGui.QWheelEvent) -> None
QWheelEvent(pos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], globalPos: Union[PySide6.QtCore.QPointF, PySide6.QtCore.QPoint, PySide6.QtGui.QPainterPath.Element], pixelDelta: PySide6.QtCore.QPoint, angleDelta: PySide6.QtCore.QPoint, buttons: PySide6.QtCore.Qt.MouseButton, modifiers: PySide6.QtCore.Qt.KeyboardModifier, phase: PySide6.QtCore.Qt.ScrollPhase, inverted: bool, source: PySide6.QtCore.Qt.MouseEventSource = Instance(Qt.MouseEventNotSynthesized), device: PySide6.QtGui.QPointingDevice = Default(QPointingDevice.primaryPointingDevice)) -> None

鼠标事件类QMouseEvent涉及鼠标按键的单击释放和鼠标移动操作与QMouseEvent关联的事件类型有:

  • QEvent.MouseButtonDblClick
  • QEvent.MouseButtonPress
  • QEvent.MouseButtonRelease
  • QEvent.MouseMove

当在一个窗口或控件中按住鼠标按键或释放按键时会产生鼠标事件QMouseEvent

鼠标移动事件只会在按下鼠标按键的情况下才会发生,除非通过显式调用窗口的 setMouseTracking(True)函数来开启鼠标轨迹跟踪,这种情况下只要鼠标指针移动,就会产生一系列鼠标事件。

处理QMouseEvent类鼠标事件的函数有:

  • mouseDoubleClickEvent(QMouseEvent) 双击鼠标按键
  • mouseMoveEvent(QMouseEvent) 移动鼠标
  • mousePressEvent(QMouseEvent) 按下鼠标按键
  • mouseReleaseEvent(QMouseEvent) 释放鼠标按键

鼠标滚轮的滚动事件类是QWheelEvent,处理QWheelEvent 滚轮事件的函数是 wheelEvent(QWheelEvent)。

鼠标事件QMouseEvent 的常用方法

当产生鼠标事件时,会生成 QMouseEvent 类的实例对象,并将实例对象作为实参传递给相关的处理函数。

QMouseEvent 类包含了用于描述鼠标事件的参数。

QMouseEvent类在 QtGui模块中,它的常用方法如表 4-5所示,主要方法介绍如下。

  • 用button()方法可以获取产生鼠标事件的按键,用buttons()方法获取产生鼠标事件时被按住的按键,返回值可以是:

    • Qt.NoButton

    • Qt.AllButtons

    • Qt.LeftButton

    • Qt.RightButton

    • Qt.MidButton

    • Qt.MiddleButton

    • Qt.BackButton

    • Qt.ForwardButton

    • Qt.TaskButton

    • Qt.ExtraButtoni(i=1,2,…,24)

  • 用source()方法可以获取鼠标事件的来源,返回值可以是:

    PySide6.QtCore.Qt.MouseEventSource

    此枚举描述了鼠标事件的来源,可用于确定该事件是否是源自另一个设备(如触摸屏)的人工鼠标事件。

    ConstantDescription
    Qt.MouseEventNotSynthesized最常见的值。在有此类信息的平台上,此值指示事件是响应系统中真正的鼠标事件而生成的。
    Qt.MouseEventSynthesizedBySystem表示鼠标事件是由平台从触摸事件合成的。
    Qt.MouseEventSynthesizedByQt指示鼠标事件是由Qt从未处理的触摸事件合成的。
    Qt.MouseEventSynthesizedByApplication指示鼠标事件是由应用程序合成的。这允许将应用程序生成的鼠标事件与来自系统或由Qt合成的鼠标事件区分开来。这个值是在Qt 5.6中引入的
  • 产生鼠标事件的同时,有可能按下了键盘上的 Ctrl Shift 或 Alt 等修饰键,用modifiers()方法可以获取这些键。modifiers()方法的返回值可以是:

    • Qt.NoModifier(没有修饰键)
    • Qt.ShiftModifier(Shift 键)
    • Qt.ControlModifier(Ctrl键)
    • Qt.AltModifier(Alt 键)
    • Qt.MetaModifier(Meta 键,Windows 系统为 window键)
    • Qt.KeypadModifier(小键盘上的键)
    • Qt.GroupSwitchModifier(Modeswitch 键)
  • 用deviceType()方法可以获取产生鼠标事件的设备类型,返回值是 QInputDevice.DeviceType 的枚举值可取:

    PySide6.QtGui.QInputDevice.DeviceType

    (继承enum. Flag)此枚举表示生成QPointerEvent的设备类型。

    ConstantDescription
    QInputDevice.DeviceType.Unknown无法识别设备。
    QInputDevice.DeviceType.Mouse鼠标
    QInputDevice.DeviceType.TouchScreen在这种类型的设备中,触摸表面和显示器是集成的。这意味着表面和显示器通常具有相同的尺寸,因此触摸点的物理位置和QEventPoint报告的坐标之间存在直接关系。因此,Qt允许用户同时直接与多个QWidget、QGraphicsItems或Qt Quick Items进行交互。
    QInputDevice.DeviceType.TouchPad在这种类型的设备中,触摸表面与显示器是分开的。物理触摸位置和屏幕坐标之间没有直接关系。相反,它们是相对于当前鼠标位置计算的,用户必须使用触摸板来移动这个参考点。与触摸屏不同,Qt允许用户一次只与单个QWidget或QGraphicsItem交互。
    QInputDevice.DeviceType.Stylus一种笔状设备,用于Wacom平板电脑等图形平板电脑或提供单独触控笔感应功能的触摸屏。
    QInputDevice.DeviceType.Airbrush带有拇指轮的触控笔,用于调整切向压力。
    QInputDevice.DeviceType.Puck一种类似于扁平鼠标的装置,有一个带十字毛的透明圆圈。
    QInputDevice.DeviceType.Keyboard键盘
    QInputDevice.DeviceType.AllDevices以上任何一项(用作默认过滤器值)。
  • 用flags()方法可以识别产生鼠标事件时的标识,返回值是 QtMouseEventFlags的枚举值,只可以取Qt.MouseEventCreatedDoubleClick,用于标识鼠标的双击事件

QMouseEvent的方法返回值的类型说 明
button()Qt.MouseButton获取产生鼠标事件的按键
buttons()Qt.MouseButtons获取产生鼠标事件时被按下的按键
flags()Qt.MouseEventFlags获取鼠标事件的标识
source()Qt.MouseEventSource获取鼠标事件的来源
modifiers()Qt.KeyboardModifiers获取修饰键
device()QInputDevice获取产生鼠标事件的设备
deviceType()QInputDevice.DeviceType获取产生鼠标事件的设备类型
globalPos()QPoint获取全局的鼠标位置
globalX()int获取全局的X坐标
globalY()int获取全局的Y坐标
localPos()QPointF获取局部鼠标位置
screenPos()QPointF获取屏幕的鼠标位置
windowPos()QPointF获取相对于接受事件窗口的鼠标位置
pos()QPoint获取相对于控件的鼠标位置
此方法在新版本已经会发出弃用警告改用position()
position()QPoint获取相对于控件的鼠标位置
x()int获取相对于控件的X坐标
此方法在新版本已经会发出弃用警告改用position().x()
y()int获取相对于控件的Y坐标
此方法在新版本已经会发出弃用警告改用position().y()
滚轮事件QWheelEvent的方法

滚轮事件 QWheelEvent类处理鼠标的滚轮事件,其常用方法如表所示大部分方法与QMouseEvent 的方法相同,主要不同的方法如下所述。

  • 滚轮角度

    • angleDelta() -> PySide6.QtCore.QPoint

      返回滚轮旋转的相对量,以八度为单位。正值表示滚轮向前旋转离开用户;负值表示滚轮向后旋转朝向用户。angleDelta(). y()提供自上一个事件以来常见的垂直鼠标滚轮旋转的角度。angleDelta().x()提供水平鼠标滚轮旋转的角度,如果鼠标有水平滚轮;否则保持为零。一些鼠标允许用户倾斜滚轮来执行水平滚动,一些触摸板支持水平滚动手势;这也将出现在angleDelta().x()中。

      大多数鼠标类型以15度的步长工作,在这种情况下,增量值是120的倍数;即120个单位*1/8=15度。

      然而,一些鼠标具有更精细的轮子分辨率,并发送小于120个单位(小于15度)的增量值。为了支持这种可能性,您可以累积添加事件中的增量值,直到达到120的值,然后滚动小部件,或者您可以部分滚动小部件以响应每个轮子事件。但是为了提供更原生的感觉,您应该在可用的平台上选择像素三角洲()。

      def wheelEvent(self, event):
      
          numPixels = event.pixelDelta()
          numDegrees = event.angleDelta() / 8
          if not numPixels.isNull():
              scrollWithPixels(numPixels)
      	elif not numDegrees.isNull():
              numSteps = numDegrees / 15
              scrollWithDegrees(numSteps)
      
          event.accept()
      

      在支持滚动阶段的平台上,当出现以下情况时,delta可能为空:

      • 滚动即将开始,但距离尚未改变(ScrollBegin),
      • 或滚动已结束,距离不再改变(ScrollEnd)。
    • angleDelta().y()返回两次事件之间鼠标竖直滚轮旋转的角度

    • angleDelta()x()返回两次事件之间鼠标水平滚轮旋转的角度。

    • 如果没有水平滚轮,则angleDetal().x()的值为 0,正数值表示滚轮相对于用户在向前滑动,负数值表示滚轮相对于用户在向后滑动

  • pixelDelta()方法返回两次事件之间控件在屏幕上的移动距离(单位是像素)

  • inverted()方法将 angleDelta()和 pixelDelta()的值与滚轮转之间的取值关系反向,即正数值表示滑轮相对于用户在向后滑动,负数值表示滑轮相对于用户在向前滑动。

    返回是否反转随事件传递的增量值。

    通常情况下,如果车轮顶部远离操作它的手旋转,垂直车轮将产生具有正增量值的QWheelEvent。同样,如果车轮顶部向左移动,水平车轮运动将产生具有正增量值的QWheelEvent。

    然而,在某些平台上,这是可配置的,因此上述相同的操作将产生负增量值(但具有相同的幅度)。使用反转属性,轮子事件消费者可以选择始终跟随轮子的方向,而不管系统设置如何,但仅限于特定的小部件。(一个这样的用例可能是用户按照视觉Tumbler旋转的相同方向旋转轮子。另一个用法是使滑块手柄跟随触摸板上手指的移动方向,而不管系统配置如何。)

    许多平台不提供此类信息。在这类平台上,反转总是返回错误。

  • phase()方法返回设备的状态,返回值有:

    • Qt.NoScrollPhase(不支持滚动)
    • Qt.ScrollBegin(开始位置)
    • Qt.ScrollUpdate(处于滚动状态)
    • Qt.ScrollEnd(结束位置)
    • Qt.ScrollMomentum(不触碰设备,由于惯性仍处于滚动状态)
QWheelEvent的方法返回值的类型描述
angleDelta()QPoint返回滚轮旋转的相对量,以八度为单位。正值表示滚轮向前旋转离开用户;负值表示滚轮向后旋转朝向用户。angleDelta(). y()提供自上一个事件以来常见的垂直鼠标滚轮旋转的角度。angleDelta().x()提供水平鼠标滚轮旋转的角度,如果鼠标有水平滚轮;否则保持为零。一些鼠标允许用户倾斜滚轮来执行水平滚动,一些触摸板支持水平滚动手势;这也将出现在angleDelta().x()中。

大多数鼠标类型以15度的步长工作,在这种情况下,增量值是120的倍数;即120个单位*1/8=15度。

然而,一些鼠标具有更精细的轮子分辨率,并发送小于120个单位(小于15度)的增量值。为了支持这种可能性,您可以累积添加事件中的增量值,直到达到120的值,然后滚动小部件,或者您可以部分滚动小部件以响应每个轮子事件。但是为了提供更原生的感觉,您应该在可用的平台上选择像素三角洲()。
在支持滚动阶段的平台上,当出现以下情况时,delta可能为空:
滚动即将开始,但距离尚未改变(ScrollBegin)
或滚动已结束,距离不再改变(ScrollEnd)。
pixelDelta()QPoint返回屏幕上以像素为单位的滚动距离。此值在支持高分辨率基于像素的增量值的平台(例如macOS)上提供。该值应直接用于在屏幕上滚动内容。
phase()Qt.ScrollPhase返回此轮子事件的滚动阶段。
inverted()bool返回是否反转随事件传递的增量值。

通常情况下,如果车轮顶部远离操作它的手旋转,垂直车轮将产生具有正增量值的QWheelEvent。同样,如果车轮顶部向左移动,水平车轮运动将产生具有正增量值的QWheelEvent。

然而,在某些平台上,这是可配置的,因此上述相同的操作将产生负增量值(但具有相同的幅度)。使用反转属性,轮子事件消费者可以选择始终跟随轮子的方向,而不管系统设置如何,但仅限于特定的小部件。(一个这样的用例可能是用户按照视觉Tumbler旋转的相同方向旋转轮子。另一个用法是使滑块手柄跟随触摸板上手指的移动方向,而不管系统配置如何。)
source()Qt.MouseEventSource请改用PointingDevice()。

返回有关车轮事件源的信息。

该源可用于区分来自带有物理滚轮的鼠标的事件和由其他方式生成的事件,例如触摸板上的轻弹手势。这个枚举告诉你它是从哪里合成的;但是通常知道它是从哪个设备合成的更有用,所以试着改用point ingDevice()。
buttons()Qt.MouseButtons
globalPos()QPoint
globalPosF()QPointF
deviceType()QInputDevice.DeviceType
modifiers()Qt.KeyboardModifiers|
globalPosition()QPointF返回此事件中的点在屏幕或虚拟桌面上的位置。
鼠标指针的全局位置是在事件发生时记录的。这在异步窗口系统(如X11)上很重要;每当您移动小部件以响应鼠标事件时,global alPoition()可能与pos()返回的当前光标位置有很大不同。
globalX()int
globalY()int
pos()QPoint
posF()QPointF
position()QPointF返回此事件中点相对于接收事件的小部件或项目的位置。

如果您为了响应鼠标事件而移动小部件,请改用global alPoition()。
x()int
y()int
scenePosition()PySide6.QtCore.QPointF返回此事件中点相对于窗口或场景的位置。
QMouseEvent 类和QWheelEvent 类的应用实例

下面的程序涉及鼠标单击、拖拽、双击和滚轮滚动的事件

双击窗口的空白处或者单击菜单,弹出打开图片的对话框,选择图片后,显示出图片

按住 Ctrl 键和鼠标左键并拖动鼠标可以移动图片,按住 Ctrl键并滚动滚轮可以缩放图片。

程序中通过控制绘图区域的中心位置来移动图像,通过控制图像区域的宽度和高度来缩放图像。

# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/1 23:00
# File_name: 01-鼠标事件QMouseEvent和滚轮事件QWheelEvent .py
from PySide6.QtWidgets import QApplication,QWidget,QFileDialog,QMenuBar
from PySide6.QtGui import QPixmap,QPainter,QMouseEvent,QWheelEvent
from PySide6.QtCore import QRect,QPoint,Qt.QPointF
import sys


class MyWindow(QWidget):
    def __init__(self,parent=None):
        super().__init__(parent)

        self.resize(600,600)
        self.pixmap = QPixmap()# 创建QPixmap图像
        self.pix_width = 0  # 获取初始宽度
        self.pix_height = 0  # 获取初始高度
        self.translate_x = 0  # 用于控制x向平移
        self.translate_y = 0  # 并用于控制y向平移
        self.pixmap_scal_x = 0  # 用于记录图像的长度比例,用于图像缩放
        self.pixmap_scal_y = 0  # 用于记录图像的高度比例,用于图像缩放
        self.start = QPoint(0,0)# 鼠标单击时光标位置

        # 记录图像中心的变量,初始定义在窗口的中心
        self.center = QPoint(int(self.width()/ 2),int(self.height()/ 2))
        menuBar = QMenuBar(self)
        menuFile = menuBar.addMenu("文件(&F)")
        menuFile.addAction("打开(&0)").triggered.connect(self.actionOpen_triggered)
        menuFile.addSeparator()
        menuFile.addAction("退出(&E)").triggered.connect(self.close)# 动作与槽连接

    def paintEvent(self,event):  # 窗口绘制处理函数,当窗口刷新时调用该函数
        self.center = QPoint(self.center.x()+ self.translate_x,self.center.y()+ self.translate_y)

        # 图像绘制区域的左上角点,用于缩放图像
        point_1 = QPoint(self.center.x()- self.pix_width,self.center.y()- self.pix_height)

        # 图像绘制区域的右下角点,用于缩放图像
        point_2 = QPoint(self.center.x()+ self.pix_width,self.center.y()+ self.pix_height)

        self.rect = QRect(point_1,point_2)# 图像绘制区域
        painter = QPainter(self)# 绘图
        painter.drawPixmap(self.rect,self.pixmap)

    def mousePressEvent(self,event: QMouseEvent):  # 鼠标按键按下事件的处理函数
        self.start: QPointF = event.position()# 鼠标位置
        # 这里在Qt6使用pos()会发出弃用警告官方建议的是用position()代替pos()

    def mouseMoveEvent(self,event: QMouseEvent)-> None:  # 鼠标移动事件的处理函数
        if event.modifiers()== Qt.ControlModifier and event.buttons()== Qt.LeftButton:
            # 这里直接从event获取坐标会发出弃用警告,官方建议的是用position()间接获得坐标
            self.translate_x = event.position().x()- self.start.x()# 鼠标的移动量
            self.translate_y = event.position().y()- self.start.y()

            self.start = event.position()
            self.update()# 会调用paintEvent()

    def wheelEvent(self,event: QWheelEvent)-> None:  # 鼠标滚轮事件的处理函数
        if event.modifiers()== Qt.ControlModifier:
            if(self.pix_width > 10 and self.pix_height > 10)or event.angleDelta().y()> 0:
                self.pix_width = self.pix_width + int(event.angleDelta().y()/ 10 * self.pixmap_scal_x)
                self.pix_height = self.pix_height + int(event.angleDelta().y()/ 10 * self.pixmap_scal_y)

                self.update()

    def mouseDoubleClickEvent(self,event):  # 双击鼠标事件的处理函数
        self.actionOpen_triggered()

    def actionOpen_triggered(self):  # 打开文件动作
        fileDialog = QFileDialog(self)
        fileDialog.setNameFilter("图像文件(*.png *.jpeg *.jpg)")
        fileDialog.setFileMode(QFileDialog.FileMode.ExistingFile)

        if fileDialog.exec():
            self.pixmap.load(fileDialog.selectedFiles()[0])
            self.pix_width = int(self.pixmap.width()/ 2)# 获取初始宽高
            self.pix_height = int(self.pixmap.height()/ 2)

            self.pixmap_scal_x = self.pix_width /(self.pix_width + self.pix_height)
            self.pixmap_scal_y = self.pix_height /(self.pix_width + self.pix_height)
            self.center = QPoint(int(self.width()/ 2),int(self.height()/ 2))

            self.update()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MyWindow()

    win.show()
    sys.exit(app.exec())

键盘事件QKeyEvent

键盘事件QKeyEvent 涉及键盘键的按下和释放与QKeyEvent 关联的事件类型有:

  • QEvent.KeyPress
  • QEvent.KeyRelease
  • QEvent.ShortcutOverride

处理键盘事件的函数是 keyPressEvent(QKeyEvent)和 keyReleaseEvent(QKeyEvent)。

  • 当发生键盘事件时将创建QKeyEvent的实例对象,并将实例对象作为实参传递给处理函数键盘事件QKeyEvent的常用方法如所示主要方法介绍如下:
    • 如果同时按下多个键,可以用count()方法获取按键的数量。
    • 如果按下一个键不放,将连续触发键盘事件,用isAutoRepeat()方法可以获取某个事件是否是重复事件。
    • 用key()方法可以获取按键的Qtkey代码值不区分大小写;可以用text()方法获取按键的字符,区分大小写。
    • 用matches(QKeySequenceStandardKey)方法可以判断按下的键是否匹配标准的按键,QKeySequenceStandardKey中定义了常规的标准按键例如:
      • Ctrl+C表示复制、Ctrl+V表示粘贴、Ctrl+S表示保存Ctrl+0表示打开Ctrl+w或Ctrl+F4表示关闭。
QKeyEvent的方法返回值的类型说明
count()int获取按键的数量
isAutoRepeat()bool获取是否是重复事件
key()int获取按键的代码
matches(QKeySequence.StandardKey)bool如果按键匹配标准的按键,则返 回 True
modifiers()Qt.KeyboardModifiers获取修饰键
text()Str返回按键上的字符

鼠标拖放事件QDropEvent和QDragMoveEvent

许多QWidget 对象都支持拖曳动作,允许拖曳数据的控件必须设置 QWidget.setDragEnabled()为 True

可视化开发中经常会用鼠标拖放动作来完成一些操作,例如把一个 docx 文档拖到Word 中直接打开,把图片拖放到一个图片浏览器中打开,拖放一段文字到其他位置等。

拖放事件包括鼠标进人、鼠标移动和鼠标释放事件,还可以有鼠标移出事件,对应的事件类型分别是:

  • QEvent.DragEnter
  • QEvent.DragMove
  • QEvent.Drop
  • QEvent.DragLeave。

拖放事件类分别为QDragEnterEvent,QDragMoveEvent,QDropEvent和QDragLeaveEvent,其实例对象中保存着拖放信息,例如被拖放文件路径、被拖放的文本等。拖放事件的处理函数分别是:

  • dragEnterEvent(QDragEnterEvent)
  • dragMoveEvent(QDragMoveEvent)
  • dropEvent(QDropEvent)
  • dragLeaveEvent(QDragLeaveEvent)
事件描 述
QDrag支持基于 MIME的拖放数据传输
mousePressEvent按下鼠标按键触发事件
mouseReleaseEvent释放鼠标按键触发事件
mouseMoveEvent移动鼠标触发事件
DragEnterEvent当拖曳动作进入该控件时触发该事件。在这个事件中可以获得被操作的窗口控件,还可以 有条件地接受或拒绝该拖曳操作 -
DragMoveEvent在拖曳操作进行时会触发该事件
DragLeaveEvent当执行拖曳控件的操作,并且鼠标指针离开该控件时,这个事件将被触发
DropEvent当拖曳操作在目标控件上被释放时,这个事件将被触发
QDropEvent 和 QDragMoveEvent 的方法

QDragEnterEvent类是从QDropEvent类和QDragMoveEvent 类继承而来的,它没有自己特有的方法;

QDragMoveEvent 类是从QDropEvent 类继承而来的,它继承了QDropEvent类的方法,又添加了自已新的方法;

QDragLeaveEvent类是从QEvent类继承而来的,它也没有自己特有的方法。

QDropEvent类和QDragMoveEvent类的方法分别如表所示,主要方法介绍如下。

  • 使一个控件或窗口接受拖放
    • 必须用setAcceptDrops(True)方法设置成接受拖放,
    • 在进入事件的处理函数 dragEnterEvent(QDragEnterEvent)中,需要把事件对象设置成 accept(),否则无法接受后续的移动和释放事件
  • 在拖放事件中用mimeData()方法获取被拖放物体的QMimeData 数据MIME(multipurposeinternet mail extensions)是多用途互联网邮件扩展类型。关于QMimeData的介绍参见下面的内容。
  • 在释放动作中,被拖拽的物体可以从原控件中被复制或移动到目标控件中
    • 复制或移动动作可以通过 setDropAction(QtDropAction)方法来设置其中Qt.DropAction可以取:
      • Qt.CopyAction(复制)
      • Qt.MoveAction(移动)
      • Qt.LinkAction(链接)
      • Qt.IgnoreAction(什么都不做)
      • Qt.TargetMoveAction(目标对象接管)
    • 另外系统也会推荐一个动作,可以用proposedAction()方法获取推荐的动作,用possibleActions()方法获取有可能实现的动作用dropAction()方法获取采取的动作。
QDropEvent的常用方法
QDropEvent 的方法返回值的类型说明
keyboardModifiers()Qt.KeyboardModifiers获取修饰键
mimeData()QMimeData获取 mime 数据
mouseButtons()Qt.MouseButtons获取按下的鼠标按键
pos()QPoint获取释放时的位置
posF()QPointF获取释放时的位置
dropAction()Qt.DropAction获取采取的动作
possibleActions()Qt.DropActions获取可能实现的动作
proposedAction()Qt.DropAction系统推荐的动作
acceptProposedAction()None接受推荐的动作
setDropAction(Qt.DropAction)None设置释放动作
source()QObject获取被拖对象
QDrgMoveEvenl的常用方法
QDragMoveEvent的方法返回值的类型说,明
accept()None在控件或窗口的边界内都可接受移动事件
accept(QRect)None在指定的区域内接受移动事件
ignore()None在整个边界内部忽略移动事件
ignore(QRect)None在指定的区域内部忽略移动事件
answerRect()QRect返回可以释放的区域
QMimeData类

QMimeData 类用于描述存放到粘贴板上的数据,并通过拖放事件传递粘贴板上的数据,从而在不同的程序间传递数据,也可以在同一个程序内传递数据。

创建 QMimeData 实例对象的方法是 QMimeData(),它在 QtCore模块中。QMimeData 可以存储的数据有文本、图像、颜色和地址等。

QMimeData类的方法如表所示,可以分项设置和获取数据,也可以用setData(str,QByteArray)方法设置数据

QMimeData的数据格式各种数据设置和获取的方法如表所示。

QMimeData类的方法

QMimeData的方法及参数类型返回值的类型说明
formats()List[str]获取格式列表
hasFormat(str)bo0l获取是否有某种格式
removeFormat(str)None移除格式
setColorData(Any)None设置颜色数据
hasColor()bool获取是否有颜色数据
colorData()Any获取颜色数据
setHtml(str)None设置Html数据
hasHtml()bool判断是否有 Html数据
html()Str获取 Html数据
setImageData(Any)None设置图像数据
hasImage()bool获取是否有图像数据
imageData()Any获取图像数据
setText(str)None设置文本数据
hasText()bool判断是否有文本数据
text()str获取文本数据
setUrls(Sequence[QUrl])None设置Url数据
hasUrls()bool判断是否有Url数据
urls()List[QUrl]获取 Url数据
setData(str,QByteArray)None设置某种格式的数据
data(str)QByteArray获取某种格式的数据
clear()None清空格式和数据

QMimeData的数据格式和方法

格式是否存在获取方法设置方法举例
text/plainhasText()text()setText()setText(“拖动文本”)
text/htmlhasHtml()html()setHtml()setHtml(“拖动文本”)
text/uri-listhasUrls()urls()setUrls()setUrls([QUrl(“www.qq.com/”)])
image/ *hasImage()imageData()setImageData()setImageData(QImage(“ix.png”))
application/x-colorbasColor()colorData()setColorData()setColorData(QColor(23,56,53))
拖放事件的应用实例

下面的程序是在上一个实例的基础上增加了拖拽功能,除了可以双击窗口、用菜单打开二个图像文件外,也可以把一个图像文件拖拽到窗口上打开。

需要注意的是,要使窗口或控件接受拖放操作,应该用setAcceptDrops(bool)方法将其设置成 True。

# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/1 20:29
# File_name: demo.py

import sys
from PySide6.QtWidgets import QApplication,QWidget,QFileDialog,QMenuBar
from PySide6.QtGui import QPixmap,QPainter,QMouseEvent,QWheelEvent,QDragEnterEvent,QDropEvent
from PySide6.QtCore import QRect,QPoint,Qt.QPointF


class MyWindow(QWidget):
    def __init__(self,parent=None):
        super().__init__(parent)

        self.setAcceptDrops(True)# 设置成接受拖放事件
        self.resize(600,600)

        self.pixmap = QPixmap()# 创建QPixmap图像
        self.pix_width = 0  # 获取初始宽度
        self.pix_height = 0  # 获取初始高度

        self.translate_x = 0  # 用于控制x向平移
        self.translate_y = 0  # 用于控制y向平移

        self.pixmap_scale_x = 0  # 用于记录图像的长度比例,用于图像缩放
        self.pixmap_scale_y = 0  # 用于记录图像的高度比例,用于图像缩放

        self.start = QPoint(0,0)# 鼠标单击时光标位置

        # 记录图像中心的变量,初始定义在窗口的中心
        self.center = QPoint(int(self.width()/ 2),int(self.height()/ 2))
        menuBar = QMenuBar(self)
        menuFile = menuBar.addMenu("文件(&F)")
        menuFile.addAction("打开(80)").triggered.connect(self.actionOpen_triggered)
        menuFile.addSeparator()
        menuFile.addAction("退出(8E)").triggered.connect(self.close)# 动作与槽连接

    def paintEvent(self,event):  # 窗口绘制处理函数,当窗口刷新时调用该函数
        self.center = QPoint(self.center.x()+ self.translate_x,self.center.y()+ self.translate_y)

        # 图像绘制区域的左上角点,用于缩放图像
        point_1 = QPoint(self.center.x()- self.pix_width,self.center.y()- self.pix_height)

        # 图像绘制区域的右下角点,用于缩放图像
        point_2 = QPoint(self.center.x()+ self.pix_width,self.center.y()+ self.pix_height)

        self.rect = QRect(point_1,point_2)# 图像绘制区域
        painter = QPainter(self)# 绘图
        painter.drawPixmap(self.rect,self.pixmap)

    def actionOpen_triggered(self):
        fileDialog = QFileDialog(self)
        fileDialog.setNameFilter("图像文件(*.png *.jpeg *.jpg)")
        fileDialog.setFileMode(QFileDialog.FileMode.ExistingFile)

        if fileDialog.exec():
            self.pixmap.load(fileDialog.selectedFiles()[0])
            self.pix_width = int(self.pixmap.width()/ 2)# 获取初始宽高
            self.pix_height = int(self.pixmap.height()/ 2)

            self.pixmap_scal_x = self.pix_width /(self.pix_width + self.pix_height)
            self.pixmap_scal_y = self.pix_height /(self.pix_width + self.pix_height)
            self.center = QPoint(int(self.width()/ 2),int(self.height()/ 2))

            self.update()

    def mousePressEvent(self,event: QMouseEvent)-> None:  # 鼠标按下事件处理
        self.start: QPointF = event.position()# 鼠标位置
        # 这里在Qt6使用pos()会发出弃用警告官方建议的是用position()代替pos()

    def mouseMoveEvent(self,event: QMouseEvent)-> None:  # 鼠标移动事件处理
        if event.modifiers()== Qt.ControlModifier and event.buttons()== Qt.LeftButton:
            # 这里直接从event获取坐标会发出弃用警告,官方建议的是用position()间接获得坐标
            self.translate_x = event.position().x()- self.start.x()# 鼠标的移动量
            self.translate_y = event.position().y()- self.start.y()

            self.start = event.position()
            self.update()# 会调用paintEvent()

    def wheelEvent(self,event: QWheelEvent)-> None:  # 鼠标滚动事件
        if event.modifiers()== Qt.ControlModifier:
            if(self.pix_width > 10 and self.pix_height > 10)or event.angleDelta().y()> 0:
                self.pix_width = self.pix_width + int(event.angleDelta().y()/ 3 * self.pixmap_scal_x)
                self.pix_height = self.pix_height + int(event.angleDelta().y()/ 3 * self.pixmap_scal_y)

                self.update()

    def mouseDoubleClickEvent(self,event: QMouseEvent)-> None:
        self.actionOpen_triggered()

    def dragEnterEvent(self,event: QDragEnterEvent)-> None:  # 进入拖动事件
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self,event: QDropEvent)-> None:  # 拖动释放事件
        urls = event.mimeData().urls()# 获取被拖动文件的地址列表
        fileName = urls[0].toLocalFile()# 将文件地址转成本地地址
        self.pixmap.load(fileName)
        self.pix_width = int(self.pixmap.width()/ 2)# 获取初始宽度
        self.pix_height = int(self.pixmap.height()/ 2)# 获取初始高度

        self.pixmap_scal_x = self.pix_width /(self.pix_width + self.pix_height)
        self.pixmap_scal_y = self.pix_height /(self.pix_width + self.pix_height)
        self.center = QPoint(int(self.width()/ 2),int(self.height()/ 2))

        self.update()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MyWindow()

    win.show()
    sys.exit(app.exec())

拖拽类QDrag

如果要在程序内部拖放控件,需要先把控件定义成可移动控件,可移动控件需要在其内部定义 QDrag 的实例对象。

QDrag 类用于拖放物体,它继承自 QObject 类,创建 QDrag实例对象的方法是 QDrag(QObject),参数 QObject 表示只要是从 QObject 类继承的控件都可以。

拖电过程中会有一些默认图像随着鼠标移动(常见的有一个拖曳的"+"或透明的快捷方式),这些图像会根据 QMimeData 中的数据类型显示不同的效果,也可以使用setPixmap0函数设置其他图片的效果。

可以使用 setHotSpot()函数设置鼠标指针相对于控件左上角的位置。

可以使用source()函数和 target()函数找到拖曳源和目标小部件,方便实现特殊行为。

拖曳操作通常在一些拖曳事件中完成,常用的拖曳事件如表所示。

事件描 述
QDrag支持基于 MIME的拖放数据传输
mousePressEvent按下鼠标按键触发事件
mouseReleaseEvent释放鼠标按键触发事件
mouseMoveEvent移动鼠标触发事件
DragEnterEvent当拖曳动作进入该控件时触发该事件。在这个事件中可以获得被操作的窗口控件,还可以 有条件地接受或拒绝该拖曳操作 -
DragMoveEvent在拖曳操作进行时会触发该事件
DragLeaveEvent当执行拖曳控件的操作,并且鼠标指针离开该控件时,这个事件将被触发
DropEvent当拖曳操作在目标控件上被释放时,这个事件将被触发
QDrag类的方法

QDrag类的方法如表所示,主要方法介绍如下:

  • 创建QDrag实例对象后,用exec(supportedActions;Qt.DropActions,delaultAction;QtDropAction)或 exec(supportedActions; Qt.DropActions一Qt.MoveAction)方法开启拖放,参数是拖放事件支持的动作和默认动作,QtDropAction可以取:
    • QtCopyAction(复制数据到目标对象)
    • Qt.MoveAction(移动数据到目标对象)
    • Qt.LinkAction(在目标和原对象之间建立链接关系)
    • Qt.IgnoreAction(忽略,对数据不做任何事情)
    • QtTargetMoveAction(目标对象接管数据)
  • 用setMimeData(QMimeData)方法设置mime对象,传递数据;用mimeData()方法获取mime数据
  • 用setPixmap(QPixmap)方法设置拖拽时鼠标显示的图像,用setDragCursor(QPixmap,QtDropAction)方法设置拖拽时光标的形状用setHotSpot(QPoint)方法设置热点位置。热点位置是拖拽过程中,光标相对于控件左上角的位置。
  • 为了防止误操作,可以用QApplication的setStartDragDistance(int)方法和setStartDragTime(msec)方法设置拖动开始一定距离或一段时间后才开始进行拖放事件。
QDrag的方法及参数类型返回值的类型说明
exec(supportedActions: Qt.DropActions = Qt.MoveAction)Qt.DropAction开始拖动操作,并返回释放时的 动作
exec(supportedActions: Qt.DropActions,defaultAction: Qt.DropAction)
defaultAction()Qt.DropAction返回默认的释放动作
setDragCursor(QPixmap,Qt.DropAction)None设置拖拽时的光标形状
dragCursor(Qt.DropAction)QPixmap获取拖拽时的光标形状
setHotSpot(QPoint)None设置热点位置
hotSpot()QPoint获取热点位置
setMimeData(QMimeData)None设置拖放中传递的数据
mimeData()QMimeData获取数据
setPixmap(QPixmap)None设定拖拽时鼠标显示的图像
pixmap()QPixmap获取图像
source()QObject返回被拖放物体的父控件
target()QObject返回目标控件
supportedActions()Qt.DropActions获取支持的动作
cancel()None取消拖放
QDrag类信号
信号说明
actionChanged(Qt.DropAction)
targetChanged(QObject)
QDrag的应用实例

下面实例先重写了QPushButton 的 mousePressEvent()事件,在该事件中定义了QDrag的实例,这样QPushButton的实例对象就是可移动控件;

然后又重新定义了QFrame框架,在内部定义了两个QPushButton,重写了dragEnterEvent()函数dragMoveEvent()函数和dropEvent()函数。

程序运行后,可以随机用鼠标左键移动按钮的位置。

# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/2 20:51
# File_name: 03-QDrag的应用实例.py
from PySide6.QtWidgets import QApplication,QWidget,QPushButton,QFrame,QHBoxLayout
from PySide6.QtGui import QDrag,QMouseEvent
import sys
from PySide6.QtCore import QMimeData,Qt


class MyPushButton(QPushButton):
    def __init__(self,parent=None):
        super().__init__(parent)

    def mousePressEvent(self,event: QMouseEvent)-> None:
        if event.button()== Qt.LeftButton:  # 按键事件
            self.drag = QDrag(self)
            self.drag.setHotSpot(event.position().toPoint())
            mime = QMimeData()
            self.drag.setMimeData(mime)
            self.drag.exec()


class MyFame(QFrame):
    def __init__(self,parent=None):
        super().__init__(parent)
        self.setAcceptDrops(True)
        self.setFrameShape(QFrame.Shape.Box)
        self.btn_1 = MyPushButton(self)
        self.btn_1.setText("push button 1")
        self.btn_1.move(100,100)
        self.btn_2 = MyPushButton(self)
        self.btn_2.setText("push button 2")
        self.btn_2.move(200,200)

    def dragEnterEvent(self,event):
        self.child = self.childAt(event.position().toPoint())# 获取指定位置的控件
        event.accept()

    def dragMoveEvent(self,event):
        if self.child:
            self.child.move(event.position().toPoint()- self.child.drag.hotSpot())

    def dropEvent(self,event):
        if self.child:
            self.child.move(event.position().toPoint()- self.child.drag.hotSpot())


class MyWindow(QWidget):
    def __init__(self,parent=None):
        super().__init__(parent)
        self.setupUi()

        self.resize(600,400)
        self.setAcceptDrops(True)

    def setupUi(self):
        self.frame_1 = MyFame(self)
        self.frame_2 = MyFame(self)
        H = QHBoxLayout(self)
        H.addWidget(self.frame_1)
        H.addWidget(self.frame_2)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MyWindow()

    win.show()
    sys.exit(app.exec())

QDrag 的使用方法2

本案例实现了两种对按钮的拖曳方式,一种是自定义实例化按钮(button),另一种是基于父窗口(button2),本案例部分参考了官方的C++ demo。拖操作比较混乱的是事件触发顺序,下面简要说明。
对于 button1,事件触发顺序如下:

w mousePressEventbl 
b1 mouseMoveEvent 1
b2 mouseMoveEvent 2
w dragEnterEventw 
dragMoveEvent
# 省略若干次触发
w dragMoveEvent
w dropEnvent
w dropEnvent b1 1
w dropEnvent bl 2
b1 mouseMoveEvent 3
PySide6.OtCore.Qt.DropAction.MoveAction

# ---------------------------------------
# 对于 button2,事件触发顺序如下:
w mousePressEvent
mousePressEvent b2 1
WmousePressEvent b2 2
w dragEnterEvent
w dragMoveEvent
省略若干次触发
w dragMoveEvent
dropEnvent
dropEnvent b2 1
dropEnvent b2 2
w mousePressEvent b2 3
PySide6.QtCore.Ot.DropAction.MoveAction

可以看到,drag.exec0函数会阻断当前事件运行(不会阻断主程序),执行拖曳的其他事件,等待拖曳操作完成之后才会继续执行当前事件。

button 的拖曳操作在自己的 OPushButtonmouseMoveEvent0函数中完成,button2的拖曳操作在 QWidgetmousePressEvent0函数中完成。两个按钮的移动操作都在 QWidgetdropEvent0函数中完成,通过 OMimeDatahasFormat(“application/x-MyButton2”)来识别button2按钮。两个按钮实现的功能是一样的,都可以通过鼠标右键拖曳移动。

二者的区别在于:button的拖曳操作只有按住鼠标右键拖动才能触发,右击button2按也能触发

# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/15 0:05
# File_name: 05-QDrag 例1.py


import sys
from PySide6.QtWidgets import QPushButton,QWidget,QApplication
from PySide6.QtCore import Qt,QMimeData,QPoint,QByteArray
from PySide6.QtGui import QDrag
import PySide6


class Button(QPushButton):
    def __init__(self,title,parent):
        super().__init__(title,parent)

    def mouseMoveEvent(self,e):
        # print('b1 mouseMoveEvent 1')
        if e.buttons()!= Qt.RightButton:
            return

        print('b1 mouseMoveEvent 1')
        mimeData = QMimeData()
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        self.hotSpot = e.pos()- self.rect().topLeft()
        drag.setHotSpot(self.hotSpot)
        print('b1 mouseMoveEvent 2')
        dropAcion = drag.exec_(Qt.MoveAction)
        print('b1 mouseMoveEvent 3')
        print(dropAcion)

    def mousePressEvent(self,e):
        QPushButton.mousePressEvent(self,e)

        if e.button()== Qt.LeftButton:
            print("请使用右键拖动")


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        self.button = Button("鼠标用右键拖动",self)
        self.button.move(100,65)

        self.button2 = QPushButton("鼠标用右键拖动2",self)
        self.button2.move(50,35)

        self.setWindowTitle("拖拽应用案例1")
        self.setGeometry(300,300,280,150)

    def dragEnterEvent(self,event):
        print('w dragEnterEvent')
        if event.mimeData().hasFormat("application/x-MyButton2"):
            if event.source()== self:
                event.setDropAction(Qt.MoveAction)
                event.accept()
            else:
                event.acceptProposedAction()
        else:
            event.accept()

    def dropEvent(self,event: PySide6.QtGui.QDropEvent):
        print('w dropEnvent')
        if event.mimeData().hasFormat("application/x-MyButton2"):
            print('w dropEnvent b2 1')
            offset = self.offset
            self.child.move(event.position().toPoint()- offset)

            if event.source()== self:
                event.setDropAction(Qt.MoveAction)
                event.accept()
            else:
                event.acceptProposedAction()
            print('w dropEnvent b2 2')
        else:
            print('w dropEnvent b1 1')
            position = event.pos()
            self.button.move(position - self.button.hotSpot)
            event.setDropAction(Qt.MoveAction)
            event.accept()
            print('w dropEnvent b1 2')
            # event.ignore()

    def dragMoveEvent(self,event: PySide6.QtGui.QDragMoveEvent)-> None:
        print('w dragMoveEvent')
        if event.mimeData().hasFormat("application/x-MyButton2"):
            if event.source()== self:
                event.setDropAction(Qt.MoveAction)
                event.accept()
            else:
                event.acceptProposedAction()
        else:
            # self.dragMoveEvent(event)
            event.accept()

    def mousePressEvent(self,event: PySide6.QtGui.QMouseEvent)-> None:
        print('w mousePressEvent')
        child = self.childAt(event.position().toPoint())

        if child is not self.button2:
            return
        print('w mousePressEvent b2 1')
        self.offset = QPoint(event.position().toPoint()- child.pos())
        self.child = child
        mimeData = QMimeData()
        mimeData.setData("application/x-MyButton2",QByteArray())

        drag = QDrag(self)
        drag.setMimeData(mimeData)
        # drag.setPixmap(self.pixmap)
        drag.setHotSpot(event.position().toPoint()- child.pos())
        print('w mousePressEvent b2 2')
        moveAction = drag.exec_(Qt.CopyAction | Qt.MoveAction,Qt.CopyAction)
        print('w mousePressEvent b2 3')
        print(moveAction)


if __name__ =="__main__":
    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    app.exec()

QDrag 的使用方法3

本案例的用于演示拖曳的更多功能。本案例基于官方的C++demo改写更全面地演示了拖曳功能及不同窗体之间的数据传递方式

本案例实现了控件在窗体内的拖曳和移动,以及不同窗体之间的拖曳和复制。mousePressEvent()函数定义了拖曳操作,dropEvent0函数定义了移动或复制操作。

与上一案例不同,本案例通过关闭原来的控件,并在目标位置上新建控件来实现拖曳效果,而不是采用移动的方式。本案例的大部分内容都和之前的相同,这里需要注意以下几点。

  • 无论是移动还是复制,都需要传递两种数据:一是鼠标指针相对于控件左上角的位移offset;二是控件填充的背景图pixmap。
  • 对于 offset,本案例通过父窗口传递,即 self.parent0.offset=offset; 对于 pixmap;转换成QByteArray 通过QMimeData传递。
  • pixmap 也可以像 offset 一样通过 selfparent0.offset = offset 传递,还可以通过mimeData.setImageData(pixmap)传递,通过 QMimeDataimageData0获取(请参考注释内容)。这两种传递方式更简单,都不需要转换成OByteArray。本案例选择的是比较复杂的方式,可以帮助读者更好地理解数据传递的实现方式。

image-20230328001529110

# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/15 0:05
# File_name: 06-QDrag 例2.py


from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
import PySide6
import sys
import os

os.chdir(os.path.dirname(__file__))


class DragWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setMinimumSize(400,400)
        self.setAcceptDrops(True)

        self.icon1 = QLabel('icon1',self)
        self.icon1.setPixmap(QPixmap("../../Resources/Images/save.png"))
        self.icon1.move(10,10)
        self.icon1.setAttribute(Qt.WA_DeleteOnClose)

        self.icon2 = QLabel('icon2',self)
        self.icon2.setPixmap(QPixmap("../../Resources/Images/new.png"))
        self.icon2.move(100,10)
        self.icon2.setAttribute(Qt.WA_DeleteOnClose)

        self.icon3 = QLabel('icon3',self)
        self.icon3.setPixmap(QPixmap("../../Resources/Images/open.png"))
        self.icon3.move(10,80)
        self.icon3.setAttribute(Qt.WA_DeleteOnClose)

    def dragEnterEvent(self,event: PySide6.QtGui.QDragEnterEvent)-> None:
        if event.mimeData().hasFormat("application/x-dnditemdata"):
            if event.source()== self:
                event.setDropAction(Qt.MoveAction)
                event.accept()
            else:
                event.acceptProposedAction()
        else:
            event.ignore()

    def dragMoveEvent(self,event: PySide6.QtGui.QDragMoveEvent)-> None:
        if event.mimeData().hasFormat("application/x-dnditemdata"):
            if event.source()== self:
                event.setDropAction(Qt.MoveAction)
                event.accept()
            else:
                event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(self,event: PySide6.QtGui.QDropEvent)-> None:
        if event.mimeData().hasFormat("application/x-dnditemdata"):

            # 接收 QMimeData中的 QPixmap数据
            itemData = event.mimeData().data("application/x-dnditemdata")
            pixmap = self.QByteArray2QPixmap(itemData)
            # pixmap = event.mimeData().imageData()
            # pixmap = self.parent().pixmap

            # 接收父类中的 QPoint 数据
            offset = self.parent().offset

            # 新建icon
            newIcon = QLabel('哈哈',self)
            newIcon.setPixmap(pixmap)
            newIcon.move(event.position().toPoint()- offset)
            newIcon.show()
            newIcon.setAttribute(Qt.WA_DeleteOnClose)

            if event.source()== self:
                event.setDropAction(Qt.MoveAction)
                event.accept()
            else:
                event.acceptProposedAction()
        else:
            event.ignore()

    def mousePressEvent(self,event: PySide6.QtGui.QMouseEvent)-> None:

        child = self.childAt(event.position().toPoint())
        if not child:
            return

        # 通过 QMimeData传递 QPixmap数据
        pixmap = child.pixmap()
        # self.parent().pixmap = pixmap
        itemData = self.QPixmap2QByteArray(pixmap)
        mimeData = QMimeData()
        mimeData.setData("application/x-dnditemdata",itemData)
        # mimeData.setImageData(pixmap)

        # 通过共同的父类传递 QPoint 数据
        offset = QPoint(event.position().toPoint()- child.pos())
        self.parent().offset = offset

        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setPixmap(pixmap)
        drag.setHotSpot(event.position().toPoint()- child.pos())

        # 触发MoveAction行为会关闭原来的icon,否则不关闭。
        action = drag.exec(Qt.CopyAction | Qt.MoveAction,Qt.CopyAction)
        print(action)
        if action == Qt.MoveAction:
            child.close()
        else:
            child.show()
            # child.setPixmap(pixmap)

    def QPixmap2QByteArray(self,q_image: QImage)-> QByteArray:
		"""
            Args:
                 q_image: 待转化为字节流的QImage。
            Returns:
                 q_image转化成的byte array。
		"""
        # 获取一个空的字节数组
        byte_array = QByteArray()
        # 将字节数组绑定到输出流上
        buffer = QBuffer(byte_array)
        buffer.open(QIODevice.WriteOnly)
        # 将数据使用png格式进行保存
        q_image.save(buffer,"png",quality=100)
        return byte_array

    def QByteArray2QPixmap(self,byte_array: QByteArray):
		"""
        Args:
            byte_array: 字节流图像。
        Returns:
            byte_array对应的字节流数组。
		"""
        # 设置字节流输入池。
        buffer = QBuffer(byte_array)
        buffer.open(QIODevice.ReadOnly)
        # 读取图片。
        reader = QImageReader(buffer)
        img = QPixmap(reader.read())

        return img


if __name__ =="__main__":
    app = QApplication(sys.argv)
    mainWidget = QWidget()
    horizontalLayout = QHBoxLayout(mainWidget)
    horizontalLayout.addWidget(DragWidget())
    horizontalLayout.addWidget(DragWidget())
    mainWidget.setWindowTitle('实现窗体内的拖拽和窗体间的复制')
    mainWidget.show()
    sys.exit(app.exec())

上下文菜单事件QContextMenuEvent

上下文菜单事件QContextMenuEvent的方法

上下文菜单通常通过单击鼠标右键后弹出。

上下文菜单的事件类型是 QEvent.ContextMenu,处理函数是 contextMenuEvent(QContextMenuEvent),其中上下文菜单类QContextMenuEvent 的方法如表所示。主要方法介绍如下。

QContextMenuEvent的方法返回值的类型说明
globalPos()QPoint光标的全局坐标点
globalX()int全局坐标的X值
globalY()int全局坐标的Y值
pos()QPoint局部坐标点
x()int局部坐标的x值
y()int局部坐标的y值
reason()QContextMenuEvent.Reason获得产生上下文菜单的原因
modifiers()Qt.KeyboardModifiers获取修饰键
  • 用globalPos()方法、globalX()方法和 globalY()方法可以获得单击鼠标右键时的全局坐标位置

  • 用pos()方法、X()方法和 y()方法可以获得窗口的局部坐标位置

  • 用reason()方法可以获得产生上下文菜单的原因,返回值是 QContextMenuEvent.Reason 的枚举值,可能是:

    • QContextMenuEvent.Mouse 值为0,上下文菜单来源于鼠标
    • QContextMenuEvent.Keyboard 值为1,键盘(Windows 系统是菜单键)
    • QContextMenuEvent.Other 值为2,除鼠标及键盘之外的
  • 在contextMenuEvent(QContextMenuEvent)处理函数中,用菜单的 exec(QPoint)方法在指定位置显示菜单,菜单可以是在其他位置已经定义好的,也可以是在处理函数中临时定义的。
    只有在窗口或控件的 contextMenuPlolicy 属性为Qt.DefaultContextMenu 时,单击鼠标右键才会执行处理函数,通常情况下 Qt.DefaultContextMenu是默认值。

  • 如果不想弹出右键菜单,可以通过 setContextMenuPolicy(Qt.ContextMenuPolicy)方法将该属性设置为其他值,Qt.ContextMenuPolicy的取值如表所示:

    Qt.ContextMenuPolicy 取值说明
    Qt.NoContextMenu0控件不具有上下文菜单,上下文菜单被推到控件的父窗口
    Qt.DefaultContextMenu1控件或窗口的contextMenuEvent()被调用
    Qt.ActionsContextMenu2将控件 actions()方法返回的QActions当作上下文菜单项,单击 鼠标右键后显示该菜单
    Qt.CustomContextMenu3控件发送 customContextMenuRequested(Qpoint)信号,如果要 自定义菜单,用这个枚举值,并自定义一个处理函数
    Qt.PreventContextMenu4控件不具有上下文菜单,所有的鼠标右键事件都传递到 mousePressEvent()和 mouseReleaseEvent()函数
上下文菜单事件QContextMenuEvent应用实例

下面的程序建立一个空白窗口,在窗口单击鼠标右键,弹出上下文菜单,然后选择打开项,选择一幅图片后,在窗口上显示该图片。

# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/2 21:29
# File_name: 04-上下文菜单事件QContextMenuEvent 应用实例.py

from PySide6.QtWidgets import QApplication,QWidget,QFileDialog,QMenu
from PySide6.QtGui import QPixmap,QPainter
import sys


class MyWindow(QWidget):
    def __init__(self,parent=None):
        super().__init__(parent)
        self.setAcceptDrops(True)# 设置可接受拖放事件
        self.resize(600,400)
        self.pixmap = QPixmap()# 创建QPixmap图像

    def contextMenuEvent(self,event):
        contextMenu = QMenu(self)

        contextMenu.addAction("打开(&0)").triggered.connect(self.actionOpen_triggered)# 槽连接

        contextMenu.addSeparator()
        contextMenu.addAction("退出(&E)").triggered.connect(self.close)# 动作与槽连接

        contextMenu.exec(event.globalPos())

    def paintEvent(self,event):  # 窗口绘制处理函数,当窗口刷新时调用该函数
        painter = QPainter(self)# 绘图
        painter.drawPixmap(self.rect(),self.pixmap)

    def mouseDoubleClickEvent(self,event):  # 双击鼠标事件的处理函数
        self.actionOpen_triggered()

    def actionOpen_triggered(self):  # 打开文件的动作
        fileDialog = QFileDialog(self)
        fileDialog.setNameFilter("图像文件(*.png *jpeg *.jpg)")
        fileDialog.setFileMode(QFileDialog.FileMode.ExistingFile)
        if fileDialog.exec():
            self.pixmap.load(fileDialog.selectedFiles()[0])
            self.update()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MyWindow()

    win.show()
    sys.exit(app.exec())

剪贴板QClipboard

剪贴板 QClipboard类似于拖放,可以在不同的程序间用复制和粘贴操作来传递数据QClipboard 位于QtGui模块中,继承自 QObject类,用QClipboard(parent=None)方法可以创建剪贴板对象。

可以直接往剪贴板中复制文本数据、QPixmap 和 QImage,其他数据类型可以通过QMimeData来传递,剪贴板QClipboard 的常用方法如表所示。

QClipboard的方法及参数类型返回值的类型说 明
setText(str)None将文本复制到剪贴板
text()str从剪贴板上获取文本
text(str)Tuple[str,str]从str指定的数据类型中获取文本,数据类型如 plain 或html
setPixmap(QPixmap)None将QPixmap 图像复制到剪贴板上
pixmap()QPixmap从剪贴板上获取QPixmap 图像
setImage(QImage)None将QImage图像复制到剪贴板上
image()QImage从剪贴板上获取QImage图像
setMimeData(QMimeData)None将QMimeData数据赋值到剪贴板上
mimeData()QMimeData从剪贴板上获取QMimeData数据
elear()None清空剪贴板

信号

剪贴板的主要信号是 dataChanged(),当剪贴板上的数据发生变化时发送该信号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

士别三日,当挖目相待

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值