QtDesigner和pyqt5编程

一 graphics view简单使用

  本节内容来自这里

1 概述

  Graphics View 提供了一个平面,用于管理和交互大量自定义的2D图形图元,以及一个用于可视化图元的视图窗口小部件,支持缩放和旋转。
  该框架包括一个事件传播架构,允许场景中图元的精确双精度交互功能。图元可以处理关键事件,鼠标按下,移动,释放和双击事件,还可以跟踪鼠标移动。
  Graphics View 使用BSP(二进制空间分区)树来提供非常快速的图元发现,因此,即使有数百万个图元,它也可以实时显示大型场景。

2 图形视图架构

  Graphics View 提供了一种基于图元的模型-视图编程方法,就像 QTableView ,QTreeView 和 QListView 一样。多个视图可以观察单个场景,并且场景包含具有不同几何形状的图元。
  如同下面的绿巨人,视图:强壮的颜值,模型:所有的分析来源于其强大的头脑,人家也好歹也是著名物理学家啊!
在这里插入图片描述
  在正式介绍场景、视图、图元的概念前,我们尝试通过烧螃蟹的过程,举例说明这几个概念,如下图:
在这里插入图片描述
  在上图中,我们假设,锅就是场景、螃蟹就是图元、厨房就是视图。烧螃蟹的几个步骤如下:
  1、往锅中放入4只螃蟹(向场景中加入图元)
  2、场景受外部影响的同时也影响到了图元(给锅加热,螃蟹烧红了)
  3、这一切都发生在厨房之中(场景在视图中)
  在了解到烧螃蟹这几个步骤后我们再来说明以下几个概念。

3 场景(The Scene)

  QGraphicsScene 提供图形视图场景。该场景具有以下职责:

  • 提供用于管理大量图元的快速界面(锅)
  • 将事件传播到每个图元(把螃蟹烧熟了)
  • 管理图元状态,例如选择和焦点处理
  • 提供未转换的渲染功能;主要用于打印

  该场景充当 QGraphicsItem 对象的容器(锅)。通过调用QGraphicsScene.addItem() 将图元(螃蟹)添加到场景中,然后通过调用许多图元发现函数的一个来检索图元。QGraphicsScene.items() 及其重载返回由点、矩形、多边形或一般矢量路径包含或相交的所有图元。QGraphicsScene.itemAt() 返回特定点的最顶层项。所有图元发现函数都按递减堆叠顺序返回图元(即,第一个返回的图元位于最顶层,最后一个图元位于最底层)。
  QGraphicsScene 的事件传播体系结构调度场景事件以传递到图元,还管理图元之间的传播(把螃蟹烧熟的过程)。如果场景在某个位置接收到鼠标按下事件,则场景将事件传递给该位置处的任何图元。
  QGraphicsScene 还管理某些图元状态,例如图元选择和焦点。您可以通过调用 QGraphicsScene.setSelectionArea() 来选择场景中的图元,并传递任意形状。此功能还可用作 QGraphicsView 中“橡皮筋”选择框的基础。要获取所有当前所选图元的列表,请调用 QGraphicsScene.selectedItems()。QGraphicsScene 处理的另一个状态是图元是否具有键盘输入焦点。您可以通过调用 QGraphicsScene.setFocusItem()QGraphicsItem.setFocus()来设置焦点,或通过调用 QGraphicsScene.focusItem() 获取当前焦点的图元。
  最后,QGraphicsScene 允许您通过 QGraphicsScene.render() 函数将场景的一部分渲染到绘图设备中。
  注:在图形编辑应用中常会用到橡皮筋线,如选择图形的某个区域等,最常见的就是在系统桌面上用鼠标拖动,可以绘制一个类似蚂蚁线的选区,并且选区线能够跟随鼠标的移动而伸缩,因此叫作橡皮筋线(橡皮筋框)。如下图:
在这里插入图片描述

4 视图(The View)

  QGraphicsView 提供了视图窗口小部件(厨房),可以显示场景的内容。 您可以将多个视图附加到同一场景,以将多个视口提供到同一数据集中。 视图小部件是一个滚动区域,并提供滚动条以浏览大型场景。 要启用OpenGL支持,可以通过调用QGraphicsView.setViewport()将QGLWidget设置为视口。
  视图从键盘和鼠标接收输入事件,并在将事件发送到可视化场景之前将这些事件转换为场景事件(在适当的情况下将使用的坐标转换为场景坐标)。
  使用其变换矩阵QGraphicsView.transform(),视图可以变换场景的坐标系。 这允许高级导航功能,如缩放和旋转。 为方便起见,QGraphicsView还提供了在视图和场景坐标之间进行转换的功能:QGraphicsView.mapToScene()和QGraphicsView.mapFromScene()。

5 图元(The Item)

  QGraphicsItem是场景中图形项的基类。 Graphics View 为典型形状提供了几个标准图元,例如矩形(QGraphicsRectItem),椭圆(QGraphicsEllipseItem)和文本项(QGraphicsTextItem),但是当您编写自定义图元时,可以使用最强大的QGraphicsItem功能。除其他外,QGraphicsItem支持以下功能:

  • 鼠标按下,移动,释放和双击事件,以及鼠标悬停事件,滚轮事件和上下文菜单事件。
  • 键盘输入焦点和键事件
  • 拖放
  • 通过父子关系和QGraphicsItemGroup进行分组
  • 碰撞检测

  图元位于本地坐标系中,与QGraphicsView一样,它还提供了许多功能,用于映射图元和场景之间以及图元之间的坐标。此外,与QGraphicsView一样,它可以使用矩阵变换其坐标系:QGraphicsItem.transform()。这对于旋转和缩放单个图元很有用。
  图元可以包含其他图元(子图元)。父图元的转换由其所有子图元继承。然而,无论图元的累积变换如何,其所有函数(例如,QGraphicsItem. contains(),QGraphicsItem.boundingRect(),QGraphicsItem.collidesWith())仍然在本地坐标中操作。
  QGraphicsItem通过QGraphicsItem.shape()函数和QGraphicsItem.collidesWith()支持碰撞检测,它们都是虚函数。通过将图元的形状作为QGraphicsItem.shape()的局部坐标QPainterPath返回,QGraphicsItem将为您处理所有碰撞检测。但是,如果要提供自己的碰撞检测,可以重新实现QGraphicsItem. collidesWith()。

6 Graphics View Framework中的类

  这些类提供了创建交互式应用程序的框架:

序号类名称
1QAbstractGraphicsShapeltem, QGraphicsltem, QGraphicsObject
2QGraphicsAnchor, QGraphicsltemGroup, QGraphicsPathltem
3QGraphicsAnchorLayout. QGraphicsLayout, QGraphicsPixmapltem
4QGraphicsEffect. QGraphicsLayoutItem, QGraphicsPolygonltem
5QGraphicsEllipseltem, QGraphicsLineltem, QGraphicsProxyWidget
6QGraphicsGridLayout, QGraphicsLinearLayout, QGraphicsRectltem
7QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsTextItem
8QGraphicsScenecontextMenuEvent, QGraphicsSceneMoveEvent. QGraphicsTransform
9QGraphicsSceneDragDropEvent. QGraphicsSceneResizeEvent. QGrapssView
10QGraphicsSceneEvent, QGraphicsSceneWheelEvent, QGraphicsWidget
11QGraphicsSceneHelpEvent, QGraphicsSimpleTextltem, QStyleOptionGraphicsltem
12QGraphicsSceneHoverEvent, QGraphicsSvgItem

  一共35个类,每个类的具体作用请参考帮助文档。

