Qt学习笔记

因为毕设要用到PyQt,所以正在学习Qt以及Qt Designer的使用。

本篇笔记记录我使用当中发现的种种我认为值得记录的要点,可能比较凌乱,新人也可能看得糊涂,不过反正我自己看,先将就着吧,以后有时间再做整理。

开发环境的版本选择以及安装

毕设的要求是使用Python27和PyQt4,我需要先使用Qt做好界面,然后将.ui文件转换成对应的.py,然后再使用Python完成剩下的程序。
至于IDE,我用的是PyCharm。
由于PyCharm的运行需要java环境,可能你还需要安装jdk。。。

因为是老版本了,PyQt4有点麻烦,一开始我还以为PyQt4就是一个软件,可以直接设计UI呢,结果发现就是一个库而已,我要做的是UI,如果纯靠编程,那还不弄死我自己? 所以我需要一个IDE,网上一查,原来Qt里面含的Qt Creater就是一个很好的IDE,那还说什么?直接到官网下载最新的Qt5.6版本,但是最新的Qt Creater真是非常难以入门啊。。。。。安装以后各种问题,我都懒得说了。。。反正就是用不了。 最后弄来弄去,还是没有解决,最后心一横,换了一个老版本,Qt4.8,幸运的是,这个版本的Qt Creater里面还有一个独立的Qt Designer,使用它可以直接先设计好用户界面,并生成.ui文件,最后在用python编程,完成毕设。

Window下:

首先安装python2.7和PyCharm,如果必要,安装JDK。

PyQt4可以直接从官网下载Binary Packages安装,里面自带安装好sip和Qt Designer,不用像有些老的帖子那样去弄这些东西了。。。。
需要注意的是,如果你的python是安装的32位的,那么PyQt4也需要下载32位的。

Linux下:

我使用的是最新的XUbuntu 16.04 LTS,XUbuntu就是Ubuntu,不同之处在于图形化界面是Xfce,而原版是Gnome,Xfce是一个相对轻量化的界面,因为我是用的虚拟机安装,所以Xfce要比Gnome流畅100倍不止。

Linux自带python,而且PyQt4安装也非常简单

sudo apt-get install python-qt4

但注意的是这里的PyQt4不包含designer,需要自行安装

sudo apt-get install qt4-designer

PyCharm的安装,首先你必须要安装好jdk,然后

Installation Instructions
Copy the pycharm-*.tar.gz to the desired installation location (make sure you have rw permissions for that directory)
Unpack the pycharm-*.tar.gz
using the following command: tar xfz pycharm-*.tar.gz
Remove the pycharm-*.tar.gz to save disk space (optional)
Run pycharm.sh from the bin subdirectory

至此,Linux下面的开发环境就算是搭建好了。

PyQt4使用的一些小窍门

当.ui转换为.py文件后:

得到的py文件是没有主函数的,虽然Python本身也没有主函数一说。。。但是直接运行这个文件是没有任何效果的,需要额外的代码:

