HelloWorld
1.1简单的窗口绘制
这个简单的小例子展示的是一个小窗口。但是我们可以在这个小窗口上面做很多事情,改变大小,最大化,最小化等,这需要很多代码才能实现。这在很多应用中很常见,没必要每次都要重写这部分代码,Qt已经提供了这些功能。PyQt5
是一个高级的工具集合,相比使用低级的工具,能省略上百行代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import sys from PyQt5.QtWidgets import QApplication, QWidget if __name__ == '__main__': app = QApplication(sys.argv) w = QWidget() w.resize(250, 150) w.move(300, 300) w.setWindowTitle('Simple') w.show() sys.exit(app.exec_())
|
执行结果
1.2带窗口图标
窗口图标通常是显示在窗口的左上角,标题栏的最左边。下面的例子就是怎么用PyQt5创建一个这样的窗口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import sys from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtGui import QIcon
class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 300, 220) self.setWindowTitle('Icon') self.setWindowIcon(QIcon('Ex2.jpg')) self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
1.3提示框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import sys from PyQt5.QtWidgets import (QWidget, QToolTip, QPushButton, QApplication) from PyQt5.QtGui import QFont class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): QToolTip.setFont(QFont('SansSerif', 10)) self.setToolTip('This is a <b>QWidget</b> widget') btn = QPushButton('Button', self) btn.setToolTip('This is a <b>QPushButton</b> widget') btn.resize(btn.sizeHint()) btn.move(50, 50) self.setGeometry(300, 300, 300, 200) self.setWindowTitle('Tooltips') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
1.4关闭窗口
关闭一个窗口最直观的方式就是点击标题栏的那个叉,这个例子里,我们展示的是如何用程序关闭一个窗口。这里我们将接触到一点single和slots的知识。
本例使用的是QPushButton
组件类。创建了一个点击之后就退出窗口的按钮。
1
| QPushButton(string text, QWidget parent = None)
|
text
参数是想要显示的按钮名称,parent
参数是放在按钮上的组件,在我们的例子里,这个参数是QWidget
。应用中的组件都是一层一层继承而来的,在这个层里,大部分的组件都有自己的父级,没有父级的组件,是顶级的窗口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import sys from PyQt5.QtWidgets import QWidget, QPushButton, QApplication from PyQt5.QtCore import QCoreApplication class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): qbtn = QPushButton('Quit', self) qbtn.clicked.connect(QCoreApplication.instance().quit) qbtn.resize(qbtn.sizeHint()) qbtn.move(50, 50) self.setGeometry(300, 300, 250, 150) self.setWindowTitle('Quit button') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
1.5消息盒子
默认情况下,我们点击标题栏的×
按钮,QWidget
就会关闭。但是有时候,我们修改默认行为。比如,如果我们打开的是一个文本编辑器,并且做了一些修改,我们就会想在关闭按钮的时候让用户进一步确认操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import sys from PyQt5.QtWidgets import QWidget, QMessageBox, QApplication class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 250, 150) self.setWindowTitle('Message box') self.show() def closeEvent(self, event): reply = QMessageBox.question( self, 'Message', "Are you sure to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
1.6窗口居中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import sys from PyQt5.QtWidgets import QWidget, QDesktopWidget, QApplication class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.resize(500, 500) self.center() self.setWindowTitle('Center') self.show() def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
菜单和工具栏
这个章节,我们会创建状态栏、菜单和工具栏。菜单是一组位于菜单栏的命令。工具栏是应用的一些常用工具按钮。状态栏显示一些状态信息,通常在应用的底部。
2.1状态栏
状态栏是用来显示应用的状态信息的组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import sys from PyQt5.QtWidgets import QMainWindow, QApplication class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.statusBar().showMessage('Ready') self.setGeometry(300, 300, 250, 150) self.setWindowTitle('Statusbar') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
2.2菜单栏
菜单栏是非常常用的。是一组命令的集合(Mac OS下状态栏的显示不一样,为得到最相似的外观,我们可以增加一行语句menubar.setNativeMenuBar(False))。
在下面的示例中,我们创建了只有一个命令的菜单栏,这个命令就是终止应用。同时也创建了一个状态栏。而且还能使用快捷键Ctrl+Q退出应用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import sys from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication from PyQt5.QtGui import QIcon class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): exitAct = QAction(QIcon('Ex2.2.jpg'), '&Exit', self) exitAct.setShortcut('Ctrl+Q') exitAct.setStatusTip('Exit application') exitAct.triggered.connect(qApp.quit) self.statusBar() menubar = self.menuBar() fileMenu = menubar.addMenu('&File') fileMenu.addAction(exitAct) self.setGeometry(300, 300, 300, 200) self.setWindowTitle('Simple menu') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
2.3子菜单
子菜单是嵌套在菜单里面的二级或者三级等的菜单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import sys from PyQt5.QtWidgets import QMainWindow, QAction, QMenu, QApplication class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): menubar = self.menuBar() fileMenu = menubar.addMenu('File') impMenu = QMenu('Import', self) impAct = QAction('Import mail', self) impMenu.addAction(impAct) newAct = QAction('New', self) fileMenu.addAction(newAct) fileMenu.addMenu(impMenu) self.setGeometry(300, 300, 300, 200) self.setWindowTitle('Submenu') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
```
这个例子里,有两个子菜单,一个在File菜单下面,一个在File的Import下面。
2.4勾选菜单
本例创建了一个行为菜单。这个行为/动作能切换状态栏显示或者隐藏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import sys from PyQt5.QtWidgets import QMainWindow, QAction, QApplication class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.statusbar = self.statusBar() self.statusbar.showMessage('Ready') menubar = self.menuBar() viewMenu = menubar.addMenu('View') viewStatAct = QAction('View statusbar', self, checkable=True) viewStatAct.setStatusTip('View statusbar') viewStatAct.setChecked(True) viewStatAct.triggered.connect(self.toggleMenu) viewMenu.addAction(viewStatAct) self.setGeometry(300, 300, 300, 200) self.setWindowTitle('Check menu') self.show() def toggleMenu(self, state): if state: self.statusbar.show() else: self.statusbar.hide() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
2.5右键菜单
右键菜单也叫弹出框,是在某些场合下显示的一组命令。例如,Opera浏览器里,网页上的右键菜单里会有刷新,返回或者查看页面源代码。如果在工具栏上右键,会得到一个不同的用来管理工具栏的菜单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import sys from PyQt5.QtWidgets import QMainWindow, qApp, QMenu, QApplication class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 300, 200) self.setWindowTitle('Context menu') self.show() def contextMenuEvent(self, event): cmenu = QMenu(self) newAct = cmenu.addAction("New") opnAct = cmenu.addAction("Open") quitAct = cmenu.addAction("Quit") action = cmenu.exec_(self.mapToGlobal(event.pos())) if action == quitAct: qApp.quit() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
2.6工具栏
菜单栏包含了所有的命令,工具栏就是常用的命令的集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import sys from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication from PyQt5.QtGui import QIcon class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): exitAct = QAction(QIcon('Ex2.6.jpg'), 'Exit', self) exitAct.setShortcut('Ctrl+Q') exitAct.triggered.connect(qApp.quit) self.toolbar = self.addToolBar('Exit') self.toolbar.addAction(exitAct) self.setGeometry(300, 300, 300, 200) self.setWindowTitle('Toolbar') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
创建了一个工具栏。这个工具栏只有一个退出应用的动作
2.7主窗口
主窗口就是上面三种栏目的总称,现在我们把上面的三种栏在一个应用里展示出来。
上面的代码创建了一个很经典的菜单框架,有右键菜单,工具栏和状态栏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import sys from PyQt5.QtWidgets import QMainWindow, QTextEdit, QAction, QApplication from PyQt5.QtGui import QIcon class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): textEdit = QTextEdit() self.setCentralWidget(textEdit) exitAct = QAction(QIcon('Ex2.6.jpg'), 'Exit', self) exitAct.setShortcut('Ctrl+Q') exitAct.setStatusTip('Exit application') exitAct.triggered.connect(self.close) self.statusBar() menubar = self.menuBar() fileMenu = menubar.addMenu('&File') fileMenu.addAction(exitAct) toolbar = self.addToolBar('Exit') toolbar.addAction(exitAct) self.setGeometry(300, 300, 350, 250) self.setWindowTitle('Main window') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
布局管理
在一个GUI程序里,布局是一个很重要的方面。布局就是如何管理应用中的元素和窗口。有两种方式可以搞定:绝对定位和PyQt5
的layout
类
3.1绝对定位
每个程序都是以像素为单位区分元素的位置,衡量元素的大小。所以我们完全可以使用绝对定位搞定每个元素和窗口的位置。但是这也有局限性:
元素不会随着我们更改窗口的位置和大小而变化。
不能适用于不同的平台和不同分辨率的显示器
更改应用字体大小会破坏布局
如果我们决定重构这个应用,需要全部计算一下每个元素的位置和大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import sys from PyQt5.QtWidgets import QWidget, QLabel, QApplication class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): # 这个元素的左上角就在这个程序的左上角开始的(15, 10)的位置。 lbl1 = QLabel('Zetcode', self) lbl1.move(15, 10) lbl2 = QLabel('tutorials', self) lbl2.move(35, 40) lbl3 = QLabel('for programmers', self) lbl3.move(55, 70) self.setGeometry(300, 300, 250, 150) self.setWindowTitle('Absolute') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
我们使用move()
方法定位了每一个元素,使用x、y坐标。x、y坐标的原点是程序的左上角。
3.2盒布局
使用盒布局能让程序具有更强的适应性。这个才是布局一个应用的更合适的方式。QHBoxLayout
和QVBoxLayout
是基本的布局类,分别是水平布局和垂直布局。
如果我们需要把两个按钮放在程序的右下角,创建这样的布局,我们只需要一个水平布局加一个垂直布局的盒子就可以了。再用弹性布局增加一点间隙。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import sys from PyQt5.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QApplication) class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): okButton = QPushButton("OK") cancelButton = QPushButton("Cancel") hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(okButton) hbox.addWidget(cancelButton) vbox = QVBoxLayout() vbox.addStretch(1) vbox.addLayout(hbox) self.setLayout(vbox) self.setGeometry(300, 300, 300, 150) self.setWindowTitle('Buttons') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
完成了在应用的右下角放了两个按钮的需求。当改变窗口大小的时候,它们能依然保持在相对的位置。我们同时使用了QHBoxLayout
和QVBoxLayout
。
3.3栅格布局
最常用的还是栅格布局了。这种布局是把窗口分为行和列。创建和使用栅格布局,需要使用QGridLayout
模块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import sys from PyQt5.QtWidgets import (QWidget, QGridLayout, QPushButton, QApplication) class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): grid = QGridLayout() self.setLayout(grid) names = ['Cls', 'Bck', '', 'Close', '7', '8', '9', '/', '4', '5', '6', '*', '1', '2', '3', '-', '0', '.', '=', '+'] positions = [(i,j) for i in range(5) for j in range(4)] for position, name in zip(positions, names): if name == '': continue button = QPushButton(name) grid.addWidget(button, *position) self.move(300, 150) self.setWindowTitle('Calculator') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
创建了栅格化的按钮。
3.4制作提交反馈信息的布局
组件能跨列和跨行展示,这个例子里,我们就试试这个功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import sys from PyQt5.QtWidgets import (QWidget, QLabel, QLineEdit, QTextEdit, QGridLayout, QApplication) class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): title = QLabel('Title') author = QLabel('Author') review = QLabel('Review') titleEdit = QLineEdit() authorEdit = QLineEdit() reviewEdit = QTextEdit() grid = QGridLayout() grid.setSpacing(10) grid.addWidget(title, 1, 0) grid.addWidget(titleEdit, 1, 1) grid.addWidget(author, 2, 0) grid.addWidget(authorEdit, 2, 1) grid.addWidget(review, 3, 0) grid.addWidget(reviewEdit, 3, 1, 5, 1) self.setLayout(grid) self.setGeometry(300, 300, 350, 300) self.setWindowTitle('Review') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
我们创建了一个有三个标签的窗口。两个行编辑和一个文版编辑,这是用QGridLayout模块搞定的。
事件和信号
事件
signals and slots
被其他人翻译成信号和槽机制,(⊙o⊙)…我这里还是不翻译好了。
所有的应用都是事件驱动的。事件大部分都是由用户的行为产生的,当然也有其他的事件产生方式,比如网络的连接,窗口管理器或者定时器等。调用应用的exec_()方法时,应用会进入主循环,主循环会监听和分发事件。
在事件模型中,有三个角色:
事件源就是发生了状态改变的对象。事件是这个对象状态改变的内容。事件目标是事件想作用的目标。事件源绑定事件处理函数,然后作用于事件目标身上。
PyQt5处理事件方面有个signal and slot机制。Signals and slots用于对象间的通讯。事件触发的时候,发生一个signal,slot是用来被Python调用的(相当于一个句柄?这个词也好恶心,就是相当于事件的绑定函数)slot只有在事件触发的时候才能调用。
4.1 Signals & slots
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider, QVBoxLayout, QApplication) class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): lcd = QLCDNumber(self) sld = QSlider(Qt.Horizontal, self) vbox = QVBoxLayout() vbox.addWidget(lcd) vbox.addWidget(sld) self.setLayout(vbox) sld.valueChanged.connect(lcd.display) self.setGeometry(300, 300, 250, 150) self.setWindowTitle('Signal and slot') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
下面的例子中,显示了QtGui.QLCDNumber和QtGui.QSlider模块,我们能拖动滑块让数字跟着发生改变。
- 事件源:滑块sender
- 事件:改变数字slot
- 事件目标:LCD数字变化receiver
sender是信号的发送者,receiver是信号的接收者,slot是对这个信号应该做出的反应。
4.2重构事件处理器
在PyQt5中,事件处理器经常被重写(也就是用自己的覆盖库自带的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget, QApplication class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 250, 150) self.setWindowTitle('Event handler') self.show() def keyPressEvent(self, e): if e.key() == Qt.Key_Escape: self.close() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
这个例子中,我们替换了事件处理器函数keyPressEvent()此时如果按下ESC键程序就会退出。
4.3事件对象
事件对象是用python来描述一系列的事件自身属性的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QLabel class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): grid = QGridLayout() grid.setSpacing(10) x = 0 y = 0 self.text = "x: {0}, y: {1}".format(x, y) self.label = QLabel(self.text, self) grid.addWidget(self.label, 0, 0, Qt.AlignTop) self.setMouseTracking(True) self.setLayout(grid) self.setGeometry(300, 300, 350, 200) self.setWindowTitle('Event object') self.show() def mouseMoveEvent(self, e): x = e.x() y = e.y() text = "x: {0}, y: {1}".format(x, y) self.label.setText(text) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
我们在一个组件里显示鼠标的X和Y坐标
4.4事件发送
有时候我们会想知道是哪个组件发出了一个信号,PyQt5里的sender()方法能搞定这件事。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import sys from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): btn1 = QPushButton("Button 1", self) btn1.move(30, 50) btn2 = QPushButton("Button 2", self) btn2.move(150, 50) btn1.clicked.connect(self.buttonClicked) btn2.clicked.connect(self.buttonClicked) self.statusBar() self.setGeometry(300, 300, 290, 150) self.setWindowTitle('Event sender') self.show() def buttonClicked(self): sender = self.sender() self.statusBar().showMessage(sender.text() + ' was pressed') if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
这个例子里有俩按钮,buttonClicked()方法决定了是哪个按钮能调用sender()方法。
4.5信号发送
QObject实例能发送事件信号。下面的例子是发送自定义的信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import sys from PyQt5.QtCore import pyqtSignal, QObject from PyQt5.QtWidgets import QMainWindow, QApplication
class Communicate(QObject): closeApp = pyqtSignal() class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.c = Communicate() self.c.closeApp.connect(self.close) self.setGeometry(300, 300, 290, 150) self.setWindowTitle('Emit signal') self.show() def mousePressEvent(self, event): self.c.closeApp.emit() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
我们创建了一个叫closeApp的信号,这个信号会在鼠标按下的时候触发,事件与QMainWindow绑定。
对话框
对话框是一个现代GUI应用不可或缺的一部分。对话是两个人之间的交流,对话框就是人与电脑之间的对话。对话框用来输入数据,修改数据,修改应用设置等等。
5.1输入文字
QInputDialog
提供了一个简单方便的对话框,可以输入字符串,数字或列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| from PyQt5.QtWidgets import (QWidget, QPushButton, QLineEdit, QInputDialog, QApplication) import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.btn = QPushButton('Dialog', self) self.btn.move(20, 20) self.btn.clicked.connect(self.showDialog) self.le = QLineEdit(self) self.le.move(130, 22) self.setGeometry(300, 300, 290, 150) self.setWindowTitle('Input dialog') self.show() def showDialog(self): text, ok = QInputDialog.getText(self, 'Input Dialog', 'Enter your name:') if ok: self.le.setText(str(text)) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
这个示例有一个按钮和一个输入框,点击按钮显示对话框,输入的文本会显示在输入框里。
5.2选取颜色
QColorDialog提供颜色的选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| from PyQt5.QtWidgets import (QWidget, QPushButton, QFrame, QColorDialog, QApplication) from PyQt5.QtGui import QColor import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): col = QColor(0, 0, 0) self.btn = QPushButton('Dialog', self) self.btn.move(20, 20) self.btn.clicked.connect(self.showDialog) self.frm = QFrame(self) self.frm.setStyleSheet("QWidget { background-color: %s }" % col.name()) self.frm.setGeometry(130, 22, 100, 100) self.setGeometry(300, 300, 250, 180) self.setWindowTitle('Color dialog') self.show() def showDialog(self): col = QColorDialog.getColor() if col.isValid(): self.frm.setStyleSheet("QWidget { background-color: %s }" % col.name()) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
例子里有一个按钮和一个QFrame,默认的背景颜色为黑色,我们可以使用QColorDialog改变背景颜色
5.3选择字体
QFontDialog能做字体的选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QPushButton, QSizePolicy, QLabel, QFontDialog, QApplication) import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): vbox = QVBoxLayout() btn = QPushButton('Dialog', self) btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) btn.move(20, 20) vbox.addWidget(btn) btn.clicked.connect(self.showDialog) self.lbl = QLabel('Knowledge only matters', self) self.lbl.move(130, 20) vbox.addWidget(self.lbl) self.setLayout(vbox) self.setGeometry(300, 300, 250, 180) self.setWindowTitle('Font dialog') self.show() def showDialog(self): font, ok = QFontDialog.getFont() if ok: self.lbl.setFont(font) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
创建了一个有一个按钮和一个标签的QFontDialog的对话框,我们可以使用这个功能修改字体样式。
5.4选择文件
QFileDialog给用户提供文件或者文件夹选择的功能。能打开和保存文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| from PyQt5.QtWidgets import (QMainWindow, QTextEdit, QAction, QFileDialog, QApplication) from PyQt5.QtGui import QIcon import sys #这里设置了一个文本编辑框,文本编辑框是基于QMainWindow组件的。 class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.textEdit = QTextEdit() self.setCentralWidget(self.textEdit) self.statusBar() openFile = QAction(QIcon('Ex5.4.jpg'), 'Open', self) openFile.setShortcut('Ctrl+O') openFile.setStatusTip('Open new File') openFile.triggered.connect(self.showDialog) menubar = self.menuBar() fileMenu = menubar.addMenu('&File') fileMenu.addAction(openFile) self.setGeometry(300, 300, 350, 300) self.setWindowTitle('File dialog') self.show() def showDialog(self): #弹出QFileDialog窗口。getOpenFileName()方法的第一个参数是说明文字,第二个参数是默认打开的文件夹路径。默认情况下显示所有类型的文件。 fname = QFileDialog.getOpenFileName(self, 'Open file', '.\\') #读取选中的文件,并显示在文本编辑框内(但是打开HTML文件时,是渲染后的结果,汗)。 if fname[0]: f = open(fname[0], 'r') with f: data = f.read() self.textEdit.setText(data) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
本例中有一个菜单栏,一个置中的文本编辑框,一个状态栏。点击菜单栏选项会弹出一个QtGui.QFileDialog
对话框,在这个对话框里,你能选择文件,然后文件的内容就会显示在文本编辑框里。
控件(1)
控件就像是应用这座房子的一块块砖。PyQt5有很多的控件,比如按钮,单选框,滑动条,复选框等等。在本章,我们将介绍一些很有用的控件:QCheckBox,ToggleButton,QSlider,QProgressBar 和 QCalendarWidget。
6.1QCheckBox
QCheckBox 组件有俩状态:开和关。通常跟标签一起使用,用在激活和关闭一些选项的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| from PyQt5.QtWidgets import QWidget, QCheckBox, QApplication from PyQt5.QtCore import Qt import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): cb = QCheckBox('Show title', self) cb.move(20, 20) cb.toggle() cb.stateChanged.connect(self.changeTitle) self.setGeometry(300, 300, 250, 150) self.setWindowTitle('QCheckBox') self.show() def changeTitle(self, state): if state == Qt.Checked: self.setWindowTitle('QCheckBox') else: self.setWindowTitle(' ') if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
这个例子中,有一个能切换窗口标题的单选框
6.2切换按钮
切换按钮就是QPushButton的一种特殊模式。 它只有两种状态:按下和未按下。我们在点击的时候切换两种状态,有很多场景会使用到这个功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| from PyQt5.QtWidgets import (QWidget, QPushButton, QFrame, QApplication) from PyQt5.QtGui import QColor import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.col = QColor(0, 0, 0) redb = QPushButton('Red', self) redb.setCheckable(True) redb.move(10, 10) redb.clicked[bool].connect(self.setColor) greenb = QPushButton('Green', self) greenb.setCheckable(True) greenb.move(10, 60) greenb.clicked[bool].connect(self.setColor) blueb = QPushButton('Blue', self) blueb.setCheckable(True) blueb.move(10, 110) blueb.clicked[bool].connect(self.setColor) self.square = QFrame(self) self.square.setGeometry(150, 20, 100, 100) self.square.setStyleSheet("QWidget { background-color: %s }" % self.col.name()) self.setGeometry(300, 300, 280, 170) self.setWindowTitle('Toggle button') self.show() def setColor(self, pressed): source = self.sender() if pressed: val = 255 else: val = 0 if source.text() == "Red": self.col.setRed(val) elif source.text() == "Green": self.col.setGreen(val) else: self.col.setBlue(val) self.square.setStyleSheet("QFrame { background-color: %s }" % self.col.name()) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
我们创建了一个切换按钮和一个QWidget,并把QWidget的背景设置为黑色。点击不同的切换按钮,背景色会在红、绿、蓝之间切换(而且能看到颜色合成的效果,而不是单纯的颜色覆盖)
6.3滑块
QSlider是个有一个小滑块的组件,这个小滑块能拖着前后滑动,这个经常用于修改一些具有范围的数值,比文本框或者点击增加减少的文本框(spin box)方便多了。
本例用一个滑块和一个标签展示。标签为一个图片,滑块控制标签(的值)。
先准备四个分别表示静音、小音量、中音量、大音量的图标,文件名分别叫Ex6.3.1.jpg, Ex6.3.2.jpg, Ex6.3.3.jpg, Ex6.3.4.jpg。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| from PyQt5.QtWidgets import (QWidget, QSlider, QLabel, QApplication) from PyQt5.QtCore import Qt from PyQt5.QtGui import QPixmap import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): sld = QSlider(Qt.Horizontal, self) sld.setFocusPolicy(Qt.NoFocus) sld.setGeometry(30, 40, 100, 30) sld.valueChanged[int].connect(self.changeValue) self.label = QLabel(self) self.label.setPixmap(QPixmap('mute.png')) self.label.setGeometry(160, 40, 80, 30) self.setGeometry(300, 300, 280, 170) self.setWindowTitle('QSlider') self.show() def changeValue(self, value): if value == 0: self.label.setPixmap(QPixmap('Ex6.3.1.jpg')) elif value > 0 and value <= 30: self.label.setPixmap(QPixmap('Ex6.3.2.jpg')) elif value > 30 and value < 80: self.label.setPixmap(QPixmap('Ex6.3.3.jpg')) else: self.label.setPixmap(QPixmap('Ex6.3.4.jpg')) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
这里是模拟的音量控制器。拖动滑块,能改变标签位置的图片。
6.4进度条
进度条是用来展示任务进度的(我也不想这样说话)。它的滚动能让用户了解到任务的进度。QProgressBar组件提供了水平和垂直两种进度条,进度条可以设置最大值和最小值,默认情况是0~99。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| from PyQt5.QtWidgets import (QWidget, QProgressBar, QPushButton, QApplication) from PyQt5.QtCore import QBasicTimer import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.pbar = QProgressBar(self) self.pbar.setGeometry(30, 40, 200, 25) self.btn = QPushButton('Start', self) self.btn.move(40, 80) self.btn.clicked.connect(self.doAction) self.timer = QBasicTimer() self.step = 0 self.setGeometry(300, 300, 280, 170) self.setWindowTitle('QProgressBar') self.show() def timerEvent(self, e): if self.step >= 100: self.timer.stop() self.btn.setText('Finished') return self.step = self.step + 1 self.pbar.setValue(self.step) def doAction(self): if self.timer.isActive(): self.timer.stop() self.btn.setText('Start') else: self.timer.start(100, self) self.btn.setText('Stop') if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
6.5日历
QCalendarWidget提供了基于月份的日历插件,十分简易而且直观。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| from PyQt5.QtWidgets import (QWidget, QCalendarWidget, QLabel, QApplication, QVBoxLayout) from PyQt5.QtCore import QDate import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): vbox = QVBoxLayout(self) cal = QCalendarWidget(self) cal.setGridVisible(True) cal.clicked[QDate].connect(self.showDate) vbox.addWidget(cal) self.lbl = QLabel(self) date = cal.selectedDate() self.lbl.setText(date.toString()) vbox.addWidget(self.lbl) self.setLayout(vbox) self.setGeometry(300, 300, 350, 300) self.setWindowTitle('Calendar') self.show() def showDate(self, date): self.lbl.setText(date.toString()) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
这个例子有日期组件和标签组件组成,标签显示被选中的日期。
控件(2)
本章我们继续介绍PyQt5控件。这次的有QPixmap,QLineEdit,QSplitter,和QComboBox。
7.1图片
QPixmap是处理图片的组件。本例中,我们使用QPixmap在窗口里显示一张图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QLabel, QApplication) from PyQt5.QtGui import QPixmap import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): hbox = QHBoxLayout(self) pixmap = QPixmap("Ex7.1.jpg") lbl = QLabel(self) lbl.setPixmap(pixmap) hbox.addWidget(lbl) self.setLayout(hbox) self.move(300, 200) self.setWindowTitle('Red Rock') self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
7.2行编辑
QLineEdit组件提供了编辑文本的功能,自带了撤销、重做、剪切、粘贴、拖拽等功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import sys from PyQt5.QtWidgets import (QWidget, QLabel, QLineEdit, QApplication) class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.lbl = QLabel(self) qle = QLineEdit(self) qle.move(60, 100) self.lbl.move(60, 40) qle.textChanged[str].connect(self.onChanged) self.setGeometry(300, 300, 280, 170) self.setWindowTitle('QLineEdit') self.show() def onChanged(self, text): self.lbl.setText(text) self.lbl.adjustSize() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
例子中展示了一个编辑组件和一个标签,我们在输入框里键入的文本,会立即在标签里显示出来。
7.3QSplitter
QSplitter组件能让用户通过拖拽分割线的方式改变子窗口大小的组件。本例中我们展示用两个分割线隔开的三个QFrame组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QFrame, QSplitter, QStyleFactory, QApplication) from PyQt5.QtCore import Qt import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): hbox = QHBoxLayout(self) topleft = QFrame(self) topleft.setFrameShape(QFrame.StyledPanel) topright = QFrame(self) topright.setFrameShape(QFrame.StyledPanel) bottom = QFrame(self) bottom.setFrameShape(QFrame.StyledPanel) splitter1 = QSplitter(Qt.Horizontal) splitter1.addWidget(topleft) splitter1.addWidget(topright) splitter2 = QSplitter(Qt.Vertical) splitter2.addWidget(splitter1) splitter2.addWidget(bottom) hbox.addWidget(splitter2) self.setLayout(hbox) self.setGeometry(300, 300, 300, 200) self.setWindowTitle('QSplitter') self.show() def onChanged(self, text): self.lbl.setText(text) self.lbl.adjustSize() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
三个窗口和两个分割线的布局创建完成了,但是要注意,有些主题下,分割线的显示效果不太好。
7.4下拉选框
QComboBox组件能让用户在多个选择项中选择一个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| from PyQt5.QtWidgets import (QWidget, QLabel, QComboBox, QApplication) import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.lbl = QLabel("Ubuntu", self) combo = QComboBox(self) combo.addItem("Ubuntu") combo.addItem("Mandriva") combo.addItem("Fedora") combo.addItem("Arch") combo.addItem("Gentoo") combo.move(50, 50) self.lbl.move(50, 150) combo.activated[str].connect(self.onActivated) self.setGeometry(300, 300, 300, 200) self.setWindowTitle('QComboBox') self.show() def onActivated(self, text): self.lbl.setText(text) self.lbl.adjustSize() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
本例包含了一个QComboBox和一个QLabel。下拉选择框有五个选项,都是Linux的发行版名称,标签内容为选定的发行版名称。
拖拽
在GUI里,拖放是指用户点击一个虚拟的对象,拖动,然后放置到另外一个对象上面的动作。一般情况下,需要调用很多动作和方法,创建很多变量。
拖放能让用户很直观的操作很复杂的逻辑。
一般情况下,我们可以拖放两种东西:数据和图形界面。把一个图像从一个应用拖放到另外一个应用上的实质是操作二进制数据。把一个表格从Firefox上拖放到另外一个位置 的实质是操作一个图形组。
8.1简单的拖放
本例使用了QLineEdit和QPushButton。把一个文本从编辑框里拖到按钮上,更新按钮上的标签(文字)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| from PyQt5.QtWidgets import (QPushButton, QWidget, QLineEdit, QApplication) import sys
class Button(QPushButton): def __init__(self, title, parent): super().__init__(title, parent) self.setAcceptDrops(True) def dragEnterEvent(self, e): if e.mimeData().hasFormat('text/plain'): e.accept() else: e.ignore() def dropEvent(self, e): self.setText(e.mimeData().text()) class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): edit = QLineEdit('', self) edit.setDragEnabled(True) edit.move(30, 65) button = Button("Button", self) button.move(190, 65) self.setWindowTitle('Simple drag and drop') self.setGeometry(300, 300, 300, 150) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() ex.show() app.exec_()
|
执行结果
8.2拖放按钮组件
这个例子展示怎么拖放一个button组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| from PyQt5.QtWidgets import QPushButton, QWidget, QApplication from PyQt5.QtCore import Qt, QMimeData from PyQt5.QtGui import QDrag import sys
class Button(QPushButton): def __init__(self, title, parent): super().__init__(title, parent) def mouseMoveEvent(self, e): if e.buttons() != Qt.RightButton: return mimeData = QMimeData() drag = QDrag(self) drag.setMimeData(mimeData) drag.setHotSpot(e.pos() - self.rect().topLeft()) dropAction = drag.exec_(Qt.MoveAction) def mousePressEvent(self, e): super().mousePressEvent(e) if e.button() == Qt.LeftButton: print('press') class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setAcceptDrops(True) self.button = Button('Button', self) self.button.move(100, 65) self.setWindowTitle('Click or Move') self.setGeometry(300, 300, 280, 150) def dragEnterEvent(self, e): e.accept() def dropEvent(self, e): position = e.pos() self.button.move(position) e.setDropAction(Qt.MoveAction) e.accept() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() ex.show() app.exec_()
|
执行结果
上面的例子中,窗口上有一个QPushButton组件。左键点击按钮,控制台就会输出press。右键可以点击然后拖动按钮
绘图
PyQt5绘图系统能渲染矢量图像、位图图像和轮廓字体文本。一般会使用在修改或者提高现有组件的功能,或者创建自己的组件。使用PyQt5的绘图API进行操作。
绘图由paintEvent()方法完成,绘图的代码要放在QPainter对象的begin()和end()方法之间。是低级接口。
9.1文本涂鸦
我们从画一些Unicode文本开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import sys from PyQt5.QtWidgets import QWidget, QApplication from PyQt5.QtGui import QPainter, QColor, QFont from PyQt5.QtCore import Qt class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.text = "Лев Николаевич Толстой\nАнна Каренина" self.setGeometry(300, 300, 280, 170) self.setWindowTitle('Drawing text') self.show() def paintEvent(self, event): qp = QPainter() qp.begin(self) self.drawText(event, qp) qp.end() def drawText(self, event, qp): qp.setPen(QColor(168, 34, 3)) qp.setFont(QFont('Decorative', 10)) qp.drawText(event.rect(), Qt.AlignCenter, self.text) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
写了一些文本上下居中对齐的俄罗斯Cylliric语言的文字
9.2点的绘画
点是最简单的绘画了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| from PyQt5.QtWidgets import QWidget, QApplication from PyQt5.QtGui import QPainter from PyQt5.QtCore import Qt import sys, random class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 300, 190) self.setWindowTitle('Points') self.show() def paintEvent(self, e): qp = QPainter() qp.begin(self) self.drawPoints(qp) qp.end() def drawPoints(self, qp): qp.setPen(Qt.red) size = self.size() for i in range(1000): x = random.randint(1, size.width()-1) y = random.randint(1, size.height()-1) qp.drawPoint(x, y) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
我们在窗口里随机的画出了1000个点。
9.3颜色
颜色是一个物体显示的RGB的混合色。RBG值的范围是0255。我们有很多方式去定义一个颜色,最常见的方式就是RGB和16进制表示法,也可以使用RGBA,增加了一个透明度的选项,透明度值的范围是01,0代表完全透明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| from PyQt5.QtWidgets import QWidget, QApplication from PyQt5.QtGui import QPainter, QColor, QBrush import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 350, 100) self.setWindowTitle('Colours') self.show() def paintEvent(self, e): qp = QPainter() qp.begin(self) self.drawRectangles(qp) qp.end() def drawRectangles(self, qp): col = QColor(0, 0, 0) col.setNamedColor('#d4d4d4') qp.setPen(col) qp.setBrush(QColor(200, 0, 0)) qp.drawRect(10, 15, 90, 60) qp.setBrush(QColor(255, 80, 0, 160)) qp.drawRect(130, 15, 90, 60) qp.setBrush(QColor(25, 0, 90, 200)) qp.drawRect(250, 15, 90, 60) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
我们画出了三个颜色的矩形。
9.4QPen
QPen是基本的绘画对象,能用来画直线、曲线、矩形框、椭圆、多边形和其他形状。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| from PyQt5.QtWidgets import QWidget, QApplication from PyQt5.QtGui import QPainter, QPen from PyQt5.QtCore import Qt import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 280, 270) self.setWindowTitle('Pen styles') self.show() def paintEvent(self, e): qp = QPainter() qp.begin(self) self.drawLines(qp) qp.end() def drawLines(self, qp): pen = QPen(Qt.black, 2, Qt.SolidLine) qp.setPen(pen) qp.drawLine(20, 40, 250, 40) pen.setStyle(Qt.DashLine) qp.setPen(pen) qp.drawLine(20, 80, 250, 80) pen.setStyle(Qt.DashDotLine) qp.setPen(pen) qp.drawLine(20, 120, 250, 120) pen.setStyle(Qt.DotLine) qp.setPen(pen) qp.drawLine(20, 160, 250, 160) pen.setStyle(Qt.DashDotDotLine) qp.setPen(pen) qp.drawLine(20, 200, 250, 200) pen.setStyle(Qt.CustomDashLine) pen.setDashPattern([1, 4, 5, 4]) qp.setPen(pen) qp.drawLine(20, 240, 250, 240) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
在这个例子里,我们用不同的笔画了6条直线。PyQt5有五个预定义的笔,另外一个笔的样式使我们自定义的。
9.5QBrush
QBrush也是图像的一个基本元素。是用来填充一些物体的背景图用的,比如矩形,椭圆,多边形等。有三种类型:预定义、渐变和纹理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| from PyQt5.QtWidgets import QWidget, QApplication from PyQt5.QtGui import QPainter, QBrush from PyQt5.QtCore import Qt import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 355, 280) self.setWindowTitle('Brushes') self.show() def paintEvent(self, e): qp = QPainter() qp.begin(self) self.drawBrushes(qp) qp.end() def drawBrushes(self, qp): brush = QBrush(Qt.SolidPattern) qp.setBrush(brush) qp.drawRect(10, 15, 90, 60) brush.setStyle(Qt.Dense1Pattern) qp.setBrush(brush) qp.drawRect(130, 15, 90, 60) brush.setStyle(Qt.Dense2Pattern) qp.setBrush(brush) qp.drawRect(250, 15, 90, 60) brush.setStyle(Qt.DiagCrossPattern) qp.setBrush(brush) qp.drawRect(10, 105, 90, 60) brush.setStyle(Qt.Dense5Pattern) qp.setBrush(brush) qp.drawRect(130, 105, 90, 60) brush.setStyle(Qt.Dense6Pattern) qp.setBrush(brush) qp.drawRect(250, 105, 90, 60) brush.setStyle(Qt.HorPattern) qp.setBrush(brush) qp.drawRect(10, 195, 90, 60) brush.setStyle(Qt.VerPattern) qp.setBrush(brush) qp.drawRect(130, 195, 90, 60) brush.setStyle(Qt.BDiagPattern) qp.setBrush(brush) qp.drawRect(250, 195, 90, 60) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
我们画了9个不同的矩形。
9.6贝塞尔曲线
可以使用PyQt5的QPainterPath创建贝塞尔曲线。绘画路径是由许多构建图形的对象,具体表现就是一些线的形状,比如矩形,椭圆,线和曲线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| from PyQt5.QtWidgets import QWidget, QApplication from PyQt5.QtGui import QPainter, QPainterPath from PyQt5.QtCore import Qt import sys class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setGeometry(300, 300, 380, 250) self.setWindowTitle('Bézier curve') self.show() def paintEvent(self, e): qp = QPainter() qp.begin(self) qp.setRenderHint(QPainter.Antialiasing) self.drawBezierCurve(qp) qp.end() def drawBezierCurve(self, qp): path = QPainterPath() path.moveTo(30, 30) path.cubicTo(30, 30, 200, 350, 350, 30) qp.drawPath(path) if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
这个示例中,我们画出了一个贝塞尔曲线。
自定义组件
PyQt5有丰富的组件,但是肯定满足不了所有开发者的所有需求,PyQt5只提供了基本的组件,像按钮,文本,滑块等。如果你还需要其他的模块,应该尝试自己去自定义一些。自定义组件使用绘画工具创建,有两个基本方式:根据已有的创建或改进;通过自己绘图创建。
这个组件我们会在Nero,K3B,或者其他CD/DVD烧录软件中见到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
| from PyQt5.QtWidgets import (QWidget, QSlider, QApplication, QHBoxLayout, QVBoxLayout) from PyQt5.QtCore import QObject, Qt, pyqtSignal from PyQt5.QtGui import QPainter, QFont, QColor, QPen import sys class Communicate(QObject): updateBW = pyqtSignal(int)
class BurningWidget(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setMinimumSize(1, 30) self.value = 75 self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675] def setValue(self, value): self.value = value def paintEvent(self, e): qp = QPainter() qp.begin(self) self.drawWidget(qp) qp.end() def drawWidget(self, qp): MAX_CAPACITY = 700 OVER_CAPACITY = 750 font = QFont('Serif', 7, QFont.Light) qp.setFont(font) size = self.size() w = size.width() h = size.height() step = int(round(w / 10)) till = int(((w / OVER_CAPACITY) * self.value)) full = int(((w / OVER_CAPACITY) * MAX_CAPACITY)) if self.value >= MAX_CAPACITY: qp.setPen(QColor(255, 255, 255)) qp.setBrush(QColor(255, 255, 184)) qp.drawRect(0, 0, full, h) qp.setPen(QColor(255, 175, 175)) qp.setBrush(QColor(255, 175, 175)) qp.drawRect(full, 0, till-full, h) else: qp.setPen(QColor(255, 255, 255)) qp.setBrush(QColor(255, 255, 184)) qp.drawRect(0, 0, till, h) pen = QPen(QColor(20, 20, 20), 1, Qt.SolidLine) qp.setPen(pen) qp.setBrush(Qt.NoBrush) qp.drawRect(0, 0, w-1, h-1) j = 0 for i in range(step, 10*step, step): qp.drawLine(i, 0, i, 5) metrics = qp.fontMetrics() fw = metrics.width(str(self.num[j])) qp.drawText(i-fw/2, h/2, str(self.num[j])) j = j + 1 class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): OVER_CAPACITY = 750 sld = QSlider(Qt.Horizontal, self) sld.setFocusPolicy(Qt.NoFocus) sld.setRange(1, OVER_CAPACITY) sld.setValue(75) sld.setGeometry(30, 40, 150, 30) self.c = Communicate() self.wid = BurningWidget() self.c.updateBW[int].connect(self.wid.setValue) sld.valueChanged[int].connect(self.changeValue) hbox = QHBoxLayout() hbox.addWidget(self.wid) vbox = QVBoxLayout() vbox.addStretch(1) vbox.addLayout(hbox) self.setLayout(vbox) self.setGeometry(300, 300, 390, 210) self.setWindowTitle('Burning widget') self.show() def changeValue(self, value): self.c.updateBW.emit(value) self.wid.repaint() if __name__ == '__main__': app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_())
|
执行结果
本例中,我们使用了QSlider和一个自定义组件,由进度条控制。显示的有物体(也就是CD/DVD)的总容量和剩余容量。进度条的范围是1~750。如果值达到了700(OVER_CAPACITY),就显示为红色,代表了烧毁了的意思。
烧录组件在窗口的底部,这个组件是用QHBoxLayout和QVBoxLayout组成的。
参考文章:https://blog.csdn.net/weixin_44848751/article/details/127054964