7 Graphics View 的坐标体系

  图形视图基于笛卡尔坐标系(平面直角坐标系x、y轴); 场景中的图元位置和几何图形由两个数字组成:x坐标和y坐标。 当使用未转换的视图观察场景时,场景上的一个单元由屏幕上的一个像素表示。
  注意:不支持反转的Y轴坐标系(y向上增长),因为图形视图使用Qt的坐标系,也就是说x轴向右,y轴向下。如下图:
在这里插入图片描述
  图形视图中有三个有效的坐标系:图元坐标,场景坐标和视图坐标。 为了简化您的实现,Graphics View 提供了便利功能,允许您在三个坐标系之间进行映射。
  渲染时,Graphics View 的场景坐标对应于QPainter的逻辑坐标,视图坐标与设备坐标相同。

8 图元坐标(Item Coordinates)

  图元存在于他们自己的本地坐标系中。它们的坐标通常以其中心点(0,0)为中心,这也是所有变换的中心,如下图:
在这里插入图片描述
  图元坐标系中的几何图元通常称为图元点,图元线或图元矩形。
  创建自定义图元时,您需要考虑图元坐标; QGraphicsScene和QGraphicsView将为您执行所有转换。这使得实现自定义图元变得非常容易。例如,如果您收到鼠标按下或拖动输入事件,则事件位置以图元坐标给出。 QGraphicsItem.contains()虚函数,如果某个点在您的图元内,则返回True,否则返回False,在图元坐标中获取一个点参数。类似地,图元的边界矩形和形状在图元坐标中。
  在图元的位置是图元中心点在其父坐标系中的坐标;有时也称为父坐标。在这个意义上,场景被视为所有无父图元的“父母”。顶级图元的位置在场景坐标中。
  子坐标是相对于父坐标的。如果子图元未转换,子坐标和父坐标之间的差异与父坐标中图元之间的距离相同。例如:如果未转换的子图元精确定位在其父项的中心点,则两个图元的坐标系统将完全相同。但是,如果孩子的位置是(10,0),则孩子的(0,10)点将对应于其父坐标的(10,10)点。
在这里插入图片描述
  由于图元的位置和变换是相对于父项的,因此子项的坐标不受父项转换的影响,尽管父项的转换会隐式转换子项。即使父项被旋转和缩放,子项(0,10)点仍将对应于父项(10,10)点。然而,相对于场景,孩子将遵循父母的转变和位置。如果缩放父级(2x,2x),则子级的位置将位于场景坐标(20,0),并且其(10,0)点将对应于场景上的点(40,0)。
在这里插入图片描述
  由于QGraphicsItem.pos()是少数例外之一,QGraphicsItem的函数在项坐标中运行,无论图元或其父项的任何转换如何。例如,图元的边界矩形(即QGraphicsItem.boundingRect())总是在图元坐标中给出。

9 场景坐标(Scene Coordinates)

  场景表示其所有图元的基本坐标系。场景坐标系描述每个顶级图元的位置,并且还形成从视图传递到场景的所有场景事件的基础。除了本地图元pos和边界矩形之外,场景中的每个图元都有一个图元位置和边界矩形(QGraphicsItem.scenePos(),QGraphicsItem. sceneBoundingRect())。场景位置描述了图元在场景坐标中的位置,其场景边界矩形构成了QGraphicsScene如何确定场景的哪些区域已经改变的基础。场景中的变化通过QGraphicsScene.changed()信号传递,参数是场景矩形列表。

10 视图坐标(View Coordinates)

  视图坐标是小部件的坐标。视图坐标中的每个单元对应于一个像素。这个坐标系的特殊之处在于它相对于窗口小部件或视口,并且不受观察场景的影响。 QGraphicsView视口的左上角始终为(0,0),右下角始终为(视口宽度,视口高度)。所有鼠标事件和拖放事件最初都作为视图坐标接收,您需要将这些坐标映射到场景以便与图元进行交互。

11 坐标映射(Coordinate Mapping)

  通常在处理场景中的图元时,将场景中的坐标和任意形状映射到图元,图元之间或视图到场景都很有用。例如,当您在 QGraphicsView 的视口中单击鼠标时,可以通过调用 QGraphicsView.mapToScene(),然后调用 QGraphicsScene.itemAt() 来询问场景下光标下的图元。如果您想知道图元所在视口中的位置,可以在图元上调用 QGraphicsItem.mapToScene(),然后在视图上调用 QGraphicsView.mapFromScene()。最后,如果您使用想要查找视图椭圆内的图元,可以将QPainterPath传递给 mapToScene(),然后将映射的路径传递给QGraphicsScene.items()
  您可以通过调用 QGraphicsItem.mapToScene()QGraphicsItem.mapFromScene() 来将坐标和形状映射到图元的场景中。您还可以通过调用QGraphicsItem.mapToParent()和QGraphicsItem.mapFromParent()或通过调用QGraphicsItem.mapToItem()和QGraphicsItem.mapFromItem()来调用图元的父图元。所有映射函数都可以映射点,矩形,多边形和路径。
  视图中提供了相同的映射函数,用于映射到场景和从场景映射。 QGraphicsView.mapFromScene()和QGraphicsView.mapToScene()。 要从视图映射到图元,首先映射到场景,然后从场景映射到图元。