class MyWindow(QtGui.QMainWindow, NeuronenUI.Ui_MainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setupUi(self)
        # 此处插入代码
        # 建立connect
        self.show()
    # 此处可以定义各种slot

app = QtGui.QApplication(sys.argv)
window = MyWindow()
sys.exit(app.exec_())

固定QMainWindow的大小:

由于要支持跨平台,一些基于平台的功能就不能直接写入类库,导致了使用 Qt 时一些功能需要变通的方法来实现。

比如:Qt 并不支持直接禁止用户调整窗口大小。变通的方法是将窗口的最小大小和最大大小设置为一样。

widget->setMinimumSize(10, 10);
widget->setMaximumSize(10, 10);

PS:暂时不知道如何保留最小化按钮,也就是说,要么最大最小都有,要么都没有

下面是为sizepolicy的各种属性介绍

Constants Description
QSizePolicy::Fixed widget 的实际尺寸只参考 sizeHint() 的返回值,不能伸展(grow)和收缩(shrink)
QSizePolicy::Minimum 可以伸展和收缩,不过sizeHint() 的返回值规定了 widget 能缩小到的最小尺寸
QSizePolicy::Maximum 可以伸展和收缩,不过sizeHint() 的返回值规定了 widget 能伸展到的最大尺寸
QSizePolicy::Preferred 可以伸展和收缩,但没有优势去获取更大的额外空间使自己的尺寸比 sizeHint() 的返回值更大
QSizePolicy::Expanding 可以伸展和收缩,它会尽可能多地去获取额外的空间,也就是比 Preferred 更具优势
QSizePolicy::MinimumExpanding 可以伸展和收缩,不过sizeHint() 的返回值规定了 widget 能缩小到的最小尺寸,同时它比 Preferred 更具优势去获取额外空间
QSizePolicy::Ignored 忽略 sizeHint() 的作用

使用Layout分配各个widget之间的大小:

对于像我这样的新手,往往搞不定Layout,结果就是控件的位置各种凌乱,即使一开始正确,主窗口大小一变,要么就乱,要么控件无法随窗口大小改变。

弄了好几天,总结下来的经验就是:无论如何,不要把控件直接放到第一层的QWidget下面,要添加控件,外面都要套一层Container,嗯,注意带套~~~嗯。。。就这样。。

还有一点要注意的是,网上大多数教程都是以QWidget为基础,但是有的时候我们需要以QMainWindow为基础,这个时候就必须在QMainWindow下面额外放入以个QWidget,并且设置为CentralWidget,然后在此之下新建一个mainlayout

centralwidget = QWidget()
QMainWindow.setCentralWidget(centralwidget)
mainlayout = QVBoxLayout(centralwidget)

mainlayout.addWidget(...)

这样就相当于在正常的Widget下套上一层MainWindow的衣服,而Widget改怎么操作就怎么操作

关于绘图:

简单的绘图,我们通常用QPainter,举个例子:

# -*- coding: utf-8 -*-
# 简单的QPainter程序

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class MyWindow(QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setFixedSize(400, 400)
        self.show()

    def paintEvent(self, event):
        # 在QMainWindow上绘图
        qp = QPainter(self)
        # 蓝色,粗细为2,实线
        pen = QPen(Qt.blue, 2, Qt.SolidLine)
        qp.setPen(pen)
        # 画一个直径400的圆
        qp.drawEllipse(0, 0, 400, 400)

app = QApplication(sys.argv)
win = MyWindow()
sys.exit(app.exec_())

Qt学习笔记_第1张图片

这就是一个简单的Qt程序,效果就是建立一个400*400大小的窗口,并在中心化一个直径400的圆。

但这个程序其实很有问题,因为我们自己做开发的时候,不可能直接在主窗口上绘图,通常来讲,我们会给主窗口一个或则多个container,然后container里面再放入各种widget,而我们需要绘制的内容,往往是在这些widget当中的。比如我们将上面的code改改,添加一个Qwidget:

    def __init__(self):
        super(MyWindow, self).__init__()
        self.setFixedSize(400, 400)
        self.widget = QWidget(self)
        self.widget.setFixedSize(300, 300)
        self.widget.setStyleSheet("background-color:white")
        self.show()

Qt学习笔记_第2张图片

这里我就遇到一个问题,QPainter说明文档里面倒是提到了可以在别的QPaintDevice上作图,按照我本来的理解是,只要把paintEvent里面的QPainter参数变一下,应该就可以在对应的device上绘图。

    def paintEvent(self, event):

        # 在QWidget上绘图
        qp = QPainter(self.widget)

        # 蓝色,粗细为2,实线
        pen = QPen(Qt.blue, 2, Qt.SolidLine)
        qp.setPen(pen)
        # 画一个直径400的圆
        qp.drawEllipse(0, 0, 400, 400)

结果总是提示

QPainter::begin: Widget painting can only begin as a result of a paintEvent

那么如何能够将圆画如textEdit里面呢?

经过探索,以下几种方法可以解决这个问题:

方法1:自己定义一个类,并继承QWidget,在自己的类里面重写paintEvent()

# -*- coding: utf-8 -*-
# 简单的QPainter程序

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class MyWindow(QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setFixedSize(400, 400)
        # self.widget = QWidget(self)
        self.widget = MyWidget(self)
        self.widget.setFixedSize(300, 300)
        self.widget.setStyleSheet("background-color:white")
        self.show()

    def paintEvent(self, event):
        # 在QMainWindow上绘图
        qp = QPainter(self)
        # 蓝色,粗细为2,实线
        pen = QPen(Qt.blue, 2, Qt.SolidLine)
        qp.setPen(pen)
        # 画一个直径400的圆
        qp.drawEllipse(0, 0, 400, 400)

class MyWidget(QWidget):
    def __init__(self,parent):
        super(MyWidget, self).__init__(parent)

    def paintEvent(self, event):
        # 在QWidget上绘图
        qp = QPainter(self)
        # 黑色,粗细为2,实线
        pen = QPen(Qt.black, 2, Qt.SolidLine)
        qp.setPen(pen)
        # 画一个直径400的圆
        qp.drawEllipse(0, 0, 400, 400)

app = QApplication(sys.argv)
win = MyWindow()
sys.exit(app.exec_())

Qt学习笔记_第3张图片

这样虽然解决了问题,但是仔细想想,如果我的GUI有100个Widget需要绘图,那我岂不是要定义100个QWidget的子类,重写100次paintEvent方法??

这样不仅写代码的人痛苦,读代码的人估计更痛苦。

不仅如此,更严重的问题是,我的PyQt4配置文件其实是通过pyuic从.ui文件转换而来的。得到的文件可以说是自成一体,如果我直接在此文件上更改,添加,甚至重写函数,那么如果万一我在designer上调整了ui,并重新更新这个文件,那么我在上面的任何修改都将付诸流水。

既然我不可能修改配置文件,那么我有没有可能从外部去修改或者重写Widget里的paintEvent方法呢?

答案是有!

方法2:动态的添加方法

自定义 函数 paintEvent,然后动态添加到已经存在的实例当中,这样就相当于重写paintEvent 方法,举个例子:

>>> def foo(self):# 这就是函数
...     print "foo"
...
>>> class A:
...     def act_a(self):# 而这里是方法
...         print "actA"
>>> a = A() # 这是一个instance
>>> foo
<function foo at 0x00A98D70> #函数foo的相关信息
>>> a.act_a
<bound method A.bar of <__main__.A instance at 0x00A9BC88>> # 方法 bound
>>>

那么怎样将函数 foo 添加到 class A里面呢?

注意上面的关键字bound,在python当中,函数和方法还是不同的,class之外是函数,他没什么特别的,而在class之内的方法,就要分为bound和unbound两大类。

bound:即为”绑定”,意思就是该方法会自动绑定到所有的instance当中去,所有的instances都会具有这个方法。

>>> A.foo = foo # 就这一步,简不简单~~~~^o^
>>> a2 = A()
>>> a2.foo
<bound method A.foo of <__main__.A instance at 0x00A9BEB8>> >>> a2.foo() foo

unbound:”非绑定”,则该次添加的方法,只对当前实例有效。

>>> a = A()
>>> import types
>>> a.foo = types.MethodType( foo, a )
>>> a.foo
<bound method ?.foo of <__main__.A instance at 0x00A9BC88>> >>> a.foo() foo

网上搜到两篇文章,讲得非常好,可以看看: 英文版 中文版

再来看我自己的代码:

# -*- coding: utf-8 -*-
# 简单的QPainter程序

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class MyWindow(QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.initUI()
        self.show()

    def initUI(self):
        centralwidget = QWidget()
        self.setCentralWidget(centralwidget)
        # 添加一个有两个Tab的QTabWidget
        self.widget = QTabWidget(centralwidget)
        tab_1 = QWidget()
        tab_2 = QWidget()
        self.widget.addTab(tab_1,"tab1")
        self.widget.addTab(tab_2,"tab2")

        vbox = QVBoxLayout(centralwidget)
        vbox.addWidget(self.widget)
        self.setFixedSize(400, 400)

        # bound
        QWidget.paintEvent = paintEvent

def paintEvent(self, event):
    # 在QMainWindow上绘图
    qp = QPainter(self)
    # 蓝色,粗细为2,实线
    pen = QPen(Qt.blue, 2, Qt.SolidLine)
    qp.setPen(pen)
    # 画一个直径400的圆
    qp.drawEllipse(0, 0, 400, 400)


app = QApplication(sys.argv)
win = MyWindow()
sys.exit(app.exec_())

Qt学习笔记_第4张图片

呵呵,是不是和想想的不一样?

不要慌,这是因为我们现在添加的是绑定的方法,所以所有QWidget及其子类都会运行paintEvent方法,这就造成了混乱,而我们只是想在tab_1上画圆。

这时候,就需要添加非绑定的方法到tab_1

        # bound
        # QWidget.paintEvent = paintEvent
        import types
        tab_1.paintEvent = types.MethodType(paintEvent,tab_1)

Qt学习笔记_第5张图片

这个方法真是折磨了我好几天,所幸解决了,不过这个方法也有局限性,虽然比方法1要简单,至少不用定义类,但是paint函数仍然需要大量定义。

方法3:使用QGraphicsView框架。

QGraphicsView提供了一个可以看到QGraphicsScene内容的窗口

QGraphicsScene则提供一个能够管理大量2D图形的界面。

你可能感兴趣的:(Qt学习笔记)