add pyqt6 docs
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2015-2020 maicss github.com/maicss
|
||||
Copyright (c) 2015-2021 maicss https://github.com/maicss
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
30
README.md
@ -1,28 +1,30 @@
|
||||
# 前言
|
||||
# 项目介绍
|
||||
翻译自 zetcode 的 PyQt 教程。
|
||||
|
||||
PyQt5中文教程,翻译自 [zetcode](http://zetcode.com/gui/pyqt5/),GitBook 预览地址:[https://maicss.gitbook.io/pyqt5-chinese-tutoral/](https://maicss.gitbook.io/pyqt5-chinese-tutoral/)
|
||||
GitBook 预览地址:[https://maicss.gitbook.io/pyqt-chinese-tutoral/](https://maicss.gitbook.io/pyqt-chinese-tutoral/)。
|
||||
|
||||
这个教程比较好的地方是,能讲解每一段代码的含义。
|
||||
开始阅读 [PyQt5](./pyqt5/index.md) 教程。
|
||||
|
||||
虽然PyQt的函数命名已经非常语义化了,但是对于新手来说,有这一步还是更好的。
|
||||
开始阅读 [PyQt6](./pyqt6/index.md) 教程。
|
||||
|
||||
所以我选择了翻译这篇教程,希望能给刚入门的你带来帮助。
|
||||
## 前言
|
||||
|
||||
这个教程比较好的地方是,能讲解每一段代码的含义。虽然PyQt的函数命名已经非常语义化了,但是对于新手来说,有这一步还是更好的。希望这篇能给刚入门的你带来帮助。
|
||||
|
||||
翻译的水平有限\(如有错误,请指出\),而且有些地方是自己的理解,也加入了自己的提示在里面(并没有标注出来),所以并不完全等于原文。
|
||||
|
||||
我尽量让翻译不带英语腔,做到即使一个完全不懂编程的人来看,虽然不知道说的啥,但是最起码语句通顺,不会读着别扭。也算是对老师的一点敬意吧~~
|
||||
我尽量让翻译不带英语腔,做到即使一个完全不懂编程的人来看,虽然不知道说的啥,但是最起码语句通顺,不会读着别扭。
|
||||
|
||||
## 翻译吐槽:
|
||||
添加了一些原文没有带的图片。所以有的图片风格跟原文的不一样。
|
||||
|
||||
* label 这个词好难翻译,有时候就是个占位符的意思,说是文字说明吧,有专门的词 caption,但是像 checkbox 的名称这种的,不是文字说明又是啥...,但是居然还用 label 说图标这种事情,也是醉了。
|
||||
* 源文档更新了,但是没有更新日志,只能一段段的比对……长了记性,创建了一个源文档的文件,下次直接 copy 的时候用 VCS 对比就可以了。
|
||||
* 更新了一些图片,主要是原来没有的。因为手头没有Windows,而且源文档的图片也是不是Windows10,都不是一个风格的,凑合着看吧……
|
||||
|
||||
## 更新:
|
||||
|
||||
### 2017-8
|
||||
## 更新日志:
|
||||
|
||||
### 2017-08
|
||||
* [原文地址](https://zetcode.com/gui/pyqt5/)
|
||||
* 菜单和工具栏 【新增】 右键菜单 子菜单 勾选菜单
|
||||
* 事件和信号 【新增】 事件对象
|
||||
* 绘图 【新增】 贝塞尔曲线
|
||||
|
||||
### 2021-12
|
||||
|
||||
* 新增 PyQt6 教程,原文地址:[Python PyQt6](https://zetcode.com/pyqt6/)
|
||||
|
224
original/pyqt6/customwidgets.md
Normal file
@ -0,0 +1,224 @@
|
||||
# Custom widgets in PyQt6
|
||||
*last modified May 17, 2021*
|
||||
|
||||
PyQt6 has a rich set of widgets. However, no toolkit can provide all widgets that programmers might need in their applications. Toolkits usually provide only the most common widgets like buttons, text widgets, or sliders. If there is a need for a more specialised widget, we must create it ourselves.
|
||||
|
||||
Custom widgets are created by using the drawing tools provided by the toolkit. There are two basic possibilities: a programmer can modify or enhance an existing widget or he can create a custom widget from scratch.
|
||||
|
||||
## PyQt6 burning widget
|
||||
This is a widget that we can see in Nero, K3B, or other CD/DVD burning software.
|
||||
|
||||
``` python
|
||||
# file: burning_widget.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we create a custom widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QSlider, QApplication,
|
||||
QHBoxLayout, QVBoxLayout)
|
||||
from PyQt6.QtCore import QObject, Qt, pyqtSignal
|
||||
from PyQt6.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.Weight.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.PenStyle.SolidLine)
|
||||
|
||||
qp.setPen(pen)
|
||||
qp.setBrush(Qt.BrushStyle.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.horizontalAdvance(str(self.num[j]))
|
||||
|
||||
x, y = int(i - fw/2), int(h / 2)
|
||||
qp.drawText(x, y, 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.Orientations.Horizontal, self)
|
||||
sld.setFocusPolicy(Qt.FocusPolicy.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()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we have a `QSlider` and a custom widget. The slider controls the custom widget. This widget shows graphically the total capacity of a medium and the free space available to us. The minimum value of our custom widget is 1, the maximum is OVER_CAPACITY. If we reach value MAX_CAPACITY, we begin drawing in red colour. This normally indicates overburning.
|
||||
|
||||
The burning widget is placed at the bottom of the window. This is achieved using one `QHBoxLayout` and one `QVBoxLayout`.
|
||||
|
||||
```python
|
||||
class BurningWidget(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
```
|
||||
The burning widget it based on the `QWidget` widget.
|
||||
|
||||
```python
|
||||
self.setMinimumSize(1, 30)
|
||||
```
|
||||
We change the minimum size (height) of the widget. The default value is a bit small for us.
|
||||
```python
|
||||
font = QFont('Serif', 7, QFont.Weight.Light)
|
||||
qp.setFont(font)
|
||||
```
|
||||
We use a smaller font than the default one. This better suits our needs.
|
||||
|
||||
```python
|
||||
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))
|
||||
```
|
||||
We draw the widget dynamically. The greater is the window, the greater is the burning widget and vice versa. That is why we must calculate the size of the widget onto which we draw the custom widget. The till parameter determines the total size to be drawn. This value comes from the slider widget. It is a proportion of the whole area. The full parameter determines the point where we begin to draw in red colour.
|
||||
|
||||
The actual drawing consists of three steps. We draw the yellow or the red and yellow rectangle. Then we draw the vertical lines which divide the widget into several parts. Finally, we draw the numbers which indicate the capacity of the medium.
|
||||
|
||||
``` python
|
||||
metrics = qp.fontMetrics()
|
||||
fw = metrics.horizontalAdvance(str(self.num[j]))
|
||||
|
||||
x, y = int(i - fw/2), int(h / 2)
|
||||
qp.drawText(x, y, str(self.num[j]))
|
||||
```
|
||||
We use font metrics to draw the text. We must know the width of the text in order to center it around the vertical line.
|
||||
|
||||
``` python
|
||||
def changeValue(self, value):
|
||||
|
||||
self.c.updateBW.emit(value)
|
||||
self.wid.repaint()
|
||||
```
|
||||
When we move the slider, the changeValue method is called. Inside the method, we send a custom updateBW signal with a parameter. The parameter is the current value of the slider. The value is later used to calculate the capacity of the Burning widget to be drawn. The custom widget is then repainted.
|
||||
|
||||
![The burning widget](./images/burning.png)
|
||||
|
||||
Figure: The burning widget
|
||||
|
||||
In this part of the PyQt6 tutorial, we created a custom widget.
|
361
original/pyqt6/datetime.md
Normal file
@ -0,0 +1,361 @@
|
||||
# PyQt6 date and time
|
||||
*last modified April 23, 2021*
|
||||
|
||||
This part of the PyQt6 tutorial shows how to work with date and time in PyQt6.
|
||||
|
||||
## QDate, QTime, QDateTime
|
||||
|
||||
PyQt6 has `QDate`, `QDateTime`, `QTime` classes to work with date and time. The `QDate` is a class for working with a calendar date in the Gregorian calendar. It has methods for determining the date, comparing, or manipulating dates. The `QTime` class works with a clock time. It provides methods for comparing time, determining the time and various other time manipulating methods. The `QDateTime` is a class that combines both `QDate` and `QTime` objects into one object.
|
||||
|
||||
## PyQt current date and time
|
||||
|
||||
PyQt6 has `currentDate`, `currentTime` and `currentDateTime` methods for determining current date and time.
|
||||
|
||||
``` python
|
||||
# file: current_date_time.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate, QTime, QDateTime, Qt
|
||||
|
||||
now = QDate.currentDate()
|
||||
|
||||
print(now.toString(Qt.DateFormat.ISODate))
|
||||
print(now.toString(Qt.DateFormat.RFC2822Date))
|
||||
|
||||
datetime = QDateTime.currentDateTime()
|
||||
|
||||
print(datetime.toString())
|
||||
|
||||
time = QTime.currentTime()
|
||||
print(time.toString(Qt.DateFormat.ISODate))
|
||||
```
|
||||
|
||||
The example prints the current date, date and time, and time in various formats.
|
||||
|
||||
``` python
|
||||
now = QDate.currentDate()
|
||||
```
|
||||
|
||||
The `currentDate` method returns the current date.
|
||||
|
||||
``` python
|
||||
print(now.toString(Qt.DateFormat.ISODate))
|
||||
print(now.toString(Qt.DateFormat.RFC2822Date))
|
||||
```
|
||||
The date is printed in two different formats by passing the values Qt.DateFormat.ISODate and `Qt.DateFormat.RFC2822Date` to the toString method.
|
||||
|
||||
```python
|
||||
datetime = QDateTime.currentDateTime()
|
||||
```
|
||||
|
||||
The `currentDateTime` returns the current date and time.
|
||||
|
||||
``` python
|
||||
time = QTime.currentTime()
|
||||
```
|
||||
|
||||
Finally, the `currentTime` method returns the current time.
|
||||
|
||||
``` sh
|
||||
$ ./current_date_time.py
|
||||
2021-04-23
|
||||
23 Apr 2021
|
||||
Fri Apr 23 11:41:39 2021
|
||||
11:41:39
|
||||
```
|
||||
## PyQt6 UTC time
|
||||
Our planet is a sphere; it revolves round its axis. The Earth rotates towards the east, so the Sun rises at different times in different locations. The Earth rotates once in about 24 hours. Therefore, the world was divided into 24 time zones. In each time zone, there is a different local time. This local time is often further modified by the daylight saving.
|
||||
|
||||
There is a pragmatic need for one global time. One global time helps to avoid confusion about time zones and daylight saving time. The UTC (Universal Coordinated time) was chosen to be the primary time standard. UTC is used in aviation, weather forecasts, flight plans, air traffic control clearances, and maps. Unlike local time, UTC does not change with a change of seasons.
|
||||
``` python
|
||||
# file: utc_local.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDateTime, Qt
|
||||
|
||||
now = QDateTime.currentDateTime()
|
||||
|
||||
print('Local datetime: ', now.toString(Qt.DateFormat.ISODate))
|
||||
print('Universal datetime: ', now.toUTC().toString(Qt.DateFormat.ISODate))
|
||||
|
||||
print(f'The offset from UTC is: {now.offsetFromUtc()} seconds')
|
||||
The example determines the current universal and local date and time.
|
||||
|
||||
print('Local datetime: ', now.toString(Qt.DateFormat.ISODate))
|
||||
```
|
||||
The `currentDateTime` method returns the current date and time expressed as local time. We can use the `toLocalTime` to convert a universal time into a local time.
|
||||
|
||||
``` python
|
||||
print('Universal datetime: ', now.toUTC().toString(Qt.DateFormat.ISODate))
|
||||
```
|
||||
|
||||
We get the universal time with the `toUTC` method from the date time object.
|
||||
|
||||
``` python
|
||||
print(f'The offset from UTC is: {now.offsetFromUtc()} seconds')
|
||||
```
|
||||
|
||||
The `offsetFromUtc` gives the difference between universal time and local time in seconds.
|
||||
|
||||
``` sh
|
||||
$ ./utc_local.py
|
||||
Local datetime: 2021-04-23T11:44:15
|
||||
Universal datetime: 2021-04-23T09:44:15Z
|
||||
The offset from UTC is: 7200 seconds
|
||||
```
|
||||
## PyQt6 number of days
|
||||
The number of days in a particular month is returned by the `daysInMonth` method and the number of days in a year by the `daysInYear` method.
|
||||
|
||||
``` python
|
||||
# file: n_of_days.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate
|
||||
|
||||
now = QDate.currentDate()
|
||||
|
||||
d = QDate(1945, 5, 7)
|
||||
|
||||
print(f'Days in month: {d.daysInMonth()}')
|
||||
print(f'Days in year: {d.daysInYear()}')
|
||||
```
|
||||
The example prints the number of days in a month and year for the chosen date.
|
||||
``` sh
|
||||
$ ./n_of_days.py
|
||||
Days in month: 31
|
||||
Days in year: 365
|
||||
```
|
||||
|
||||
## PyQt6 difference in days
|
||||
The `daysTo` method returns the number of days from a date to another date.
|
||||
|
||||
``` python
|
||||
# file: xmas.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate, Qt
|
||||
|
||||
now = QDate.currentDate()
|
||||
y = now.year()
|
||||
|
||||
print(f'today is {now.toString(Qt.DateFormat.ISODate)}')
|
||||
|
||||
xmas1 = QDate(y-1, 12, 25)
|
||||
xmas2 = QDate(y, 12, 25)
|
||||
|
||||
dayspassed = xmas1.daysTo(now)
|
||||
print(f'{dayspassed} days have passed since last XMas')
|
||||
|
||||
nofdays = now.daysTo(xmas2)
|
||||
print(f'There are {nofdays} days until next XMas')
|
||||
```
|
||||
The example calculates the number of days passed from the last XMas and the number of days until the next XMas.
|
||||
|
||||
``` sh
|
||||
$ ./xmas.py
|
||||
today is 2021-04-23
|
||||
119 days have passed since last XMas
|
||||
There are 246 days until next XMas
|
||||
```
|
||||
## PyQt6 datetime arithmetic
|
||||
We often need to add or subtract days, seconds, or years to a datetime value.
|
||||
``` python
|
||||
# file: arithmetic.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDateTime, Qt
|
||||
|
||||
now = QDateTime.currentDateTime()
|
||||
|
||||
print(f'Today: {now.toString(Qt.DateFormat.ISODate)}')
|
||||
print(f'Adding 12 days: {now.addDays(12).toString(Qt.DateFormat.ISODate)}')
|
||||
print(f'Subtracting 22 days: {now.addDays(-22).toString(Qt.DateFormat.ISODate)}')
|
||||
|
||||
print(f'Adding 50 seconds: {now.addSecs(50).toString(Qt.DateFormat.ISODate)}')
|
||||
print(f'Adding 3 months: {now.addMonths(3).toString(Qt.DateFormat.ISODate)}')
|
||||
print(f'Adding 12 years: {now.addYears(12).toString(Qt.DateFormat.ISODate)}')
|
||||
```
|
||||
The example determines the current datetime and add or subtract days, seconds, months, and years.
|
||||
|
||||
``` sh
|
||||
$ ./arithmetic.py
|
||||
Today: 2021-04-23T12:06:10
|
||||
Adding 12 days: 2021-05-05T12:06:10
|
||||
Subtracting 22 days: 2021-04-01T12:06:10
|
||||
Adding 50 seconds: 2021-04-23T12:07:00
|
||||
Adding 3 months: 2021-07-23T12:06:10
|
||||
Adding 12 years: 2033-04-23T12:06:10
|
||||
```
|
||||
## PyQt6 daylight saving time
|
||||
Daylight saving time (DST) is the practice of advancing clocks during summer months so that evening daylight lasts longer. The time is adjusted forward one hour in the beginning of spring and adjusted backward in the autumn to standard time.
|
||||
|
||||
``` python
|
||||
# file: daylight_saving.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDateTime, QTimeZone, Qt
|
||||
|
||||
now = QDateTime.currentDateTime()
|
||||
|
||||
print(f'Time zone: {now.timeZoneAbbreviation()}')
|
||||
|
||||
if now.isDaylightTime():
|
||||
print('The current date falls into DST time')
|
||||
else:
|
||||
print('The current date does not fall into DST time')
|
||||
```
|
||||
The example checks if the datetime is in the daylight saving time.
|
||||
|
||||
```python
|
||||
print(f'Time zone: {now.timeZoneAbbreviation()}')
|
||||
```
|
||||
|
||||
The `timeZoneAbbreviation` method returns the time zone abbreviation for the datetime.
|
||||
|
||||
``` python
|
||||
if now.isDaylightTime():
|
||||
...
|
||||
```
|
||||
The `isDaylightTime` returns if the datetime falls in daylight saving time.
|
||||
|
||||
``` sh
|
||||
$ ./daylight_saving.py
|
||||
Time zone: CEST
|
||||
The current date falls into DST time
|
||||
```
|
||||
The current date falls into DST time The program was executed in Bratislava, which is a city in Central Europe, during summer. Central European Summer Time (CEST) is 2 hours ahead of universtal time. This time zone is a daylight saving time time zone and is used in Europe and Antarctica. The standard time, which is used in winter, is Central European Time (CET).
|
||||
|
||||
## PyQt6 unix epoch
|
||||
An epoch is an instant in time chosen as the origin of a particular era. For example in western Christian countries the time epoch starts from day 0, when Jesus was born. Another example is the French Republican Calendar which was used for twelve years. The epoch was the beginning of the Republican Era which was proclaimed on September 22, 1792, the day the First Republic was declared and the monarchy abolished.
|
||||
|
||||
Computers have their epochs too. One of the most popular is the Unix epoch. The Unix epoch is the time 00:00:00 UTC on 1 January 1970 (or 1970- 01-01T00:00:00Z ISO 8601). The date and time in a computer is determined according to the number of seconds or clock ticks that have elapsed since the defined epoch for that computer or platform.
|
||||
|
||||
Unix time is the number of seconds elapsed since Unix epoch.
|
||||
|
||||
``` sh
|
||||
$ date +%s
|
||||
1619172620
|
||||
```
|
||||
Unix date command can be used to get the Unix time. At this particular moment, 1619172620 seconds have passed since the Unix epoch.
|
||||
|
||||
``` python
|
||||
# file: unix_time.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDateTime, Qt
|
||||
|
||||
now = QDateTime.currentDateTime()
|
||||
|
||||
unix_time = now.toSecsSinceEpoch()
|
||||
print(unix_time)
|
||||
|
||||
d = QDateTime.fromSecsSinceEpoch(unix_time)
|
||||
print(d.toString(Qt.DateFormat.ISODate))
|
||||
```
|
||||
The example prints the Unix time and converts it back to the `QDateTime`.
|
||||
|
||||
``` python
|
||||
now = QDateTime.currentDateTime()
|
||||
```
|
||||
|
||||
First, we retrieve the current date and time.
|
||||
|
||||
```python
|
||||
unix_time = now.toSecsSinceEpoch()
|
||||
```
|
||||
|
||||
The `toSecsSinceEpoch` returns the Unix time.
|
||||
|
||||
```python
|
||||
d = QDateTime.fromSecsSinceEpoch(unix_time)
|
||||
```
|
||||
|
||||
With the `fromSecsSinceEpoch` we convert the Unix time to `QDateTime`.
|
||||
|
||||
``` sh
|
||||
$ ./unix_time.py
|
||||
1619172679
|
||||
2021-04-23T12:11:19
|
||||
```
|
||||
## PyQt6 Julian day
|
||||
Julian day refers to a continuous count of days since the beginning of the Julian Period. It is used primarily by astronomers. It should not be confused with the Julian calendar. The Julian Period started in 4713 BC. The Julian day number 0 is assigned to the day starting at noon on January 1, 4713 BC.
|
||||
|
||||
The Julian Day Number (JDN) is the number of days elapsed since the beginning of this period. The Julian Date (JD) of any instant is the Julian day number for the preceding noon plus the fraction of the day since that instant. (Qt does not compute this fraction.) Apart from astronomy, Julian dates are often used by military and mainframe programs.
|
||||
|
||||
``` python
|
||||
# file: julian_day.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate, Qt
|
||||
|
||||
now = QDate.currentDate()
|
||||
|
||||
print('Gregorian date for today:', now.toString(Qt.DateFormat.ISODate))
|
||||
print('Julian day for today:', now.toJulianDay())
|
||||
```
|
||||
In the example, we compute the Gregorian date and the Julian day for today.
|
||||
|
||||
``` python
|
||||
print('Julian day for today:', now.toJulianDay())
|
||||
```
|
||||
|
||||
The Julian day is returned with the toJulianDay() method.
|
||||
|
||||
``` sh
|
||||
$ ./julian_day.py
|
||||
Gregorian date for today: 2021-04-23
|
||||
Julian day for today: 2459328
|
||||
```
|
||||
## Historical battles
|
||||
With Julian day it is possible to do calculations that span centuries.
|
||||
|
||||
``` python
|
||||
# file: battles.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate, Qt
|
||||
|
||||
borodino_battle = QDate(1812, 9, 7)
|
||||
slavkov_battle = QDate(1805, 12, 2)
|
||||
|
||||
now = QDate.currentDate()
|
||||
|
||||
j_today = now.toJulianDay()
|
||||
j_borodino = borodino_battle.toJulianDay()
|
||||
j_slavkov = slavkov_battle.toJulianDay()
|
||||
|
||||
d1 = j_today - j_slavkov
|
||||
d2 = j_today - j_borodino
|
||||
|
||||
print(f'Days since Slavkov battle: {d1}')
|
||||
print(f'Days since Borodino battle: {d2}')
|
||||
```
|
||||
The example counts the number of days passed since two historical events.
|
||||
|
||||
``` python
|
||||
borodino_battle = QDate(1812, 9, 7)
|
||||
slavkov_battle = QDate(1805, 12, 2)
|
||||
```
|
||||
We have two dates of battles of the Napoleonic era.
|
||||
|
||||
```python
|
||||
j_today = now.toJulianDay()
|
||||
j_borodino = borodino_battle.toJulianDay()
|
||||
j_slavkov = slavkov_battle.toJulianDay()
|
||||
```
|
||||
We compute the Julian days for today and for the Battles of Slavkov and Borodino.
|
||||
|
||||
``` python
|
||||
d1 = j_today - j_slavkov
|
||||
d2 = j_today - j_borodino
|
||||
```
|
||||
We compute the number of days passed since the two battles.
|
||||
|
||||
``` sh
|
||||
$ ./battles.py
|
||||
Days since Slavkov battle: 78670
|
||||
Days since Borodino battle: 76199
|
||||
```
|
||||
When we run this script, 78670 days have passed since the Slavkov battle, and 76199 since the Borodino battle.
|
||||
|
||||
In this part of the PyQt6 tutorial, we have worked with date and time.
|
365
original/pyqt6/dialogs.md
Normal file
@ -0,0 +1,365 @@
|
||||
# Dialogs in PyQt6
|
||||
*last modified April 30, 2021*
|
||||
|
||||
A dialog is defined as a conversation between two or more persons. In a computer application a dialog is a window which is used to "talk" to the application. Dialogs are used for things such as getting data from users or changing application settings.
|
||||
|
||||
## PyQt6 QInputDialog
|
||||
QInputDialog provides a simple convenience dialog to get a single value from the user. The input value can be a string, a number, or an item from a list.
|
||||
|
||||
``` python
|
||||
# file: input_dialog.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we receive data from
|
||||
a QInputDialog dialog.
|
||||
|
||||
Aauthor: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.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, 450, 350)
|
||||
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))
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
The example has a button and a line edit widget. The button shows the input dialog for getting text values. The entered text will be displayed in the line edit widget.
|
||||
|
||||
``` python
|
||||
text, ok = QInputDialog.getText(self, 'Input Dialog',
|
||||
'Enter your name:')
|
||||
```
|
||||
This line displays the input dialog. The first string is a dialog title, the second one is a message within the dialog. The dialog returns the entered text and a boolean value. If we click the Ok button, the boolean value is true.
|
||||
|
||||
``` python
|
||||
if ok:
|
||||
self.le.setText(str(text))
|
||||
```
|
||||
The text that we have received from the dialog is set to the line edit widget with `setText()`.
|
||||
|
||||
![Input dialog](./images/inputdialog.png)
|
||||
|
||||
Figure: Input dialog
|
||||
|
||||
## PyQt6 QColorDialog
|
||||
`QColorDialog` provides a dialog widget for selecting colour values.
|
||||
|
||||
``` python
|
||||
# file: color_dialog.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we select a color value
|
||||
from the QColorDialog and change the background
|
||||
color of a QFrame widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QPushButton, QFrame,
|
||||
QColorDialog, QApplication)
|
||||
from PyQt6.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, 200, 200)
|
||||
|
||||
self.setGeometry(300, 300, 450, 350)
|
||||
self.setWindowTitle('Color dialog')
|
||||
self.show()
|
||||
|
||||
|
||||
def showDialog(self):
|
||||
|
||||
col = QColorDialog.getColor()
|
||||
|
||||
if col.isValid():
|
||||
|
||||
self.frm.setStyleSheet("QWidget { background-color: %s }"
|
||||
% col.name())
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
The application example shows a push button and a `QFrame`. The widget background is set to black colour. Using the `QColorDialog`, we can change its background.
|
||||
``` python
|
||||
col = QColor(0, 0, 0)
|
||||
```
|
||||
This is an initial colour of the QFrame background.
|
||||
``` python
|
||||
col = QColorDialog.getColor()
|
||||
```
|
||||
This line pops up the QColorDialog.
|
||||
``` python
|
||||
if col.isValid():
|
||||
|
||||
self.frm.setStyleSheet("QWidget { background-color: %s }"
|
||||
% col.name())
|
||||
```
|
||||
We check if the colour is valid. If we click on the Cancel button, no valid colour is returned. If the colour is valid, we change the background colour using style sheets.
|
||||
|
||||
## PyQt6 QFontDialog
|
||||
`QFontDialog` is a dialog widget for selecting a font.
|
||||
|
||||
``` python
|
||||
# file: font_dialog.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we select a font name
|
||||
and change the font of a label.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.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.Policy.Fixed, QSizePolicy.Policy.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, 450, 350)
|
||||
self.setWindowTitle('Font dialog')
|
||||
self.show()
|
||||
|
||||
|
||||
def showDialog(self):
|
||||
|
||||
font, ok = QFontDialog.getFont()
|
||||
|
||||
if ok:
|
||||
self.lbl.setFont(font)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we have a button and a label. With the `QFontDialog`, we change the font of the label.
|
||||
``` python
|
||||
font, ok = QFontDialog.getFont()
|
||||
```
|
||||
Here we pop up the font dialog. The getFont method returns the font name and the ok parameter. It is equal to True if the user clicked Ok; otherwise it is False.
|
||||
``` python
|
||||
if ok:
|
||||
self.label.setFont(font)
|
||||
```
|
||||
If we clicked Ok, the font of the label is changed with setFont.
|
||||
|
||||
## PyQt6 QFileDialog
|
||||
|
||||
`QFileDialog` is a dialog that allows users to select files or directories. The files can be selected for both opening and saving.
|
||||
|
||||
``` python
|
||||
# file: file_dialog.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we select a file with a
|
||||
QFileDialog and display its contents
|
||||
in a QTextEdit.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QMainWindow, QTextEdit,
|
||||
QFileDialog, QApplication)
|
||||
from PyQt6.QtGui import QIcon, QAction
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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('open.png'), '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, 550, 450)
|
||||
self.setWindowTitle('File dialog')
|
||||
self.show()
|
||||
|
||||
|
||||
def showDialog(self):
|
||||
|
||||
home_dir = str(Path.home())
|
||||
fname = QFileDialog.getOpenFileName(self, 'Open file', home_dir)
|
||||
|
||||
if fname[0]:
|
||||
|
||||
f = open(fname[0], 'r')
|
||||
|
||||
with f:
|
||||
|
||||
data = f.read()
|
||||
self.textEdit.setText(data)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
The example shows a menubar, centrally set text edit widget, and a statusbar. The menu item shows the `QFileDialog` which is used to select a file. The contents of the file are loaded into the text edit widget.
|
||||
|
||||
``` python
|
||||
class Example(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
```
|
||||
The example is based on the `QMainWindow` widget because we centrally set a text edit widget.
|
||||
``` python
|
||||
home_dir = str(Path.home())
|
||||
fname = QFileDialog.getOpenFileName(self, 'Open file', home_dir)
|
||||
```
|
||||
We pop up the QFileDialog. The first string in the getOpenFileName method is the caption. The second string specifies the dialog working directory. We use the path module to determine the user's home directory. By default, the file filter is set to All files (*).
|
||||
|
||||
``` python
|
||||
if fname[0]:
|
||||
|
||||
f = open(fname[0], 'r')
|
||||
|
||||
with f:
|
||||
|
||||
data = f.read()
|
||||
self.textEdit.setText(data)
|
||||
```
|
||||
The selected file name is read and the contents of the file are set to the text edit widget.
|
||||
|
||||
In this part of the PyQt6 tutorial, we worked with dialogs.
|
284
original/pyqt6/dragdrop.md
Normal file
@ -0,0 +1,284 @@
|
||||
# Drag and drop in PyQt6
|
||||
*last modified May 15, 2021*
|
||||
|
||||
In this part of the PyQt6 tutorial, we cover drag & drop operations.
|
||||
|
||||
In computer graphical user interfaces, drag-and-drop is the action of (or support for the action of) clicking on a virtual object and dragging it to a different location or onto another virtual object. In general, it can be used to invoke many kinds of actions, or create various types of associations between two abstract objects.
|
||||
|
||||
Drag and drop is part of the graphical user interface. Drag and drop operations enable users to do complex things intuitively.
|
||||
|
||||
Usually, we can drag and drop two things: data or some graphical objects. If we drag an image from one application to another, we drag and drop binary data. If we drag a tab in Firefox and move it to another place, we drag and drop a graphical component.
|
||||
|
||||
## QDrag
|
||||
QDrag provides support for MIME-based drag and drop data transfer. It handles most of the details of a drag and drop operation. The transferred data is contained in a QMimeData object.
|
||||
|
||||
## Simple drag and drop example in PyQt6
|
||||
In the first example, we have a `QLineEdit` and a `QPushButton`. We drag plain text from the line edit widget and drop it onto the button widget. The button's label will change.
|
||||
|
||||
``` python
|
||||
# file: simple.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This is a simple drag and
|
||||
drop example.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt6.QtWidgets import (QPushButton, QWidget,
|
||||
QLineEdit, QApplication)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
ex.show()
|
||||
app.exec()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
The example presents a simple drag & drop operation.
|
||||
|
||||
``` python
|
||||
class Button(QPushButton):
|
||||
|
||||
def __init__(self, title, parent):
|
||||
super().__init__(title, parent)
|
||||
|
||||
...
|
||||
```
|
||||
In order to drop text on the `QPushButton` widget, we must reimplement some methods. Therefore, we create our own Button class which inherits from the QPushButton class.
|
||||
|
||||
``` python
|
||||
self.setAcceptDrops(True)
|
||||
```
|
||||
We enable drop events for the widget with setAcceptDrops.
|
||||
|
||||
```python
|
||||
def dragEnterEvent(self, e):
|
||||
|
||||
if e.mimeData().hasFormat('text/plain'):
|
||||
e.accept()
|
||||
else:
|
||||
e.ignore()
|
||||
```
|
||||
First, we reimplement the dragEnterEvent method. We inform about the data type that we accept. In our case it is plain text.
|
||||
|
||||
```python
|
||||
def dropEvent(self, e):
|
||||
|
||||
self.setText(e.mimeData().text())
|
||||
```
|
||||
By reimplementing the dropEvent method we define what happes at the drop event. Here we change the text of the button widget.
|
||||
|
||||
```python
|
||||
edit = QLineEdit('', self)
|
||||
edit.setDragEnabled(True)
|
||||
```
|
||||
The QLineEdit widget has a built-in support for drag operations. All we need to do is to call the setDragEnabled method to activate it.
|
||||
|
||||
![Simple drag and drop](./images/dragdrop.png)
|
||||
|
||||
Figure: Simple drag and drop
|
||||
|
||||
## Drag and drop a button widget
|
||||
The following example demonstrates how to drag and drop a button widget.
|
||||
|
||||
``` python
|
||||
# file: drag_button.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this program, we can press on a button with a left mouse
|
||||
click or drag and drop the button with the right mouse click.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt6.QtCore import Qt, QMimeData
|
||||
from PyQt6.QtGui import QDrag
|
||||
from PyQt6.QtWidgets import QPushButton, QWidget, QApplication
|
||||
|
||||
|
||||
class Button(QPushButton):
|
||||
|
||||
def __init__(self, title, parent):
|
||||
super().__init__(title, parent)
|
||||
|
||||
|
||||
def mouseMoveEvent(self, e):
|
||||
|
||||
if e.buttons() != Qt.MouseButtons.RightButton:
|
||||
return
|
||||
|
||||
mimeData = QMimeData()
|
||||
|
||||
drag = QDrag(self)
|
||||
drag.setMimeData(mimeData)
|
||||
|
||||
drag.setHotSpot(e.position().toPoint() - self.rect().topLeft())
|
||||
|
||||
dropAction = drag.exec(Qt.DropActions.MoveAction)
|
||||
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
|
||||
super().mousePressEvent(e)
|
||||
|
||||
if e.button() == Qt.MouseButtons.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, 550, 450)
|
||||
|
||||
|
||||
def dragEnterEvent(self, e):
|
||||
|
||||
e.accept()
|
||||
|
||||
|
||||
def dropEvent(self, e):
|
||||
|
||||
position = e.position()
|
||||
self.button.move(position.toPoint())
|
||||
|
||||
e.setDropAction(Qt.DropActions.MoveAction)
|
||||
e.accept()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
ex.show()
|
||||
app.exec()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
In our code example, we have a `QPushButton` on the window. If we click on the button with a left mouse button, the 'press' message is printed to the console. By right clicking and moving the button, we perform a drag and drop operation on the button widget.
|
||||
|
||||
``` python
|
||||
class Button(QPushButton):
|
||||
|
||||
def __init__(self, title, parent):
|
||||
super().__init__(title, parent)
|
||||
```
|
||||
We create a Button class which derives from the `QPushButton`. We also reimplement two methods of the `QPushButton`: the mouseMoveEvent and the mousePressEvent. The mouseMoveEvent method is the place where the drag and drop operation begins.
|
||||
|
||||
``` python
|
||||
if e.buttons() != Qt.MouseButtons.RightButton:
|
||||
return
|
||||
```
|
||||
Here we decide that we can perform drag and drop only with a right mouse button. The left mouse button is reserved for clicking on the button.
|
||||
|
||||
``` python
|
||||
drag = QDrag(self)
|
||||
drag.setMimeData(mimeData)
|
||||
|
||||
drag.setHotSpot(e.position().toPoint() - self.rect().topLeft())
|
||||
```
|
||||
The `QDrag` object is created. The class provides support for MIME-based drag and drop data transfer.
|
||||
|
||||
``` python
|
||||
dropAction = drag.exec(Qt.DropActions.MoveAction)
|
||||
```
|
||||
The exec method of the drag object starts the drag and drop operation.
|
||||
|
||||
``` python
|
||||
def mousePressEvent(self, e):
|
||||
|
||||
super().mousePressEvent(e)
|
||||
|
||||
if e.button() == Qt.MouseButtons.LeftButton:
|
||||
print('press')
|
||||
```
|
||||
We print 'press' to the console if we left click on the button with the mouse. Notice that we call mousePressEvent method on the parent as well. Otherwise, we would not see the button being pushed.
|
||||
|
||||
``` python
|
||||
position = e.pos()
|
||||
self.button.move(position)
|
||||
```
|
||||
In the dropEvent method we specify what happens after we release the mouse button and finish the drop operation. In our case, we find out the current mouse pointer position and move the button accordingly.
|
||||
|
||||
``` python
|
||||
e.setDropAction(Qt.MoveAction)
|
||||
e.accept()
|
||||
```
|
||||
We specify the type of the drop action with `setDropAction`. In our case it is a move action.
|
||||
|
||||
This part of the PyQt6 tutorial was dedicated to drag and drop operations.
|
411
original/pyqt6/eventssignals.md
Normal file
@ -0,0 +1,411 @@
|
||||
# PyQt6 events and signals
|
||||
*last modified April 29, 2021*
|
||||
|
||||
In this part of the PyQt6 programming tutorial, we explore events and signals occurring in applications.
|
||||
|
||||
## Events in PyQt6
|
||||
GUI applications are event-driven. Events are generated mainly by the user of an application. But they can be generated by other means as well; e.g. an Internet connection, a window manager, or a timer. When we call the application's `exec()` method, the application enters the main loop. The main loop fetches events and sends them to the objects.
|
||||
|
||||
In the event model, there are three participants:
|
||||
|
||||
- event source
|
||||
- event object
|
||||
- event target
|
||||
|
||||
The event source is the object whose state changes. It generates events. The *event object* (event) encapsulates the state changes in the event source. The *event target* is the object that wants to be notified. Event source object delegates the task of handling an event to the event target.
|
||||
|
||||
PyQt6 has a unique signal and slot mechanism to deal with events. Signals and slots are used for communication between objects. A signal is emitted when a particular event occurs. A slot can be any Python callable. A slot is called when its connected signal is emitted.
|
||||
|
||||
## PyQt6 signals and slots
|
||||
This is a simple example demonstrating signals and slots in PyQt6.
|
||||
|
||||
``` python
|
||||
# file: signals_slots.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we connect a signal
|
||||
of a QSlider to a slot of a QLCDNumber.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.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.Orientations.Horizontal, self)
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addWidget(lcd)
|
||||
vbox.addWidget(sld)
|
||||
|
||||
self.setLayout(vbox)
|
||||
sld.valueChanged.connect(lcd.display)
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('Signal and slot')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we display a `QtGui.QLCDNumber` and a `QtGui.QSlider`. We change the lcd number by dragging the slider knob.
|
||||
``` python
|
||||
sld.valueChanged.connect(lcd.display)
|
||||
```
|
||||
Here we connect a `valueChanged` signal of the slider to the `display` slot of the lcd number.
|
||||
|
||||
The *sender* is an object that sends a signal. The *receiver* is the object that receives the signal. The *slot* is the method that reacts to the signal.
|
||||
|
||||
![Signal & slot](./images/sigslot.png)
|
||||
|
||||
Figure: Signal & slot
|
||||
|
||||
## PyQt6 reimplementing event handler
|
||||
|
||||
Events in PyQt6 are processed often by reimplementing event handlers.
|
||||
|
||||
``` python
|
||||
# file: reimplement_handler.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we reimplement an
|
||||
event handler.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('Event handler')
|
||||
self.show()
|
||||
|
||||
|
||||
def keyPressEvent(self, e):
|
||||
|
||||
if e.key() == Qt.Key.Key_Escape.value:
|
||||
self.close()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we reimplement the `keyPressEvent` event handler.
|
||||
``` python
|
||||
def keyPressEvent(self, e):
|
||||
|
||||
if e.key() == Qt.Key.Key_Escape.value:
|
||||
self.close()
|
||||
```
|
||||
If we click the Escape button, the application terminates.
|
||||
|
||||
## PyQt6 event object
|
||||
Event object is a Python object that contains a number of attributes describing the event. Event object is specific to the generated event type.
|
||||
|
||||
``` python
|
||||
# file: event_object.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we display the x and y
|
||||
coordinates of a mouse pointer in a label widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QWidget, QApplication, QGridLayout, QLabel
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
grid = QGridLayout()
|
||||
|
||||
x = 0
|
||||
y = 0
|
||||
|
||||
self.text = f'x: {x}, y: {y}'
|
||||
|
||||
self.label = QLabel(self.text, self)
|
||||
grid.addWidget(self.label, 0, 0, Qt.Alignment.AlignTop)
|
||||
|
||||
self.setMouseTracking(True)
|
||||
self.setLayout(grid)
|
||||
|
||||
self.setGeometry(300, 300, 450, 300)
|
||||
self.setWindowTitle('Event object')
|
||||
self.show()
|
||||
|
||||
|
||||
def mouseMoveEvent(self, e):
|
||||
|
||||
x = int(e.position().x())
|
||||
y = int(e.position().y())
|
||||
|
||||
text = f'x: {x}, y: {y}'
|
||||
self.label.setText(text)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In this example, we display the x and y coordinates of a mouse pointer in a label widget.
|
||||
``` python
|
||||
self.setMouseTracking(True)
|
||||
```
|
||||
Mouse tracking is disabled by default, so the widget only receives mouse move events when at least one mouse button is pressed while the mouse is being moved. If mouse tracking is enabled, the widget receives mouse move events even if no buttons are pressed.
|
||||
``` python
|
||||
def mouseMoveEvent(self, e):
|
||||
|
||||
x = int(e.position().x())
|
||||
y = int(e.position().y())
|
||||
...
|
||||
```
|
||||
The e is the event object; it contains data about the event that was triggered. In our case it is mouse move event. With the `position().x()` and `e.position().y()` methods we determine the x and y coordinates of the mouse pointer.
|
||||
``` python
|
||||
self.text = f'x: {x}, y: {y}'
|
||||
self.label = QLabel(self.text, self)
|
||||
```
|
||||
The x and y coordinates are displayd in a `QLabel` widget.
|
||||
|
||||
![Event object](./images/eventobject.png)
|
||||
|
||||
Figure: Event object
|
||||
|
||||
# PyQt6 event sender
|
||||
|
||||
Sometimes it is convenient to know which widget is the sender of a signal. For this, PyQt6 has the sender method.
|
||||
|
||||
``` python
|
||||
event_sender.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we determine the event sender
|
||||
object.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.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, 450, 350)
|
||||
self.setWindowTitle('Event sender')
|
||||
self.show()
|
||||
|
||||
|
||||
def buttonClicked(self):
|
||||
|
||||
sender = self.sender()
|
||||
|
||||
msg = f'{sender.text()} was pressed'
|
||||
self.statusBar().showMessage(msg)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
We have two buttons in our example. In the `buttonClicked` method we determine which button we have clicked by calling the sender method.
|
||||
``` python
|
||||
btn1.clicked.connect(self.buttonClicked)
|
||||
btn2.clicked.connect(self.buttonClicked)
|
||||
```
|
||||
Both buttons are connected to the same slot.
|
||||
``` python
|
||||
def buttonClicked(self):
|
||||
|
||||
sender = self.sender()
|
||||
|
||||
msg = f'{sender.text()} was pressed'
|
||||
self.statusBar().showMessage(msg)
|
||||
```
|
||||
We determine the signal source by calling the sender method. In the statusbar of the application, we show the label of the button being pressed.
|
||||
|
||||
![Event sender](./images/eventsender.png)
|
||||
|
||||
Figure: Event sender
|
||||
|
||||
## PyQt6 emit signal
|
||||
|
||||
Objects created from a `QObject` can emit signals. The following example shows how we to emit custom signals.
|
||||
|
||||
``` python
|
||||
# file: custom_signal.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we show how to
|
||||
emit a custom signal.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtCore import pyqtSignal, QObject
|
||||
from PyQt6.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, 450, 350)
|
||||
self.setWindowTitle('Emit signal')
|
||||
self.show()
|
||||
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
|
||||
self.c.closeApp.emit()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
We create a new signal called `closeApp`. This signal is emitted during a mouse press event. The signal is connected to the close slot of the `QMainWindow`.
|
||||
|
||||
``` python
|
||||
class Communicate(QObject):
|
||||
|
||||
closeApp = pyqtSignal()
|
||||
```
|
||||
A signal is created with the `pyqtSignal` as a class attribute of the external Communicate class.
|
||||
``` python
|
||||
self.c = Communicate()
|
||||
self.c.closeApp.connect(self.close)
|
||||
```
|
||||
The custom `closeApp` signal is connected to the close slot of the `QMainWindow`.
|
||||
``` python
|
||||
def mousePressEvent(self, event):
|
||||
|
||||
self.c.closeApp.emit()
|
||||
```
|
||||
When we click on the window with a mouse pointer, the `closeApp` signal is emitted. The application terminates.
|
||||
|
||||
In this part of the PyQt6 tutorial, we have covered signals and slots.
|
402
original/pyqt6/firstprograms.md
Normal file
@ -0,0 +1,402 @@
|
||||
# First programs in PyQt6
|
||||
*last modified April 23, 2021*
|
||||
|
||||
In this part of the PyQt6 tutorial we learn some basic functionality. The examples show a tooltip and an icon, close a window, show a message box and center a window on the desktop.
|
||||
|
||||
## PyQt6 simple example
|
||||
This is a simple example showing a small window. Yet we can do a lot with this window. We can resize it, maximise it or minimise it. This requires a lot of coding. Someone already coded this functionality. Because it is repeated in most applications, there is no need to code it over again. PyQt6 is a high level toolkit. If we would code in a lower level toolkit, the following code example could easily have hundreds of lines.
|
||||
|
||||
``` python
|
||||
# file: simple.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we create a simple
|
||||
window in PyQt6.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QApplication, QWidget
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
w = QWidget()
|
||||
w.resize(250, 200)
|
||||
w.move(300, 300)
|
||||
|
||||
w.setWindowTitle('Simple')
|
||||
w.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
The above code example shows a small window on the screen.
|
||||
|
||||
``` python
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QApplication, QWidget
|
||||
```
|
||||
Here we provide the necessary imports. The basic widgets are located in `PyQt6.QtWidgets` module.
|
||||
|
||||
``` python
|
||||
app = QApplication(sys.argv)
|
||||
```
|
||||
Every PyQt6 application must create an application object. The `sys.argv` parameter is a list of arguments from a command line. Python scripts can be run from the shell. It is a way how we can control the startup of our scripts.
|
||||
|
||||
``` python
|
||||
w = QWidget()
|
||||
```
|
||||
The QWidget widget is the base class of all user interface objects in PyQt6. We provide the default constructor for QWidget. The default constructor has no parent. A widget with no parent is called a window.
|
||||
|
||||
``` python
|
||||
w.resize(250, 150)
|
||||
```
|
||||
The resize method resizes the widget. It is 250px wide and 150px high.
|
||||
``` python
|
||||
w.move(300, 300)
|
||||
```
|
||||
The move method moves the widget to a position on the screen at x=300, y=300 coordinates.
|
||||
``` python
|
||||
w.setWindowTitle('Simple')
|
||||
```
|
||||
We set the title of the window with `setWindowTitle`. The title is shown in the titlebar.
|
||||
``` python
|
||||
w.show()
|
||||
```
|
||||
The show method displays the widget on the screen. A widget is first created in memory and later shown on the screen.
|
||||
``` python
|
||||
sys.exit(app.exec())
|
||||
```
|
||||
Finally, we enter the mainloop of the application. The event handling starts from this point. The mainloop receives events from the window system and dispatches them to the application widgets. The mainloop ends if we call the exit method or the main widget is destroyed. The `sys.exit` method ensures a clean exit. The environment will be informed how the application ended.
|
||||
|
||||
![Simple](./images/simple.png)
|
||||
|
||||
Figure: Simple
|
||||
## PyQt6 tooltip
|
||||
We can provide a balloon help for any of our widgets.
|
||||
|
||||
``` python
|
||||
# file: tooltip.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example shows a tooltip on
|
||||
a window and a button.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import (QWidget, QToolTip,
|
||||
QPushButton, QApplication)
|
||||
from PyQt6.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()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In this example, we show a tooltip for two PyQt6 widgets.
|
||||
|
||||
``` python
|
||||
QToolTip.setFont(QFont('SansSerif', 10))
|
||||
```
|
||||
|
||||
This static method sets a font used to render tooltips. We use a 10pt SansSerif font.
|
||||
|
||||
``` python
|
||||
self.setToolTip('This is a <b>QWidget</b> widget')
|
||||
```
|
||||
|
||||
To create a tooltip, we call the `setTooltip` method. We can use rich text formatting.
|
||||
|
||||
``` python
|
||||
btn = QPushButton('Button', self)
|
||||
btn.setToolTip('This is a <b>QPushButton</b> widget')
|
||||
```
|
||||
|
||||
We create a push button widget and set a tooltip for it.
|
||||
|
||||
``` python
|
||||
btn.resize(btn.sizeHint())
|
||||
btn.move(50, 50)
|
||||
```
|
||||
The button is being resized and moved on the window. The `sizeHint` method gives a recommended size for the button.
|
||||
|
||||
![Tooltips](./images/tooltips.png)
|
||||
Figure: Tooltips
|
||||
## PyQt6 quit button
|
||||
The obvious way to close a window is to click on the x mark on the titlebar. In the next example, we show how we can programatically close our window. We will briefly touch signals and slots.
|
||||
|
||||
The following is the constructor of a QPushButton widget that we use in our example.
|
||||
``` python
|
||||
QPushButton(string text, QWidget parent = None)
|
||||
```
|
||||
The text parameter is a text that will be displayed on the button. The parent is a widget on which we place our button. In our case it will be a `QWidget`. Widgets of an application form a hierarchy. In this hierarchy, most widgets have their parents. Widgets without parents are toplevel windows.
|
||||
|
||||
```python
|
||||
# file: quit_button.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program creates a quit
|
||||
button. When we press the button,
|
||||
the application terminates.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QWidget, QPushButton, QApplication
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
qbtn = QPushButton('Quit', self)
|
||||
qbtn.clicked.connect(QApplication.instance().quit)
|
||||
qbtn.resize(qbtn.sizeHint())
|
||||
qbtn.move(50, 50)
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('Quit button')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In this example, we create a quit button. Upon clicking on the button, the application terminates.
|
||||
``` python
|
||||
qbtn = QPushButton('Quit', self)
|
||||
```
|
||||
We create a push button. The button is an instance of the QPushButton class. The first parameter of the constructor is the label of the button. The second parameter is the parent widget. The parent widget is the Example widget, which is a `QWidget` by inheritance.
|
||||
``` python
|
||||
qbtn.clicked.connect(QApplication.instance().quit)
|
||||
```
|
||||
The event processing system in PyQt6 is built with the signal & slot mechanism. If we click on the button, the signal clicked is emitted. The slot can be a Qt slot or any Python callable.
|
||||
|
||||
`QCoreApplication`, which is retrieved with `QApplication.instance`, contains the main event loop—it processes and dispatches all events. The clicked signal is connected to the quit method which terminates the application. The communication is done between two objects: the sender and the receiver. The sender is the push button, the receiver is the application object.
|
||||
|
||||
![Quit button](./images/quitbutton.png)
|
||||
|
||||
Figure: Quit button
|
||||
|
||||
## PyQt6 message box
|
||||
By default, if we click on the x button on the titlebar, the `QWidget` is closed. Sometimes we want to modify this default behaviour. For example, if we have a file opened in an editor to which we did some changes. We show a message box to confirm the action.
|
||||
|
||||
``` python
|
||||
# file: messagebox.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program shows a confirmation
|
||||
message box when we click on the close
|
||||
button of the application window.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QWidget, QMessageBox, QApplication
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
|
||||
self.setGeometry(300, 300, 350, 200)
|
||||
self.setWindowTitle('Message box')
|
||||
self.show()
|
||||
|
||||
def closeEvent(self, event):
|
||||
|
||||
reply = QMessageBox.question(self, 'Message',
|
||||
"Are you sure to quit?", QMessageBox.StandardButtons.Yes |
|
||||
QMessageBox.StandardButtons.No, QMessageBox.StandardButtons.No)
|
||||
|
||||
if reply == QMessageBox.StandardButtons.Yes:
|
||||
|
||||
event.accept()
|
||||
else:
|
||||
|
||||
event.ignore()
|
||||
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
If we close a `QWidget`, the `QCloseEvent` is generated. To modify the widget behaviour we need to reimplement the `closeEvent` event handler.
|
||||
|
||||
``` python
|
||||
reply = QMessageBox.question(self, 'Message',
|
||||
"Are you sure to quit?", QMessageBox.Yes |
|
||||
QMessageBox.No, QMessageBox.No)
|
||||
```
|
||||
We show a message box with two buttons: Yes and No. The first string appears on the titlebar. The second string is the message text displayed by the dialog. The third argument specifies the combination of buttons appearing in the dialog. The last parameter is the default button. It is the button which has initially the keyboard focus. The return value is stored in the reply variable.
|
||||
|
||||
``` python
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
```
|
||||
Here we test the return value. If we click the Yes button, we accept the event which leads to the closure of the widget and to the termination of the application. Otherwise we ignore the close event.
|
||||
|
||||
![Message box](./images/messagebox.png)
|
||||
|
||||
Figure: Message box
|
||||
|
||||
## PyQt6 center window
|
||||
The following script shows how we can center a window on the desktop screen.
|
||||
``` python
|
||||
# file: center.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program centers a window
|
||||
on the screen.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
|
||||
self.resize(350, 250)
|
||||
self.center()
|
||||
|
||||
self.setWindowTitle('Center')
|
||||
self.show()
|
||||
|
||||
def center(self):
|
||||
|
||||
qr = self.frameGeometry()
|
||||
cp = self.screen().availableGeometry().center()
|
||||
|
||||
qr.moveCenter(cp)
|
||||
self.move(qr.topLeft())
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
The QScreen class class is used to query screen properties.
|
||||
``` python
|
||||
self.center()
|
||||
```
|
||||
The code that will center the window is placed in the custom `center` method.
|
||||
``` python
|
||||
qr = self.frameGeometry()
|
||||
```
|
||||
We get a rectangle specifying the geometry of the main window. This includes any window frame.
|
||||
``` python
|
||||
cp = self.screen().availableGeometry().center()
|
||||
```
|
||||
We figure out the screen resolution of our monitor. And from this resolution, we get the center point.
|
||||
``` python
|
||||
qr.moveCenter(cp)
|
||||
```
|
||||
Our rectangle has already its width and height. Now we set the center of the rectangle to the center of the screen. The rectangle's size is unchanged.
|
||||
``` python
|
||||
self.move(qr.topLeft())
|
||||
```
|
||||
We move the top-left point of the application window to the top-left point of the qr rectangle, thus centering the window on our screen.
|
||||
|
||||
In this part of the PyQt6 tutorial, we have created simple code examples in PyQt6.
|
BIN
original/pyqt6/images/absolute.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
original/pyqt6/images/beziercurve.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
original/pyqt6/images/brushes.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
original/pyqt6/images/burning.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
original/pyqt6/images/buttons.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
original/pyqt6/images/calculator.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
original/pyqt6/images/checkmenu.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
original/pyqt6/images/colours.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
original/pyqt6/images/coordinates.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
original/pyqt6/images/dragdrop.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
original/pyqt6/images/drawingtext.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
original/pyqt6/images/eventobject.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
original/pyqt6/images/eventsender.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
original/pyqt6/images/inputdialog.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
original/pyqt6/images/mainwindow.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
original/pyqt6/images/messagebox.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
original/pyqt6/images/penstyles.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
original/pyqt6/images/points.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
original/pyqt6/images/qcheckbox.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
original/pyqt6/images/qcombobox.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
original/pyqt6/images/qlineedit.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
original/pyqt6/images/qprogressbar.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
original/pyqt6/images/qslider.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
original/pyqt6/images/qsplitter.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
original/pyqt6/images/quitbutton.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
original/pyqt6/images/review.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
original/pyqt6/images/sigslot.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
original/pyqt6/images/simple.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
original/pyqt6/images/submenu.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
original/pyqt6/images/tetris.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
original/pyqt6/images/tetrominoes.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
original/pyqt6/images/togglebutton.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
original/pyqt6/images/toolbar.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
original/pyqt6/images/tooltips.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
24
original/pyqt6/index.md
Normal file
@ -0,0 +1,24 @@
|
||||
Python PyQt6
|
||||
last modified May 18, 2021
|
||||
|
||||
This is PyQt6 tutorial. The tutorial is suited for beginners and intermediate programmers. After reading this tutorial, you will be able to program non-trivial PyQt6 applications. The code examples are availabe at the author's [PyQt6-Tutorial-Examples](https://github.com/janbodnar/PyQt6-Tutorial-Examples) repository.
|
||||
|
||||
Table of contents
|
||||
Introduction
|
||||
Date and time
|
||||
First programs
|
||||
Menus and toolbars
|
||||
Layout management
|
||||
Events and signals
|
||||
Dialogs
|
||||
Widgets
|
||||
Widgets II
|
||||
Drag & drop
|
||||
Painting
|
||||
Custom widgets
|
||||
The Tetris game
|
||||
E-book
|
||||
A unique e-book covering advanced features of the PyQt5 library: [Advanced PyQt5 e-book](https://zetcode.com/ebooks/advancedpyqt5/).
|
||||
|
||||
Related tutorials
|
||||
[PyQt5 tutorial](https://zetcode.com/gui/pyqt5/) covers the previous version of PyQt. [wxPython tutorial](https://zetcode.com/wxpython/), [Python Gtk tutorial](https://zetcode.com/python/gtk/), and [Tkinter tutorial](https://zetcode.com/tkinter/) are tutorials for other popular Python GUI bindings.
|
62
original/pyqt6/introduction.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Introduction to PyQt6
|
||||
*last modified April 22, 2021*
|
||||
|
||||
This is an introductory PyQt6 tutorial. The purpose of this tutorial is to get you started with the PyQt6 toolkit.
|
||||
|
||||
About PyQt6
|
||||
PyQt6 is a set of Python bindings for Qt5 application framework from Digia. Qt library is one of the most powerful GUI libraries. The official home site for PyQt6 is www.riverbankcomputing.co.uk/news. PyQt6 is developed by Riverbank Computing.
|
||||
|
||||
PyQt6 is implemented as a set of Python modules. It is a multiplatform toolkit which runs on all major operating systems, including Unix, Windows, and Mac OS. PyQt6 is dual licensed; developers can choose between a GPL and a commercial license.
|
||||
|
||||
PyQt6 installation
|
||||
```sh
|
||||
$ pip install PyQt6
|
||||
```
|
||||
We can install PyQt6 with the `pip` tool.
|
||||
|
||||
PyQt6 modules
|
||||
PyQt6's classes are divided into several modules, including the following:
|
||||
|
||||
- QtCore
|
||||
- QtGui
|
||||
- QtWidgets
|
||||
- QtDBus
|
||||
- QtNetwork
|
||||
- QtHelp
|
||||
- QtXml
|
||||
- QtSvg
|
||||
- QtSql
|
||||
- QtTest
|
||||
|
||||
The `QtCore` module contains the core non-GUI functionality. This module is used for working with time, files and directories, various data types, streams, URLs, mime types, threads or processes. The `QtGui` contains classes for windowing system integration, event handling, 2D graphics, basic imaging, fonts and text. The `QtWidgets` module contains classes that provide a set of UI elements to create classic desktop-style user interfaces.
|
||||
|
||||
The `QtDBus` contains classes to support IPC using the D-Bus protocol.The `QtNetwork` module contains the classes for network programming. These classes facilitate the coding of TCP/IP and UDP clients and servers by making the network programming easier and more portable. The `QtHelp` contains classes for creating and viewing searchable documentation.
|
||||
|
||||
The `QtXml` contains classes for working with XML files. This module provides implementation for both SAX and DOM APIs. The `QtSvg` module provides classes for displaying the contents of SVG files. Scalable Vector Graphics (SVG) is a language for describing two-dimensional graphics and graphical applications in XML. The `QtSql` module provides classes for working with databases. The `QtTest` contains functions that enable unit testing of PyQt6 applications.
|
||||
|
||||
Python
|
||||
Python is a general-purpose, dynamic, object-oriented programming language. The design purpose of the Python language emphasizes programmer productivity and code readability. It was first released in 1991. Python was inspired by ABC, Haskell, Java, Lisp, Icon, and Perl programming languages. Python is a high-level, general purpose, multiplatform, interpreted language. Python is a minimalistic language. Python is maintained by a large group of volunteers worldwide.
|
||||
|
||||
The official web site for the Python programming language is python.org
|
||||
|
||||
PyQt6 version
|
||||
The `QT_VERSION_STR` provides the version of Qt and the `PYQT_VERSION_STR` version of PyQt6.
|
||||
|
||||
``` python
|
||||
# file: version.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QT_VERSION_STR
|
||||
from PyQt6.QtCore import PYQT_VERSION_STR
|
||||
|
||||
print(QT_VERSION_STR)
|
||||
print(PYQT_VERSION_STR)
|
||||
```
|
||||
We print the version of Qt library and PyQt6 module.
|
||||
|
||||
``` sh
|
||||
$ ./version.py
|
||||
6.0.2
|
||||
6.0.3
|
||||
```
|
||||
This chapter was an introduction to PyQt6 toolkit.
|
361
original/pyqt6/layout.md
Normal file
@ -0,0 +1,361 @@
|
||||
# Layout management in PyQt6
|
||||
*last modified April 27, 2021*
|
||||
|
||||
Layout management is the way how we place the widgets on the application window. We can place our widgets using absolute positioning or with layout classes. Managing the layout with layout managers is the preferred way of organizing our widgets.
|
||||
|
||||
## Absolute positioning
|
||||
The programmer specifies the position and the size of each widget in pixels. When you use absolute positioning, we have to understand the following limitations:
|
||||
|
||||
- The size and the position of a widget do not change if we resize a window
|
||||
- Applications might look different on various platforms
|
||||
- Changing fonts in our application might spoil the layout
|
||||
- If we decide to change our layout, we must completely redo our layout, which is tedious and time consuming
|
||||
|
||||
The following example positions widgets in absolute coordinates.
|
||||
|
||||
``` python
|
||||
# file: absolute.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example shows three labels on a window
|
||||
using absolute positioning.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QWidget, QLabel, QApplication
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
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, 350, 250)
|
||||
self.setWindowTitle('Absolute')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
We use the move method to position our widgets. In our case these are labels. We position them by providing the x and y coordinates. The beginning of the coordinate system is at the left top corner. The x values grow from left to right. The y values grow from top to bottom.
|
||||
|
||||
``` python
|
||||
lbl1 = QLabel('ZetCode', self)
|
||||
lbl1.move(15, 10)
|
||||
The label widget is positioned at x=15 and y=10.
|
||||
```
|
||||
|
||||
![Absolute positioning](./images/absolute.png)
|
||||
|
||||
Figure: Absolute positioning
|
||||
|
||||
## PyQt6 QHBoxLayout
|
||||
`QHBoxLayout` and `QVBoxLayout` are basic layout classes that line up widgets horizontally and vertically.
|
||||
|
||||
Imagine that we wanted to place two buttons in the right bottom corner. To create such a layout, we use one horizontal and one vertical box. To create the necessary space, we add a *stretch factor*.
|
||||
|
||||
``` python
|
||||
# file: box_layout.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we position two push
|
||||
buttons in the bottom-right corner
|
||||
of the window.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.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, 350, 250)
|
||||
self.setWindowTitle('Buttons')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
The example places two buttons in the bottom-right corner of the window. They stay there when we resize the application window. We use both a `HBoxLayout` and a `QVBoxLayout`.
|
||||
``` python
|
||||
okButton = QPushButton("OK")
|
||||
cancelButton = QPushButton("Cancel")
|
||||
```
|
||||
Here we create two push buttons.
|
||||
``` python
|
||||
hbox = QHBoxLayout()
|
||||
hbox.addStretch(1)
|
||||
hbox.addWidget(okButton)
|
||||
hbox.addWidget(cancelButton)
|
||||
```
|
||||
We create a horizontal box layout and add a stretch factor and both buttons. The stretch adds a stretchable space before the two buttons. This will push them to the right of the window.
|
||||
``` python
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addStretch(1)
|
||||
vbox.addLayout(hbox)
|
||||
```
|
||||
The horizontal layout is placed into the vertical layout. The stretch factor in the vertical box will push the horizontal box with the buttons to the bottom of the window.
|
||||
``` python
|
||||
self.setLayout(vbox)
|
||||
```
|
||||
Finally, we set the main layout of the window.
|
||||
|
||||
![Buttons](./images/buttons.png)
|
||||
|
||||
Figure: Buttons
|
||||
|
||||
## PyQt6 QGridLayout
|
||||
|
||||
`QGridLayout` is the most universal layout class. It divides the space into rows and columns.
|
||||
``` python
|
||||
# file: calculator.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we create a skeleton
|
||||
of a calculator using QGridLayout.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.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()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we create a grid of buttons.
|
||||
``` python
|
||||
grid = QGridLayout()
|
||||
self.setLayout(grid)
|
||||
```
|
||||
The instance of a `QGridLayout` is created and set to be the layout for the application window.
|
||||
``` python
|
||||
names = ['Cls', 'Bck', '', 'Close',
|
||||
'7', '8', '9', '/',
|
||||
'4', '5', '6', '*',
|
||||
'1', '2', '3', '-',
|
||||
'0', '.', '=', '+']
|
||||
```
|
||||
These are the labels used later for buttons.
|
||||
``` python
|
||||
positions = [(i,j) for i in range(5) for j in range(4)]
|
||||
```
|
||||
We create a list of positions in the grid.
|
||||
``` python
|
||||
for position, name in zip(positions, names):
|
||||
|
||||
if name == '':
|
||||
continue
|
||||
|
||||
button = QPushButton(name)
|
||||
grid.addWidget(button, *position)
|
||||
```
|
||||
Buttons are created and added to the layout with the `addWidget` method.
|
||||
|
||||
![Calculator skeleton](./images/calculator.png)
|
||||
|
||||
Figure: Calculator skeleton
|
||||
|
||||
## Review example
|
||||
Widgets can span multiple columns or rows in a grid. In the next example we illustrate this.
|
||||
|
||||
``` python
|
||||
# file: review.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we create a bit
|
||||
more complicated window layout using
|
||||
the QGridLayout manager.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.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()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
We create a window in which we have three labels, two line edits and one text edit widget. The layout is done with the `QGridLayout`.
|
||||
|
||||
``` python
|
||||
grid = QGridLayout()
|
||||
grid.setSpacing(10)
|
||||
```
|
||||
|
||||
We create a grid layout and set spacing between widgets.
|
||||
|
||||
``` python
|
||||
grid.addWidget(reviewEdit, 3, 1, 5, 1)
|
||||
```
|
||||
|
||||
If we add a widget to a grid, we can provide row span and column span of the widget. In our case, we make the `reviewEdit` widget span 5 rows.
|
||||
|
||||
![Review example](./images/review.png)
|
||||
|
||||
Figure: Review example
|
||||
|
||||
This part of the PyQt6 tutorial was dedicated to layout management.
|
524
original/pyqt6/menustoolbars.md
Normal file
@ -0,0 +1,524 @@
|
||||
# Menus and toolbars in PyQt6
|
||||
*last modified April 24, 2021*
|
||||
|
||||
In this part of the PyQt6 tutorial, we create a statusbar, menubar and a toolbar. A menu is a group of commands located in a menubar. A toolbar has buttons with some common commands in the application. Statusbar shows status information, usually at the bottom of the application window.
|
||||
|
||||
## PyQt6 QMainWindow
|
||||
The `QMainWindow` class provides a main application window. This enables to create a classic application skeleton with a statusbar, toolbars, and a menubar.
|
||||
|
||||
## PyQt6 statusbar
|
||||
A statusbar is a widget that is used for displaying status information.
|
||||
|
||||
``` python
|
||||
# file: statusbar.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program creates a statusbar.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.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, 350, 250)
|
||||
self.setWindowTitle('Statusbar')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
The statusbar is created with the help of the `QMainWindow` widget.
|
||||
``` python
|
||||
self.statusBar().showMessage('Ready')
|
||||
```
|
||||
To get the statusbar, we call the statusBar method of the `QtGui.QMainWindow` class. The first call of the method creates a status bar. Subsequent calls return the statusbar object. The `showMessage` displays a message on the statusbar.
|
||||
|
||||
## PyQt6 simple menu
|
||||
A menubar is a common part of a GUI application. It is a group of commands located in various menus. (Mac OS treats menubars differently. To get a similar outcome, we can add the following line: `menubar.setNativeMenuBar(False)`.)
|
||||
``` python
|
||||
# file: simple_menu.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program creates a menubar. The
|
||||
menubar has one menu with an exit action.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QMainWindow, QApplication
|
||||
from PyQt6.QtGui import QIcon, QAction
|
||||
|
||||
|
||||
class Example(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
exitAct = QAction(QIcon('exit.png'), '&Exit', self)
|
||||
exitAct.setShortcut('Ctrl+Q')
|
||||
exitAct.setStatusTip('Exit application')
|
||||
exitAct.triggered.connect(QApplication.instance().quit)
|
||||
|
||||
self.statusBar()
|
||||
|
||||
menubar = self.menuBar()
|
||||
fileMenu = menubar.addMenu('&File')
|
||||
fileMenu.addAction(exitAct)
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('Simple menu')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In the above example, we create a menubar with one menu. This menu contains one action which terminates the application if selected. A statusbar is created as well. The action is accessible with the `Ctrl+Q` shortcut.
|
||||
``` python
|
||||
exitAct = QAction(QIcon('exit.png'), '&Exit', self)
|
||||
exitAct.setShortcut('Ctrl+Q')
|
||||
exitAct.setStatusTip('Exit application')
|
||||
```
|
||||
`QAction` is an abstraction for actions performed with a menubar, toolbar, or with a custom keyboard shortcut. In the above three lines, we create an action with a specific icon and an 'Exit' label. Furthermore, a shortcut is defined for this action. The third line creates a status tip which is shown in the statusbar when we hover a mouse pointer over the menu item.
|
||||
``` python
|
||||
exitAct.triggered.connect(QApplication.instance().quit)
|
||||
```
|
||||
When we select this particular action, a triggered signal is emitted. The signal is connected to the quit method of the `QApplication` widget. This terminates the application.
|
||||
``` python
|
||||
menubar = self.menuBar()
|
||||
fileMenu = menubar.addMenu('&File')
|
||||
fileMenu.addAction(exitAction)
|
||||
```
|
||||
The menuBar method creates a menubar. We create a file menu with `addMenu` and add the action with `addAction`.
|
||||
|
||||
## PyQt6 submenu
|
||||
A submenu is a menu located inside another menu.
|
||||
``` python
|
||||
submenu.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program creates a submenu.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QMainWindow, QMenu, QApplication
|
||||
from PyQt6.QtGui import QAction
|
||||
|
||||
|
||||
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, 350, 250)
|
||||
self.setWindowTitle('Submenu')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In the example, we have two menu items; one is located in the File menu and the other one in the File's Import submenu.
|
||||
|
||||
``` python
|
||||
impMenu = QMenu('Import', self)
|
||||
```
|
||||
|
||||
New menu is created with `QMenu`.
|
||||
|
||||
``` python
|
||||
impAct = QAction('Import mail', self)
|
||||
impMenu.addAction(impAct)
|
||||
```
|
||||
|
||||
An action is added to the submenu with addAction.
|
||||
|
||||
![Submenu](./images/submenu.png)
|
||||
|
||||
Figure: Submenu
|
||||
|
||||
## PyQt6 check menu
|
||||
In the following example, we create a menu that can be checked and unchecked.
|
||||
|
||||
``` python
|
||||
# file: check_menu.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program creates a checkable menu.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QMainWindow, QApplication
|
||||
from PyQt6.QtGui import QAction
|
||||
|
||||
|
||||
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, 350, 250)
|
||||
self.setWindowTitle('Check menu')
|
||||
self.show()
|
||||
|
||||
|
||||
def toggleMenu(self, state):
|
||||
|
||||
if state:
|
||||
self.statusbar.show()
|
||||
else:
|
||||
self.statusbar.hide()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
The code example creates a View menu with one action. The action shows or hides a statusbar. When the statusbar is visible, the menu item is checked.
|
||||
|
||||
``` python
|
||||
viewStatAct = QAction('View statusbar', self, checkable=True)
|
||||
```
|
||||
With the `checkable` option we create a checkable menu.
|
||||
|
||||
``` python
|
||||
viewStatAct.setChecked(True)
|
||||
```
|
||||
Since the statusbar is visible from the start, we check the action with `setChecked` method.
|
||||
|
||||
``` python
|
||||
def toggleMenu(self, state):
|
||||
|
||||
if state:
|
||||
self.statusbar.show()
|
||||
else:
|
||||
self.statusbar.hide()
|
||||
```
|
||||
Depending on the state of the action, we show or hide the statusbar.
|
||||
|
||||
![Check menu](./images/checkmenu.png)
|
||||
|
||||
Figure: Check menu
|
||||
## PyQt6 context menu
|
||||
A context menu, also called a popup menu, is a list of commands that appears under some context. For example, in a Opera web browser when we right click on a web page, we get a context menu. Here we can reload a page, go back, or view a page source. If we right click on a toolbar, we get another context menu for managing toolbars.
|
||||
|
||||
``` python
|
||||
# file: context_menu.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program creates a context menu.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QMainWindow, QMenu, QApplication
|
||||
|
||||
|
||||
class Example(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('Context menu')
|
||||
self.show()
|
||||
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
|
||||
cmenu = QMenu(self)
|
||||
|
||||
newAct = cmenu.addAction("New")
|
||||
openAct = cmenu.addAction("Open")
|
||||
quitAct = cmenu.addAction("Quit")
|
||||
action = cmenu.exec(self.mapToGlobal(event.pos()))
|
||||
|
||||
if action == quitAct:
|
||||
QApplication.instance().quit()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
To work with a context menu, we have to reimplement the `contextMenuEvent` method.
|
||||
|
||||
``` python
|
||||
action = cmenu.exec(self.mapToGlobal(event.pos()))
|
||||
```
|
||||
The context menu is displayed with the `exec` method. The get the coordinates of the mouse pointer from the event object. The `mapToGlobal` method translates the widget coordinates to the global screen coordinates.
|
||||
``` python
|
||||
if action == quitAct:
|
||||
QApplication.instance().quit()
|
||||
```
|
||||
If the action returned from the context menu equals to quit action, we terminate the application.
|
||||
|
||||
## PyQt6 toolbar
|
||||
Menus group all commands that we can use in an application. Toolbars provide a quick access to the most frequently used commands.
|
||||
|
||||
``` python
|
||||
# file: toolbar.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program creates a toolbar.
|
||||
The toolbar has one action, which
|
||||
terminates the application, if triggered.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QMainWindow, QApplication
|
||||
from PyQt6.QtGui import QIcon, QAction
|
||||
|
||||
|
||||
class Example(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
exitAct = QAction(QIcon('exit24.png'), 'Exit', self)
|
||||
exitAct.setShortcut('Ctrl+Q')
|
||||
exitAct.triggered.connect(QApplication.instance().quit)
|
||||
|
||||
self.toolbar = self.addToolBar('Exit')
|
||||
self.toolbar.addAction(exitAct)
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('Toolbar')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In the above example, we create a simple toolbar. The toolbar has one tool action, an exit action which terminates the application when triggered.
|
||||
|
||||
``` python
|
||||
exitAct = QAction(QIcon('exit24.png'), 'Exit', self)
|
||||
exitAct.setShortcut('Ctrl+Q')
|
||||
exitAct.triggered.connect(QApplication.instance().quit)
|
||||
```
|
||||
Similar to the menubar example above, we create an action object. The object has a label, icon, and a shorcut. A quit method of the `QApplication` is connected to the triggered signal.
|
||||
|
||||
``` python
|
||||
self.toolbar = self.addToolBar('Exit')
|
||||
self.toolbar.addAction(exitAction)
|
||||
```
|
||||
The toolbar is created with the `addToolBar` method. We add an action object to the toolbar with `addAction`.
|
||||
|
||||
![Toolbar](./images/toolbar.png)
|
||||
|
||||
Figure: Toolbar
|
||||
## PyQt6 main window
|
||||
In the last example of this section, we create a menubar, toolbar, and a statusbar. We also create a central widget.
|
||||
|
||||
``` python
|
||||
# file: main_window.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program creates a skeleton of
|
||||
a classic GUI application with a menubar,
|
||||
toolbar, statusbar, and a central widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QMainWindow, QTextEdit, QApplication
|
||||
from PyQt6.QtGui import QIcon, QAction
|
||||
|
||||
|
||||
class Example(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
textEdit = QTextEdit()
|
||||
self.setCentralWidget(textEdit)
|
||||
|
||||
exitAct = QAction(QIcon('exit24.png'), '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()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
This code example creates a skeleton of a classic GUI application with a menubar, toolbar, and a statusbar.
|
||||
``` python
|
||||
textEdit = QTextEdit()
|
||||
self.setCentralWidget(textEdit)
|
||||
```
|
||||
Here we create a text edit widget. We set it to be the central widget of the `QMainWindow`. The central widget occupies all space that is left.
|
||||
|
||||
![Main window](./images/mainwindow.png)
|
||||
|
||||
Figure: Main window
|
||||
|
||||
In this part of the PyQt6 tutorial, we worked with menus, toolbars, a statusbar, and a main application window.
|
582
original/pyqt6/painting.md
Normal file
@ -0,0 +1,582 @@
|
||||
# Painting in PyQt6
|
||||
*last modified May 15, 2021*
|
||||
|
||||
PyQt6 painting system is able to render vector graphics, images, and outline font-based text. Painting is needed in applications when we want to change or enhance an existing widget, or if we are creating a custom widget from scratch. To do the drawing, we use the painting API provided by the PyQt6 toolkit.
|
||||
|
||||
## QPainter
|
||||
QPainter performs low-level painting on widgets and other paint devices. It can draw everything from simple lines to complex shapes.
|
||||
|
||||
## The paintEvent method
|
||||
The painting is done within the paintEvent method. The painting code is placed between the begin and end methods of the QPainter object. It performs low-level painting on widgets and other paint devices.
|
||||
|
||||
## PyQt6 draw text
|
||||
We begin with drawing some Unicode text on the client area of a window.
|
||||
|
||||
``` python
|
||||
# file: draw_text.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we draw text in Russian Cylliric.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
from PyQt6.QtGui import QPainter, QColor, QFont
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
self.text = "Лев Николаевич Толстой\nАнна Каренина"
|
||||
|
||||
self.setGeometry(300, 300, 350, 300)
|
||||
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.Alignment.AlignCenter, self.text)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we draw some text in Cylliric. The text is vertically and horizontally aligned.
|
||||
|
||||
``` python
|
||||
def paintEvent(self, event):
|
||||
...
|
||||
```
|
||||
Drawing is done within the paint event.
|
||||
|
||||
``` python
|
||||
qp = QPainter()
|
||||
qp.begin(self)
|
||||
self.drawText(event, qp)
|
||||
qp.end()
|
||||
```
|
||||
The `QPainter` class is responsible for all the low-level painting. All the painting methods go between begin and end methods. The actual painting is delegated to the drawText method.
|
||||
``` python
|
||||
qp.setPen(QColor(168, 34, 3))
|
||||
qp.setFont(QFont('Decorative', 10))
|
||||
```
|
||||
Here we define a pen and a font which are used to draw the text.
|
||||
|
||||
``` python
|
||||
qp.drawText(event.rect(), Qt.Alignment.AlignCenter, self.text)
|
||||
```
|
||||
The drawText method draws text on the window. The rect method of the paint event returns the rectangle that needs to be updated. With the `Qt.Alignment.AlignCenter` we align the text in both dimensions.
|
||||
|
||||
![Drawing text](./images/drawingtext.png)
|
||||
|
||||
Figure: Drawing text
|
||||
|
||||
## PyQt6 draw points
|
||||
A point is the most simple graphics object that can be drawn. It is a small spot on the window.
|
||||
|
||||
``` python
|
||||
# file: draw_points.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In the example, we draw randomly 1000 red points
|
||||
on the window.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
from PyQt6.QtGui import QPainter
|
||||
from PyQt6.QtCore import Qt
|
||||
import sys, random
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
self.setMinimumSize(50, 50)
|
||||
self.setGeometry(300, 300, 350, 300)
|
||||
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.GlobalColor.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)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we draw randomly 1000 red points on the client area of the window.
|
||||
|
||||
``` python
|
||||
qp.setPen(Qt.GlobalColor.red)
|
||||
```
|
||||
We set the pen to red colour. We use a predefined `Qt.GlobalColor.red` colour constant.
|
||||
|
||||
``` python
|
||||
size = self.size()
|
||||
```
|
||||
Each time we resize the window, a paint event is generated. We get the current size of the window with the size method. We use the size of the window to distribute the points all over the client area of the window.
|
||||
|
||||
``` python
|
||||
qp.drawPoint(x, y)
|
||||
```
|
||||
We draw the point with the drawPoint method.
|
||||
|
||||
![Points](./images/points.png)
|
||||
|
||||
Figure: Points
|
||||
|
||||
## PyQt6 colours
|
||||
|
||||
A colour is an object representing a combination of Red, Green, and Blue (RGB) intensity values. Valid RGB values are in the range from 0 to 255. We can define a colour in various ways. The most common are RGB decimal values or hexadecimal values. We can also use an RGBA value which stands for Red, Green, Blue, and Alpha. Here we add some extra information regarding transparency. Alpha value of 255 defines full opacity, 0 is for full transparency, e.g. the colour is invisible.
|
||||
|
||||
``` python
|
||||
# file: colours.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example draws three rectangles in three
|
||||
different colours.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
from PyQt6.QtGui import QPainter, QColor
|
||||
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)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we draw three coloured rectangles.
|
||||
|
||||
``` python
|
||||
color = QColor(0, 0, 0)
|
||||
color.setNamedColor('#d4d4d4')
|
||||
```
|
||||
Here we define a colour using a hexadecimal notation.
|
||||
|
||||
``` python
|
||||
qp.setBrush(QColor(200, 0, 0))
|
||||
qp.drawRect(10, 15, 90, 60)
|
||||
```
|
||||
Here we define a brush and draw a rectangle. A *brush* is an elementary graphics object which is used to draw the background of a shape. The `drawRect` method accepts four parameters. The first two are x and y values on the axis. The third and fourth parameters are the width and height of the rectangle. The method draws the rectangle using the current pen and brush.
|
||||
|
||||
![Colours](./images/colours.png)
|
||||
|
||||
Figure: Colours
|
||||
|
||||
## PyQt6 QPen
|
||||
The `QPen` is an elementary graphics object. It is used to draw lines, curves and outlines of rectangles, ellipses, polygons, or other shapes.
|
||||
|
||||
``` python
|
||||
# file: pens.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example we draw 6 lines using
|
||||
different pen styles.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
from PyQt6.QtGui import QPainter, QPen
|
||||
from PyQt6.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.GlobalColor.black, 2, Qt.PenStyle.SolidLine)
|
||||
|
||||
qp.setPen(pen)
|
||||
qp.drawLine(20, 40, 250, 40)
|
||||
|
||||
pen.setStyle(Qt.PenStyle.DashLine)
|
||||
qp.setPen(pen)
|
||||
qp.drawLine(20, 80, 250, 80)
|
||||
|
||||
pen.setStyle(Qt.PenStyle.DashDotLine)
|
||||
qp.setPen(pen)
|
||||
qp.drawLine(20, 120, 250, 120)
|
||||
|
||||
pen.setStyle(Qt.PenStyle.DotLine)
|
||||
qp.setPen(pen)
|
||||
qp.drawLine(20, 160, 250, 160)
|
||||
|
||||
pen.setStyle(Qt.PenStyle.DashDotDotLine)
|
||||
qp.setPen(pen)
|
||||
qp.drawLine(20, 200, 250, 200)
|
||||
|
||||
pen.setStyle(Qt.PenStyle.CustomDashLine)
|
||||
pen.setDashPattern([1, 4, 5, 4])
|
||||
qp.setPen(pen)
|
||||
qp.drawLine(20, 240, 250, 240)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
In our example, we draw six lines. The lines are drawn in six different pen styles. There are five predefined pen styles. We can create also custom pen styles. The last line is drawn using a custom pen style.
|
||||
|
||||
``` python
|
||||
pen = QPen(Qt.GlobalColor.black, 2, Qt.PenStyle.SolidLine)
|
||||
```
|
||||
We create a `QPen` object. The colour is black. The width is set to 2 pixels so that we can see the differences between the pen styles. `Qt.SolidLine` is one of the predefined pen styles.
|
||||
|
||||
``` python
|
||||
pen.setStyle(Qt.PenStyle.CustomDashLine)
|
||||
pen.setDashPattern([1, 4, 5, 4])
|
||||
qp.setPen(pen)
|
||||
```
|
||||
Here we define a custom pen style. We set a `Qt.PenStyle.CustomDashLine` pen style and call the `setDashPattern` method. The list of numbers defines a style. There must be an even number of numbers. Odd numbers define a dash, even numbers space. The greater the number, the greater the space or the dash. Our pattern is 1 px dash, 4 px space, 5 px dash, 4 px space etc.
|
||||
|
||||
![Pen styles](./images/penstyles.png)
|
||||
|
||||
Figure: Pen styles
|
||||
|
||||
## PyQt6 QBrush
|
||||
`QBrush` is an elementary graphics object. It is used to paint the background of graphics shapes, such as rectangles, ellipses, or polygons. A brush can be of three different types: a predefined brush, a gradient, or a texture pattern.
|
||||
|
||||
``` python
|
||||
# file: brushes.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example draws nine rectangles in different
|
||||
brush styles.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
from PyQt6.QtGui import QPainter, QBrush
|
||||
from PyQt6.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.BrushStyle.SolidPattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(10, 15, 90, 60)
|
||||
|
||||
brush.setStyle(Qt.BrushStyle.Dense1Pattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(130, 15, 90, 60)
|
||||
|
||||
brush.setStyle(Qt.BrushStyle.Dense2Pattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(250, 15, 90, 60)
|
||||
|
||||
brush.setStyle(Qt.BrushStyle.DiagCrossPattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(10, 105, 90, 60)
|
||||
|
||||
brush.setStyle(Qt.BrushStyle.Dense5Pattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(130, 105, 90, 60)
|
||||
|
||||
brush.setStyle(Qt.BrushStyle.Dense6Pattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(250, 105, 90, 60)
|
||||
|
||||
brush.setStyle(Qt.BrushStyle.HorPattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(10, 195, 90, 60)
|
||||
|
||||
brush.setStyle(Qt.BrushStyle.VerPattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(130, 195, 90, 60)
|
||||
|
||||
brush.setStyle(Qt.BrushStyle.BDiagPattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(250, 195, 90, 60)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we draw nine different rectangles.
|
||||
|
||||
``` python
|
||||
brush = QBrush(Qt.BrushStyle.SolidPattern)
|
||||
qp.setBrush(brush)
|
||||
qp.drawRect(10, 15, 90, 60)
|
||||
```
|
||||
We define a brush object. We set it to the painter object and draw the rectangle by calling the drawRect method.
|
||||
|
||||
![Brushes](./images/brushes.png)
|
||||
|
||||
Figure: Brushes
|
||||
|
||||
## Bézier curve
|
||||
Bézier curve is a cubic line. Bézier curve in PyQt6 can be created with `QPainterPath`. A painter path is an object composed of a number of graphical building blocks, such as rectangles, ellipses, lines, and curves.
|
||||
|
||||
``` python
|
||||
# file: bezier_curve.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program draws a Bézier curve with
|
||||
QPainterPath.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt6.QtGui import QPainter, QPainterPath
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
|
||||
|
||||
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.RenderHints.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)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
This example draws a Bézier curve.
|
||||
|
||||
```python
|
||||
path = QPainterPath()
|
||||
path.moveTo(30, 30)
|
||||
path.cubicTo(30, 30, 200, 350, 350, 30)
|
||||
```
|
||||
We create a Bézier curve with `QPainterPath` path. The curve is created with `cubicTo` method, which takes three points: starting point, control point, and ending point.
|
||||
|
||||
``` python
|
||||
qp.drawPath(path)
|
||||
```
|
||||
The final path is drawn with drawPath method.
|
||||
|
||||
![Bézier curve](./images/beziercurve.png)
|
||||
|
||||
Figure: Bézier curve
|
||||
|
||||
In this part of the PyQt6 tutorial, we did some basic painting.
|
861
original/pyqt6/tetris.md
Normal file
@ -0,0 +1,861 @@
|
||||
# Tetris in PyQt6
|
||||
*last modified May 5, 2021*
|
||||
|
||||
In this chapter, we create a Tetris game clone.
|
||||
|
||||
## Tetris
|
||||
The Tetris game is one of the most popular computer games ever created. The original game was designed and programmed by a Russian programmer *Alexey Pajitnov* in 1985. Since then, Tetris is available on almost every computer platform in lots of variations.
|
||||
|
||||
Tetris is called a falling block puzzle game. In this game, we have seven different shapes called tetrominoes: an S-shape, a Z-shape, a T-shape, an L-shape, a Line-shape, a MirroredL-shape, and a Square-shape. Each of these shapes is formed with four squares. The shapes are falling down the board. The object of the Tetris game is to move and rotate the shapes so that they fit as much as possible. If we manage to form a row, the row is destroyed and we score. We play the Tetris game until we top out.
|
||||
|
||||
![Tetrominoes](./images/tetrominoes.png)
|
||||
|
||||
Figure: Tetrominoes
|
||||
|
||||
PyQt6 is a toolkit designed to create applications. There are other libraries which are targeted at creating computer games. Nevertheless, PyQt6 and other application toolkits can be used to create simple games.
|
||||
|
||||
Creating a computer game is a good way for enhancing programming skills.
|
||||
|
||||
## The development
|
||||
We do not have images for our Tetris game, we draw the tetrominoes using the drawing API available in the PyQt6 programming toolkit. Behind every computer game, there is a mathematical model. So it is in Tetris.
|
||||
|
||||
Some ideas behind the game:
|
||||
|
||||
- We use a `QtCore.QBasicTimer` to create a game cycle.
|
||||
- The tetrominoes are drawn.
|
||||
- The shapes move on a square by square basis (not pixel by pixel).
|
||||
- Mathematically a board is a simple list of numbers.
|
||||
|
||||
The code consists of four classes: `Tetris`, `Board`, `Tetrominoe` and `Shape`. The `Tetris` class sets up the game. The `Board` is where the game logic is written. The `Tetrominoe` class contains names for all tetris pieces and the `Shape` class contains the code for a tetris piece.
|
||||
|
||||
``` python
|
||||
# files: tetris.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This is a Tetris game clone.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import random
|
||||
import sys
|
||||
|
||||
from PyQt6.QtCore import Qt, QBasicTimer, pyqtSignal
|
||||
from PyQt6.QtGui import QPainter, QColor
|
||||
from PyQt6.QtWidgets import QMainWindow, QFrame, QApplication
|
||||
|
||||
|
||||
class Tetris(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
"""initiates application UI"""
|
||||
|
||||
self.tboard = Board(self)
|
||||
self.setCentralWidget(self.tboard)
|
||||
|
||||
self.statusbar = self.statusBar()
|
||||
self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
|
||||
|
||||
self.tboard.start()
|
||||
|
||||
self.resize(180, 380)
|
||||
self.center()
|
||||
self.setWindowTitle('Tetris')
|
||||
self.show()
|
||||
|
||||
|
||||
def center(self):
|
||||
"""centers the window on the screen"""
|
||||
|
||||
qr = self.frameGeometry()
|
||||
cp = self.screen().availableGeometry().center()
|
||||
|
||||
qr.moveCenter(cp)
|
||||
self.move(qr.topLeft())
|
||||
|
||||
|
||||
class Board(QFrame):
|
||||
|
||||
msg2Statusbar = pyqtSignal(str)
|
||||
|
||||
BoardWidth = 10
|
||||
BoardHeight = 22
|
||||
Speed = 300
|
||||
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.initBoard()
|
||||
|
||||
|
||||
def initBoard(self):
|
||||
"""initiates board"""
|
||||
|
||||
self.timer = QBasicTimer()
|
||||
self.isWaitingAfterLine = False
|
||||
|
||||
self.curX = 0
|
||||
self.curY = 0
|
||||
self.numLinesRemoved = 0
|
||||
self.board = []
|
||||
|
||||
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||
self.isStarted = False
|
||||
self.isPaused = False
|
||||
self.clearBoard()
|
||||
|
||||
|
||||
def shapeAt(self, x, y):
|
||||
"""determines shape at the board position"""
|
||||
|
||||
return self.board[(y * Board.BoardWidth) + x]
|
||||
|
||||
|
||||
def setShapeAt(self, x, y, shape):
|
||||
"""sets a shape at the board"""
|
||||
|
||||
self.board[(y * Board.BoardWidth) + x] = shape
|
||||
|
||||
|
||||
def squareWidth(self):
|
||||
"""returns the width of one square"""
|
||||
|
||||
return self.contentsRect().width() // Board.BoardWidth
|
||||
|
||||
|
||||
def squareHeight(self):
|
||||
"""returns the height of one square"""
|
||||
|
||||
return self.contentsRect().height() // Board.BoardHeight
|
||||
|
||||
|
||||
def start(self):
|
||||
"""starts game"""
|
||||
|
||||
if self.isPaused:
|
||||
return
|
||||
|
||||
self.isStarted = True
|
||||
self.isWaitingAfterLine = False
|
||||
self.numLinesRemoved = 0
|
||||
self.clearBoard()
|
||||
|
||||
self.msg2Statusbar.emit(str(self.numLinesRemoved))
|
||||
|
||||
self.newPiece()
|
||||
self.timer.start(Board.Speed, self)
|
||||
|
||||
|
||||
def pause(self):
|
||||
"""pauses game"""
|
||||
|
||||
if not self.isStarted:
|
||||
return
|
||||
|
||||
self.isPaused = not self.isPaused
|
||||
|
||||
if self.isPaused:
|
||||
self.timer.stop()
|
||||
self.msg2Statusbar.emit("paused")
|
||||
|
||||
else:
|
||||
self.timer.start(Board.Speed, self)
|
||||
self.msg2Statusbar.emit(str(self.numLinesRemoved))
|
||||
|
||||
self.update()
|
||||
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""paints all shapes of the game"""
|
||||
|
||||
painter = QPainter(self)
|
||||
rect = self.contentsRect()
|
||||
|
||||
boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()
|
||||
|
||||
for i in range(Board.BoardHeight):
|
||||
for j in range(Board.BoardWidth):
|
||||
shape = self.shapeAt(j, Board.BoardHeight - i - 1)
|
||||
|
||||
if shape != Tetrominoe.NoShape:
|
||||
self.drawSquare(painter,
|
||||
rect.left() + j * self.squareWidth(),
|
||||
boardTop + i * self.squareHeight(), shape)
|
||||
|
||||
if self.curPiece.shape() != Tetrominoe.NoShape:
|
||||
|
||||
for i in range(4):
|
||||
x = self.curX + self.curPiece.x(i)
|
||||
y = self.curY - self.curPiece.y(i)
|
||||
self.drawSquare(painter, rect.left() + x * self.squareWidth(),
|
||||
boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
|
||||
self.curPiece.shape())
|
||||
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""processes key press events"""
|
||||
|
||||
if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:
|
||||
super(Board, self).keyPressEvent(event)
|
||||
return
|
||||
|
||||
key = event.key()
|
||||
|
||||
if key == Qt.Key.Key_P:
|
||||
self.pause()
|
||||
return
|
||||
|
||||
if self.isPaused:
|
||||
return
|
||||
|
||||
elif key == Qt.Key.Key_Left.value:
|
||||
self.tryMove(self.curPiece, self.curX - 1, self.curY)
|
||||
|
||||
elif key == Qt.Key.Key_Right.value:
|
||||
self.tryMove(self.curPiece, self.curX + 1, self.curY)
|
||||
|
||||
elif key == Qt.Key.Key_Down.value:
|
||||
self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)
|
||||
|
||||
elif key == Qt.Key.Key_Up.value:
|
||||
self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
|
||||
|
||||
elif key == Qt.Key.Key_Space.value:
|
||||
self.dropDown()
|
||||
|
||||
elif key == Qt.Key.Key_D.value:
|
||||
self.oneLineDown()
|
||||
|
||||
else:
|
||||
super(Board, self).keyPressEvent(event)
|
||||
|
||||
|
||||
def timerEvent(self, event):
|
||||
"""handles timer event"""
|
||||
|
||||
if event.timerId() == self.timer.timerId():
|
||||
|
||||
if self.isWaitingAfterLine:
|
||||
self.isWaitingAfterLine = False
|
||||
self.newPiece()
|
||||
else:
|
||||
self.oneLineDown()
|
||||
|
||||
else:
|
||||
super(Board, self).timerEvent(event)
|
||||
|
||||
|
||||
def clearBoard(self):
|
||||
"""clears shapes from the board"""
|
||||
|
||||
for i in range(Board.BoardHeight * Board.BoardWidth):
|
||||
self.board.append(Tetrominoe.NoShape)
|
||||
|
||||
|
||||
def dropDown(self):
|
||||
"""drops down a shape"""
|
||||
|
||||
newY = self.curY
|
||||
|
||||
while newY > 0:
|
||||
|
||||
if not self.tryMove(self.curPiece, self.curX, newY - 1):
|
||||
break
|
||||
|
||||
newY -= 1
|
||||
|
||||
self.pieceDropped()
|
||||
|
||||
|
||||
def oneLineDown(self):
|
||||
"""goes one line down with a shape"""
|
||||
|
||||
if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
|
||||
self.pieceDropped()
|
||||
|
||||
|
||||
def pieceDropped(self):
|
||||
"""after dropping shape, remove full lines and create new shape"""
|
||||
|
||||
for i in range(4):
|
||||
x = self.curX + self.curPiece.x(i)
|
||||
y = self.curY - self.curPiece.y(i)
|
||||
self.setShapeAt(x, y, self.curPiece.shape())
|
||||
|
||||
self.removeFullLines()
|
||||
|
||||
if not self.isWaitingAfterLine:
|
||||
self.newPiece()
|
||||
|
||||
|
||||
def removeFullLines(self):
|
||||
"""removes all full lines from the board"""
|
||||
|
||||
numFullLines = 0
|
||||
rowsToRemove = []
|
||||
|
||||
for i in range(Board.BoardHeight):
|
||||
|
||||
n = 0
|
||||
for j in range(Board.BoardWidth):
|
||||
if not self.shapeAt(j, i) == Tetrominoe.NoShape:
|
||||
n = n + 1
|
||||
|
||||
if n == 10:
|
||||
rowsToRemove.append(i)
|
||||
|
||||
rowsToRemove.reverse()
|
||||
|
||||
for m in rowsToRemove:
|
||||
|
||||
for k in range(m, Board.BoardHeight):
|
||||
for l in range(Board.BoardWidth):
|
||||
self.setShapeAt(l, k, self.shapeAt(l, k + 1))
|
||||
|
||||
numFullLines = numFullLines + len(rowsToRemove)
|
||||
|
||||
if numFullLines > 0:
|
||||
self.numLinesRemoved = self.numLinesRemoved + numFullLines
|
||||
self.msg2Statusbar.emit(str(self.numLinesRemoved))
|
||||
|
||||
self.isWaitingAfterLine = True
|
||||
self.curPiece.setShape(Tetrominoe.NoShape)
|
||||
self.update()
|
||||
|
||||
|
||||
def newPiece(self):
|
||||
"""creates a new shape"""
|
||||
|
||||
self.curPiece = Shape()
|
||||
self.curPiece.setRandomShape()
|
||||
self.curX = Board.BoardWidth // 2 + 1
|
||||
self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
|
||||
|
||||
if not self.tryMove(self.curPiece, self.curX, self.curY):
|
||||
|
||||
self.curPiece.setShape(Tetrominoe.NoShape)
|
||||
self.timer.stop()
|
||||
self.isStarted = False
|
||||
self.msg2Statusbar.emit("Game over")
|
||||
|
||||
|
||||
def tryMove(self, newPiece, newX, newY):
|
||||
"""tries to move a shape"""
|
||||
|
||||
for i in range(4):
|
||||
|
||||
x = newX + newPiece.x(i)
|
||||
y = newY - newPiece.y(i)
|
||||
|
||||
if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
|
||||
return False
|
||||
|
||||
if self.shapeAt(x, y) != Tetrominoe.NoShape:
|
||||
return False
|
||||
|
||||
self.curPiece = newPiece
|
||||
self.curX = newX
|
||||
self.curY = newY
|
||||
self.update()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def drawSquare(self, painter, x, y, shape):
|
||||
"""draws a square of a shape"""
|
||||
|
||||
colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
|
||||
0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
|
||||
|
||||
color = QColor(colorTable[shape])
|
||||
painter.fillRect(x + 1, y + 1, self.squareWidth() - 2,
|
||||
self.squareHeight() - 2, color)
|
||||
|
||||
painter.setPen(color.lighter())
|
||||
painter.drawLine(x, y + self.squareHeight() - 1, x, y)
|
||||
painter.drawLine(x, y, x + self.squareWidth() - 1, y)
|
||||
|
||||
painter.setPen(color.darker())
|
||||
painter.drawLine(x + 1, y + self.squareHeight() - 1,
|
||||
x + self.squareWidth() - 1, y + self.squareHeight() - 1)
|
||||
painter.drawLine(x + self.squareWidth() - 1,
|
||||
y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)
|
||||
|
||||
|
||||
class Tetrominoe:
|
||||
|
||||
NoShape = 0
|
||||
ZShape = 1
|
||||
SShape = 2
|
||||
LineShape = 3
|
||||
TShape = 4
|
||||
SquareShape = 5
|
||||
LShape = 6
|
||||
MirroredLShape = 7
|
||||
|
||||
|
||||
class Shape:
|
||||
|
||||
coordsTable = (
|
||||
((0, 0), (0, 0), (0, 0), (0, 0)),
|
||||
((0, -1), (0, 0), (-1, 0), (-1, 1)),
|
||||
((0, -1), (0, 0), (1, 0), (1, 1)),
|
||||
((0, -1), (0, 0), (0, 1), (0, 2)),
|
||||
((-1, 0), (0, 0), (1, 0), (0, 1)),
|
||||
((0, 0), (1, 0), (0, 1), (1, 1)),
|
||||
((-1, -1), (0, -1), (0, 0), (0, 1)),
|
||||
((1, -1), (0, -1), (0, 0), (0, 1))
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.coords = [[0, 0] for i in range(4)]
|
||||
self.pieceShape = Tetrominoe.NoShape
|
||||
|
||||
self.setShape(Tetrominoe.NoShape)
|
||||
|
||||
|
||||
def shape(self):
|
||||
"""returns shape"""
|
||||
|
||||
return self.pieceShape
|
||||
|
||||
|
||||
def setShape(self, shape):
|
||||
"""sets a shape"""
|
||||
|
||||
table = Shape.coordsTable[shape]
|
||||
|
||||
for i in range(4):
|
||||
for j in range(2):
|
||||
self.coords[i][j] = table[i][j]
|
||||
|
||||
self.pieceShape = shape
|
||||
|
||||
|
||||
def setRandomShape(self):
|
||||
"""chooses a random shape"""
|
||||
|
||||
self.setShape(random.randint(1, 7))
|
||||
|
||||
|
||||
def x(self, index):
|
||||
"""returns x coordinate"""
|
||||
|
||||
return self.coords[index][0]
|
||||
|
||||
|
||||
def y(self, index):
|
||||
"""returns y coordinate"""
|
||||
|
||||
return self.coords[index][1]
|
||||
|
||||
|
||||
def setX(self, index, x):
|
||||
"""sets x coordinate"""
|
||||
|
||||
self.coords[index][0] = x
|
||||
|
||||
|
||||
def setY(self, index, y):
|
||||
"""sets y coordinate"""
|
||||
|
||||
self.coords[index][1] = y
|
||||
|
||||
|
||||
def minX(self):
|
||||
"""returns min x value"""
|
||||
|
||||
m = self.coords[0][0]
|
||||
for i in range(4):
|
||||
m = min(m, self.coords[i][0])
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def maxX(self):
|
||||
"""returns max x value"""
|
||||
|
||||
m = self.coords[0][0]
|
||||
for i in range(4):
|
||||
m = max(m, self.coords[i][0])
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def minY(self):
|
||||
"""returns min y value"""
|
||||
|
||||
m = self.coords[0][1]
|
||||
for i in range(4):
|
||||
m = min(m, self.coords[i][1])
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def maxY(self):
|
||||
"""returns max y value"""
|
||||
|
||||
m = self.coords[0][1]
|
||||
for i in range(4):
|
||||
m = max(m, self.coords[i][1])
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def rotateLeft(self):
|
||||
"""rotates shape to the left"""
|
||||
|
||||
if self.pieceShape == Tetrominoe.SquareShape:
|
||||
return self
|
||||
|
||||
result = Shape()
|
||||
result.pieceShape = self.pieceShape
|
||||
|
||||
for i in range(4):
|
||||
result.setX(i, self.y(i))
|
||||
result.setY(i, -self.x(i))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def rotateRight(self):
|
||||
"""rotates shape to the right"""
|
||||
|
||||
if self.pieceShape == Tetrominoe.SquareShape:
|
||||
return self
|
||||
|
||||
result = Shape()
|
||||
result.pieceShape = self.pieceShape
|
||||
|
||||
for i in range(4):
|
||||
result.setX(i, -self.y(i))
|
||||
result.setY(i, self.x(i))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication([])
|
||||
tetris = Tetris()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
The game is simplified a bit so that it is easier to understand. The game starts immediately after it is launched. We can pause the game by pressing the p key. The `Space` key will drop the tetris piece instantly to the bottom. The game goes at constant speed, no acceleration is implemented. The score is the number of lines that we have removed.
|
||||
|
||||
```python
|
||||
self.tboard = Board(self)
|
||||
self.setCentralWidget(self.tboard)
|
||||
```
|
||||
An instance of the Board class is created and set to be the central widget of the application.
|
||||
|
||||
```python
|
||||
self.statusbar = self.statusBar()
|
||||
self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
|
||||
```
|
||||
We create a statusbar where we will display messages. We will display three possible messages: the number of lines already removed, the paused message, or the game over message. The msg2Statusbar is a custom signal that is implemented in the Board class. The showMessage is a built-in method that displays a message on a statusbar.
|
||||
|
||||
``` python
|
||||
self.tboard.start()
|
||||
```
|
||||
This line initiates the game.
|
||||
|
||||
```python
|
||||
class Board(QFrame):
|
||||
|
||||
msg2Statusbar = pyqtSignal(str)
|
||||
...
|
||||
```
|
||||
A custom signal is created with `pyqtSignal`. The `msg2Statusbar` is a signal that is emitted when we want to write a message or the score to the statusbar.
|
||||
|
||||
``` python
|
||||
BoardWidth = 10
|
||||
BoardHeight = 22
|
||||
Speed = 300
|
||||
```
|
||||
These are `Board`'s class variables. The `BoardWidth` and the `BoardHeight` define the size of the board in blocks. The `Speed` defines the speed of the game. Each 300 ms a new game cycle will start.
|
||||
|
||||
``` python
|
||||
...
|
||||
self.curX = 0
|
||||
self.curY = 0
|
||||
self.numLinesRemoved = 0
|
||||
self.board = []
|
||||
...
|
||||
```
|
||||
In the `initBoard` method we initialize some important variables. The `self.board` variable is a list of numbers from 0 to 7. It represents the position of various shapes and remains of the shapes on the board.
|
||||
|
||||
``` python
|
||||
def shapeAt(self, x, y):
|
||||
"""determines shape at the board position"""
|
||||
|
||||
return self.board[(y * Board.BoardWidth) + x]
|
||||
```
|
||||
The `shapeAt` method determines the type of a shape at a given block.
|
||||
|
||||
```python
|
||||
def squareWidth(self):
|
||||
"""returns the width of one square"""
|
||||
|
||||
return self.contentsRect().width() // Board.BoardWidth
|
||||
```
|
||||
The board can be dynamically resized. As a consequence, the size of a block may change. The `squareWidth` calculates the width of the single square in pixels and returns it. The `Board.BoardWidth` is the size of the board in blocks.
|
||||
|
||||
```python
|
||||
def pause(self):
|
||||
"""pauses game"""
|
||||
|
||||
if not self.isStarted:
|
||||
return
|
||||
|
||||
self.isPaused = not self.isPaused
|
||||
|
||||
if self.isPaused:
|
||||
self.timer.stop()
|
||||
self.msg2Statusbar.emit("paused")
|
||||
|
||||
else:
|
||||
self.timer.start(Board.Speed, self)
|
||||
self.msg2Statusbar.emit(str(self.numLinesRemoved))
|
||||
|
||||
self.update()
|
||||
```
|
||||
The `pause` method pauses the game. It stops the timer and displays a message on the statusbar.
|
||||
|
||||
```python
|
||||
def paintEvent(self, event):
|
||||
"""paints all shapes of the game"""
|
||||
|
||||
painter = QPainter(self)
|
||||
rect = self.contentsRect()
|
||||
...
|
||||
```
|
||||
The painting happens in the `paintEvent` method. The `QPainter` is responsible for all low-level painting in PyQt6.
|
||||
|
||||
```python
|
||||
for i in range(Board.BoardHeight):
|
||||
for j in range(Board.BoardWidth):
|
||||
shape = self.shapeAt(j, Board.BoardHeight - i - 1)
|
||||
|
||||
if shape != Tetrominoe.NoShape:
|
||||
self.drawSquare(painter,
|
||||
rect.left() + j * self.squareWidth(),
|
||||
boardTop + i * self.squareHeight(), shape)
|
||||
```
|
||||
The painting of the game is divided into two steps. In the first step, we draw all the shapes, or remains of the shapes that have been dropped to the bottom of the board. All the squares are remembered in the `self.board` list variable. The variable is accessed using the `shapeAt` method.
|
||||
|
||||
```python
|
||||
if self.curPiece.shape() != Tetrominoe.NoShape:
|
||||
|
||||
for i in range(4):
|
||||
|
||||
x = self.curX + self.curPiece.x(i)
|
||||
y = self.curY - self.curPiece.y(i)
|
||||
self.drawSquare(painter, rect.left() + x * self.squareWidth(),
|
||||
boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
|
||||
self.curPiece.shape())
|
||||
```
|
||||
The next step is the drawing of the actual piece that is falling down.
|
||||
|
||||
```python
|
||||
elif key == Qt.Key.Key_Right.value:
|
||||
self.tryMove(self.curPiece, self.curX + 1, self.curY)
|
||||
```
|
||||
In the `keyPressEvent` method we check for pressed keys. If we press the right arrow key, we try to move the piece to the right. We say try because the piece might not be able to move.
|
||||
|
||||
```python
|
||||
elif key == Qt.Key.Key_Up.value:
|
||||
self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
|
||||
```
|
||||
The `Up` arrow key will rotate the falling piece to the left.
|
||||
|
||||
```python
|
||||
elif key == Qt.Key.Key_Space.value:
|
||||
self.dropDown()
|
||||
```
|
||||
The `Space` key will drop the falling piece instantly to the bottom.
|
||||
|
||||
```python
|
||||
elif key == Qt.Key.Key_D.value:
|
||||
self.oneLineDown()
|
||||
```
|
||||
Pressing the d key, the piece will go one block down. It can be used to accellerate the falling of a piece a bit.
|
||||
|
||||
```python
|
||||
def timerEvent(self, event):
|
||||
"""handles timer event"""
|
||||
|
||||
if event.timerId() == self.timer.timerId():
|
||||
|
||||
if self.isWaitingAfterLine:
|
||||
self.isWaitingAfterLine = False
|
||||
self.newPiece()
|
||||
else:
|
||||
self.oneLineDown()
|
||||
|
||||
else:
|
||||
super(Board, self).timerEvent(event)
|
||||
```
|
||||
In the timer event, we either create a new piece after the previous one was dropped to the bottom or we move a falling piece one line down.
|
||||
|
||||
```python
|
||||
def clearBoard(self):
|
||||
"""clears shapes from the board"""
|
||||
|
||||
for i in range(Board.BoardHeight * Board.BoardWidth):
|
||||
self.board.append(Tetrominoe.NoShape)
|
||||
```
|
||||
The `clearBoard` method clears the board by setting `Tetrominoe.NoShape` at each block of the board.
|
||||
|
||||
```python
|
||||
def removeFullLines(self):
|
||||
"""removes all full lines from the board"""
|
||||
|
||||
numFullLines = 0
|
||||
rowsToRemove = []
|
||||
|
||||
for i in range(Board.BoardHeight):
|
||||
|
||||
n = 0
|
||||
for j in range(Board.BoardWidth):
|
||||
if not self.shapeAt(j, i) == Tetrominoe.NoShape:
|
||||
n = n + 1
|
||||
|
||||
if n == 10:
|
||||
rowsToRemove.append(i)
|
||||
|
||||
rowsToRemove.reverse()
|
||||
|
||||
|
||||
for m in rowsToRemove:
|
||||
|
||||
for k in range(m, Board.BoardHeight):
|
||||
for l in range(Board.BoardWidth):
|
||||
self.setShapeAt(l, k, self.shapeAt(l, k + 1))
|
||||
|
||||
numFullLines = numFullLines + len(rowsToRemove)
|
||||
...
|
||||
```
|
||||
If the piece hits the bottom, we call the `removeFullLines` method. We find out all full lines and remove them. We do it by moving all lines above the current full line to be removed one line down. Notice that we reverse the order of the lines to be removed. Otherwise, it would not work correctly. In our case we use a `naive gravity`. This means that the pieces may be floating above empty gaps.
|
||||
|
||||
```python
|
||||
def newPiece(self):
|
||||
"""creates a new shape"""
|
||||
|
||||
self.curPiece = Shape()
|
||||
self.curPiece.setRandomShape()
|
||||
self.curX = Board.BoardWidth // 2 + 1
|
||||
self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
|
||||
|
||||
if not self.tryMove(self.curPiece, self.curX, self.curY):
|
||||
|
||||
self.curPiece.setShape(Tetrominoe.NoShape)
|
||||
self.timer.stop()
|
||||
self.isStarted = False
|
||||
self.msg2Statusbar.emit("Game over")
|
||||
```
|
||||
The `newPiece` method creates randomly a new tetris piece. If the piece cannot go into its initial position, the game is over.
|
||||
|
||||
``` python
|
||||
def tryMove(self, newPiece, newX, newY):
|
||||
"""tries to move a shape"""
|
||||
|
||||
for i in range(4):
|
||||
|
||||
x = newX + newPiece.x(i)
|
||||
y = newY - newPiece.y(i)
|
||||
|
||||
if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
|
||||
return False
|
||||
|
||||
if self.shapeAt(x, y) != Tetrominoe.NoShape:
|
||||
return False
|
||||
|
||||
self.curPiece = newPiece
|
||||
self.curX = newX
|
||||
self.curY = newY
|
||||
self.update()
|
||||
|
||||
return True
|
||||
```
|
||||
In the `tryMove` method we try to move our shapes. If the shape is at the edge of the board or is adjacent to some other piece, we return `False`. Otherwise we place the current falling piece to a new position.
|
||||
|
||||
``` python
|
||||
class Tetrominoe:
|
||||
|
||||
NoShape = 0
|
||||
ZShape = 1
|
||||
SShape = 2
|
||||
LineShape = 3
|
||||
TShape = 4
|
||||
SquareShape = 5
|
||||
LShape = 6
|
||||
MirroredLShape = 7
|
||||
```
|
||||
The `Tetrominoe` class holds names of all possible shapes. We have also a `NoShape` for an empty space.
|
||||
|
||||
The Shape class saves information about a tetris piece.
|
||||
|
||||
``` python
|
||||
class Shape(object):
|
||||
|
||||
coordsTable = (
|
||||
((0, 0), (0, 0), (0, 0), (0, 0)),
|
||||
((0, -1), (0, 0), (-1, 0), (-1, 1)),
|
||||
...
|
||||
)
|
||||
...
|
||||
```
|
||||
The `coordsTable` tuple holds all possible coordinate values of our tetris pieces. This is a template from which all pieces take their coordinate values.
|
||||
|
||||
```python
|
||||
self.coords = [[0,0] for i in range(4)]
|
||||
```
|
||||
Upon creation we create an empty coordinates list. The list will save the coordinates of the tetris piece.
|
||||
|
||||
![Coordinates](./images/coordinates.png)
|
||||
|
||||
Figure: Coordinates
|
||||
|
||||
The above image will help understand the coordinate values a bit more. For example, the tuples (0, -1), (0, 0), (-1, 0), (-1, -1) represent a Z-shape. The diagram illustrates the shape.
|
||||
|
||||
``` python
|
||||
def rotateLeft(self):
|
||||
"""rotates shape to the left"""
|
||||
|
||||
if self.pieceShape == Tetrominoe.SquareShape:
|
||||
return self
|
||||
|
||||
result = Shape()
|
||||
result.pieceShape = self.pieceShape
|
||||
|
||||
for i in range(4):
|
||||
|
||||
result.setX(i, self.y(i))
|
||||
result.setY(i, -self.x(i))
|
||||
|
||||
return result
|
||||
```
|
||||
The `rotateLeft` method rotates a piece to the left. The square does not have to be rotated. That is why we simply return the reference to the current object. A new piece is created and its coordinates are set to the ones of the rotated piece.
|
||||
|
||||
![Tetris](./images/tetris.png)
|
||||
|
||||
Figure: Tetris
|
||||
|
||||
This was Tetris game in PyQt6.
|
528
original/pyqt6/widgets.md
Normal file
@ -0,0 +1,528 @@
|
||||
# PyQt6 widgets
|
||||
*last modified May 3, 2021*
|
||||
|
||||
Widgets are basic building blocks of an application. PyQt6 has a wide range of various widgets, including buttons, check boxes, sliders, or list boxes. In this section of the tutorial, we describe several useful widgets: a QCheckBox, a QPushButton in tooggle mode, a QSlider, a QProgressBar, and a QCalendarWidget.
|
||||
|
||||
## PyQt6 QCheckBox
|
||||
`QCheckBox` is a widget that has two states: on and off. It is a box with a label. Checkboxes are typically used to represent features in an application that can be enabled or disabled.
|
||||
|
||||
``` python
|
||||
# file: check_box.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, a QCheckBox widget
|
||||
is used to toggle the title of a window.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import QWidget, QCheckBox, QApplication
|
||||
from PyQt6.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, 350, 250)
|
||||
self.setWindowTitle('QCheckBox')
|
||||
self.show()
|
||||
|
||||
|
||||
def changeTitle(self, state):
|
||||
|
||||
if state == Qt.CheckState.Checked.value:
|
||||
self.setWindowTitle('QCheckBox')
|
||||
else:
|
||||
self.setWindowTitle(' ')
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
We create a checkbox that toggles the window title.
|
||||
``` python
|
||||
cb = QCheckBox('Show title', self)
|
||||
```
|
||||
This is a `QCheckBox` constructor.
|
||||
|
||||
``` python
|
||||
cb.toggle()
|
||||
```
|
||||
We have set the window title, so we also check the checkbox.
|
||||
``` python
|
||||
cb.stateChanged.connect(self.changeTitle)
|
||||
```
|
||||
We connect the user defined `changeTitle` method to the stateChanged signal. The `changeTitle` method toggles the window title.
|
||||
``` python
|
||||
if state == Qt.CheckState.Checked.value:
|
||||
self.setWindowTitle('QCheckBox')
|
||||
else:
|
||||
self.setWindowTitle(' ')
|
||||
```
|
||||
The state of the widget is given to the `changeTitle` method in the state variable. If the widget is checked, we set a title of the window. Otherwise, we set an empty string to the titlebar.
|
||||
|
||||
![QCheckBox](./images/qcheckbox.png)
|
||||
|
||||
Figure: QCheckBox
|
||||
|
||||
## Toggle button
|
||||
A toggle button is a QPushButton in a special mode. It is a button that has two states: pressed and not pressed. We toggle between these two states by clicking on it.
|
||||
|
||||
``` python
|
||||
# file: toggle_button.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we create three toggle buttons.
|
||||
They control the background color of a QFrame.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QPushButton,
|
||||
QFrame, QApplication)
|
||||
from PyQt6.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, 300, 250)
|
||||
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())
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we create three toggle buttons and a `QWidget`. We set the background colour of the `QWidget` to black. The toggle buttons toggle the red, green, and blue parts of the colour value. The background colour depends on which toggle buttons is pressed.
|
||||
|
||||
``` python
|
||||
self.col = QColor(0, 0, 0)
|
||||
```
|
||||
This is the initial, black colour value.
|
||||
``` python
|
||||
redb = QPushButton('Red', self)
|
||||
redb.setCheckable(True)
|
||||
redb.move(10, 10)
|
||||
```
|
||||
To create a toggle button, we create a QPushButton and make it checkable by calling the `setCheckable` method.
|
||||
``` python
|
||||
redb.clicked[bool].connect(self.setColor)
|
||||
```
|
||||
We connect a clicked signal to our user defined method. We use the clicked signal that operates with a Boolean value.
|
||||
``` python
|
||||
source = self.sender()
|
||||
```
|
||||
We get the button which was toggled.
|
||||
``` python
|
||||
if source.text() == "Red":
|
||||
self.col.setRed(val)
|
||||
```
|
||||
In case it is a red button, we update the red part of the colour accordingly.
|
||||
``` python
|
||||
self.square.setStyleSheet("QFrame { background-color: %s }" %
|
||||
self.col.name())
|
||||
```
|
||||
We use style sheets to change the background colour. The stylesheet is updated with `setStyleSheet` method.
|
||||
|
||||
![Toggle button](./images/togglebutton.png)
|
||||
|
||||
Figure: Toggle button
|
||||
|
||||
## PyQt6 QSlider
|
||||
A QSlider is a widget that has a simple handle. This handle can be pulled back and forth. This way we are choosing a value for a specific task. Sometimes using a slider is more natural than entering a number or using a spin box.
|
||||
|
||||
In our example we show one slider and one label. The label displays an image. The slider controls the label.
|
||||
|
||||
``` python
|
||||
# file: slider.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example shows a QSlider widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QSlider,
|
||||
QLabel, QApplication)
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QPixmap
|
||||
import sys
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
sld = QSlider(Qt.Orientations.Horizontal, self)
|
||||
sld.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||
sld.setGeometry(30, 40, 200, 30)
|
||||
sld.valueChanged[int].connect(self.changeValue)
|
||||
|
||||
self.label = QLabel(self)
|
||||
self.label.setPixmap(QPixmap('mute.png'))
|
||||
self.label.setGeometry(250, 40, 80, 30)
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('QSlider')
|
||||
self.show()
|
||||
|
||||
|
||||
def changeValue(self, value):
|
||||
|
||||
if value == 0:
|
||||
|
||||
self.label.setPixmap(QPixmap('mute.png'))
|
||||
elif 0 < value <= 30:
|
||||
|
||||
self.label.setPixmap(QPixmap('min.png'))
|
||||
elif 30 < value < 80:
|
||||
|
||||
self.label.setPixmap(QPixmap('med.png'))
|
||||
else:
|
||||
|
||||
self.label.setPixmap(QPixmap('max.png'))
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example we simulate a volume control. By dragging the handle of a slider, we change an image on the label.
|
||||
|
||||
``` python
|
||||
sld = QSlider(Qt.Orientations.Horizontal, self)
|
||||
```
|
||||
|
||||
Here we create a horizontal QSlider.
|
||||
``` python
|
||||
self.label = QLabel(self)
|
||||
self.label.setPixmap(QPixmap('mute.png'))
|
||||
```
|
||||
We create a QLabel widget and set an initial mute image to it.
|
||||
``` python
|
||||
sld.valueChanged[int].connect(self.changeValue)
|
||||
```
|
||||
We connect the valueChanged signal to the user defined changeValue method.
|
||||
``` python
|
||||
if value == 0:
|
||||
self.label.setPixmap(QPixmap('mute.png'))
|
||||
...
|
||||
```
|
||||
Based on the value of the slider, we set an image to the label. In the above code, we set the mute.png image to the label if the slider is equal to zero.
|
||||
|
||||
![QSlider widget](./images/qslider.png)
|
||||
|
||||
Figure: QSlider widget
|
||||
|
||||
## PyQt6 QProgressBar
|
||||
|
||||
A progress bar is a widget that is used when we process lengthy tasks. It is animated so that the user knows that the task is progressing. The QProgressBar widget provides a horizontal or a vertical progress bar in PyQt6 toolkit. The programmer can set the minimum and maximum value for the progress bar. The default values are 0 and 99.
|
||||
|
||||
``` python
|
||||
# file: progressbar.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example shows a QProgressBar widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QProgressBar,
|
||||
QPushButton, QApplication)
|
||||
from PyQt6.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')
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example we have a horizontal progress bar and a push button. The push button starts and stops the progress bar.
|
||||
``` python
|
||||
self.pbar = QProgressBar(self)
|
||||
```
|
||||
This is a QProgressBar constructor.
|
||||
``` python
|
||||
self.timer = QBasicTimer()
|
||||
```
|
||||
To activate the progress bar, we use a timer object.
|
||||
``` python
|
||||
self.timer.start(100, self)
|
||||
```
|
||||
To launch a timer event, we call its start method. This method has two parameters: the timeout and the object which receive the events.
|
||||
``` python
|
||||
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)
|
||||
```
|
||||
Each `QObject` and its descendants have a timerEvent event handler. In order to react to timer events, we reimplement the event handler.
|
||||
``` python
|
||||
def doAction(self):
|
||||
|
||||
if self.timer.isActive():
|
||||
self.timer.stop()
|
||||
self.btn.setText('Start')
|
||||
|
||||
else:
|
||||
self.timer.start(100, self)
|
||||
self.btn.setText('Stop')
|
||||
```
|
||||
Inside the doAction method, we start and stop the timer.
|
||||
|
||||
![QProgressBar](./images/qprogressbar.png)
|
||||
|
||||
Figure: QProgressBar
|
||||
|
||||
## PyQt6 QCalendarWidget
|
||||
A QCalendarWidget provides a monthly based calendar widget. It allows a user to select a date in a simple and intuitive way.
|
||||
|
||||
``` python
|
||||
# file: calendar.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example shows a QCalendarWidget widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QCalendarWidget,
|
||||
QLabel, QApplication, QVBoxLayout)
|
||||
from PyQt6.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())
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
The example has a calendar widget and a label widget. The currently selected date is displayed in the label widget.
|
||||
``` python
|
||||
cal = QCalendarWidget(self)
|
||||
```
|
||||
The QCalendarWidget is created.
|
||||
``` python
|
||||
cal.clicked[QDate].connect(self.showDate)
|
||||
```
|
||||
If we select a date from the widget, a clicked[QDate] signal is emitted. We connect this signal to the user defined showDate method.
|
||||
``` python
|
||||
def showDate(self, date):
|
||||
|
||||
self.lbl.setText(date.toString())
|
||||
```
|
||||
We retrieve the selected date by calling the selectedDate method. Then we transform the date object into string and set it to the label widget.
|
||||
|
||||
In this part of the PyQt6 tutorial, we have covered the following widgets: QCheckBox, `QPushButton` in tooggle mode, `QSlider`, `QProgressBar`, and `QCalendarWidget`.
|
346
original/pyqt6/widgets2.md
Normal file
@ -0,0 +1,346 @@
|
||||
# PyQt6 widgets II
|
||||
*last modified May 5, 2021*
|
||||
|
||||
In this chapter we continue introducing PyQt6 widgets. We cover `QPixmap`, `QLineEdit`, `QSplitter`, and `QComboBox`.
|
||||
|
||||
## PyQt6 QPixmap
|
||||
A QPixmap is one of the widgets used to work with images. It is optimized for showing images on screen. In our code example, we will use the QPixmap to display an image on the window.
|
||||
|
||||
``` python
|
||||
# file: pixmap.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we display an image
|
||||
on the window.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QHBoxLayout,
|
||||
QLabel, QApplication)
|
||||
from PyQt6.QtGui import QPixmap
|
||||
import sys
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
hbox = QHBoxLayout(self)
|
||||
pixmap = QPixmap('sid.jpg')
|
||||
|
||||
lbl = QLabel(self)
|
||||
lbl.setPixmap(pixmap)
|
||||
|
||||
hbox.addWidget(lbl)
|
||||
self.setLayout(hbox)
|
||||
|
||||
self.move(300, 200)
|
||||
self.setWindowTitle('Sid')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we display an image on the window.
|
||||
``` python
|
||||
pixmap = QPixmap('sid.jpg')
|
||||
```
|
||||
We create a QPixmap object. It takes the name of the file as a parameter.
|
||||
``` python
|
||||
lbl = QLabel(self)
|
||||
lbl.setPixmap(pixmap)
|
||||
```
|
||||
We put the pixmap into the QLabel widget.
|
||||
|
||||
## PyQt6 QLineEdit
|
||||
QLineEdit is a widget that allows to enter and edit a single line of plain text. There are undo and redo, cut and paste, and drag & drop functions available for the widget.
|
||||
|
||||
``` python
|
||||
# file: line_edit.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example shows text which
|
||||
is entered in a QLineEdit
|
||||
in a QLabel widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.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, 350, 250)
|
||||
self.setWindowTitle('QLineEdit')
|
||||
self.show()
|
||||
|
||||
|
||||
def onChanged(self, text):
|
||||
|
||||
self.lbl.setText(text)
|
||||
self.lbl.adjustSize()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
This example shows a line edit widget and a label. The text that we key in the line edit is displayed immediately in the label widget.
|
||||
``` python
|
||||
qle = QLineEdit(self)
|
||||
```
|
||||
The QLineEdit widget is created.
|
||||
``` python
|
||||
qle.textChanged[str].connect(self.onChanged)
|
||||
```
|
||||
If the text in the line edit widget changes, we call the onChanged method.
|
||||
``` python
|
||||
def onChanged(self, text):
|
||||
|
||||
self.lbl.setText(text)
|
||||
self.lbl.adjustSize()
|
||||
```
|
||||
Inside the onChanged method, we set the typed text to the label widget. We call the adjustSize method to adjust the size of the label to the length of the text.
|
||||
|
||||
![QLineEdit](./images/qlineedit.png)
|
||||
|
||||
Figure: QLineEdit
|
||||
|
||||
## PyQt6 QSplitter
|
||||
QSplitter lets the user control the size of child widgets by dragging the boundary between its children. In our example, we show three QFrame widgets organized with two splitters.
|
||||
``` python
|
||||
# file: splitter.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example shows
|
||||
how to use QSplitter widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import (QWidget, QHBoxLayout, QFrame,
|
||||
QSplitter, QApplication)
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
hbox = QHBoxLayout(self)
|
||||
|
||||
topleft = QFrame(self)
|
||||
topleft.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
|
||||
topright = QFrame(self)
|
||||
topright.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
|
||||
bottom = QFrame(self)
|
||||
bottom.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
|
||||
splitter1 = QSplitter(Qt.Orientations.Horizontal)
|
||||
splitter1.addWidget(topleft)
|
||||
splitter1.addWidget(topright)
|
||||
|
||||
splitter2 = QSplitter(Qt.Orientations.Vertical)
|
||||
splitter2.addWidget(splitter1)
|
||||
splitter2.addWidget(bottom)
|
||||
|
||||
hbox.addWidget(splitter2)
|
||||
self.setLayout(hbox)
|
||||
|
||||
self.setGeometry(300, 300, 450, 400)
|
||||
self.setWindowTitle('QSplitter')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
In our example, we have three frame widgets and two splitters. Note that under some themes, the splitters may not be visible very well.
|
||||
``` python
|
||||
topleft = QFrame(self)
|
||||
topleft.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
```
|
||||
We use a styled frame in order to see the boundaries between the QFrame widgets.
|
||||
``` python
|
||||
splitter1 = QSplitter(Qt.Orientations.Horizontal)
|
||||
splitter1.addWidget(topleft)
|
||||
splitter1.addWidget(topright)
|
||||
```
|
||||
We create a QSplitter widget and add two frames into it.
|
||||
``` python
|
||||
splitter2 = QSplitter(Qt.Orientations.Vertical)
|
||||
splitter2.addWidget(splitter1)
|
||||
```
|
||||
We can also add a splitter to another splitter widget.
|
||||
|
||||
![QSplitter widget](./images/qsplitter.png)
|
||||
|
||||
Figure: QSplitter widget
|
||||
|
||||
## PyQt6 QComboBox
|
||||
`QComboBox` is a widget that allows a user to choose from a list of options.
|
||||
|
||||
``` python
|
||||
# file: combobox.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example shows how to use
|
||||
a QComboBox widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QLabel,
|
||||
QComboBox, QApplication)
|
||||
|
||||
|
||||
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.textActivated[str].connect(self.onActivated)
|
||||
|
||||
self.setGeometry(300, 300, 450, 400)
|
||||
self.setWindowTitle('QComboBox')
|
||||
self.show()
|
||||
|
||||
|
||||
def onActivated(self, text):
|
||||
|
||||
self.lbl.setText(text)
|
||||
self.lbl.adjustSize()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
The example shows a QComboBox and a QLabel. The combo box has a list of five options. These are the names of Linux distros. The label widget displays the selected option from the combo box.
|
||||
|
||||
``` python
|
||||
combo = QComboBox(self)
|
||||
|
||||
combo.addItem('Ubuntu')
|
||||
combo.addItem('Mandriva')
|
||||
combo.addItem('Fedora')
|
||||
combo.addItem('Arch')
|
||||
combo.addItem('Gentoo')
|
||||
```
|
||||
We create a QComboBox widget with five options.
|
||||
``` python
|
||||
combo.textActivated[str].connect(self.onActivated)
|
||||
```
|
||||
Upon an item selection, we call the onActivated method.
|
||||
``` python
|
||||
def onActivated(self, text):
|
||||
|
||||
self.lbl.setText(text)
|
||||
self.lbl.adjustSize()
|
||||
```
|
||||
Inside the method, we set the text of the chosen item to the label widget. We adjust the size of the label.
|
||||
|
||||
![QComboBox](./images/qcombobox.png)
|
||||
|
||||
Figure: QComboBox
|
||||
|
||||
In this part of the PyQt6 tutorial, we have covered `QPixmap`, `QLineEdit`, `QSplitter`, and `QComboBox`.
|
@ -1,10 +1,10 @@
|
||||
# 控件\(1\)
|
||||
|
||||
控件就像是应用这座房子的一块块砖。PyQt5有很多的控件,比如按钮,单选框,滑动条,复选框等等。在本章,我们将介绍一些很有用的控件:`QCheckBox`,`ToggleButton`,`QSlider`,`QProgressBar`和`QCalendarWidget`。
|
||||
控件就像是应用这座房子的一块块砖。PyQt5有很多的控件,比如按钮,单选框,滑动条,复选框等等。在本章,我们将介绍一些很有用的控件:`QCheckBox`,`ToggleButton`,`QSlider`,`QProgressBar` 和 `QCalendarWidget`。
|
||||
|
||||
## QCheckBox
|
||||
|
||||
`QCheckBox`组件有俩状态:开和关。通常跟标签一起使用,用在激活和关闭一些选项的场景。
|
||||
`QCheckBox` 组件有俩状态:开和关。通常跟标签一起使用,用在激活和关闭一些选项的场景。
|
||||
|
||||
```python
|
||||
#!/usr/bin/python3
|
||||
@ -97,7 +97,7 @@ def changeTitle(self, state):
|
||||
|
||||
## 切换按钮
|
||||
|
||||
切换按钮就是`QPushButton`的一种特殊模式。 它只有两种状态:按下和未按下。我们再点击的时候切换两种状态,有很多场景会使用到这个功能。
|
||||
切换按钮就是`QPushButton`的一种特殊模式。 它只有两种状态:按下和未按下。我们在点击的时候切换两种状态,有很多场景会使用到这个功能。
|
||||
|
||||
```python
|
||||
#!/usr/bin/python3
|
224
translated/pyqt6/customwidgets.md
Normal file
@ -0,0 +1,224 @@
|
||||
# PyQt6 自定义部件
|
||||
*最后更新于 2021.05.17*
|
||||
|
||||
PyQt6 已经有丰富的部件,但是没有任何工具包能提供开发者开发应用中需要的全部部件。工具包通常只提供最常见的小部件,如按钮、文本小部件或滑块。如果需要满足特定需求的小部件,我们必须自己创建。
|
||||
|
||||
自定义小部件是使用工具包提供的绘图工具创建的。基本上有两种方式:程序员可以修改或增强现有的小部件,或者他可以从头开始创建自定义小部件。
|
||||
|
||||
## PyQt6 烧录部件
|
||||
这个部件可以在 Nero、K3B 或其他的 CD/DVD 烧录软件里看到。
|
||||
|
||||
``` python
|
||||
# file: burning_widget.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we create a custom widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QSlider, QApplication,
|
||||
QHBoxLayout, QVBoxLayout)
|
||||
from PyQt6.QtCore import QObject, Qt, pyqtSignal
|
||||
from PyQt6.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.Weight.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.PenStyle.SolidLine)
|
||||
|
||||
qp.setPen(pen)
|
||||
qp.setBrush(Qt.BrushStyle.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.horizontalAdvance(str(self.num[j]))
|
||||
|
||||
x, y = int(i - fw/2), int(h / 2)
|
||||
qp.drawText(x, y, 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.Orientations.Horizontal, self)
|
||||
sld.setFocusPolicy(Qt.FocusPolicy.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()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
这个示例中,有一个 `QSlider` 和一个自定义小部件——滑块控制自定义小部件。此小部件以图形方式显示介质的总容量和可用的可用空间。自定义小部件的最小值为 1,最大值为 `OVER_CAPACITY`。 如果值达到 `MAX_CAPACITY` ,会变成红色,代表需要烧录的数据大于介质的容量。
|
||||
|
||||
烧录部件位于窗口底部。用 `QHBoxLayout` 和 `QVBoxLayout` 实现。
|
||||
|
||||
```python
|
||||
class BurningWidget(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
```
|
||||
烧录组件基于 `QWidget`。
|
||||
|
||||
```python
|
||||
self.setMinimumSize(1, 30)
|
||||
```
|
||||
设置部件的高,默认的高度有点小。
|
||||
```python
|
||||
font = QFont('Serif', 7, QFont.Weight.Light)
|
||||
qp.setFont(font)
|
||||
```
|
||||
这里使用了较小的字体大小,这样看起来更适合我们的需求。
|
||||
|
||||
```python
|
||||
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))
|
||||
```
|
||||
部件是动态渲染的。窗口越大,部件就越大,反之亦然。所以我们需要动态计算部件的大小必须计算在其上绘制自定义小部件的小部件的大小。参数 `till` 决定了部件的总大小,这个值来自于滑块部件,它是相对整个区域的一个比例。参数 `full` 是红色色块的起点。
|
||||
|
||||
绘图包括三个步骤,先绘制有黄色或红色和黄色的矩形,然后绘制垂直线,将小部件分成几个部分,最后画出表示介质容量的数字。
|
||||
|
||||
``` python
|
||||
metrics = qp.fontMetrics()
|
||||
fw = metrics.horizontalAdvance(str(self.num[j]))
|
||||
|
||||
x, y = int(i - fw/2), int(h / 2)
|
||||
qp.drawText(x, y, str(self.num[j]))
|
||||
```
|
||||
我们使用字体材料来绘制文本,所以必须知道文本的宽度才能使其垂直居中。
|
||||
|
||||
``` python
|
||||
def changeValue(self, value):
|
||||
|
||||
self.c.updateBW.emit(value)
|
||||
self.wid.repaint()
|
||||
```
|
||||
移动滑块时,调用 `changeValue` 方法。在方法内部,触发一个带有参数的自定义 `updateBW` 信号,参数是滑块的当前值,这个值也要用于计算要绘制的 `Burning` 小部件的容量,这样,这个部件就绘制出来了。
|
||||
|
||||
![The burning widget](./images/burning.png)
|
||||
|
||||
图像: 烧录组件
|
||||
|
||||
本章的 PyQt6 教程里,我们创建了一个自定义部件。
|
359
translated/pyqt6/datetime.md
Normal file
@ -0,0 +1,359 @@
|
||||
# PyQt6 日期和时间
|
||||
*最后更新于 2021.04.23*
|
||||
|
||||
## QDate, QTime, QDateTime
|
||||
|
||||
PyQt6 有 `QDate`, `QDateTime`, `QTime` 类处理日期和时间。`QDate` 是用于处理公历中的日期的类。它有获取、比较或操作日期的方法。`QTime` 类用于处理时间。它提供了比较时间、确定时间和其他各种时间操作方法。`QDateTime` 是 `QDate` 和 `QTime` 的组合。
|
||||
|
||||
## PyQt 当前日期和时间
|
||||
|
||||
PyQt6 有 `currentDate`、`currentTime` 和 `currentDateTime` 方法获取当前的日期或时间。
|
||||
|
||||
``` python
|
||||
# file: current_date_time.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate, QTime, QDateTime, Qt
|
||||
|
||||
now = QDate.currentDate()
|
||||
|
||||
print(now.toString(Qt.DateFormat.ISODate))
|
||||
print(now.toString(Qt.DateFormat.RFC2822Date))
|
||||
|
||||
datetime = QDateTime.currentDateTime()
|
||||
|
||||
print(datetime.toString())
|
||||
|
||||
time = QTime.currentTime()
|
||||
print(time.toString(Qt.DateFormat.ISODate))
|
||||
```
|
||||
|
||||
上面的代码打印出了当前日期,当前日期和时间,不同格式的时间。
|
||||
|
||||
``` python
|
||||
now = QDate.currentDate()
|
||||
```
|
||||
|
||||
`currentDate` 方法返回当前的日期。
|
||||
|
||||
``` python
|
||||
print(now.toString(Qt.DateFormat.ISODate))
|
||||
print(now.toString(Qt.DateFormat.RFC2822Date))
|
||||
```
|
||||
给 `toString` 传入不同的参数: `Qt.DateFormat.ISODate` 和 `Qt.DateFormat.RFC2822Date` 获取不同格式的日期。
|
||||
|
||||
```python
|
||||
datetime = QDateTime.currentDateTime()
|
||||
```
|
||||
|
||||
`currentDateTime` 方法返回当前的日期和时间。
|
||||
|
||||
``` python
|
||||
time = QTime.currentTime()
|
||||
```
|
||||
|
||||
`currentTime` 方法返回了当前时间。
|
||||
|
||||
``` sh
|
||||
$ ./current_date_time.py
|
||||
2021-04-23
|
||||
23 Apr 2021
|
||||
Fri Apr 23 11:41:39 2021
|
||||
11:41:39
|
||||
```
|
||||
## PyQt6 UTC 时间
|
||||
我们的星球是一个球体,绕着它自己的轴旋转。地球向东旋转,所以太阳在不同的时间在不同的地点升起。地球大约每24小时自转一次。因此,世界被划分为24个时区。在每个时区,都有不同的当地时间。当地时间通常会被夏时制进一步修改。
|
||||
|
||||
实际上也需要一个标准时间。一个标准时间有助于避免时区和夏令时的混淆。选择UTC(通用协调时间)作为主要的时间标准。UTC时间用于航空、天气预报、飞行计划、空中交通管制许可和地图。与当地时间不同,UTC时间不随季节变化而变化。
|
||||
|
||||
``` python
|
||||
# file: utc_local.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDateTime, Qt
|
||||
|
||||
now = QDateTime.currentDateTime()
|
||||
|
||||
print('Local datetime: ', now.toString(Qt.DateFormat.ISODate))
|
||||
print('Universal datetime: ', now.toUTC().toString(Qt.DateFormat.ISODate))
|
||||
|
||||
print(f'The offset from UTC is: {now.offsetFromUtc()} seconds')
|
||||
本例获取了标准时间和本地时间。
|
||||
|
||||
print('Local datetime: ', now.toString(Qt.DateFormat.ISODate))
|
||||
```
|
||||
`currentDateTime` 方法返回了本地时间的当前时间。我们可以使用 `toLocalTime` 方法把标准时间转换成本地时间。
|
||||
|
||||
``` python
|
||||
print('Universal datetime: ', now.toUTC().toString(Qt.DateFormat.ISODate))
|
||||
```
|
||||
|
||||
我们使用 `toUTC` 方法从时间对象里获取了标准时间。
|
||||
|
||||
``` python
|
||||
print(f'The offset from UTC is: {now.offsetFromUtc()} seconds')
|
||||
```
|
||||
|
||||
`offsetFromUtc` 方法给出了本地时间与标准时间的差,以秒为单位。
|
||||
|
||||
``` sh
|
||||
$ ./utc_local.py
|
||||
Local datetime: 2021-04-23T11:44:15
|
||||
Universal datetime: 2021-04-23T09:44:15Z
|
||||
The offset from UTC is: 7200 seconds
|
||||
```
|
||||
## PyQt6 天数
|
||||
`daysInMonth` 方法返回了指定月份的天数,`daysInYear` 方法返回了指定年份的天数。
|
||||
|
||||
``` python
|
||||
# file: n_of_days.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate
|
||||
|
||||
now = QDate.currentDate()
|
||||
|
||||
d = QDate(1945, 5, 7)
|
||||
|
||||
print(f'Days in month: {d.daysInMonth()}')
|
||||
print(f'Days in year: {d.daysInYear()}')
|
||||
```
|
||||
本例打印了指定年份和月份的天数。
|
||||
``` sh
|
||||
$ ./n_of_days.py
|
||||
Days in month: 31
|
||||
Days in year: 365
|
||||
```
|
||||
|
||||
## PyQt6 天数差
|
||||
`daysTo` 方法返回了一个日期到另外一个日期的差。
|
||||
|
||||
``` python
|
||||
# file: xmas.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate, Qt
|
||||
|
||||
now = QDate.currentDate()
|
||||
y = now.year()
|
||||
|
||||
print(f'today is {now.toString(Qt.DateFormat.ISODate)}')
|
||||
|
||||
xmas1 = QDate(y-1, 12, 25)
|
||||
xmas2 = QDate(y, 12, 25)
|
||||
|
||||
dayspassed = xmas1.daysTo(now)
|
||||
print(f'{dayspassed} days have passed since last XMas')
|
||||
|
||||
nofdays = now.daysTo(xmas2)
|
||||
print(f'There are {nofdays} days until next XMas')
|
||||
```
|
||||
该示例计算出了从上一个圣诞节到下一个圣诞节的天数。
|
||||
|
||||
``` sh
|
||||
$ ./xmas.py
|
||||
today is 2021-04-23
|
||||
119 days have passed since last XMas
|
||||
There are 246 days until next XMas
|
||||
```
|
||||
## PyQt6 时间的计算
|
||||
我们经常需要对天,秒或者年进行加减等计算。
|
||||
``` python
|
||||
# file: arithmetic.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDateTime, Qt
|
||||
|
||||
now = QDateTime.currentDateTime()
|
||||
|
||||
print(f'Today: {now.toString(Qt.DateFormat.ISODate)}')
|
||||
print(f'Adding 12 days: {now.addDays(12).toString(Qt.DateFormat.ISODate)}')
|
||||
print(f'Subtracting 22 days: {now.addDays(-22).toString(Qt.DateFormat.ISODate)}')
|
||||
|
||||
print(f'Adding 50 seconds: {now.addSecs(50).toString(Qt.DateFormat.ISODate)}')
|
||||
print(f'Adding 3 months: {now.addMonths(3).toString(Qt.DateFormat.ISODate)}')
|
||||
print(f'Adding 12 years: {now.addYears(12).toString(Qt.DateFormat.ISODate)}')
|
||||
```
|
||||
该示例展示了对当前日期时间添加或减去天、秒、月或年。
|
||||
|
||||
``` sh
|
||||
$ ./arithmetic.py
|
||||
Today: 2021-04-23T12:06:10
|
||||
Adding 12 days: 2021-05-05T12:06:10
|
||||
Subtracting 22 days: 2021-04-01T12:06:10
|
||||
Adding 50 seconds: 2021-04-23T12:07:00
|
||||
Adding 3 months: 2021-07-23T12:06:10
|
||||
Adding 12 years: 2033-04-23T12:06:10
|
||||
```
|
||||
## PyQt6 daylight saving time
|
||||
夏令时 (DST) 是在夏季调快时间,使晚上变得更长。 初春时调前调一小时,秋季时调后调至标准时间。
|
||||
``` python
|
||||
# file: daylight_saving.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDateTime, QTimeZone, Qt
|
||||
|
||||
now = QDateTime.currentDateTime()
|
||||
|
||||
print(f'Time zone: {now.timeZoneAbbreviation()}')
|
||||
|
||||
if now.isDaylightTime():
|
||||
print('The current date falls into DST time')
|
||||
else:
|
||||
print('The current date does not fall into DST time')
|
||||
```
|
||||
该例判断一个时间是不是夏令时。
|
||||
|
||||
```python
|
||||
print(f'Time zone: {now.timeZoneAbbreviation()}')
|
||||
```
|
||||
|
||||
`timeZoneAbbreviation` 方法返回了时区的缩写。
|
||||
|
||||
``` python
|
||||
if now.isDaylightTime():
|
||||
...
|
||||
```
|
||||
`isDaylightTime` 判断日期是不是夏令时。
|
||||
|
||||
``` sh
|
||||
$ ./daylight_saving.py
|
||||
Time zone: CEST
|
||||
The current date falls into DST time
|
||||
```
|
||||
当前日期属于夏令时时间,中欧城市布拉迪斯拉发在夏季执行。 中欧夏令时 (CEST) 比世界时间早 2 小时。 该时区是夏令时时区,用于欧洲和南极洲。
|
||||
|
||||
## PyQt6 unix 纪元
|
||||
纪元是被选为特定纪元起源的时间瞬间。 例如,在西方基督教国家,时间纪元从耶稣诞生的第 0 天开始。 另一个例子是使用了十二年的法国共和历。 这个时代是共和时代的开始,1792 年 9 月 22 日宣布第一共和国成立,君主制也被废除。
|
||||
|
||||
计算机也有它的时代。 最受欢迎的时代之一是 Unix 时代。 Unix 纪元是 1970 年 1 月 1 日 UTC 时间 00:00:00(或 1970-01-01T00:00:00Z ISO 8601)。 计算机中的日期和时间是根据自该计算机或平台定义的纪元以来经过的秒数或时钟滴答数确定的。
|
||||
|
||||
Unix 时间是自 Unix 纪元以来经过的秒数。
|
||||
|
||||
``` sh
|
||||
$ date +%s
|
||||
1619172620
|
||||
```
|
||||
Unix date 命令可用于获取 Unix 时间。 在这个特殊的时刻,自 Unix 时代以来已经过去了 1619172620 秒。
|
||||
|
||||
``` python
|
||||
# file: unix_time.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDateTime, Qt
|
||||
|
||||
now = QDateTime.currentDateTime()
|
||||
|
||||
unix_time = now.toSecsSinceEpoch()
|
||||
print(unix_time)
|
||||
|
||||
d = QDateTime.fromSecsSinceEpoch(unix_time)
|
||||
print(d.toString(Qt.DateFormat.ISODate))
|
||||
```
|
||||
该例展示了 Unix 时间,并把它转换成了 `QDateTime`。
|
||||
|
||||
``` python
|
||||
now = QDateTime.currentDateTime()
|
||||
```
|
||||
|
||||
首先获取当前的日期和时间。
|
||||
|
||||
```python
|
||||
unix_time = now.toSecsSinceEpoch()
|
||||
```
|
||||
|
||||
`toSecsSinceEpoch` 返回了 Unix 时间。
|
||||
|
||||
```python
|
||||
d = QDateTime.fromSecsSinceEpoch(unix_time)
|
||||
```
|
||||
|
||||
使用 `fromSecsSinceEpoch` 方法把 Unix 时间转换成 `QDateTime`。
|
||||
|
||||
``` sh
|
||||
$ ./unix_time.py
|
||||
1619172679
|
||||
2021-04-23T12:11:19
|
||||
```
|
||||
## PyQt6 Julian day
|
||||
儒略日是指自儒略时期开始以来的连续天数。 它主要由天文学家使用。 它不应与儒略历混淆。 它开始于公元前 4713 年。第 0 天是公元前 4713 年 1 月 1 日中午的那天。
|
||||
|
||||
儒略日数 (JDN) 是自此期间开始以来经过的天数。 任何时刻的儒略日期 (JD) 是前一个中午的儒略日数加上自该时刻起当天的分数。 (Qt 不计算这个分数。)除了天文学,儒略日期经常被军事和大型机程序使用。
|
||||
|
||||
``` python
|
||||
# file: julian_day.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate, Qt
|
||||
|
||||
now = QDate.currentDate()
|
||||
|
||||
print('Gregorian date for today:', now.toString(Qt.DateFormat.ISODate))
|
||||
print('Julian day for today:', now.toJulianDay())
|
||||
```
|
||||
该例中,我们得到了今天在公历中的表达和在儒略日的表达。
|
||||
|
||||
``` python
|
||||
print('Julian day for today:', now.toJulianDay())
|
||||
```
|
||||
|
||||
`toJulianDay()` 返回了儒略日的日期。
|
||||
|
||||
``` sh
|
||||
$ ./julian_day.py
|
||||
Gregorian date for today: 2021-04-23
|
||||
Julian day for today: 2459328
|
||||
```
|
||||
## 历史战役
|
||||
使用儒略日可以进行跨越几个世纪的计算。
|
||||
|
||||
``` python
|
||||
# file: battles.py
|
||||
#!/usr/bin/python
|
||||
|
||||
from PyQt6.QtCore import QDate, Qt
|
||||
|
||||
borodino_battle = QDate(1812, 9, 7)
|
||||
slavkov_battle = QDate(1805, 12, 2)
|
||||
|
||||
now = QDate.currentDate()
|
||||
|
||||
j_today = now.toJulianDay()
|
||||
j_borodino = borodino_battle.toJulianDay()
|
||||
j_slavkov = slavkov_battle.toJulianDay()
|
||||
|
||||
d1 = j_today - j_slavkov
|
||||
d2 = j_today - j_borodino
|
||||
|
||||
print(f'Days since Slavkov battle: {d1}')
|
||||
print(f'Days since Borodino battle: {d2}')
|
||||
```
|
||||
该示例计算自两个历史事件以来经过的天数。
|
||||
|
||||
``` python
|
||||
borodino_battle = QDate(1812, 9, 7)
|
||||
slavkov_battle = QDate(1805, 12, 2)
|
||||
```
|
||||
这是两个拿破仑战斗日期。
|
||||
|
||||
```python
|
||||
j_today = now.toJulianDay()
|
||||
j_borodino = borodino_battle.toJulianDay()
|
||||
j_slavkov = slavkov_battle.toJulianDay()
|
||||
```
|
||||
这是今天以及斯拉夫科夫和博罗季诺战役的儒略日。
|
||||
|
||||
``` python
|
||||
d1 = j_today - j_slavkov
|
||||
d2 = j_today - j_borodino
|
||||
```
|
||||
这是两次大战的天数差。
|
||||
|
||||
``` sh
|
||||
$ ./battles.py
|
||||
Days since Slavkov battle: 78670
|
||||
Days since Borodino battle: 76199
|
||||
```
|
||||
当我们运行这个脚本时,距离斯拉夫科夫战役已经过去了 78670 天,距离博罗季诺战役已经过去了 76199 天。
|
||||
|
||||
在 PyQt6 教程的这一部分中,我们使用了日期和时间。
|
364
translated/pyqt6/dialogs.md
Normal file
@ -0,0 +1,364 @@
|
||||
# PyQt6 的对话框
|
||||
*最后更新于 2021.04.30*
|
||||
|
||||
对话是两个或更多人之间的交谈。在计算机程序中,对话框是用于与应用程序“交谈”的窗口,用于诸如从用户那里获取数据或更改应用程序设置之类的事情。
|
||||
|
||||
## PyQt6 QInputDialog
|
||||
`QInputDialog` 提供了一个简单方便的对话框来从用户那里获取输入。输入值可以是字符串、数字或列表中的项目。
|
||||
|
||||
``` python
|
||||
# file: input_dialog.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we receive data from
|
||||
a QInputDialog dialog.
|
||||
|
||||
Aauthor: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.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, 450, 350)
|
||||
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))
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
这个示例有一个按钮和行内编辑部件,按钮打开输入一个对话框,对话框里有一个文本输入框,用户输入的文本会显示在行内编辑部件里。
|
||||
|
||||
``` python
|
||||
text, ok = QInputDialog.getText(self, 'Input Dialog',
|
||||
'Enter your name:')
|
||||
```
|
||||
这行代码打开了输入对话框,第一个参数是对话框标题,第二个参数是对话框里的提示信息。对话框会返回输入的文本和一个布尔值。如果点击 OK 按钮,这个布尔值是 `true`。
|
||||
|
||||
``` python
|
||||
if ok:
|
||||
self.le.setText(str(text))
|
||||
```
|
||||
使用 `setText()` 从对话框里获取输入的文本。
|
||||
|
||||
![Input dialog](./images/inputdialog.png)
|
||||
|
||||
Figure: Input dialog
|
||||
|
||||
## PyQt6 QColorDialog
|
||||
`QColorDialog` 是可以选择颜色对话框。
|
||||
|
||||
``` python
|
||||
# file: color_dialog.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we select a color value
|
||||
from the QColorDialog and change the background
|
||||
color of a QFrame widget.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QPushButton, QFrame,
|
||||
QColorDialog, QApplication)
|
||||
from PyQt6.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, 200, 200)
|
||||
|
||||
self.setGeometry(300, 300, 450, 350)
|
||||
self.setWindowTitle('Color dialog')
|
||||
self.show()
|
||||
|
||||
|
||||
def showDialog(self):
|
||||
|
||||
col = QColorDialog.getColor()
|
||||
|
||||
if col.isValid():
|
||||
|
||||
self.frm.setStyleSheet("QWidget { background-color: %s }"
|
||||
% col.name())
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
示例里有个按钮和一个 `QFrame`。部件的背景颜色是默认颜色,可以使用 `QColorDialog` 修改部件的背景颜色。
|
||||
``` python
|
||||
col = QColor(0, 0, 0)
|
||||
```
|
||||
This is an initial colour of the QFrame background.
|
||||
``` python
|
||||
col = QColorDialog.getColor()
|
||||
```
|
||||
This line pops up the QColorDialog.
|
||||
``` python
|
||||
if col.isValid():
|
||||
|
||||
self.frm.setStyleSheet("QWidget { background-color: %s }"
|
||||
% col.name())
|
||||
```
|
||||
这里检查了颜色是不是有效的。如果点击取消按钮,没有返回可用的颜色值。如果返回的颜色是有效值,就使用样式表修改背景颜色。
|
||||
|
||||
## PyQt6 QFontDialog
|
||||
`QFontDialog` 是选择字体的对话框。
|
||||
|
||||
``` python
|
||||
# file: font_dialog.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we select a font name
|
||||
and change the font of a label.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.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.Policy.Fixed, QSizePolicy.Policy.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, 450, 350)
|
||||
self.setWindowTitle('Font dialog')
|
||||
self.show()
|
||||
|
||||
|
||||
def showDialog(self):
|
||||
|
||||
font, ok = QFontDialog.getFont()
|
||||
|
||||
if ok:
|
||||
self.lbl.setFont(font)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
本例中,有个有文本的按钮。使用 `QFontDialog` 可以修改按钮文本的字体。
|
||||
``` python
|
||||
font, ok = QFontDialog.getFont()
|
||||
```
|
||||
这里弹出了字体选择对话框。`getFont` 方法返回了选择的字体名称和 `ok` 参数,如果点击 Ok 按钮,`ok` 的值是 `True`,反则是 `False`。
|
||||
``` python
|
||||
if ok:
|
||||
self.label.setFont(font)
|
||||
```
|
||||
如果点击 Ok 按钮,`setFont` 方法会修改文本的字体。
|
||||
|
||||
## PyQt6 QFileDialog
|
||||
|
||||
`QFileDialog` 是选择文件或者文件夹的对话框,可以用作选择或者保存操作。
|
||||
|
||||
``` python
|
||||
# file: file_dialog.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we select a file with a
|
||||
QFileDialog and display its contents
|
||||
in a QTextEdit.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
from PyQt6.QtWidgets import (QMainWindow, QTextEdit,
|
||||
QFileDialog, QApplication)
|
||||
from PyQt6.QtGui import QIcon, QAction
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
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('open.png'), '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, 550, 450)
|
||||
self.setWindowTitle('File dialog')
|
||||
self.show()
|
||||
|
||||
|
||||
def showDialog(self):
|
||||
|
||||
home_dir = str(Path.home())
|
||||
fname = QFileDialog.getOpenFileName(self, 'Open file', home_dir)
|
||||
|
||||
if fname[0]:
|
||||
|
||||
f = open(fname[0], 'r')
|
||||
|
||||
with f:
|
||||
|
||||
data = f.read()
|
||||
self.textEdit.setText(data)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
该示例有一个有居中显示的文本编辑部件的菜单栏和一个状态栏。菜单项显示了用于选择文件的 `QFileDialog`。文件的内容被加载到文本编辑小部件中。
|
||||
``` python
|
||||
class Example(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
```
|
||||
示例是基于 `QMainWindow` 部件,是因为需要把文本编辑部件居中显示。
|
||||
``` python
|
||||
home_dir = str(Path.home())
|
||||
fname = QFileDialog.getOpenFileName(self, 'Open file', home_dir)
|
||||
```
|
||||
这里弹出 `QFileDialog`。`getOpenFileName` 的第一个参数字符串是标题,第二个字符串指定对话框工作目录。我们使用 `path` 模块来确定用户的主目录。默认情况下,文件过滤器设置为所有文件 (*)。
|
||||
|
||||
``` python
|
||||
if fname[0]:
|
||||
|
||||
f = open(fname[0], 'r')
|
||||
|
||||
with f:
|
||||
|
||||
data = f.read()
|
||||
self.textEdit.setText(data)
|
||||
```
|
||||
读取选择文件并把内容放置岛文本编辑部件里。
|
||||
|
||||
本章教程,我们学习了对话框。
|
286
translated/pyqt6/dragdrop.md
Normal file
@ -0,0 +1,286 @@
|
||||
# PyQt6 中的拖拽操作
|
||||
*最后更新于 2021.05.15*
|
||||
|
||||
本章教程,讲的是 PyQt6 中的拖拽操作。
|
||||
|
||||
计算机图形界面中,拖拽操作是点击一个对象不放,把它放在另外一个地方或者另外一个对象上面的操作。一半来说,这会触发很多类型的行为,或者在两个对象上建立多种关系。
|
||||
|
||||
在计算机图形用户界面中,拖放是(或支持)点击虚拟对象并将其拖到不同位置或另一个虚拟对象上的动作。 一般来说,它可以用来调用多种动作,或者在两个抽象对象之间创建各种类型的关联。
|
||||
|
||||
拖放是图形界面的一部分,使用户能够直观地做复杂的事情。
|
||||
|
||||
通常,我们可以拖放两个东西:数据或图形对象。将图像从一个应用程序拖到另一个应用程序,操作的是二进制数据。如果在 Firefox 中拖动一个选项卡并将其移动到另一个位置,操作的是一个图形组件。
|
||||
|
||||
## QDrag
|
||||
`QDrag` 提供对基于 `MIME` 的拖放数据传输的支持。它处理拖放操作的大部分细节。传输的数据包含在 `QMimeData` 对象中
|
||||
|
||||
## Simple drag and drop example in PyQt6
|
||||
In the first example, we have a `QLineEdit` and a `QPushButton`. We drag plain text from the line edit widget and drop it onto the button widget. The button's label will change.
|
||||
示例中,有一个 `QLineEdit` 和 `QPushButton` 部件,我们将纯文本从行编辑小部件拖放到按钮小部件上,以改变按钮的标签。
|
||||
|
||||
``` python
|
||||
# file: simple.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This is a simple drag and
|
||||
drop example.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt6.QtWidgets import (QPushButton, QWidget,
|
||||
QLineEdit, QApplication)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
ex.show()
|
||||
app.exec()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
示例展示了简单的拖拽操作。
|
||||
|
||||
``` python
|
||||
class Button(QPushButton):
|
||||
|
||||
def __init__(self, title, parent):
|
||||
super().__init__(title, parent)
|
||||
|
||||
...
|
||||
```
|
||||
为了完成把文本拖到 `QPushButton` 部件上,我们必须实现某些方法才可以,所以这里创建了一个继承自 `QPushButton` 的 `Button` 类。
|
||||
|
||||
``` python
|
||||
self.setAcceptDrops(True)
|
||||
```
|
||||
使用 `setAcceptDrops` 方法处理部件的释放事件。
|
||||
|
||||
```python
|
||||
def dragEnterEvent(self, e):
|
||||
|
||||
if e.mimeData().hasFormat('text/plain'):
|
||||
e.accept()
|
||||
else:
|
||||
e.ignore()
|
||||
```
|
||||
`dragEnterEvent` 方法,定义了我们接受的数据类型————纯文本。
|
||||
|
||||
```python
|
||||
def dropEvent(self, e):
|
||||
|
||||
self.setText(e.mimeData().text())
|
||||
```
|
||||
`dropEvent` 方法,处理释放事件————修改按钮组件的文本。
|
||||
|
||||
```python
|
||||
edit = QLineEdit('', self)
|
||||
edit.setDragEnabled(True)
|
||||
```
|
||||
`QLineEdit` 部件支持拖放操作,这里只需要调用 `setDragEnabled` 方法激活它。
|
||||
|
||||
![Simple drag and drop](./images/dragdrop.png)
|
||||
|
||||
图片:简单的拖放操作
|
||||
|
||||
## 拖放按钮组件
|
||||
接下来的示例演示了如何拖放按钮组件。
|
||||
|
||||
``` python
|
||||
# file: drag_button.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
在这个程序里,有一个按钮,可以用鼠标左键点击,也可以鼠标右键拖拽
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt6.QtCore import Qt, QMimeData
|
||||
from PyQt6.QtGui import QDrag
|
||||
from PyQt6.QtWidgets import QPushButton, QWidget, QApplication
|
||||
|
||||
|
||||
class Button(QPushButton):
|
||||
|
||||
def __init__(self, title, parent):
|
||||
super().__init__(title, parent)
|
||||
|
||||
|
||||
def mouseMoveEvent(self, e):
|
||||
|
||||
if e.buttons() != Qt.MouseButtons.RightButton:
|
||||
return
|
||||
|
||||
mimeData = QMimeData()
|
||||
|
||||
drag = QDrag(self)
|
||||
drag.setMimeData(mimeData)
|
||||
|
||||
drag.setHotSpot(e.position().toPoint() - self.rect().topLeft())
|
||||
|
||||
dropAction = drag.exec(Qt.DropActions.MoveAction)
|
||||
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
|
||||
super().mousePressEvent(e)
|
||||
|
||||
if e.button() == Qt.MouseButtons.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, 550, 450)
|
||||
|
||||
|
||||
def dragEnterEvent(self, e):
|
||||
|
||||
e.accept()
|
||||
|
||||
|
||||
def dropEvent(self, e):
|
||||
|
||||
position = e.position()
|
||||
self.button.move(position.toPoint())
|
||||
|
||||
e.setDropAction(Qt.DropActions.MoveAction)
|
||||
e.accept()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
ex.show()
|
||||
app.exec()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
本例中,窗口里有个 `QPushButton`,鼠标左键点击它,会在控制台打印 'press'消息,鼠标右键可以点击拖拽它。
|
||||
|
||||
``` python
|
||||
class Button(QPushButton):
|
||||
|
||||
def __init__(self, title, parent):
|
||||
super().__init__(title, parent)
|
||||
```
|
||||
基于 `QPushButton` 创建了一个 `Button` 类,并实现了两个 `QPushButton` 方法:`mouseMoveEvent` 和 `mousePressEvent`。`mouseMoveEvent` 方法是处理拖放操作开始的地方。
|
||||
|
||||
``` python
|
||||
if e.buttons() != Qt.MouseButtons.RightButton:
|
||||
return
|
||||
```
|
||||
定义鼠标右键为触发拖拽操作的按钮,鼠标左键只会触发点击事件。
|
||||
|
||||
``` python
|
||||
drag = QDrag(self)
|
||||
drag.setMimeData(mimeData)
|
||||
|
||||
drag.setHotSpot(e.position().toPoint() - self.rect().topLeft())
|
||||
```
|
||||
创建 `QDrag` 对象,以提供基于 MIME 数据类型的拖拽操作。
|
||||
|
||||
``` python
|
||||
dropAction = drag.exec(Qt.DropActions.MoveAction)
|
||||
```
|
||||
`drag` 对象的 `exec` 方法执行拖拽操作。
|
||||
|
||||
``` python
|
||||
def mousePressEvent(self, e):
|
||||
|
||||
super().mousePressEvent(e)
|
||||
|
||||
if e.button() == Qt.MouseButtons.LeftButton:
|
||||
print('press')
|
||||
```
|
||||
如果鼠标左键点击按钮,会在控制台打印 'press' 消息,注意,这里在父级上也调用了 `mousePressEvent` 方法,不然按钮按下的动作不会展现出来。
|
||||
|
||||
``` python
|
||||
position = e.pos()
|
||||
self.button.move(position)
|
||||
```
|
||||
`dropEvent` 方法处理鼠标释放按钮后的操作————把组件的位置修改为鼠标当前坐标。
|
||||
|
||||
``` python
|
||||
e.setDropAction(Qt.MoveAction)
|
||||
e.accept()
|
||||
```
|
||||
使用 `setDropAction` 指定拖放操作的类型————鼠标移动。
|
||||
|
||||
本章讲述了 PyQt6 中的拖拽操作。
|
410
translated/pyqt6/eventssignals.md
Normal file
@ -0,0 +1,410 @@
|
||||
# PyQt6 事件和信号
|
||||
*最后更新于 2021.04.29*
|
||||
|
||||
这部分教程,我们探索 PyQt6 程序中的事件和信号。
|
||||
|
||||
## PyQt6 中的事件
|
||||
GUI 应用程序是事件驱动的。事件主要由应用程序的用户触发,但也可以通过其他方式生成,例如 Internet 连接、窗口管理器或定时器。当我们调用应用程序的 `exec()` 方法时,应用程序进入主循环。 主循环获取事件并将它们发送到对象。
|
||||
|
||||
在事件模型里,有三个要素:
|
||||
|
||||
- 事件源 event source
|
||||
- 事件对象 event object
|
||||
- 事件目标 event target
|
||||
|
||||
事件源是状态改变的对象,它会产生事件。*event object*(事件)封装了事件源中的状态变化。 *event target* 是要被通知的对象。事件源对象将处理事件的任务委托给事件目标。
|
||||
|
||||
PyQt6 有独特的信号和插槽机制来处理事件,用于对象之间的通信,当特定事件发生时触发。插槽可以是任意可调用的 Python 脚本。当发出连接的信号时,调用插槽脚本
|
||||
> 译注:可以理解成钩子 (hooks) 或回调函数(callback)。
|
||||
|
||||
## PyQt6 信号和插槽
|
||||
下面的示例展示了 PyQt6 的信号和插槽。
|
||||
|
||||
``` python
|
||||
# file: signals_slots.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
本例中,把 QSlider 触发的事件和 QLCDNumber 插槽绑定起来
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.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.Orientations.Horizontal, self)
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addWidget(lcd)
|
||||
vbox.addWidget(sld)
|
||||
|
||||
self.setLayout(vbox)
|
||||
sld.valueChanged.connect(lcd.display)
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('Signal and slot')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
本例中,展示了 `QtGui.QLCDNumber` 和 `QtGui.QSlider`。我们可以通过拖动滑块改变显示器里的数字。
|
||||
``` python
|
||||
sld.valueChanged.connect(lcd.display)
|
||||
```
|
||||
把滑块的 `valueChanged` 事件和 显示器 `display` 插槽绑定到一起。
|
||||
|
||||
*sender* 是触发信号的对象, *receiver* 是接收信号的对象,*slot* 是对信号做出反应的方法。
|
||||
|
||||
![信号和插槽](./images/sigslot.png)
|
||||
|
||||
Figure: 信号和插槽
|
||||
|
||||
## PyQt6 重新实现事件处理器
|
||||
|
||||
PyQt6里,事件的处理器一般都会重新实现。
|
||||
> 译注:所有的事件处理器都有默认的实现,也就是默认事件。默认事件可能有自己的逻辑,比如拖选,点击,有的可能只是一个空函数。空函数都需要重新覆盖原来的实现,达到事件处理的目的。有默认事件处理函数的,也有可能被覆盖实现,比如禁用自带的拖选,或者重写拖选的效果等。
|
||||
|
||||
``` python
|
||||
# file: reimplement_handler.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
本例中,我们重新实现了一个事件处理器。
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('Event handler')
|
||||
self.show()
|
||||
|
||||
|
||||
def keyPressEvent(self, e):
|
||||
|
||||
if e.key() == Qt.Key.Key_Escape.value:
|
||||
self.close()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
本例中,我们重新实现了 `keyPressEvent` 的事件处理器
|
||||
``` python
|
||||
def keyPressEvent(self, e):
|
||||
|
||||
if e.key() == Qt.Key.Key_Escape.value:
|
||||
self.close()
|
||||
```
|
||||
按下 Escape 按钮,应用会退出。
|
||||
|
||||
## PyQt6 事件对象
|
||||
事件对象是一个 Python object,包含了一系列描述这个事件的属性,具体内容要看触发的事件。
|
||||
|
||||
``` python
|
||||
# file: event_object.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
本例中,在标签组件里,展示了鼠标的坐标。
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QWidget, QApplication, QGridLayout, QLabel
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
grid = QGridLayout()
|
||||
|
||||
x = 0
|
||||
y = 0
|
||||
|
||||
self.text = f'x: {x}, y: {y}'
|
||||
|
||||
self.label = QLabel(self.text, self)
|
||||
grid.addWidget(self.label, 0, 0, Qt.Alignment.AlignTop)
|
||||
|
||||
self.setMouseTracking(True)
|
||||
self.setLayout(grid)
|
||||
|
||||
self.setGeometry(300, 300, 450, 300)
|
||||
self.setWindowTitle('Event object')
|
||||
self.show()
|
||||
|
||||
|
||||
def mouseMoveEvent(self, e):
|
||||
|
||||
x = int(e.position().x())
|
||||
y = int(e.position().y())
|
||||
|
||||
text = f'x: {x}, y: {y}'
|
||||
self.label.setText(text)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
本例中,在标签组件里,展示了鼠标的坐标。
|
||||
``` python
|
||||
self.setMouseTracking(True)
|
||||
```
|
||||
鼠标跟踪默认是关闭的,鼠标移动时,组件只能在鼠标按下的时候接收到事件。开启鼠标跟踪,只移动鼠标不按下鼠标按钮,也能接收到事件。
|
||||
``` python
|
||||
def mouseMoveEvent(self, e):
|
||||
|
||||
x = int(e.position().x())
|
||||
y = int(e.position().y())
|
||||
...
|
||||
```
|
||||
`e` 是事件对象,它包含了事件触发时候的数据。通过 `position().x()` 和 `e.position().y()` 方法,能获取到鼠标的坐标值。
|
||||
``` python
|
||||
self.text = f'x: {x}, y: {y}'
|
||||
self.label = QLabel(self.text, self)
|
||||
```
|
||||
坐标值 x 和 y 显示在 `QLabel` 组件里。
|
||||
|
||||
![事件对象](./images/eventobject.png)
|
||||
|
||||
Figure: 事件对象
|
||||
|
||||
# PyQt6 事件触发者
|
||||
|
||||
某些时候,需要知道事件的触发者是谁,PyQt6 有获取事件触发者的方法。
|
||||
|
||||
``` python
|
||||
event_sender.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we determine the event sender
|
||||
object.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.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, 450, 350)
|
||||
self.setWindowTitle('Event sender')
|
||||
self.show()
|
||||
|
||||
|
||||
def buttonClicked(self):
|
||||
|
||||
sender = self.sender()
|
||||
|
||||
msg = f'{sender.text()} was pressed'
|
||||
self.statusBar().showMessage(msg)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
本例中有两个按钮。 `buttonClicked` 调用触发者方法确定了是哪个按钮触发的事件。
|
||||
``` python
|
||||
btn1.clicked.connect(self.buttonClicked)
|
||||
btn2.clicked.connect(self.buttonClicked)
|
||||
```
|
||||
两个按钮绑定了同一个插槽。
|
||||
``` python
|
||||
def buttonClicked(self):
|
||||
|
||||
sender = self.sender()
|
||||
|
||||
msg = f'{sender.text()} was pressed'
|
||||
self.statusBar().showMessage(msg)
|
||||
```
|
||||
在应用的状态栏里,显示了是哪个按钮被按下。
|
||||
|
||||
![事件触发者](./images/eventsender.png)
|
||||
|
||||
Figure: 事件触发者
|
||||
|
||||
## PyQt6 触发信号
|
||||
|
||||
`QObject` 可以主动触发信号。下面的示例显示了如果触发自定义信号。
|
||||
|
||||
``` python
|
||||
# file: custom_signal.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we show how to
|
||||
emit a custom signal.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtCore import pyqtSignal, QObject
|
||||
from PyQt6.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, 450, 350)
|
||||
self.setWindowTitle('Emit signal')
|
||||
self.show()
|
||||
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
|
||||
self.c.closeApp.emit()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
创建了一个叫 `closeApp` 的信号,在鼠标按下的时候触发,和关闭插槽 `QMainWindow` 绑定。
|
||||
|
||||
``` python
|
||||
class Communicate(QObject):
|
||||
|
||||
closeApp = pyqtSignal()
|
||||
```
|
||||
外部 Communicate 类的属性 `pyqtSignal` 创建信号。
|
||||
``` python
|
||||
self.c = Communicate()
|
||||
self.c.closeApp.connect(self.close)
|
||||
```
|
||||
自定义信号 `closeApp` 绑定到 `QMainWindow` 的关闭插槽上。
|
||||
``` python
|
||||
def mousePressEvent(self, event):
|
||||
|
||||
self.c.closeApp.emit()
|
||||
```
|
||||
在窗口上点击鼠标按钮的时候,触发 `closeApp` 信号,程序终止。
|
||||
|
||||
本章教程,我们讲述了信号和插槽。
|
400
translated/pyqt6/firstprograms.md
Normal file
@ -0,0 +1,400 @@
|
||||
# PyQt6 的第一个程序
|
||||
*最后更新于 2021.04.23*
|
||||
|
||||
在 PyQt6 教程的这一部分中,我们将学习一些基本功能。这些示例显示工具提示和图标、关闭窗口、显示消息框以及在桌面上居中显示窗口。
|
||||
## PyQt6 简单示例
|
||||
这是一个显示小窗口的简单示例。但我们可以用这个窗口做很多事情。我们可以调整它的大小、最大化或最小化它。 实现这些功能通常需要写大量的代码,但这些功能很常见,所以这部分功能已经封装好了,我们直接使用即可。PyQt6 是一个高级工具包,如果使用低级的工具包,下面的代码示例很可能需要数百行才可以实现。
|
||||
|
||||
``` python
|
||||
# file: simple.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
In this example, we create a simple
|
||||
window in PyQt6.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QApplication, QWidget
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
w = QWidget()
|
||||
w.resize(250, 200)
|
||||
w.move(300, 300)
|
||||
|
||||
w.setWindowTitle('Simple')
|
||||
w.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
上面的代码会再屏幕上显示一个小窗口。
|
||||
|
||||
``` python
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QApplication, QWidget
|
||||
```
|
||||
这里引入了必要的包,基础小组件位于 `PyQt6.QtWidgets` 模块。
|
||||
|
||||
``` python
|
||||
app = QApplication(sys.argv)
|
||||
```
|
||||
每个 PyQt6 应用程序都必须创建一个应用程序对象。`sys.argv` 参数是来自命令行的参数列表。Python 脚本可以从 shell 运行,这是应用启动的一种方式。
|
||||
|
||||
``` python
|
||||
w = QWidget()
|
||||
```
|
||||
QWidget 小部件是 PyQt6 中所有用户界面对象的基类。我们为 QWidget 提供了默认构造函数。默认构造函数没有父级。没有父级的小部件称为窗口。
|
||||
``` python
|
||||
w.resize(250, 150)
|
||||
```
|
||||
`resize` 方法改变了小部件的尺寸,现在它250像素宽,150像素高。
|
||||
``` python
|
||||
w.move(300, 300)
|
||||
```
|
||||
`move` 方法把小部件移动到屏幕的指定坐标(300, 300)。
|
||||
``` python
|
||||
w.setWindowTitle('Simple')
|
||||
```
|
||||
使用 `setWindowTitle` 给窗口设置标题,标题显示在标题栏。
|
||||
``` python
|
||||
w.show()
|
||||
```
|
||||
`show` 方法是在屏幕上显示小部件的方法。显示一个部件的步骤是首先在内存里创建,然后在屏幕上显示。
|
||||
``` python
|
||||
sys.exit(app.exec())
|
||||
```
|
||||
最后,我们进入应用程序的主循环。事件处理从这里开始。主循环从窗口系统接收事件并将它们分派给应用程序小部件。 如果我们调用 exit 方法或主小部件被销毁,则主循环结束。`sys.exit` 方法确保一个干净的退出。环境将被告知应用程序如何结束。
|
||||
|
||||
![Simple](./images/simple.png)
|
||||
|
||||
Figure: Simple
|
||||
## PyQt6 tooltip
|
||||
我们可以为程序创建一个气泡提示。
|
||||
|
||||
``` python
|
||||
# file: tooltip.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This example shows a tooltip on
|
||||
a window and a button.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import (QWidget, QToolTip,
|
||||
QPushButton, QApplication)
|
||||
from PyQt6.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()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
该示例中,我们给两个小部件创建了一个气泡提示框。
|
||||
|
||||
``` python
|
||||
QToolTip.setFont(QFont('SansSerif', 10))
|
||||
```
|
||||
|
||||
这个静态方法给气泡提示框设置了字体,这里使用了10pt 的 SansSerif 字体。
|
||||
|
||||
``` python
|
||||
self.setToolTip('This is a <b>QWidget</b> widget')
|
||||
```
|
||||
|
||||
调用 `setTooltip` 方法创建气泡提示框,可以使用富文本内容。
|
||||
|
||||
``` python
|
||||
btn = QPushButton('Button', self)
|
||||
btn.setToolTip('This is a <b>QPushButton</b> widget')
|
||||
```
|
||||
|
||||
在气泡提示框上添加了一个按钮部件。
|
||||
|
||||
``` python
|
||||
btn.resize(btn.sizeHint())
|
||||
btn.move(50, 50)
|
||||
```
|
||||
`sizeHint` 方法是给按钮一个系统建议的尺寸,然后使用 `move` 方法移动这个按钮的位置。
|
||||
|
||||
![Tooltips](./images/tooltips.png)
|
||||
Figure: Tooltips
|
||||
## PyQt6 退出按钮
|
||||
关闭窗口的明显方法是单击标题栏上的 x 标记。在下一个示例中,我们将展示如何以编程方式关闭窗口。 我们将简要介绍信号和插槽。
|
||||
|
||||
本例中使用 `QPushButton` 部件完成这个功能。
|
||||
``` python
|
||||
QPushButton(string text, QWidget parent = None)
|
||||
```
|
||||
参数 `text` 是将显示在按钮上的文本。`parent` 是我们放置按钮的小部件。在我们的例子中,它将是一个`QWidget`。应用程序的小部件形成层次结构,在这个层次结构中,大多数小部件都有父级。没有父级的小部件的父级是顶级窗口。
|
||||
|
||||
```python
|
||||
# file: quit_button.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program creates a quit
|
||||
button. When we press the button,
|
||||
the application terminates.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QWidget, QPushButton, QApplication
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
|
||||
def initUI(self):
|
||||
|
||||
qbtn = QPushButton('Quit', self)
|
||||
qbtn.clicked.connect(QApplication.instance().quit)
|
||||
qbtn.resize(qbtn.sizeHint())
|
||||
qbtn.move(50, 50)
|
||||
|
||||
self.setGeometry(300, 300, 350, 250)
|
||||
self.setWindowTitle('Quit button')
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
本例创建了一个退出按钮,点击此按钮退出程序。
|
||||
``` python
|
||||
qbtn = QPushButton('Quit', self)
|
||||
```
|
||||
我们创建了一个按钮,它是 `QPushButton` 类的一个实例。构造函数的第一个参数是按钮的标签。 第二个参数是父级小部件。父小部件是 `Example` 小部件,它继承自 `QWidget`。
|
||||
``` python
|
||||
qbtn.clicked.connect(QApplication.instance().quit)
|
||||
```
|
||||
PyQt6 的事件处理系统是由信号和插槽机制构成的,点击按钮(事件),会发出点击信号。事件处理插槽可以是 Qt 自带的插槽,也可以是普通 Python 函数
|
||||
|
||||
使用 `QApplication.instance` 获取的 `QCoreApplication` 对象包含主事件循环————它处理和分派所有事件。 单击的信号连接到终止应用程序的退出方法。 通信是在两个对象之间完成的:发送者和接收者。 发送者是按钮,接收者是应用程序对象。
|
||||
|
||||
![Quit button](./images/quitbutton.png)
|
||||
|
||||
Figure: Quit button
|
||||
|
||||
## PyQt6 弹窗
|
||||
默认情况下,如果我们点击标题栏上的 x 按钮,`QWidget` 会被关闭。有时我们想修改这个默认行为。 例如,如果在编辑器中打开了一个文件,修改了部分内容,我们需要显示一个消息框来确认退出程序的操作。
|
||||
|
||||
``` python
|
||||
# file: messagebox.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program shows a confirmation
|
||||
message box when we click on the close
|
||||
button of the application window.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QWidget, QMessageBox, QApplication
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
|
||||
self.setGeometry(300, 300, 350, 200)
|
||||
self.setWindowTitle('Message box')
|
||||
self.show()
|
||||
|
||||
def closeEvent(self, event):
|
||||
|
||||
reply = QMessageBox.question(self, 'Message',
|
||||
"Are you sure to quit?", QMessageBox.StandardButtons.Yes |
|
||||
QMessageBox.StandardButtons.No, QMessageBox.StandardButtons.No)
|
||||
|
||||
if reply == QMessageBox.StandardButtons.Yes:
|
||||
|
||||
event.accept()
|
||||
else:
|
||||
|
||||
event.ignore()
|
||||
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
关闭 `QWidget` 操作会产生 `QCloseEvent` 事件。重新实现 `closeEvent` 事件处理,替换部件的默认行为。
|
||||
|
||||
``` python
|
||||
reply = QMessageBox.question(self, 'Message',
|
||||
"Are you sure to quit?", QMessageBox.Yes |
|
||||
QMessageBox.No, QMessageBox.No)
|
||||
```
|
||||
这里创建了一个带有两个按钮的消息框:是和否。第一个参数是标题栏,第二个参数是对话框显示的消息文本,第三个参数是对话框中的按钮组合,最后一个参数是默认选中的按钮。返回值存储在变量 `replay` 中。
|
||||
|
||||
``` python
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
```
|
||||
对返回值进行判断,如果单击 Yes 按钮,执行小部件关闭和终止应用程序,否则我们忽略关闭事件。
|
||||
|
||||
![Message box](./images/messagebox.png)
|
||||
|
||||
Figure: Message box
|
||||
|
||||
## PyQt6 窗口居中
|
||||
下面的脚本会在屏幕上显示一个居中的窗口。
|
||||
``` python
|
||||
# file: center.py
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
ZetCode PyQt6 tutorial
|
||||
|
||||
This program centers a window
|
||||
on the screen.
|
||||
|
||||
Author: Jan Bodnar
|
||||
Website: zetcode.com
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QWidget, QApplication
|
||||
|
||||
|
||||
class Example(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
|
||||
self.resize(350, 250)
|
||||
self.center()
|
||||
|
||||
self.setWindowTitle('Center')
|
||||
self.show()
|
||||
|
||||
def center(self):
|
||||
|
||||
qr = self.frameGeometry()
|
||||
cp = self.screen().availableGeometry().center()
|
||||
|
||||
qr.moveCenter(cp)
|
||||
self.move(qr.topLeft())
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
ex = Example()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
`QScreen` 类可以查询屏幕属性。
|
||||
``` python
|
||||
self.center()
|
||||
```
|
||||
使用自定义 `center` 方法居中显示窗口。
|
||||
``` python
|
||||
qr = self.frameGeometry()
|
||||
```
|
||||
这样就可以得到一个矩形的窗口,这里可以放置所有类型的窗口。
|
||||
``` python
|
||||
cp = self.screen().availableGeometry().center()
|
||||
```
|
||||
从屏幕属性里计算出分辨率,然后计算出中心点位置。
|
||||
``` python
|
||||
qr.moveCenter(cp)
|
||||
```
|
||||
我们已经知道矩形窗口的宽高,只需要把矩形窗口的中心点放置到屏幕窗口的中心点即可。这不会修改矩形窗口的大小。
|
||||
``` python
|
||||
self.move(qr.topLeft())
|
||||
```
|
||||
把应用窗口的左上方点坐标移动到矩形窗口的左上方,这样就可以居中显示了。
|
||||
|
||||
本章教程,我们创建了一个简单的 PyQt6 应用。
|
BIN
translated/pyqt6/images/absolute.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
translated/pyqt6/images/beziercurve.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
translated/pyqt6/images/brushes.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
translated/pyqt6/images/burning.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
translated/pyqt6/images/buttons.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
translated/pyqt6/images/calculator.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
translated/pyqt6/images/checkmenu.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
translated/pyqt6/images/colours.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
translated/pyqt6/images/coordinates.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
translated/pyqt6/images/dragdrop.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
translated/pyqt6/images/drawingtext.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
translated/pyqt6/images/eventobject.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
translated/pyqt6/images/eventsender.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
translated/pyqt6/images/inputdialog.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
translated/pyqt6/images/mainwindow.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
translated/pyqt6/images/messagebox.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
translated/pyqt6/images/penstyles.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
translated/pyqt6/images/points.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
translated/pyqt6/images/qcheckbox.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
translated/pyqt6/images/qcombobox.png
Normal file
After Width: | Height: | Size: 6.1 KiB |