12 主要特点

  1、缩放和旋转
  QGraphicsView 支持与 QPainter 通过 QGraphicsView.setMatrix() 相同的仿射变换。 通过对视图应用变换,您可以轻松添加对常用导航功能(如缩放和旋转)的支持。
  2、打印
  Graphics View 通过其渲染函数 QGraphicsScene.render()QGraphicsView.render() 提供单行打印。这些函数提供相同的API:您可以通过将 QPainter 传递给任一渲染函数,让场景或视图将其内容的全部或部分渲染到任何绘图设备中。
  场景和视图渲染功能之间的区别在于,一个在场景坐标中操作,另一个在视图坐标中操作。QGraphicsScene.render() 通常首选打印未转换场景的整个片段,例如绘制几何数据或打印文本文档。另一方面,QGraphicsView.render() 适用于截屏;它的默认行为是使用提供的画家(painter)渲染视口的确切内容。
  3、拖、放
  因为 QGraphicsView 间接地继承了 QWidget,所以它已经提供了与 QWidget 提供的相同的拖放功能。 此外,为方便起见,Graphics View 框架还为场景和每个图元提供拖放支持。 当视图收到拖动时,它会将拖放事件转换为 QGraphicsSceneDragDropEvent,然后将其转发到场景中。 场景接管此事件的调度,并将其发送到接受丢弃的鼠标光标下的第一个图元。
  要从图元开始拖动,请创建 QDrag 对象,将指针传递给开始拖动的窗口小部件。 许多视图可以同时观察图元,但只有一个视图可以开始拖动。 在大多数情况下,拖动是由于按下或移动鼠标而启动的,因此在 mousePressEvent()mouseMoveEvent() 中,您可以从事件中获取原始窗口小部件指针。
  要拦截场景的拖放事件,您需要在 QGraphicsItem 子类中重新实现 QGraphicsScene.dragEnterEvent() 以及您的特定场景所需的任何事件处理程序。您可以在 QGraphicsScene 的每个事件处理程序的文档中阅读有关拖放图形视图的更多信息。
  图元可以通过调用 QGraphicsItem.setAcceptDrops() 来启用拖放支持。要处理传入的拖动,请重新实现 QGraphicsItem.dragEnterEvent()QGraphicsItem.dragMoveEvent()QGraphicsItem.dragLeaveEvent()QGraphicsItem.dropEvent()
  4、光标和工具提示
  与 QWidget 一样,QGraphicsItem 也支持光标 QGraphicsItem.setCursor() 和工具提示 QGraphicsItem.setToolTip()。当鼠标光标进入图元区域时(通过调用 QGraphicsItem.contains() 检测到),QGraphicsView 将激活光标和工具提示。
  您还可以通过调用 QGraphicsView.setCursor() 直接在视图上设置默认光标。

13 常用函数

QGraphicsview::setDragMode(设置视图的拖拽模式)
  1、NoDrag(没有任何反应,鼠标事件将被忽略)
  2、ScrollHandDrag(光标变为指向手,然后拖动鼠标将滚动滚动条,此模式在交互和非交互模式下均有效)
  3、RubberBandDrag(将出现矩形块,拖动鼠标将设置矩形的大小,并选中矩形覆盖的所有项目,非交互视图禁用此模式)

14 使用案例

  基于python3.6+pyQT5利用 Graphics View 控件显示图像并实现其缩放:https://blog.csdn.net/weixin_39964552/article/details/82937144

二 QTableWidget

  本节内容来自这里

1 使用要点

  如果刚接触QTableWidget的话,一般都会对setItem和setCellWidget这两个方法感到迷惑,单元格不是由QTableWidgetItem实现的嘛,用setItem就行了,那setCellWidget用来干嘛的。
  这里我们理清楚了cellWidget其实是单元格上的视图Widget,方便我们实现组合控件的效果,就不会困惑了。
  另外要补充的是,QTableWidget是从 QTableView 派生的,QTableView 可以指定自己的数据模型,而QTableWidget不行(setModel是私有方法),其数据都依附在QTableWidgetItem上的,所以每个单元格如果需要有自己的数据的话,一般都会有一个对应的QTableWidgetItem。
  再补充一下对Qt的model/view模式的一些认知:
  model并不一定需要持有数据,数据可以保存在单独的类中、文件中或数据库中,model更像是一种操作数据的接口;出于定位、查找和访问数据的需要,model把数据抽象成list、table和tree三种组织形式,可以满足绝大多数的情况。
  view的元素选择状态是通过单独的QItemSelectionModel来维护的,通过使用同一个selectionModel,可以让不同的view同步选择状态。
  因为model和view的数据流向是单向的,即view只从model获取数据来展示,所以需要增加一个delegate的角色,来实现一个反向的数据流动,即外界通过delegate对view上的内容进行编辑,然后delegate再通过model把这个编辑后的数据保存起来;另外delegate也可以用来展示数据,通过重载paint方法来实现自定义界面。

三 事件

  本节内容来自这里
  PyQt中提供了两种针对事件处理的机制:一种是信号和槽,另一种则是事件;事件处理在PyQt中是比较底层的,这里的事件常见如下类型:
  键盘事件、鼠标事件、拖放事件、滚轮事件、定时事件、焦点事件、进入和离开事件(光标移入控件或者移出),移动事件(窗口位置变化),显示和隐藏事件,窗口事件(窗口是否为当前窗口)、以及常见的Qt事件:Socket事件、剪贴板事件、文字改变事件,布局改变事件;
  PyQt提供了5中事件处理和过滤方法,弱到强,其中前两者常用;
  1、重写事件具体的函数(例如:mousePressEvent()/keyPressEvent()…)
  2、重新实现QObject.event()一般用在PyQt没有提供该事件的处理函数的情况下,即添加一个新的事件;
  3、通过事件过滤器
  对QObject调用installEventFilter,则相当于为QObject安装了一个事件过滤器;对于QObject的全部事件来说,他们会先传递到事件过滤函数eventFilter中;
  在函数中我们可以放弃或者修改某些事件,如果该过滤事件比较多,则会降低性能;
  4、在QApplication中设置事件过滤器
  这种方式比前者更强大,QApplication的事件过滤器会捕获所有QObject的事件,且第一个获得事件,即在任意一个事件过滤器之前捕获;
  5、重写QApplication中notify()方法;
  使用notify方法来分发事件;想在任意事件处理器之前捕获事件,则唯一方法就是从写notify方法;
  例如:(1)(2)重写具体事件和event函数;

#事件机制
#信号与槽(QTabWidget略)自定义参数
from PyQt5.QtWidgets import  QComboBox,QTableView,QAbstractItemView,QHeaderView,QTableWidget, QTableWidgetItem, QMessageBox,QListWidget,QListWidgetItem, QStatusBar,  QMenuBar,QMenu,QAction,QLineEdit,QStyle,QFormLayout,   QVBoxLayout,QWidget,QApplication ,QHBoxLayout, QPushButton,QMainWindow,QGridLayout,QLabel
from PyQt5.QtGui import QIcon,QPixmap,QStandardItem,QStandardItemModel,QCursor,QFont,QBrush,QColor,QPainter
from PyQt5.QtCore import QStringListModel,QAbstractListModel,QModelIndex,QSize,Qt,QObject,pyqtSignal,QTimer,QEvent

