add pyqt6 docs

This commit is contained in:
make46 2021-12-17 15:33:41 +08:00
parent 82e27870e7
commit c0e5283250
122 changed files with 10670 additions and 18 deletions

View File

@ -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

View File

@ -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/)

View 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
View 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
View 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
View 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.

View 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.

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

24
original/pyqt6/index.md Normal file
View 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.

View 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
View 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.

View 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
View 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
View 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
View 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
View 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`.

View File

@ -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

View 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 教程里,我们创建了一个自定义部件。

View 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
View 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)
```
读取选择文件并把内容放置岛文本编辑部件里。
本章教程,我们学习了对话框。

View 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 中的拖拽操作。

View 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` 信号,程序终止。
本章教程,我们讲述了信号和插槽。

View 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 应用。

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Some files were not shown because too many files have changed in this diff Show More