import sys
class Win(QWidget):
    def __init__(self,parent=None):

        super(Win, self).__init__(parent)
        self.message=""
        self.btn1=QPushButton("点击1",self)
        self.btn1.move(20,40)
        self.btn1.clicked.connect(lambda :self.btnFn(1))#点击按钮,执行btnFn方法

        self.btn2 = QPushButton("点击2", self)
        self.btn2.move(140,40)
        self.btn2.clicked.connect(lambda: self.btnFn(2))  # 点击按钮,执行btnFn方法
    def btnFn(self,flag):
        if flag==1:
            print("点击了第一个按钮")
        else:
            print("点击了第二个按钮")


    #上面之前的实例,下面从写按键事件方法
    ###第一种方式
    def keyPressEvent(self, QKeyEvent):#重写按键事件
        if QKeyEvent.key()==Qt.Key_A:#按A建
           self.btn1.click()
        if  QKeyEvent.key()==Qt.Key_Tab:
            print("TABTAB")

    def closeEvent(self, QCloseEvent):#重写关闭窗口事件
        print("重写关闭窗口事件closeEvent")

    def contextMenuEvent(self, even):#重写上下文菜单事件
        menu=QMenu(self)
        oneAction=menu.addAction("One")
        oneAction.triggered.connect(self.one)

        menu.addSeparator()#菜单添加分割线
        twoAction=menu.addAction("Two")
        twoAction.triggered.connect(self.two)

        menu.exec_(even.globalPos())#事件触发在任意位置

    def one(self):
        print("one")
        self.message="Menu option One"
        self.update()
    def two(self):
        print("two")
        self.message="Menu option Two"
        self.update()

    def paintEvent(self, event):#重写绘制事件
        painter=QPainter(self)
        painter.setRenderHint(QPainter.TextAntialiasing)
        painter.drawText(self.rect(),Qt.AlignCenter,self.message)
        QTimer.singleShot(15000,self.clearMessage)#清空数据
        QTimer.singleShot(15000,self.update)#更新当前组件

    def clearMessage(self):
        self.message=""

    def resizeEvent(self, event):#更新窗体大小事件
        self.message="窗口大小调整为:QSize({0},{1})".format(event.size().width(),event.size().height())
        self.update()

    ###第二种方式,一般适用于PyQt没有提供事件处理函数情况,需要自定义事件,例如:
        #Tab按键不会传递给keyPressEvent;
       #第二种方式则是重写event函数,所有针对窗口的事件都会传递给event函数,event会根据事件的类型,讲事件分配给不同函数处理;

    def event(self,event):
        #?????下面捕获不到tab按键的事件,if判断始终未false,不知道为何,待解决;???
        if ( event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab ):
            print("Tab")
            self.message="在event()中捕获Tab按键触发的事件"
            self.update()
            return True
        return QWidget.event(self,event)


if __name__=='__main__':

    app=QApplication(sys.argv)
    win = Win()
    win.show()
    sys.exit(app.exec_())

  例如:(3)事件过滤器;单击按钮获取左键或者右键单击事件;

#事件机制
from PyQt5.QtWidgets import  QDialog,QComboBox,QTableView,QAbstractItemView,QHeaderView,QTableWidget, QTableWidgetItem, QMessageBox,QListWidget,QListWidgetItem, QStatusBar,  QMenuBar,QMenu,QAction,QLineEdit,QStyle,QFormLayout,   QVBoxLayout,QWidget,QApplication ,QHBoxLayout, QPushButton,QMainWindow,QGridLayout,QLabel
from PyQt5.QtGui import QIcon,QPixmap,QStandardItem,QStandardItemModel,QCursor,QFont,QBrush,QColor,QPainter,QMouseEvent,QImage,QTransform
from PyQt5.QtCore import QStringListModel,QAbstractListModel,QModelIndex,QSize,Qt,QObject,pyqtSignal,QTimer,QEvent

import sys
class Win(QWidget):
    def __init__(self,parent=None):
        super(Win, self).__init__(parent)
        self.resize(400,400)

        self.btn=QPushButton("按钮",self)
        self.btn.move(50,50)
        self.btn.setMinimumWidth(220)

        self.image=QImage("./image/7.ico")
        self.label=QLabel('图片',self)
        self.label.setMinimumWidth(6)
        self.label.setMinimumHeight(400)
        self.label.move(100,150)
        self.label.setPixmap(QPixmap(self.image))

        #对按钮添加事件过滤器
        self.btn.installEventFilter(self)

    def  eventFilter(self,obj, event):
         if obj==self.btn:
             if event.type()==QEvent.MouseButtonPress:
                mouseEvent=QMouseEvent(event)
                if mouseEvent.buttons()==Qt.LeftButton:
                   self.btn.setText("按鼠标左键缩小图片")
                   transform=QTransform()
                   transform.scale(0.5,0.5)#设置缩放多少倍
                   self.label.setPixmap(QPixmap.fromImage(self.image.transformed(transform)))#将label中图片设置缩放效果
                if mouseEvent.buttons()==Qt.RightButton:
                    self.btn.setText("按鼠标右键放大图片")
                    transform = QTransform()
                    transform.scale(1.5, 1.5)#设置缩放多少倍
                    self.label.setPixmap(QPixmap.fromImage(self.image.transformed(transform)))
         return QWidget.eventFilter(self,obj,event)
    
if __name__=='__main__':

    app=QApplication(sys.argv)
    win = Win()
    win.show()
    sys.exit(app.exec_())

  关于(4)和(5)略,这里不写例子了;
  补充:
  第四种在QApplication中安装事件监听,只需要修改上面程序即可:

app=QApplication(sys.argv)
    win = Win()
    app.installEventFilter(win)  #安装事件过滤器,将前面代码中按钮的installEventFilter注释掉即可
    win.show()
    sys.exit(app.exec_())

四 鼠标点击获取图片像素点的坐标两种办法 区域像素处理

  本节内容来自这里

  1. cv2 的办法
    区域像素可以进行处理,我做了一个鼠标选取两点,将这块区域置零的操作。
import cv2
import numpy as np
img = cv2.imread('C:\\Users\\mimi\\Desktop\\1.jpg')
a =[]
b = []
def on_EVENT_LBUTTONDOWN(event, x, y,flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        xy = "%d,%d" % (x, y)
        a.append(x)
        b.append(y)
        cv2.circle(img, (x, y), 1, (255, 0, 0), thickness=-1)
        cv2.putText(img, xy, (x, y), cv2.FONT_HERSHEY_PLAIN,
                    1.0, (0, 0, 0), thickness=1)
        cv2.imshow("image", img)

cv2.namedWindow("image")
cv2.setMouseCallback("image", on_EVENT_LBUTTONDOWN)
cv2.imshow("image", img)
cv2.waitKey(0)
print(a[0],b[0])

img[b[0]:b[1],a[0]:a[1],:] = 0   #注意是 行,列(y轴的,X轴)
cv2.imshow("image", img)
cv2.waitKey(0)
print (a,b)

2.plt的方法
不能对图像的像素处理,我做了一个区域置黑的操作没成功,上一个代码可以实现

import matplotlib.pyplot as plt
from PIL import Image

im = Image.open("C:\\Users\\mimi\\Desktop\\1.jpg")
plt.imshow(im, cmap = plt.get_cmap("gray"))
pos=plt.ginput(2)
print(pos)
a=[]
b=[]
for i in range(len(pos)):
    a.append(pos[i][0])
    b.append(pos[i][1])
print (a,b)
im[a[0]:a[1],b[0]:b[1],:]
plt.imshow(im)

五 QtDesigner之布局管理

在这里插入图片描述
  QtDesigner中有水平布局(Lay Out Horizontally)、垂直布局(Lay Out Vertically)、栅格布局(Lay Out in a Grid)、容器布局、表单布局(Lay Out in a Form Layout)、以及绝对布局、纵向分裂式布局(Lay Out Horizontally in Splitter)、横向分裂式布局(Lay Out Vertically in Splitter)、(Break Layout):不使用布局。

1 水平布局

  将多个空间在水平方向展示,并且每个控件之间的间隔是相同的。创建水平布局有两种方式:
1、控件-水平布局
  这种方式就是先将空间拖到主窗口,然后再进行水平布局。
在这里插入图片描述
  拖动三个button控件到主窗口。
在这里插入图片描述
  选中所有的空间然后右键选择layout中的horizontally即可。
2、水平布局-控件
在这里插入图片描述
  这种方法是先将水平布局控件移动到主窗口中,然后拖动比如button按钮,但是此时这个按钮会占据整行,我们只需要在该按钮的最右边或者最左边插入新的控件即可:
在这里插入图片描述
3、运行
  将布局视图转成python代码进行运行,将布局转成python代码:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'horizontal.ui'
#
# Created by: PyQt5 UI code generator 5.13.0
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.horizontalLayoutWidget.setGeometry(QtCore.QRect(110, 70, 239, 80))
        self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.pushButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)
        self.pushButton_2 = QtWidgets.QPushButton(self.horizontalLayoutWidget)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout.addWidget(self.pushButton_2)
        self.pushButton_3 = QtWidgets.QPushButton(self.horizontalLayoutWidget)
        self.pushButton_3.setObjectName("pushButton_3")
        self.horizontalLayout.addWidget(self.pushButton_3)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 23))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))
        self.pushButton_2.setText(_translate("MainWindow", "PushButton"))
        self.pushButton_3.setText(_translate("MainWindow", "PushButton"))

  然后在新建py文件,进行运行:

import sys
import horizontal
from PyQt5.QtWidgets import QApplication,QMainWindow

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = QMainWindow()
    ui = horizontal.Ui_MainWindow()

    #向主窗口添加控件
    ui.setupUi(mainWindow)
    mainWindow.show()
    sys.exit(app.exec_())

2 容器布局

  其实容器布局本质使用的还是上面水平布局、垂直布局、栅格布局、表单布局。
在这里插入图片描述
  可以拖动一个容器,比如Frame到主窗口上,然后再拖动控件到容器中,最后右键容器,此时可以将其转成各种布局方式,当然你也可以在外面先布局后,然后拖到容器中也是可以的。

3 绝对布局

  什么是绝对布局?其实绝对布局就是控件的默认布局方式,比如你拖动一个空间到主窗口中,那么它是以左侧和上侧为基准的,实质上是通过geometry参数进行控制的。
在这里插入图片描述
  值得注意的是,如果控件是在容器中的话,它就是相当于容器的左侧和上侧的位置。

4 控件应用

1、间隔与分割线
  我们知道水平布局或者垂直等布局内部控件之间默认是等间距的,那么如果想让两个控件之间有一定的距离应该怎么做呢?此时需要用到间隔线:
在这里插入图片描述
  上面就是利用水平的间隔线控制水平布局中两个控件之间的水平距离,同理,可以利用垂直间隔线控制垂直布局中两个控件之间的间隔。
  那么间隔线又是什么呢?顾名思义就是区别不同组件:
在这里插入图片描述
  这样在生成视图时会有这么一条线。

2、控件的最大最小尺寸
  当你将一个控件拖动到主窗口后,这个空间会有自己的默认属性,其中就会有控件的最大与最小尺寸:
在这里插入图片描述
  我们可以改变这个值,这样放大这个控件最大不能超过其最大值,最小值不能超过其最小值。

3、尺寸策略(sizePolicy)
  在你拖动一个控件到主窗口中时,这个控件已经有自己默认的尺寸了,这个默认尺寸就是控件的期望尺寸,那么我们可以改变其期望尺寸策略:
在这里插入图片描述
  可以看到这个控件有四种尺寸策略,分别为Horizontal Policy、Vertical Policy、Horizontal Stretch、Vertical Stretch。其中Horizontal Policy策略中是Minimum,也就是控件默认的期望尺寸取最小值。
  其中Horizontal Policy中的Expanding与Horizontal Stretch配合使用可以达到很好的布局效果,第一个控件占这个水平布局两份,其余两个控件也设置Expanding的尺寸策略,但是Horizontal Stretch为1,仅占一份。
在这里插入图片描述
4、控件伙伴关系
  控件伙伴关系的设定实际上就是设置热键,比如:
在这里插入图片描述
  上述是一个表单布局,现在给labei控件与对应的line Edit设置伙伴的关系,也就是比如通过Alt+A热键给姓名的输入框聚焦,Alt+B热键给密码的输入框的热键聚焦:
在这里插入图片描述
  这个地方需要注意的是在label中加入如上内容&A与&B。
  通过Edit–>buddies进行设置伙伴关系:在这里插入图片描述
  这样就完成了伙伴关系,在生成的页面中通过ALT+A或者ALT+B进行切换输入框。

5、修改控件Tab顺序
在这里插入图片描述
  这地方有四个控件,现在修改其排列的位置,通过Edit–>Edit Tab Order
在这里插入图片描述
  这样有序号,你可以通过双击序号改变顺序,或者右键主窗口空白处选择Tab顺序编辑即可:
在这里插入图片描述
6、信号与槽
  信号是由对象或控件发射出去的消息;槽本质上是一个函数或方法。比如说按钮的单击事件,当单击按钮时就会向外界发送消息(信号),信号需要东西来接收(槽)。信号可以理解为事件,槽可以理解为事件函数。它们之间是多对多的关系。
  那么在QtDesigner中如何来完成这个功能呢?比如单击按钮关闭窗口。
  在QtDesigner的页面中,Edit—>Edit Signals/Slots后点击按钮拖动出现:
在这里插入图片描述
  松开鼠标弹出函数选择:
在这里插入图片描述
  此时就完成了这个功能,在预览下单击按钮可关闭窗口。

六 pyqt5编程

1 窗口的最小化与最大化

  一般窗口的标题栏上有最小化、最大化按钮,用鼠标点击它可以将窗口缩小成图标或最大化到整个屏幕。使用QWidget类的方法也可达到同样的目的:

showMinimized( )- 最小化;
showMaximized() - 最大化;
showFullscreen() - 窗口全屏显示,不带标题栏和边框。
showNormal() - 回到窗口的原始尺寸。
activateWindow() 
将窗口变为活动窗口。如果窗口是最小化状态,将会恢复到窗口的原始尺寸。
setwindowState () 
根据Flags值,设置窗口的状态。Flags值可为下列值的组合,这些值来自QtCore.Qt。
windowNoState - 正常状态
windowMinimized - 最小化
windowMaximized - 最大化
windowFullScreen - 全屏显示
windowActive - 活动窗口

  可用下列函数来获得窗口的状态:

isMinimized () - 如果窗口最小化,返回值为True;否则,为False;
isMaximized() - 如果窗口最大化,返回值为True;否则,为False;
isFullScreen( ) - 如果窗口全屏显示,返回值为True;否则,为False;
isActiveWindow() - 如果是活动窗口,返回值为True;否则,为False;;
windowstate() - 返回窗口状态的组合值.

2 系统工具类显示图标

import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")

sys.exit(main())

2 ui转py

pyuic5 -o test.py test.ui

3 关闭工具栏最大以及最小化按钮

self.setWindowFlags(Qt.WindowCloseButtonHint)

4 控件的显示和隐藏

只有父控件显示,子控件才能显示;
setVisible(bool):设置控件是否可见,button.setVisible(True):True 可见;
setHidden(bool):设置控件是否隐藏,Ture:隐藏。
show():显示控件;
hide():隐藏控件;
isHidden():判定控件是否隐藏;
isVisible():判定控件是否显示;
isVisibleTo(widget):如果能随着widget控件的显示和隐藏, 而同步变化, 则返回True;
注意:
isVisible():代表控件最终的状态, 是否被我们所见(被其他控件遮挡也属于可见);
isHidden():只有控件设置了隐藏才返回True,否则返回False.(比例父控件没有显示,子控件是不可能显示,返回的是False)。

七 Python PyInstaller安装和使用教程(详解版)

  在创建了独立应用(自包含该应用的依赖包)之后,还可以使用 PyInstaller 将 Python 程序生成可直接运行的程序,这个程序就可以被分发到对应的 Windows 或 Mac OS X 平台上运行。

1 安装 PyInstalle

  使用 pip 命令安装即可:pip install pyinstaller
  强烈建议使用 pip 在线安装的方式来安装 PyInstaller 模块,不要使用离线包的方式来安装,因为 PyInstaller 模块还依赖其他模块,pip 在安装 PyInstaller 模块时会先安装它的依赖模块。
  运行上面命令,应该看到如下输出结果:Successfully installed pyinstaller-x.x.x,其中的 x.x.x 代表 PyInstaller 的版本。
  在 PyInstaller 模块安装成功之后,在 Python 的安装目录下的 Scripts(D:\Python\Python36\Scripts) 目录下会增加一个 pyinstaller.exe 程序,接下来就可以使用该工具将 Python 程序生成 EXE 程序了。

2 PyInstaller生成可执行程序

  PyInstaller 工具的命令语法如下:pyinstaller 选项 Python 源文件
  不管这个 Python 应用是单文件的应用,还是多文件的应用,只要在使用 pyinstaller 命令时编译作为程序入口的 Python 程序即可。
  PyInstaller工具是跨平台的,它既可以在 Windows平台上使用,也可以在 Mac OS X 平台上运行。在不同的平台上使用 PyInstaller 工具的方法是一样的,它们支持的选项也是一样的。
  下面先创建一个 app 目录,在该目录下创建一个 app.py 文件,文件中包含如下代码:

from say_hello import *

def main():
    print('程序开始执行')
    print(say_hello('孙悟空'))
# 增加调用main()函数
if __name__ == '__main__':
    main()

  接下来使用命令行工具进入到此 app 目录下,执行如下命令:pyinstaller -F app.py
  执行上面命令,将看到详细的生成过程。当生成完成后,将会在此 app 目录下看到多了一个 dist 目录,并在该目录下看到有一个 app.exe 文件,这就是使用 PyInstaller 工具生成的 EXE 程序。
  在命令行窗口中进入 dist 目录下,在该目录执行 app.exe ,将会看到该程序生成如下输出结果:

程序开始执行
孙悟空,您好!

  由于该程序没有图形用户界面,因此如果读者试图通过双击来运行该程序,则只能看到程序窗口一闪就消失了,这样将无法看到该程序的输出结果。
  在上面命令中使用了-F 选项,该选项指定生成单独的 EXE 文件,因此,在 dist 目录下生成了一个单独的大约为 6MB 的 app.exe 文件(在 Mac OS X 平台上生成的文件就叫 app,没有后缀);与 -F 选项对应的是 -D 选项(默认选项),该选项指定生成一个目录(包含多个文件)来作为程序。
  下面先将 PyInstaller 工具在 app 目录下生成的 build、dist 目录删除,并将 app.spec 文件也删除,然后使用如下命令来生成 EXE 文件。

pyinstaller -D app.py

  执行上面命令,将看到详细的生成过程。当生成完成后,将会在 app 目录下看到多了一个 dist 目录,并在该目录下看到有一个 app 子目录,在该子目录下包含了大量 .dll 文件和 .pyz 文件,它们都是 app.exe 程序的支撑文件。在命令行窗口中运行该 app.exe 程序,同样可以看到与前一个 app.exe 程序相同的输出结果。
  PyInstaller 不仅支持 -F、-D 选项,而且也支持如表 1 所示的常用选项。
  表 1 PyInstaller 支持的常用选项

-h,–help查看该模块的帮助信息
-F,-onefile产生单个的可执行文件
-D,–onedir产生一个目录(包含多个文件)作为可执行程序
-a,–ascii不包含 Unicode 字符集支持
-d,–debug产生 debug 版本的可执行文件
-w,–windowed,–noconsolc指定程序运行时不显示命令行窗口(仅对 Windows 有效)
-c,–nowindowed,–console指定使用命令行窗口运行程序(仅对 Windows 有效)
-o DIR,–out=DIR指定 spec 文件的生成目录。如果没有指定,则默认使用当前目录来生成 spec 文件
-p DIR,–path=DIR设置 Python 导入模块的路径(和设置 PYTHONPATH 环境变量的作用相似)。也可使用路径分隔符(Windows 使用分号,Linux 使用冒号)来分隔多个路径
-n NAME,–name=NAME指定项目(产生的 spec)名字。如果省略该选项,那么第一个脚本的主文件名将作为 spec 的名字

  在表 1 中列出的只是 PyInstaller 模块所支持的常用选项,如果需要了解 PyInstaller 选项的详细信息,则可通过 pyinstaller -h 来查看。
  下面再创建一个带图形用户界面,可以访问 MySQL 数据库的应用程序。
  在 app 当前所在目录再创建一个 dbapp 目录,并在该目录下创建 Python 程序,其中 exec_select.py 程序负责查询数据,main.py 程序负责创建图形用户界面来显示查询结果。
  exec_select.py 文件包含的代码如下:

# 导入访问MySQL的模块
import mysql.connector
def query_db():
    # ①、连接数据库
    conn = conn = mysql.connector.connect(user='root', password='32147',
        host='localhost', port='3306',
        database='python', use_unicode=True)
    # ②、获取游标
    c = conn.cursor()
    # ③、调用执行select语句查询数据
    c.execute('select * from user_tb where user_id > %s', (2,))
    # 通过游标的description属性获取列信息
    description = c.description
    # 使用fetchall获取游标中的所有结果集
    rows = c.fetchall()
    # ④、关闭游标
    c.close()
    # ⑤、关闭连接
    conn.close()
    return description, rows

  mian.py 文件包含的代码如下:

from exec_select import *
from tkinter import *
def main():
    description, rows = query_db()
    # 创建窗口
    win = Tk()
    win.title('数据库查询')
    # 通过description获取列信息
    for i, col in enumerate(description):
        lb = Button(win, text=col[0], padx=50, pady=6)
        lb.grid(row=0, column=i)
    # 直接使用for循环查询得到的结果集
    for i, row in enumerate(rows):
        for j in range(len(row)):
            en = Label(win, text=row[j])
            en.grid(row=i+1, column=j)
    win.mainloop()
if __name__ == '__main__':
    main()

  通过命令行工具进入 dbapp 目录下,在该目录下执行如下命令:Pyinstaller -F -w main.py。-F 选项指定生成单个的可执行程序,-w 选项指定生成图形用户界面程序(不需要命令行界面),在 dbapp 目录下生成了一个 dist 子目录,并在该子目录下生成了一个 main.exe 文件。

3 pyinstaller的几个坑

  1、当你使用错误的参数去打包或者打包到一半中断,等等此类运行到一半没了的情况。会导致你原来的py文件变成一个0KB的空文件。里面的代码会全部消失!!!所以以后需要有个良好的习惯,就是复制一份代码出来,用这个副本进行打包。并且参数出错,或者打错了导致失败时,检查下副本文件的py文件是否还存在再继续重新打包,不然打出来的就是空的文件,自然一直闪退,因为压根没内容。
  2、写代码的时候应当有个良好的习惯,用什么函数导什么函数,不要上来import整个库,最后你会发现你一个100KB的代码打包出来有500MB,全是库,简直无语
  3、pygame代码调试的时候要用quit()不然程序结束时会崩溃。但是直接运行py文件就不需要这个函数。这个我们之前的文章也提到过了。但是这里当我使用-w做成无窗口的程序后,一结束发现报错,暂停后发现报的是找不到quit()函数。可见这个函数其实是一个很让人无语的函数。一方面pygame官方网上说结束程序的时候需要加入这个函数。另一方面其实在正在运行时都不需要甚至会报错。虽然也不影响运行,但是弹个窗口出来说 什么不fail to execute总是让别人觉得你是个lowb。所以,调试的时候加一下,到时候execute的时候去掉。
  4、获取exe当前文件路径

import os
import sys
print(os.path.dirname(os.path.realpath(sys.argv[0])))

  __file__方法无法使用,要用sys.argv[0]。
  5、析构函数调用时间不确定
  关闭exe程序时,类方法__del__不一定能及时执行,最好不要把重要逻辑写到__del__中。实际上,python程序中任何时候调用__del__都不一定马上执行,因为python采用自动引用计数(Automatic Reference Counting)来做垃圾回收。可以将重要逻辑写到关闭事件中。

4 打包图片资源

  软件图标使用 -i 参数打包,软件的标题上的图标,使用 qrc 文件的方式显示。Qt中的qrc文件是一个xml格式的资源配置文件,qrc文件可以用安装了Qt的vs生成也可以手写,手写并不会很麻烦,qrc 大致格式为:

<RCC>
    <qresource prefix="/">
        <file>文件名</file>
        #多少个文件写多少行
    </qresource>
</RCC>

(以后的操作应注意相对路径和绝对路径)
  以下面为例我们先把图标资源文件(高亮的四张图片)放到工程所处的文件夹下面。然后创建以 .qrc 为后缀的文件:

<RCC>
    <qresource prefix="/">
        <file>1.jpg</file>
        <file>2.jpg</file>
        <file>3.jpg</file>
    </qresource>
</RCC>

  接下来使用pyrcc5进行转换得到.py文件(pyrcc5是PyQt5的附带工具,安装PyQt5后有):pyrcc5 -o resource.py resource.qrc,得到了resource.py之后就可以在主脚本snake.py中使用了,将resource.py文件添加到解决方案后import resource:
在这里插入图片描述
  然后我们就可以使用resource.py中的资源了:
在这里插入图片描述
  注意:图片名称还是原来的名称,前面的“:”冒号一定不能少(我也不知道为什么)

八 Nuitka

1 安装

  安装 Nuitka:pip install nuitka;或者安装最新版本(根据使用经验发现新版本修复问题多,兼容性更好):pip install -U "https://github.com/Nuitka/Nuitka/archive/develop.zip"

2 说明

  经测试,Nuitka打包后的exe比Pyinstaller打包后的exe运行速度提升30%,PyQT5的UI文件转换成py文件转换成C语言后,界面秒开呀。
  Numpy等类似c程式和pyd的调用还是忽略编译好,不要一咕噜全梭哈啦,编译后反而更慢。重点事项是要小本本记上,别说本豪猪没有提醒呀!
Nuitka的优化命令阅读本文后再了解下《Nuitka之乾坤大挪移-让天下的Python都可以打包》,打包和调试时间节省到5分钟内。PyQT,Numpy,Scipy,Pandas,Opencv,OpenpyXL等pyd的模块不编译,交给python3x.dll来调用,避免模块依赖失败。生成的UI_xxx.py文件和你编写的py模块(可以包含IP,密码)放到一个下一级的文件夹,设置为必须编译为C/C++。从此你的打包成功率提升到95%,exe打开速度提升到一秒左右。

3 命令

–nofollow-imports # 所有的import不编译,交给python3x.dll执行
–follow-import-to=need # need为你需要编译成C/C++的py文件夹命名
–mingw64 #默认为已经安装的vs2017去编译,否则就按指定的比如mingw
–standalone 独立文件,这是必须的
–windows-disable-console 没有CMD控制窗口
–recurse-all 所有的资源文件 这个也选上
-recurse-not-to=numpy,jinja2 不编译的模块,防止速度会更慢
–output-dir=out 生成exe到out文件夹下面去
–show-progress 显示编译的进度,很直观
–show-memory 显示内存的占用
–plugin-enable=pylint-warnings 报警信息
–plugin-enable=qt-plugins 需要加载的PyQt插件
–windows-icon=你的.ico 软件的图标

4 参考文章

  Python打包exe的王炸-Nuitka:https://zhuanlan.zhihu.com/p/133303836
  Nuitka之乾坤大挪移-让天下的Python都可以打包:https://zhuanlan.zhihu.com/p/137785388
  Quick Cut ——你最得力的视频处理工具 正式发布:https://zhuanlan.zhihu.com/p/163652478
  Python打包exe(32/64位)-Nuitka再下一城:https://zhuanlan.zhihu.com/p/141810934

九 窗口坐标

  本节内容来自这里
  本节内容来自这里

1 理解坐标体系

  Qt的坐标系统是有QPainter类控制的,而QPainter是在绘图设备上绘制。而有时我们并不需要进行绘图只需要通过鼠标事件获取坐标位置而已,这时我们需要了解两点:坐标系统和控件的层次关系。
  首先默认坐标系统中原点(0,0)在其左上角,x坐标向右增长,y坐标向下增长。在基于像素的设备上,默认的单位是一个像素,而在打印机上默认的单位是一个点(1/72英寸)。
  控件的层次关系是根据控件在界面中的布局位置决定的,如下:leftImgLabel的位置就相对于centralWidget而言,通过leftImgLabel.pos()获取的位置并不是全局位置而是相对于centralWidget的位置。
在这里插入图片描述
  如果通过leftImgLabel来显示图片,通过鼠标来获取图片中某个点的位置,如果直接使用鼠标事件中的event->pos()获取的位置并不对应你实际标注的图像上的点,而是鼠标相对于当前窗口的位置(event->globalPos()获取的鼠标相对于当前显示器的位置,可以采用任意截图工具进行测试)。这时就需要将鼠标坐标根据控件的层次关系去转换。

2 获取坐标系

  请记住一点:不管从显示屏屏幕还是程序窗口来看,左上角都为原点(0, 0),向右为x轴正向,向下为y轴正向。
  针对程序窗口的坐标系:
在这里插入图片描述
  针对窗口中控件的坐标系:
在这里插入图片描述
  接着我们来看一下Qt官方文档上关于窗口坐标的一张图:
在这里插入图片描述
  我们可以把窗口分成三块:标题栏、边框和客户区,这样进行分割后我们才能很好地理解不同的方法所获取到的坐标有什么不同。
  以上方法理解如下:

  • x()——得到窗口左上角在显示屏屏幕上的x坐标;
  • y()——得到窗口左上角在显示屏屏幕上的y坐标;
  • pos()——得到窗口左上角在显示屏屏幕上的x和y坐标,类型为QPoint();
  • geometry().x()——得到客户区左上角在显示屏屏幕上的x坐标;
  • geometry().y()——得到客户区左上角在显示屏屏幕上的y坐标;
  • geometry()——得到客户区左上角在显示屏屏幕上的x和y坐标,以及客户区的宽度和长度,类型为QRect();
  • width()——得到客户区的宽度;
  • height()——得到客户区的长度;
  • geometry().width()——得到客户区的宽度;
  • geometry().height()——得到客户区的长度;
  • frameGeometry().width()——得到窗口的宽度;
  • frameGeometry().height()——得到窗口的长度;
    补充:
  • frameGeometry().x()——即x(),得到窗口左上角在显示屏屏幕上的x坐标;
  • frameGeometry().y()——即y(),得到窗口左上角在显示屏屏幕上的y坐标;
  • frameGeometry()——即pos(),得到窗口左上角在显示屏屏幕上的x和y坐标,以及窗口的宽度和长度,类型为QRect();

  注:通过geometry()和frameGeometry()获取坐标的相关方法需要在窗口调用show()方法之后才能使用,否则获取的将是无用数据。
  现在我们来实例化一个QWidget窗口,并调用各个方法打印下它的坐标:

import sys
from PyQt5.QtWidgets import QApplication, QWidget

if __name__ == '__main__':
    app = QApplication(sys.argv)
    widget = QWidget()
    widget.resize(200, 200)                    # 1
    widget.move(100, 100)                      # 2
    # widget.setGeometry(100, 100, 200, 200)   # 3
    widget.show()
 
    print('-----------------x(), y(), pos()-----------------')
    print(widget.x())
    print(widget.y())
    print(widget.pos())
 
    print('-----------------width(), height()-----------------')
    print(widget.width())
    print(widget.height())
 
    print('-----------------geometry().x(), geometry.y(), geometry()-----------------')
    print(widget.geometry().x())
    print(widget.geometry().y())
    print(widget.geometry())
 
    print('-----------------geometry.width(), geometry().height()-----------------')
    print(widget.geometry().width())
    print(widget.geometry().height())
 
    print('-----------------frameGeometry().x(), frameGeometry().y(), frameGeometry(), '
          'frameGeometry().width(), frameGeometry().height()-----------------')
    print(widget.frameGeometry().x())
    print(widget.frameGeometry().y())
    print(widget.frameGeometry())
    print(widget.frameGeometry().width())
    print(widget.frameGeometry().height())
 
    sys.exit(app.exec_())

  1、通过resize(200, 200)方法来设置窗口大小;
  2、通过move(100, 100)方法将窗口移到屏幕坐标为(100, 100)的位置上;
  3、以上两个方法可以单单通过setGeometry(x, y, width, height)方法来完成。
  以下是在MacOS系统上的输出结果:

-----------------x(), y(), pos()-----------------
100
100
PyQt5.QtCore.QPoint(100, 100)
-----------------width(), height()-----------------
200
200
-----------------geometry().x(), geometry.y(), geometry()-----------------
100
122
PyQt5.QtCore.QRect(100, 122, 200, 200)
-----------------geometry.width(), geometry().height()-----------------
200
200
-----------------frameGeometry().x(), frameGeometry().y(), frameGeometry(), frameGeometry().width(), frameGeometry().height()-----------------
100
100
PyQt5.QtCore.QRect(100, 100, 200, 222)
200
222

  以下是在Windows系统上(win7)的输出结果:

-----------------x(), y(), pos()-----------------
100
100
PyQt5.QtCore.QPoint(100, 100)
-----------------width(), height()-----------------
200
200
-----------------geometry().x(), geometry.y(), geometry()-----------------
108
130
PyQt5.QtCore.QRect(108, 130, 200, 200)
-----------------geometry.width(), geometry().height()-----------------
200
200
-----------------frameGeometry().x(), frameGeometry().y(), frameGeometry(), frameGeometry().width(), frameGeometry().height()-----------------
100
100
PyQt5.QtCore.QRect(100, 100, 216, 238)
216
238

  以下是在Linux(Ubuntu)上的输出结果:

-----------------x(), y(), pos()-----------------
100
100
PyQt5.QtCore.QPoint(100, 100)
-----------------width(), height()-----------------
200
200
-----------------geometry().x(), geometry.y(), geometry()-----------------
100
100
PyQt5.QtCore.QRect(100, 100, 200, 200)
-----------------geometry.width(), geometry().height()-----------------
200
200
-----------------frameGeometry().x(), frameGeometry().y(), frameGeometry(), frameGeometry().width(), frameGeometry().height()-----------------
100
100
PyQt5.QtCore.QRect(100, 100, 200, 200)
200
200

  我们发现由于显示的窗口样式不同,个别坐标或者大小显示的数值也不相同。
  Mac上的窗口样式:
在这里插入图片描述
  Windows(Win7)上的窗口样式:
在这里插入图片描述
  Linux(Ubuntu)上的窗口样式:
在这里插入图片描述

3 小结

  1、窗口可分为标题栏、边框和客户区三个部分。但是从Linux系统上的输出结果来看,在Linux上的窗口并没有将窗口划分为是那个部分,而是始终保持一个整体。Mac上的窗口也没有边框这一部分;
  2、move(x, y)和resize(width, height)方法的功能可以单单通过setGeometry(x, y, width, height)方法来实现(我们也可以用该方法实现窗口中各控件的布局)。

4 Qt中mouseMoveEvent详解

  在Qt中要捕捉鼠标移动事件需要重写 MouseMoveEvent,但是 MouseMoveEvent 为了不太耗资源在默认状态下是要鼠标按下才能捕捉到。要想鼠标不安下的移动也能捕捉到,需要 setMouseTracking(true)。
  但是,QMainWindow 的 MouseMoveEvent 比较奇怪(这也是我觉得有趣的原因)。对于 QMainWindow 即使使用了 setMouseTracking(true) 依然无法捕捉到鼠标没有按下的移动,只有在鼠标按下是才能捕捉。
  解决办法:要先把 QMainWindow 的 CentrolWIdget 使用setMouseTracking(true) 开启移动监视。然后在把 QMainWindow 的setMouseTracking(true)开启监视。之后就一切正常了。

  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值