2
0
mirror of https://github.com/pyqt/examples.git synced 2025-08-29 21:07:41 +00:00

Integrate unofficial examples with screenshots

This commit is contained in:
Michael Herrmann 2019-06-27 14:38:52 +02:00
parent 2646734e4c
commit c0adffcf62
90 changed files with 2185 additions and 2 deletions

View File

@ -1,2 +1,68 @@
# examples
Examples files for PyQt5
# PyQt examples: Desktop GUIs based on PyQt5
These PyQt examples teach you to create a desktop app with Python. Start with "Hello World" and work your way up to the official [demos](src/pyqt-official) that ship with PyQt.
The screenshots below were taken on Ubuntu Linux. You can also use Windows or macOS.
| <a href="src/01 PyQt QLabel"><img src="src/01 PyQt QLabel/pyqt-qlabel.png" alt="PyQt QLabel" width=100px></a> | <a href="src/02 PyQt Widgets"><img src="src/02 PyQt Widgets/pyqt-widgets.png" alt="PyQt widgets screenshot" width=200px></a> | <a href="src/03 QVBoxLayout PyQt5"><img src="src/03 QVBoxLayout PyQt5/qvboxlayout-pyqt5.png" alt="QVBoxLayout PyQt5" width=100px></a> | <a href="src/04 PyQt Signals and Slots"><img src="src/04 PyQt Signals and Slots/pyqt-signals-and-slots.jpg" alt="PyQt Signals and Slots" width=170px></a> | <a href="src/05 Qt Designer Python"><img src="src/05 Qt Designer Python/qt-designer-windows.png" alt="Qt Designer Python" width=190px></a> |
| :--: | :--: | :--: | :--: | :--: |
| <a href="src/01 PyQt QLabel">Hello World!</a> | <a href="src/02 PyQt Widgets">Common PyQt Widgets</a> | <a href="src/03 QVBoxLayout PyQt5">Layouts</a> | <a href="src/04 PyQt Signals and Slots">Signals and Slots</a> | <a href="src/04 Qt Designer Python">Qt Designer & Python</a> |
| <a href="src/06 QML Python example"><img src="src/06 QML Python example/qml-python-example.png" alt="QML Python example" width=200px></a> | <a href="src/07 Qt Text Editor"><img src="src/07 Qt Text Editor/screenshots/qt-text-editor.png" alt="Qt Text Editor" width=180px></a> | <a href="src/08 PyQt5 exe"><img src="src/08 PyQt5 exe/pyqt5-exe.png" alt="PyQt5 exe" width=213px></a> | <a href="src/09 Qt dark theme"><img src="src/09 Qt dark theme/qt-dark-theme.png" alt="Qt dark theme" width=180px></a> |
| :--: | :--: | :--: | :--: |
| <a href="src/06 QML Python example">QML Python example</a> | <a href="src/07 Qt Text Editor">Qt Text Editor</a> | <a href="src/08 PyQt5 exe">Packaging & deployment</a> | <a href="src/09 Qt dark theme">Qt Dark Theme</a> |
| <a href="src/10 QPainter Python example"><img src="src/10 QPainter Python example/qpainter-python-example.png" alt="QPainter Python example" width=200px></a> | <a href="src/11 PyQt Thread example"><img src="src/11 PyQt Thread example/pyqt-thread-example.png" alt="PyQt Thread example" width=175px></a> | <a href="src/12 QTreeView example in Python"><img src="src/12 QTreeView example in Python/qtreeview-example-in-python.png" alt="QTreeView example in Python" width=260px></a> | <a href="src/13 PyQt5 QListView"><img src="src/13 PyQt5 QListView/pyqt5-qlistview.png" alt="PyQt5 QListView" width=138px></a> |
| :--: | :--: | :--: | :--: |
| <a href="src/10 QPainter Python example">Action Shooter</a> | <a href="src/11 PyQt Thread example">Chat Client</a> | <a href="src/12 QTreeView example in Python">Tree Views</a> | <a href="src/13 PyQt5 QListView">Lists</a> |
| <a href="src/14 QAbstractTableModel example"><img src="src/14 QAbstractTableModel example/qabstracttablemodel-example.png" alt="QAbstractTableModel example" height=120px></a> | <a href="src/15 PyQt database example"><img src="src/15 PyQt database example/pyqt-database-example.png" alt="QAbstractTableModel example" height=120px></a> |
| :--: | :--: |
| <a href="src/14 QAbstractTableModel example">Custom Tables</a> | <a href="src/15 PyQt database example">PyQt database example</a> |
## Running the examples
Running the examples is really easy. The only thing you need is [Python 3](https://www.python.org/downloads/).
First, download the [ZIP archive of this repository](https://github.com/pyqt/examples/archive/master.zip) and unpack it. Open a command prompt and use `cd` to navigate into the top-level directory of the archive.
Create a virtual environment via the command:
python3 -m venv venv
This creates the folder `venv/` in your current directory. It will contain the necessary libraries for running the examples.
To activate the virtual environment, use the following command:
```
# On Windows:
call venv\Scripts\actviate.bat
# On Mac / Linux:
source venv/bin/activate
```
Now execute the following to install the necessary dependencies:
pip install -Ur src/requirements.txt
Once you have done this, use `cd` to navigate to the example you're interested in in the [`src/`](src) folder. For example:
cd "src/01 PyQt QLabel"
You'll find a `.py` file there, typically `main.py`. You can run it with the command:
python main.py
Please note that the virtual environment must still be active for this to work.
## Using PySide2
This repository uses PyQt5 to use Qt from Python. Another, alternative binding is PySide2 (also called "Qt for Python"). It is less mature than PyQt5 but has the advantage that you can use it for free in commercial projects.
If you want to use PySide2 instead of PyQt5, simply replace all mentions of the latter by the former. For instance, in [`src/requirements.txt`](src/requirements.txt), replace `PyQt5` by `PySide2`. Similarly for any code examples: `from PyQt5.QtWidgets ...` becomes `from PySide2.QtWidgets ...` etc.
Alternatively, if you don't want to commit to either of the two bindings at this stage, you can also use [Qt.py](https://github.com/mottosso/Qt.py). This is an abstraction over PySide2 and PyQt5. It loads whichever of the two bindings is available. To use it for the examples presented here, replace all mentions of `PyQt5` by just `Qt`.
## License
Except where otherwise indicated, the contents here are © me, Michael Herrmann. I'm happy for you to use the source code under the terms of the MIT license. The screenshots may be used under the terms of the [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/).

105
src/.gitignore vendored Normal file
View File

@ -0,0 +1,105 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
*.qmlc

View File

@ -0,0 +1,39 @@
# PyQt QLabel
This example shows how you can create a Hello World app using PyQt. It uses a [`QLabel`](https://doc.qt.io/qt-5/qlabel.html) to display a simple window:
![PyQt QLabel screenshot](pyqt-qlabel.png)
```
from PyQt5.QtWidgets import *
app = QApplication([])
label = QLabel('Hello World!')
label.show()
app.exec_()
```
For instructions how you can run this code, please see the [top-level README](https://github.com/1mh/pyqt-examples#running-the-examples).
The code works as follows: First, we import the necessary PyQt classes via the statement:
from PyQt5.QtWidgets import *
Next, we create a [`QApplication`](https://doc.qt.io/Qt-5/qapplication.html). This is required in every PyQt app. In a sense, it initializes PyQt:
app = QApplication([])
Then, we create the label with the text we want:
label = QLabel('Hello World!')
By calling `.show()` on a [widget](../02%20PyQt%20Widgets), we can spawn a window that displays it:
label.show()
Finally, we hand control over to Qt:
app.exec_()
This too is required in every Qt application. It gives Qt a chance to run and process user input, such as for instance when the user clicks the "Window close" button.
And that's it! Congratulations on your first PyQt app :-)

View File

@ -0,0 +1,5 @@
from PyQt5.QtWidgets import *
app = QApplication([])
label = QLabel('Hello World!')
label.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,21 @@
# PyQt Widgets
A *widget* is a GUI element: A button, a text field, ... The sample application in this directory shows the most common PyQt widgets:
![PyQt widgets screenshot](pyqt-widgets.png)
If you know HTML: Widgets are a little like HTML elements. They can be nested, and have a different appearance and behavior depending on their type. (Eg. a link `<a>` looks and behaves differently from an image `<img>`.)
Some of the widgets you can see in this screenshot are:
* [QLabel](https://doc.qt.io/qt-5/qlabel.html)
* [QComboBox](https://doc.qt.io/qt-5/qcombobox.html)
* [QCheckBox](https://doc.qt.io/qt-5/qcheckbox.html)
* [QRadioButton](https://doc.qt.io/qt-5/qradiobutton.html)
* [QPushButton](https://doc.qt.io/qt-5/qpushbutton.html)
* [QTableWidget](https://doc.qt.io/qt-5/qtablewidget.html)
* [QLineEdit](https://doc.qt.io/qt-5/qlineedit.html)
* [QSlider](https://doc.qt.io/qt-5/qslider.html)
* [QProgressBar](https://doc.qt.io/qt-5/qprogressbar.html)
The source code for this application is in [`main.py`](main.py). For instructions how to run it, please see [here](https://github.com/1mh/pyqt-examples#running-the-examples). Don't worry if you don't yet fully understand the source code. The main purpose of this example is to give you a feel for what a widget is, and which ones are available. The next examples give you a more gradual route to more advanced PyQt topics.

243
src/02 PyQt Widgets/main.py Normal file
View File

@ -0,0 +1,243 @@
#!/usr/bin/env python
#############################################################################
##
## Copyright (C) 2013 Riverbank Computing Limited.
## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
## All rights reserved.
##
## This file is part of the examples of PyQt.
##
## $QT_BEGIN_LICENSE:BSD$
## You may use this file under the terms of the BSD license as follows:
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are
## met:
## * Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
## * Redistributions in binary form must reproduce the above copyright
## notice, this list of conditions and the following disclaimer in
## the documentation and/or other materials provided with the
## distribution.
## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
## the names of its contributors may be used to endorse or promote
## products derived from this software without specific prior written
## permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
## $QT_END_LICENSE$
##
#############################################################################
from PyQt5.QtCore import QDateTime, Qt, QTimer
from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateTimeEdit,
QDial, QDialog, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit,
QProgressBar, QPushButton, QRadioButton, QScrollBar, QSizePolicy,
QSlider, QSpinBox, QStyleFactory, QTableWidget, QTabWidget, QTextEdit,
QVBoxLayout, QWidget)
class WidgetGallery(QDialog):
def __init__(self, parent=None):
super(WidgetGallery, self).__init__(parent)
self.originalPalette = QApplication.palette()
styleComboBox = QComboBox()
styleComboBox.addItems(QStyleFactory.keys())
styleLabel = QLabel("&Style:")
styleLabel.setBuddy(styleComboBox)
self.useStylePaletteCheckBox = QCheckBox("&Use style's standard palette")
self.useStylePaletteCheckBox.setChecked(True)
disableWidgetsCheckBox = QCheckBox("&Disable widgets")
self.createTopLeftGroupBox()
self.createTopRightGroupBox()
self.createBottomLeftTabWidget()
self.createBottomRightGroupBox()
self.createProgressBar()
styleComboBox.activated[str].connect(self.changeStyle)
self.useStylePaletteCheckBox.toggled.connect(self.changePalette)
disableWidgetsCheckBox.toggled.connect(self.topLeftGroupBox.setDisabled)
disableWidgetsCheckBox.toggled.connect(self.topRightGroupBox.setDisabled)
disableWidgetsCheckBox.toggled.connect(self.bottomLeftTabWidget.setDisabled)
disableWidgetsCheckBox.toggled.connect(self.bottomRightGroupBox.setDisabled)
topLayout = QHBoxLayout()
topLayout.addWidget(styleLabel)
topLayout.addWidget(styleComboBox)
topLayout.addStretch(1)
topLayout.addWidget(self.useStylePaletteCheckBox)
topLayout.addWidget(disableWidgetsCheckBox)
mainLayout = QGridLayout()
mainLayout.addLayout(topLayout, 0, 0, 1, 2)
mainLayout.addWidget(self.topLeftGroupBox, 1, 0)
mainLayout.addWidget(self.topRightGroupBox, 1, 1)
mainLayout.addWidget(self.bottomLeftTabWidget, 2, 0)
mainLayout.addWidget(self.bottomRightGroupBox, 2, 1)
mainLayout.addWidget(self.progressBar, 3, 0, 1, 2)
mainLayout.setRowStretch(1, 1)
mainLayout.setRowStretch(2, 1)
mainLayout.setColumnStretch(0, 1)
mainLayout.setColumnStretch(1, 1)
self.setLayout(mainLayout)
self.setWindowTitle("Styles")
self.changeStyle('Windows')
def changeStyle(self, styleName):
QApplication.setStyle(QStyleFactory.create(styleName))
self.changePalette()
def changePalette(self):
if (self.useStylePaletteCheckBox.isChecked()):
QApplication.setPalette(QApplication.style().standardPalette())
else:
QApplication.setPalette(self.originalPalette)
def advanceProgressBar(self):
curVal = self.progressBar.value()
maxVal = self.progressBar.maximum()
self.progressBar.setValue(curVal + (maxVal - curVal) / 100)
def createTopLeftGroupBox(self):
self.topLeftGroupBox = QGroupBox("Group 1")
radioButton1 = QRadioButton("Radio button 1")
radioButton2 = QRadioButton("Radio button 2")
radioButton3 = QRadioButton("Radio button 3")
radioButton1.setChecked(True)
checkBox = QCheckBox("Tri-state check box")
checkBox.setTristate(True)
checkBox.setCheckState(Qt.PartiallyChecked)
layout = QVBoxLayout()
layout.addWidget(radioButton1)
layout.addWidget(radioButton2)
layout.addWidget(radioButton3)
layout.addWidget(checkBox)
layout.addStretch(1)
self.topLeftGroupBox.setLayout(layout)
def createTopRightGroupBox(self):
self.topRightGroupBox = QGroupBox("Group 2")
defaultPushButton = QPushButton("Default Push Button")
defaultPushButton.setDefault(True)
togglePushButton = QPushButton("Toggle Push Button")
togglePushButton.setCheckable(True)
togglePushButton.setChecked(True)
flatPushButton = QPushButton("Flat Push Button")
flatPushButton.setFlat(True)
layout = QVBoxLayout()
layout.addWidget(defaultPushButton)
layout.addWidget(togglePushButton)
layout.addWidget(flatPushButton)
layout.addStretch(1)
self.topRightGroupBox.setLayout(layout)
def createBottomLeftTabWidget(self):
self.bottomLeftTabWidget = QTabWidget()
self.bottomLeftTabWidget.setSizePolicy(QSizePolicy.Preferred,
QSizePolicy.Ignored)
tab1 = QWidget()
tableWidget = QTableWidget(10, 10)
tab1hbox = QHBoxLayout()
tab1hbox.setContentsMargins(5, 5, 5, 5)
tab1hbox.addWidget(tableWidget)
tab1.setLayout(tab1hbox)
tab2 = QWidget()
textEdit = QTextEdit()
textEdit.setPlainText("Twinkle, twinkle, little star,\n"
"How I wonder what you are.\n"
"Up above the world so high,\n"
"Like a diamond in the sky.\n"
"Twinkle, twinkle, little star,\n"
"How I wonder what you are!\n")
tab2hbox = QHBoxLayout()
tab2hbox.setContentsMargins(5, 5, 5, 5)
tab2hbox.addWidget(textEdit)
tab2.setLayout(tab2hbox)
self.bottomLeftTabWidget.addTab(tab1, "&Table")
self.bottomLeftTabWidget.addTab(tab2, "Text &Edit")
def createBottomRightGroupBox(self):
self.bottomRightGroupBox = QGroupBox("Group 3")
self.bottomRightGroupBox.setCheckable(True)
self.bottomRightGroupBox.setChecked(True)
lineEdit = QLineEdit('s3cRe7')
lineEdit.setEchoMode(QLineEdit.Password)
spinBox = QSpinBox(self.bottomRightGroupBox)
spinBox.setValue(50)
dateTimeEdit = QDateTimeEdit(self.bottomRightGroupBox)
dateTimeEdit.setDateTime(QDateTime.currentDateTime())
slider = QSlider(Qt.Horizontal, self.bottomRightGroupBox)
slider.setValue(40)
scrollBar = QScrollBar(Qt.Horizontal, self.bottomRightGroupBox)
scrollBar.setValue(60)
dial = QDial(self.bottomRightGroupBox)
dial.setValue(30)
dial.setNotchesVisible(True)
layout = QGridLayout()
layout.addWidget(lineEdit, 0, 0, 1, 2)
layout.addWidget(spinBox, 1, 0, 1, 2)
layout.addWidget(dateTimeEdit, 2, 0, 1, 2)
layout.addWidget(slider, 3, 0)
layout.addWidget(scrollBar, 4, 0)
layout.addWidget(dial, 3, 1, 2, 1)
layout.setRowStretch(5, 1)
self.bottomRightGroupBox.setLayout(layout)
def createProgressBar(self):
self.progressBar = QProgressBar()
self.progressBar.setRange(0, 10000)
self.progressBar.setValue(0)
timer = QTimer(self)
timer.timeout.connect(self.advanceProgressBar)
timer.start(1000)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
gallery = WidgetGallery()
gallery.show()
sys.exit(app.exec_())

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,41 @@
# QVBoxLayout PyQt5
Layouts let you position GUI elements next to each other. [`QVBoxLayout`](https://doc.qt.io/qt-5/qvboxlayout.html) for instance arranges items vertically:
<p align="center"><img src="qvboxlayout-pyqt5.png" alt="QVBoxLayout PyQt5"></p>
The [source code for this example](main.py) is not much more complex than for our [Hello World app](../01%20PyQt%20QLabel). First, we import PyQt5:
from PyQt5.QtWidgets import *
Then, we create the required `QApplication`:
app = QApplication([])
This time, we create a top-level window first. This will act as the container for the two buttons you see in the screenshot:
window = QWidget()
[`QWidget`](https://doc.qt.io/qt-5/qwidget.html) is the most basic kind of [widget](../02%20PyQt%20Widgets). It would simply be empty if we didn't add any contents to it. (Kind of like a `<div>` element in HTML.).
To tell Qt to arrange our buttons vertically, we create a `QVBoxLayout`:
layout = QVBoxLayout()
Then, we add the two buttons to it:
layout.addWidget(QPushButton('Top'))
layout.addWidget(QPushButton('Bottom'))
Finally, we add the layout - and thus its contents - to the `window` we created above:
window.setLayout(layout)
We conclude by showing the window and (as is required) handing control over to Qt:
window.show()
app.exec_()
For instructions how you can run this example yourself, please see [here](https://github.com/1mh/pyqt-examples#running-the-examples).
The related [`QHBoxLayout`](https://doc.qt.io/qt-5/qhboxlayout.html) positions items horizontally. For an even more powerful approach, see [`QGridLayout`](https://doc.qt.io/qt-5/qgridlayout.html).

View File

@ -0,0 +1,9 @@
from PyQt5.QtWidgets import *
app = QApplication([])
window = QWidget()
layout = QVBoxLayout()
layout.addWidget(QPushButton('Top'))
layout.addWidget(QPushButton('Bottom'))
window.setLayout(layout)
window.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,32 @@
# PyQt Signals and Slots
PyQt Signals let you react to user input such as mouse clicks. A *slot* is a function that gets called when such an event occurs. The file [`main.py`](main.py) in this directory shows this in action: When the user clicks a button, a popup appears:
<p align="center"><img src="pyqt-signals-and-slots.jpg" alt="PyQt Signals and Slots"></p>
The code begins in the usual way. First, we import PyQt5 and create a `QApplication`:
from PyQt5.QtWidgets import *
app = QApplication([])
Next, we create a button:
button = QPushButton('Click')
Then we define a function. It will be called when the user clicks the button. You can see that it shows an alert:
def on_button_clicked():
alert = QMessageBox()
alert.setText('You clicked the button!')
alert.exec_()
And here is where signals and slots come into play: We instruct Qt to invoke our function by _connecting_ it to the `.clicked` signal of our button:
button.clicked.connect(on_button_clicked)
Finally, we show the button on the screen and hand control over to Qt:
button.show()
app.exec_()
For instructions how you can run this example yourself, please see [here](https://github.com/1mh/pyqt-examples#running-the-examples).

View File

@ -0,0 +1,13 @@
from PyQt5.QtWidgets import *
app = QApplication([])
button = QPushButton('Click')
def on_button_clicked():
alert = QMessageBox()
alert.setText('You clicked the button!')
alert.exec_()
button.clicked.connect(on_button_clicked)
button.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,47 @@
# Qt Designer Python
[Qt Designer](https://build-system.fman.io/qt-designer-download) is a graphical tool for building Qt GUIs:
<p align="center"><img src="qt-designer-windows.png" alt="Qt Designer screenshot on Windows"></p>
It produces `.ui` files. You can load these files from C++ or Python to display the GUI.
The dialog in the following screenshot comes from the file [`dialog.ui`](dialog.ui) in this directory:
<p align="center"><img src="qt-designer-python.png" alt="Qt Designer Python"></p>
The [`main.py`](main.py) script (also in this directory) loads and invokes `dialog.ui` from Python. The steps with which it does this are quite easy.
First, [`main.py`](main.py) imports the `uic` module from PyQt5:
from PyQt5 import uic
It also imports `QApplication`. Like all (Py)Qt apps, we must create an instance of this class.
from PyQt5.QtWidgets import QApplication
Then, we use [`uic.loadUiType(...)`](https://www.riverbankcomputing.com/static/Docs/PyQt5/designer.html#PyQt5.uic.loadUiType) to load the `.ui` file. This returns two classes, which we call `Form` and `Window`:
Form, Window = uic.loadUiType("dialog.ui")
The first is an ordinary Python class. It has a `.setupUi(...)` method which takes a single parameter, the [widget](../02%20PyQt%20Widgets) in which the UI should be displayed. The type of this parameter is given by the second class, `Window`. This is configured in Qt Designer and is usually one of `QDialog`, `QMainWindow` or `QWidget`.
To show the UI, we thus proceed as follows. First, we create the necessary `QApplication`:
app = QApplication([])
Then, we instantiate the `Window` class. It will act as the container for our user interface:
window = Window()
Next, we instantiate the `Form`. We invoke its `.setupUi(...)` method, passing the window as a parameter:
form = Form()
form.setupUi(window)
We've now connected the necessary components for displaying the user interface given in the `.ui` file. All that remains is to `.show()` the window and kick off Qt's event processing mechanism:
window.show()
app.exec_()
For instructions how to run this example yourself, please see [here](https://github.com/1mh/pyqt-examples#running-the-examples).

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>197</width>
<height>72</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>-160</x>
<y>20</y>
<width>341</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,11 @@
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication
Form, Window = uic.loadUiType("dialog.ui")
app = QApplication([])
window = Window()
form = Form()
form.setupUi(window)
window.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,35 @@
This notice pertains to the following files:
* main.qml
* pinwheel.png
* background.png
They are modified versions of code / images which are originally:
Copyright (c) 2012-2014, Juergen Bocklage Ryannel and Johan Thelin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,58 @@
# QML Python example
Qt can be broadly split into two technologies: _Qt Widgets_ is the old core. It displays GUI elements in a way that is typical for operating systems such as Windows or macOS. A more recent alternative is _Qt Quick_. This technology is optimized for mobile and touch screen devices. It is better suited for very custom graphics and fluid animations.
Qt Quick uses a markup language called QML. This example shows how you can combine QML with Python.
<p align="center"><img src="qml-python-example.png" alt="QML Python Example"></p>
The sample application displays a pin wheel in front of some hills. When you click with the mouse, the wheel rotates.
The QML code lies in [`main.qml`](main.qml). It's a testament to QML that it is quite easy to read:
```
import QtQuick 2.2
import QtQuick.Window 2.2
Window {
Image {
id: background
source: "background.png"
}
Image {
id: wheel
anchors.centerIn: parent
source: "pinwheel.png"
Behavior on rotation {
NumberAnimation {
duration: 250
}
}
}
MouseArea {
anchors.fill: parent
onPressed: {
wheel.rotation += 90
}
}
visible: true
width: background.width
height: background.height
}
```
Executing the QML from Python is even easier. The code is in [`main.py`](main.py):
```
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtWidgets import QApplication
app = QApplication([])
engine = QQmlApplicationEngine()
engine.load("main.qml")
app.exec_()
```
If you'd like further instructions how you can run this code for yourself, please see [here](https://github.com/1mh/pyqt-examples#running-the-examples).
Some code in this directory has special license requirements. For more information, please see [`LICENSE.md`](LICENSE.md).

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,7 @@
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtWidgets import QApplication
app = QApplication([])
engine = QQmlApplicationEngine()
engine.load("main.qml")
app.exec_()

View File

@ -0,0 +1,28 @@
import QtQuick 2.2
import QtQuick.Window 2.2
Window {
Image {
id: background
source: "background.png"
}
Image {
id: wheel
anchors.centerIn: parent
source: "pinwheel.png"
Behavior on rotation {
NumberAnimation {
duration: 250
}
}
}
MouseArea {
anchors.fill: parent
onPressed: {
wheel.rotation += 90
}
}
visible: true
width: background.width
height: background.height
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -0,0 +1,18 @@
# Qt Text Editor
This example implements a simple text editor with (Py)Qt.
![Qt Text Editor](screenshots/qt-text-editor-windows.png) ![QMenu example](screenshots/qmenu-example.png)
![Qt QMenu](screenshots/qt-qmenu.png) ![QDialog example](screenshots/qdialog-example.png)
![QMessageBox example](screenshots/qmessagebox-example.png)
It has a surprising number of features:
* A *File* menu for opening and saving files.
* Keyboard shortcuts.
* An *About* dialog.
* A warning *Do you want to save before quitting?* if there are unmodified changes.
The full source code is in [`main.py`](main.py). For instructions on how to run it, please see [here](https://github.com/1mh/pyqt-examples#running-the-examples).

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg id="svg11300" xmlns="http://www.w3.org/2000/svg" height="48" width="48" version="1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs id="defs">
<radialGradient id="R1" xlink:href="#G1" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(-2.7744 0 0 1.9697 112.76 -872.89)" r="117.14"/>
<linearGradient id="G1">
<stop id="s1" offset="0"/>
<stop id="s2" stop-opacity="0" offset="1"/>
</linearGradient>
<radialGradient id="R2" xlink:href="#G1" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(2.7744 0 0 1.9697 -1891.6 -872.89)" r="117.14"/>
<linearGradient id="G3" y2="609.51" gradientUnits="userSpaceOnUse" x2="302.86" gradientTransform="matrix(2.7744 0 0 1.9697 -1892.2 -872.89)" y1="366.65" x1="302.86">
<stop id="s3" stop-opacity="0" offset="0"/>
<stop id="s4" offset=".5"/>
<stop id="s5" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="G12" y2="6.8334" gradientUnits="userSpaceOnUse" x2="14.284" gradientTransform="matrix(1.1379 0 0 1 -2.6609 0)" y1="42.833" x1="21.043">
<stop id="s21" stop-color="#dfdfdf" offset="0"/>
<stop id="s22" stop-color="#fff" offset="1"/>
</linearGradient>
<linearGradient id="G13" y2="42.833" gradientUnits="userSpaceOnUse" x2="26.228" y1="28.083" x1="26.612">
<stop id="s17" stop-color="#939393" offset="0"/>
<stop id="s18" stop-color="#424242" offset="1"/>
</linearGradient>
<linearGradient id="G14" y2="7.5625" gradientUnits="userSpaceOnUse" x2="40.984" y1="7.5625" x1="6">
<stop id="s15" stop-color="#a3a4a0" offset="0"/>
<stop id="s16" stop-color="#888a85" offset="1"/>
</linearGradient>
<linearGradient id="G15" y2="22.251" gradientUnits="userSpaceOnUse" x2="50.988" gradientTransform="translate(-5.6693)" y1="17.376" x1="48.906">
<stop id="s12" stop-color="#ffd1d1" offset="0"/>
<stop id="s13" stop-color="#ff1d1d" offset=".5"/>
<stop id="s14" stop-color="#6f0000" offset="1"/>
</linearGradient>
<linearGradient id="G16" y2="22.625" gradientUnits="userSpaceOnUse" x2="47.688" gradientTransform="translate(-5.6693)" y1="19.812" x1="46">
<stop id="s10" stop-color="#c1c1c1" offset="0"/>
<stop id="s11" stop-color="#acacac" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient2990" gradientUnits="userSpaceOnUse" cy="27.641" cx="29.053" gradientTransform="matrix(2.9236 0 0 2.0297 -61.555 -27.884)" r="3.2409">
<stop id="s8" stop-color="#e7e2b8" offset="0"/>
<stop id="s9" stop-color="#e7e2b8" stop-opacity="0" offset="1"/>
</radialGradient>
<linearGradient id="G17" y2="30.703" gradientUnits="userSpaceOnUse" x2="25.515" gradientTransform="translate(-5.8255 .125)" y1="31.047" x1="25.719">
<stop id="s6" offset="0"/>
<stop id="s7" stop-color="#c9c9c9" offset="1"/>
</linearGradient>
<radialGradient id="R3" gradientUnits="userSpaceOnUse" cy="40.438" cx="23.562" gradientTransform="matrix(1 0 0 .34824 0 26.355)" r="19.562">
<stop id="s19" offset="0"/>
<stop id="s20" stop-opacity="0" offset="1"/>
</radialGradient>
</defs>
<g id="layer1">
<g id="g6707" transform="matrix(.024176 0 0 .020868 45.128 40.154)">
<rect id="rect6709" opacity=".4" height="478.36" width="1339.6" y="-150.7" x="-1559.3" fill="url(#G3)"/>
<path id="p1" opacity=".4" fill="url(#R2)" d="m-219.62-150.68v478.33c142.88 0.9 345.4-107.17 345.4-239.2 0-132.02-159.44-239.13-345.4-239.13z"/>
<path id="p2" opacity=".4" fill="url(#R1)" d="m-1559.3-150.68v478.33c-142.8 0.9-345.4-107.17-345.4-239.2 0-132.02 159.5-239.13 345.4-239.13z"/>
</g>
<path id="p3" fill="url(#G12)" d="m7.1639 4.5064h32.649c0.763 0 1.377 0.5324 1.377 1.1938l2.401 34.169 0.012 2.348c0 0.661-0.614 1.193-1.376 1.193h-37.477c-0.7625 0-1.3764-0.532-1.3764-1.193l-0.0112-2.167 2.425-34.35c0-0.6612 0.6139-1.1936 1.3765-1.1936z" stroke="url(#G13)"/>
<path id="p4" opacity=".31579" d="m43.125 40.438a19.562 6.8125 0 1 1 -39.125 0 19.562 6.8125 0 1 1 39.125 0z" transform="matrix(.61661 0 0 .44037 10.614 13.943)" fill="url(#R3)"/>
<rect id="rect2851" rx=".67938" ry=".67938" height="3.0715" width="39.048" y="39.868" x="3.977" fill="#a4a4a4"/>
<path id="p5" fill="#fff" d="m3.9268 40.443s0.1508-0.531 0.704-0.575h37.564c0.755 0 0.805 0.752 0.805 0.752s0.024-1.62-1.284-1.62h-36.412c-1.0055 0.088-1.3772 0.78-1.3772 1.443z"/>
<path id="p6" fill="url(#G14)" d="m6.25 5.7344l-0.25 4.3906s0.3125-1.125 1-1.125h33.125c0.703-0.0156 0.734 0.3125 0.859 0.8281l-0.25-3.875c-0.031-0.5469-0.218-0.9531-0.781-0.9531h-32.89c-0.4536 0-0.7661 0.3437-0.813 0.7344z"/>
<path id="p7" opacity=".43860" d="m7.8126 5.5405h31.132c0.722 0 1.303-0.1522 1.303 0.4741 0 0 2.274 33.008 2.274 33.008l0.1 2.709c0 0.626-0.139 0.644-0.861 0.644h-36.899c-0.4126 0-0.4194-0.106-0.4194-0.511l-0.0106-2.671 2.2961-33.148c0-0.6264 0.3625-0.5055 1.0845-0.5055z" stroke="#fff" fill="none"/>
<g id="g2950" stroke="#886f00" fill="#fce94f">
<rect id="rect2899" rx="1" ry="1" height="5" width="2" y="2.5" x="8.5"/>
<rect id="rect2901" rx="1" ry="1" height="5" width="2" y="2.5" x="12.5"/>
<rect id="rect2903" rx="1" ry="1" height="5" width="2" y="2.5" x="16.5"/>
<rect id="rect2905" rx="1" ry="1" height="5" width="2" y="2.5" x="20.5"/>
<rect id="rect2907" rx="1" ry="1" height="5" width="2" y="2.5" x="24.5"/>
<rect id="rect2909" rx="1" ry="1" height="5" width="2" y="2.5" x="28.5"/>
<rect id="rect2911" rx="1" ry="1" height="5" width="2" y="2.5" x="32.5"/>
<rect id="rect2913" rx="1" ry="1" height="5" width="2" y="2.5" x="36.5"/>
</g>
<g id="g2941">
<rect id="rect2927" opacity=".2807" height="1" width="29" y="12" x="9"/>
<rect id="rect2929" opacity=".2807" height="1" width="29" y="14.982" x="9"/>
<rect id="rect2931" opacity=".2807" height="1" width="13" y="18.004" x="9"/>
<rect id="rect2933" opacity=".2807" height="1" width="29" y="22.986" x="9"/>
<rect id="rect2935" opacity=".2807" height="1" width="29" y="26.008" x="9"/>
<rect id="rect2937" opacity=".2807" height="1" width="29" y="29.03" x="9"/>
<rect id="rect2939" opacity=".2807" height="1" width="8" y="32.052" x="9"/>
</g>
<path id="path2960" fill="#cb9022" d="m17.341 32.5l5.625-5.625 20.094-9.75c3.25-1.25 5.187 3.375 2.312 5l-20.031 9.375-8 1z" stroke="#5c410c"/>
<path id="p8" fill="url(#G15)" d="m38.331 20s1.437 0.094 2 1.344c0.579 1.288 0 2.656 0 2.656l5.031-2.469s1.452-0.881 0.656-2.843c-0.785-1.936-2.687-1.157-2.687-1.157l-5 2.469z"/>
<path id="p9" fill="url(#G16)" d="m38.331 20s1.437 0.094 2 1.344c0.579 1.288 0 2.656 0 2.656l2-1s0.827-1.319 0.218-2.688c-0.625-1.406-2.218-1.312-2.218-1.312l-2 1z"/>
<path id="p10" fill="url(#radialGradient2990)" d="m18.768 31.781l4.5-4.5c1.5 0.813 2.281 2.157 1.875 3.719l-6.375 0.781z"/>
<path id="p11" fill="url(#G17)" d="m20.112 30.375l-1.625 1.594 2.344-0.313c0.218-0.718-0.188-1.062-0.719-1.281z"/>
<path id="p12" fill-opacity=".36364" fill="#fff" d="m23.268 27.25l1.563 1.25 15.387-7.319c-0.444-0.856-1.242-1.084-1.903-1.162l-15.047 7.231z"/>
<path id="p13" fill-opacity=".36364" d="m25.143 31.062l0.188-0.75 15.231-7.129s-0.11 0.614-0.216 0.749l-15.203 7.13z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1,79 @@
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QKeySequence
class MainWindow(QMainWindow):
def closeEvent(self, e):
if not text.document().isModified():
return
answer = QMessageBox.question(
window, None,
"You have unsaved changes. Save before closing?",
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
)
if answer & QMessageBox.Save:
save()
elif answer & QMessageBox.Cancel:
e.ignore()
app = QApplication([])
app.setApplicationName("Text Editor")
text = QPlainTextEdit()
window = MainWindow()
window.setCentralWidget(text)
file_path = None
menu = window.menuBar().addMenu("&File")
open_action = QAction("&Open")
def open_file():
global file_path
path = QFileDialog.getOpenFileName(window, "Open")[0]
if path:
text.setPlainText(open(path).read())
file_path = path
open_action.triggered.connect(open_file)
open_action.setShortcut(QKeySequence.Open)
menu.addAction(open_action)
save_action = QAction("&Save")
def save():
if file_path is None:
save_as()
else:
with open(file_path, "w") as f:
f.write(text.toPlainText())
text.document().setModified(False)
save_action.triggered.connect(save)
save_action.setShortcut(QKeySequence.Save)
menu.addAction(save_action)
save_as_action = QAction("Save &As...")
def save_as():
global file_path
path = QFileDialog.getSaveFileName(window, "Save As")[0]
if path:
file_path = path
save()
save_as_action.triggered.connect(save_as)
menu.addAction(save_as_action)
close = QAction("&Close")
close.triggered.connect(window.close)
menu.addAction(close)
help_menu = window.menuBar().addMenu("&Help")
about_action = QAction("&About")
help_menu.addAction(about_action)
def show_about_dialog():
text = "<center>" \
"<h1>Text Editor</h1>" \
"&#8291;" \
"<img src=icon.svg>" \
"</center>" \
"<p>Version 31.4.159.265358<br/>" \
"Copyright &copy; Company Inc.</p>"
QMessageBox.about(window, "About Text Editor", text)
about_action.triggered.connect(show_about_dialog)
window.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,53 @@
# PyQt5 exe
Once you have a PyQt5 application, you want to compile your Python source code into a standalone executable. Furthermore, you normally want to create an installer so your users can easily set up your app.
This example uses [fbs](https://build-system.fman.io) to create a standalone executable and an installer for the text editor in [example 07](../07%20Qt%20Text%20Editor).
<img src="pyqt5-exe.png" alt="PyQt5 exe installer" height="250px"> <img src="pyqt5-installer-mac.png" alt="Installer for a PyQt5 Mac application" height="250px">
You can find a modified version of the ["old" main.py](../07%20Qt%20Text%20Editor/main.py) in [`src/main/python/main.py`](src/main/python/main.py). It only has a few extra lines:
We import fbs's `ApplicationContext`:
from fbs_runtime.application_context.PyQt5 import ApplicationContext
Further down, we instantiate it:
appctxt = ApplicationContext()
We no longer need to create a `QApplication`. This is done automatically by fbs.
The editor's _About_ dialog shows an icon:
![QDialog example](../07%20Qt%20Text%20Editor/screenshots/qdialog-example.png)
This is done in the original code as follows:
text = "...<img src=icon.svg>..."
The new code however needs to be more flexible with regard to the icon's path. When running from source, the icon lies in [`src/main/resources/base/icon.svg`](src/main/resources/base/icon.svg). When running on the user's system however, it lies in the installation directory.
To handle this, the new code uses fbs's [`ApplicationContext.get_resource(...)`](https://build-system.fman.io/manual/#get_resource) method:
text = "...<img src=%r>..." % appctxt.get_resource("icon.svg")
This automatically handles the different possible locations of the image.
Because we didn't create the `QApplication` ourselves, we finally use the following call instead of only `app.exec_()`:
appctxt.app.exec_()
To run this example yourself, you need fbs installed as per the instructions [here](https://github.com/1mh/pyqt-examples#running-the-examples). Then, you can do use the following command to run the text editor:
fbs run
The following command then compiles the Python source code into a standalone executable in your `target/` directory:
fbs freeze
Finally, the following creates an installer that you can distribute to other people:
fbs installer
Please note that this last command requires that you have [NSIS](https://nsis.sourceforge.io/Main_Page) installed and on your `PATH` on Windows, or [`fpm`](https://github.com/jordansissel/fpm) on Linux.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,6 @@
{
"app_name": "Text Editor",
"author": "Michael",
"main_module": "src/main/python/main.py",
"version": "0.0.0"
}

View File

@ -0,0 +1,6 @@
{
"categories": "Utility;",
"description": "",
"author_email": "",
"url": ""
}

View File

@ -0,0 +1,3 @@
{
"mac_bundle_identifier": ""
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

View File

@ -0,0 +1,11 @@
![Sample app icon](linux/128.png)
This directory contains the icons that are displayed for your app. Feel free to
change them.
The difference between the icons on Mac and the other platforms is that on Mac,
they contain a ~5% transparent margin. This is because otherwise they look too
big (eg. in the Dock or in the app switcher).
You can create Icon.ico from the .png files with
[an online tool](http://icoconvert.com/Multi_Image_to_one_icon/).

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,89 @@
from fbs_runtime.application_context.PyQt5 import ApplicationContext
from PyQt5.QtWidgets import QMainWindow
import sys
appctxt = ApplicationContext() # 1. Instantiate ApplicationContext
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QKeySequence
class MainWindow(QMainWindow):
def closeEvent(self, e):
if not text.document().isModified():
return
answer = QMessageBox.question(
window, None,
"You have unsaved changes. Save before closing?",
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
)
if answer & QMessageBox.Save:
save()
elif answer & QMessageBox.Cancel:
e.ignore()
text = QPlainTextEdit()
window = MainWindow()
window.setCentralWidget(text)
file_path = None
menu = window.menuBar().addMenu("&File")
open_action = QAction("&Open")
def open_file():
global file_path
path = QFileDialog.getOpenFileName(window, "Open")[0]
if path:
text.setPlainText(open(path).read())
file_path = path
open_action.triggered.connect(open_file)
open_action.setShortcut(QKeySequence.Open)
menu.addAction(open_action)
save_action = QAction("&Save")
def save():
if file_path is None:
save_as()
else:
with open(file_path, "w") as f:
f.write(text.toPlainText())
text.document().setModified(False)
save_action.triggered.connect(save)
save_action.setShortcut(QKeySequence.Save)
menu.addAction(save_action)
save_as_action = QAction("Save &As...")
def save_as():
global file_path
path = QFileDialog.getSaveFileName(window, "Save As")[0]
if path:
file_path = path
save()
save_as_action.triggered.connect(save_as)
menu.addAction(save_as_action)
close = QAction("&Close")
close.triggered.connect(window.close)
menu.addAction(close)
help_menu = window.menuBar().addMenu("&Help")
about_action = QAction("&About")
help_menu.addAction(about_action)
def show_about_dialog():
text = "<center>" \
"<h1>Text Editor</h1>" \
"&#8291;" \
"<img src=%r>" \
"</center>" \
"<p>Version 31.4.159.265358<br/>" \
"Copyright &copy; Company Inc.</p>" \
% appctxt.get_resource("icon.svg")
about_dialog = QMessageBox(window)
about_dialog.setText(text)
about_dialog.exec_()
about_action.triggered.connect(show_about_dialog)
window.show()
exit_code = appctxt.app.exec_() # 2. Invoke appctxt.app.exec_()
sys.exit(exit_code)

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg id="svg11300" xmlns="http://www.w3.org/2000/svg" height="48" width="48" version="1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs id="defs">
<radialGradient id="R1" xlink:href="#G1" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(-2.7744 0 0 1.9697 112.76 -872.89)" r="117.14"/>
<linearGradient id="G1">
<stop id="s1" offset="0"/>
<stop id="s2" stop-opacity="0" offset="1"/>
</linearGradient>
<radialGradient id="R2" xlink:href="#G1" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(2.7744 0 0 1.9697 -1891.6 -872.89)" r="117.14"/>
<linearGradient id="G3" y2="609.51" gradientUnits="userSpaceOnUse" x2="302.86" gradientTransform="matrix(2.7744 0 0 1.9697 -1892.2 -872.89)" y1="366.65" x1="302.86">
<stop id="s3" stop-opacity="0" offset="0"/>
<stop id="s4" offset=".5"/>
<stop id="s5" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="G12" y2="6.8334" gradientUnits="userSpaceOnUse" x2="14.284" gradientTransform="matrix(1.1379 0 0 1 -2.6609 0)" y1="42.833" x1="21.043">
<stop id="s21" stop-color="#dfdfdf" offset="0"/>
<stop id="s22" stop-color="#fff" offset="1"/>
</linearGradient>
<linearGradient id="G13" y2="42.833" gradientUnits="userSpaceOnUse" x2="26.228" y1="28.083" x1="26.612">
<stop id="s17" stop-color="#939393" offset="0"/>
<stop id="s18" stop-color="#424242" offset="1"/>
</linearGradient>
<linearGradient id="G14" y2="7.5625" gradientUnits="userSpaceOnUse" x2="40.984" y1="7.5625" x1="6">
<stop id="s15" stop-color="#a3a4a0" offset="0"/>
<stop id="s16" stop-color="#888a85" offset="1"/>
</linearGradient>
<linearGradient id="G15" y2="22.251" gradientUnits="userSpaceOnUse" x2="50.988" gradientTransform="translate(-5.6693)" y1="17.376" x1="48.906">
<stop id="s12" stop-color="#ffd1d1" offset="0"/>
<stop id="s13" stop-color="#ff1d1d" offset=".5"/>
<stop id="s14" stop-color="#6f0000" offset="1"/>
</linearGradient>
<linearGradient id="G16" y2="22.625" gradientUnits="userSpaceOnUse" x2="47.688" gradientTransform="translate(-5.6693)" y1="19.812" x1="46">
<stop id="s10" stop-color="#c1c1c1" offset="0"/>
<stop id="s11" stop-color="#acacac" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient2990" gradientUnits="userSpaceOnUse" cy="27.641" cx="29.053" gradientTransform="matrix(2.9236 0 0 2.0297 -61.555 -27.884)" r="3.2409">
<stop id="s8" stop-color="#e7e2b8" offset="0"/>
<stop id="s9" stop-color="#e7e2b8" stop-opacity="0" offset="1"/>
</radialGradient>
<linearGradient id="G17" y2="30.703" gradientUnits="userSpaceOnUse" x2="25.515" gradientTransform="translate(-5.8255 .125)" y1="31.047" x1="25.719">
<stop id="s6" offset="0"/>
<stop id="s7" stop-color="#c9c9c9" offset="1"/>
</linearGradient>
<radialGradient id="R3" gradientUnits="userSpaceOnUse" cy="40.438" cx="23.562" gradientTransform="matrix(1 0 0 .34824 0 26.355)" r="19.562">
<stop id="s19" offset="0"/>
<stop id="s20" stop-opacity="0" offset="1"/>
</radialGradient>
</defs>
<g id="layer1">
<g id="g6707" transform="matrix(.024176 0 0 .020868 45.128 40.154)">
<rect id="rect6709" opacity=".4" height="478.36" width="1339.6" y="-150.7" x="-1559.3" fill="url(#G3)"/>
<path id="p1" opacity=".4" fill="url(#R2)" d="m-219.62-150.68v478.33c142.88 0.9 345.4-107.17 345.4-239.2 0-132.02-159.44-239.13-345.4-239.13z"/>
<path id="p2" opacity=".4" fill="url(#R1)" d="m-1559.3-150.68v478.33c-142.8 0.9-345.4-107.17-345.4-239.2 0-132.02 159.5-239.13 345.4-239.13z"/>
</g>
<path id="p3" fill="url(#G12)" d="m7.1639 4.5064h32.649c0.763 0 1.377 0.5324 1.377 1.1938l2.401 34.169 0.012 2.348c0 0.661-0.614 1.193-1.376 1.193h-37.477c-0.7625 0-1.3764-0.532-1.3764-1.193l-0.0112-2.167 2.425-34.35c0-0.6612 0.6139-1.1936 1.3765-1.1936z" stroke="url(#G13)"/>
<path id="p4" opacity=".31579" d="m43.125 40.438a19.562 6.8125 0 1 1 -39.125 0 19.562 6.8125 0 1 1 39.125 0z" transform="matrix(.61661 0 0 .44037 10.614 13.943)" fill="url(#R3)"/>
<rect id="rect2851" rx=".67938" ry=".67938" height="3.0715" width="39.048" y="39.868" x="3.977" fill="#a4a4a4"/>
<path id="p5" fill="#fff" d="m3.9268 40.443s0.1508-0.531 0.704-0.575h37.564c0.755 0 0.805 0.752 0.805 0.752s0.024-1.62-1.284-1.62h-36.412c-1.0055 0.088-1.3772 0.78-1.3772 1.443z"/>
<path id="p6" fill="url(#G14)" d="m6.25 5.7344l-0.25 4.3906s0.3125-1.125 1-1.125h33.125c0.703-0.0156 0.734 0.3125 0.859 0.8281l-0.25-3.875c-0.031-0.5469-0.218-0.9531-0.781-0.9531h-32.89c-0.4536 0-0.7661 0.3437-0.813 0.7344z"/>
<path id="p7" opacity=".43860" d="m7.8126 5.5405h31.132c0.722 0 1.303-0.1522 1.303 0.4741 0 0 2.274 33.008 2.274 33.008l0.1 2.709c0 0.626-0.139 0.644-0.861 0.644h-36.899c-0.4126 0-0.4194-0.106-0.4194-0.511l-0.0106-2.671 2.2961-33.148c0-0.6264 0.3625-0.5055 1.0845-0.5055z" stroke="#fff" fill="none"/>
<g id="g2950" stroke="#886f00" fill="#fce94f">
<rect id="rect2899" rx="1" ry="1" height="5" width="2" y="2.5" x="8.5"/>
<rect id="rect2901" rx="1" ry="1" height="5" width="2" y="2.5" x="12.5"/>
<rect id="rect2903" rx="1" ry="1" height="5" width="2" y="2.5" x="16.5"/>
<rect id="rect2905" rx="1" ry="1" height="5" width="2" y="2.5" x="20.5"/>
<rect id="rect2907" rx="1" ry="1" height="5" width="2" y="2.5" x="24.5"/>
<rect id="rect2909" rx="1" ry="1" height="5" width="2" y="2.5" x="28.5"/>
<rect id="rect2911" rx="1" ry="1" height="5" width="2" y="2.5" x="32.5"/>
<rect id="rect2913" rx="1" ry="1" height="5" width="2" y="2.5" x="36.5"/>
</g>
<g id="g2941">
<rect id="rect2927" opacity=".2807" height="1" width="29" y="12" x="9"/>
<rect id="rect2929" opacity=".2807" height="1" width="29" y="14.982" x="9"/>
<rect id="rect2931" opacity=".2807" height="1" width="13" y="18.004" x="9"/>
<rect id="rect2933" opacity=".2807" height="1" width="29" y="22.986" x="9"/>
<rect id="rect2935" opacity=".2807" height="1" width="29" y="26.008" x="9"/>
<rect id="rect2937" opacity=".2807" height="1" width="29" y="29.03" x="9"/>
<rect id="rect2939" opacity=".2807" height="1" width="8" y="32.052" x="9"/>
</g>
<path id="path2960" fill="#cb9022" d="m17.341 32.5l5.625-5.625 20.094-9.75c3.25-1.25 5.187 3.375 2.312 5l-20.031 9.375-8 1z" stroke="#5c410c"/>
<path id="p8" fill="url(#G15)" d="m38.331 20s1.437 0.094 2 1.344c0.579 1.288 0 2.656 0 2.656l5.031-2.469s1.452-0.881 0.656-2.843c-0.785-1.936-2.687-1.157-2.687-1.157l-5 2.469z"/>
<path id="p9" fill="url(#G16)" d="m38.331 20s1.437 0.094 2 1.344c0.579 1.288 0 2.656 0 2.656l2-1s0.827-1.319 0.218-2.688c-0.625-1.406-2.218-1.312-2.218-1.312l-2 1z"/>
<path id="p10" fill="url(#radialGradient2990)" d="m18.768 31.781l4.5-4.5c1.5 0.813 2.281 2.157 1.875 3.719l-6.375 0.781z"/>
<path id="p11" fill="url(#G17)" d="m20.112 30.375l-1.625 1.594 2.344-0.313c0.218-0.718-0.188-1.062-0.719-1.281z"/>
<path id="p12" fill-opacity=".36364" fill="#fff" d="m23.268 27.25l1.563 1.25 15.387-7.319c-0.444-0.856-1.242-1.084-1.903-1.162l-15.047 7.231z"/>
<path id="p13" fill-opacity=".36364" d="m25.143 31.062l0.188-0.75 15.231-7.129s-0.11 0.614-0.216 0.749l-15.203 7.13z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1,21 @@
# Qt Dark Theme
This example shows how Qt's style mechanisms can be used to set a dark theme. It adapts the text editor from [example 7](../07%20Qt%20Text%20Editor).
![Qt Dark Theme](qt-dark-theme.png)
As you can see in [`main.py`](main.py), this example uses `QApplication.setStyle(...)` and a `QPalette` to change the application's colors:
# Force the style to be the same on all OSs:
app.setStyle("Fusion")
# Now use a palette to switch to dark colors:
palette = QPalette()
palette.setColor(QPalette.Window, QColor(53, 53, 53))
palette.setColor(QPalette.WindowText, Qt.white)
...
app.setPalette(palette)
The rest of the code is the same as for the [original version of the text editor](../07%20Qt%20Text%20Editor).
To run this example yourself, please follow the [instructions in the README of this repository](https://github.com/1mh/pyqt-examples#running-the-examples).

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg id="svg11300" xmlns="http://www.w3.org/2000/svg" height="48" width="48" version="1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs id="defs">
<radialGradient id="R1" xlink:href="#G1" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(-2.7744 0 0 1.9697 112.76 -872.89)" r="117.14"/>
<linearGradient id="G1">
<stop id="s1" offset="0"/>
<stop id="s2" stop-opacity="0" offset="1"/>
</linearGradient>
<radialGradient id="R2" xlink:href="#G1" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(2.7744 0 0 1.9697 -1891.6 -872.89)" r="117.14"/>
<linearGradient id="G3" y2="609.51" gradientUnits="userSpaceOnUse" x2="302.86" gradientTransform="matrix(2.7744 0 0 1.9697 -1892.2 -872.89)" y1="366.65" x1="302.86">
<stop id="s3" stop-opacity="0" offset="0"/>
<stop id="s4" offset=".5"/>
<stop id="s5" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="G12" y2="6.8334" gradientUnits="userSpaceOnUse" x2="14.284" gradientTransform="matrix(1.1379 0 0 1 -2.6609 0)" y1="42.833" x1="21.043">
<stop id="s21" stop-color="#dfdfdf" offset="0"/>
<stop id="s22" stop-color="#fff" offset="1"/>
</linearGradient>
<linearGradient id="G13" y2="42.833" gradientUnits="userSpaceOnUse" x2="26.228" y1="28.083" x1="26.612">
<stop id="s17" stop-color="#939393" offset="0"/>
<stop id="s18" stop-color="#424242" offset="1"/>
</linearGradient>
<linearGradient id="G14" y2="7.5625" gradientUnits="userSpaceOnUse" x2="40.984" y1="7.5625" x1="6">
<stop id="s15" stop-color="#a3a4a0" offset="0"/>
<stop id="s16" stop-color="#888a85" offset="1"/>
</linearGradient>
<linearGradient id="G15" y2="22.251" gradientUnits="userSpaceOnUse" x2="50.988" gradientTransform="translate(-5.6693)" y1="17.376" x1="48.906">
<stop id="s12" stop-color="#ffd1d1" offset="0"/>
<stop id="s13" stop-color="#ff1d1d" offset=".5"/>
<stop id="s14" stop-color="#6f0000" offset="1"/>
</linearGradient>
<linearGradient id="G16" y2="22.625" gradientUnits="userSpaceOnUse" x2="47.688" gradientTransform="translate(-5.6693)" y1="19.812" x1="46">
<stop id="s10" stop-color="#c1c1c1" offset="0"/>
<stop id="s11" stop-color="#acacac" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient2990" gradientUnits="userSpaceOnUse" cy="27.641" cx="29.053" gradientTransform="matrix(2.9236 0 0 2.0297 -61.555 -27.884)" r="3.2409">
<stop id="s8" stop-color="#e7e2b8" offset="0"/>
<stop id="s9" stop-color="#e7e2b8" stop-opacity="0" offset="1"/>
</radialGradient>
<linearGradient id="G17" y2="30.703" gradientUnits="userSpaceOnUse" x2="25.515" gradientTransform="translate(-5.8255 .125)" y1="31.047" x1="25.719">
<stop id="s6" offset="0"/>
<stop id="s7" stop-color="#c9c9c9" offset="1"/>
</linearGradient>
<radialGradient id="R3" gradientUnits="userSpaceOnUse" cy="40.438" cx="23.562" gradientTransform="matrix(1 0 0 .34824 0 26.355)" r="19.562">
<stop id="s19" offset="0"/>
<stop id="s20" stop-opacity="0" offset="1"/>
</radialGradient>
</defs>
<g id="layer1">
<g id="g6707" transform="matrix(.024176 0 0 .020868 45.128 40.154)">
<rect id="rect6709" opacity=".4" height="478.36" width="1339.6" y="-150.7" x="-1559.3" fill="url(#G3)"/>
<path id="p1" opacity=".4" fill="url(#R2)" d="m-219.62-150.68v478.33c142.88 0.9 345.4-107.17 345.4-239.2 0-132.02-159.44-239.13-345.4-239.13z"/>
<path id="p2" opacity=".4" fill="url(#R1)" d="m-1559.3-150.68v478.33c-142.8 0.9-345.4-107.17-345.4-239.2 0-132.02 159.5-239.13 345.4-239.13z"/>
</g>
<path id="p3" fill="url(#G12)" d="m7.1639 4.5064h32.649c0.763 0 1.377 0.5324 1.377 1.1938l2.401 34.169 0.012 2.348c0 0.661-0.614 1.193-1.376 1.193h-37.477c-0.7625 0-1.3764-0.532-1.3764-1.193l-0.0112-2.167 2.425-34.35c0-0.6612 0.6139-1.1936 1.3765-1.1936z" stroke="url(#G13)"/>
<path id="p4" opacity=".31579" d="m43.125 40.438a19.562 6.8125 0 1 1 -39.125 0 19.562 6.8125 0 1 1 39.125 0z" transform="matrix(.61661 0 0 .44037 10.614 13.943)" fill="url(#R3)"/>
<rect id="rect2851" rx=".67938" ry=".67938" height="3.0715" width="39.048" y="39.868" x="3.977" fill="#a4a4a4"/>
<path id="p5" fill="#fff" d="m3.9268 40.443s0.1508-0.531 0.704-0.575h37.564c0.755 0 0.805 0.752 0.805 0.752s0.024-1.62-1.284-1.62h-36.412c-1.0055 0.088-1.3772 0.78-1.3772 1.443z"/>
<path id="p6" fill="url(#G14)" d="m6.25 5.7344l-0.25 4.3906s0.3125-1.125 1-1.125h33.125c0.703-0.0156 0.734 0.3125 0.859 0.8281l-0.25-3.875c-0.031-0.5469-0.218-0.9531-0.781-0.9531h-32.89c-0.4536 0-0.7661 0.3437-0.813 0.7344z"/>
<path id="p7" opacity=".43860" d="m7.8126 5.5405h31.132c0.722 0 1.303-0.1522 1.303 0.4741 0 0 2.274 33.008 2.274 33.008l0.1 2.709c0 0.626-0.139 0.644-0.861 0.644h-36.899c-0.4126 0-0.4194-0.106-0.4194-0.511l-0.0106-2.671 2.2961-33.148c0-0.6264 0.3625-0.5055 1.0845-0.5055z" stroke="#fff" fill="none"/>
<g id="g2950" stroke="#886f00" fill="#fce94f">
<rect id="rect2899" rx="1" ry="1" height="5" width="2" y="2.5" x="8.5"/>
<rect id="rect2901" rx="1" ry="1" height="5" width="2" y="2.5" x="12.5"/>
<rect id="rect2903" rx="1" ry="1" height="5" width="2" y="2.5" x="16.5"/>
<rect id="rect2905" rx="1" ry="1" height="5" width="2" y="2.5" x="20.5"/>
<rect id="rect2907" rx="1" ry="1" height="5" width="2" y="2.5" x="24.5"/>
<rect id="rect2909" rx="1" ry="1" height="5" width="2" y="2.5" x="28.5"/>
<rect id="rect2911" rx="1" ry="1" height="5" width="2" y="2.5" x="32.5"/>
<rect id="rect2913" rx="1" ry="1" height="5" width="2" y="2.5" x="36.5"/>
</g>
<g id="g2941">
<rect id="rect2927" opacity=".2807" height="1" width="29" y="12" x="9"/>
<rect id="rect2929" opacity=".2807" height="1" width="29" y="14.982" x="9"/>
<rect id="rect2931" opacity=".2807" height="1" width="13" y="18.004" x="9"/>
<rect id="rect2933" opacity=".2807" height="1" width="29" y="22.986" x="9"/>
<rect id="rect2935" opacity=".2807" height="1" width="29" y="26.008" x="9"/>
<rect id="rect2937" opacity=".2807" height="1" width="29" y="29.03" x="9"/>
<rect id="rect2939" opacity=".2807" height="1" width="8" y="32.052" x="9"/>
</g>
<path id="path2960" fill="#cb9022" d="m17.341 32.5l5.625-5.625 20.094-9.75c3.25-1.25 5.187 3.375 2.312 5l-20.031 9.375-8 1z" stroke="#5c410c"/>
<path id="p8" fill="url(#G15)" d="m38.331 20s1.437 0.094 2 1.344c0.579 1.288 0 2.656 0 2.656l5.031-2.469s1.452-0.881 0.656-2.843c-0.785-1.936-2.687-1.157-2.687-1.157l-5 2.469z"/>
<path id="p9" fill="url(#G16)" d="m38.331 20s1.437 0.094 2 1.344c0.579 1.288 0 2.656 0 2.656l2-1s0.827-1.319 0.218-2.688c-0.625-1.406-2.218-1.312-2.218-1.312l-2 1z"/>
<path id="p10" fill="url(#radialGradient2990)" d="m18.768 31.781l4.5-4.5c1.5 0.813 2.281 2.157 1.875 3.719l-6.375 0.781z"/>
<path id="p11" fill="url(#G17)" d="m20.112 30.375l-1.625 1.594 2.344-0.313c0.218-0.718-0.188-1.062-0.719-1.281z"/>
<path id="p12" fill-opacity=".36364" fill="#fff" d="m23.268 27.25l1.563 1.25 15.387-7.319c-0.444-0.856-1.242-1.084-1.903-1.162l-15.047 7.231z"/>
<path id="p13" fill-opacity=".36364" d="m25.143 31.062l0.188-0.75 15.231-7.129s-0.11 0.614-0.216 0.749l-15.203 7.13z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1,105 @@
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QKeySequence, QPalette, QColor
from PyQt5.QtCore import Qt
app = QApplication([])
# Force the style to be the same on all OSs:
app.setStyle("Fusion")
# Now use a palette to switch to dark colors:
palette = QPalette()
palette.setColor(QPalette.Window, QColor(53, 53, 53))
palette.setColor(QPalette.WindowText, Qt.white)
palette.setColor(QPalette.Base, QColor(25, 25, 25))
palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
palette.setColor(QPalette.ToolTipBase, Qt.white)
palette.setColor(QPalette.ToolTipText, Qt.white)
palette.setColor(QPalette.Text, Qt.white)
palette.setColor(QPalette.Button, QColor(53, 53, 53))
palette.setColor(QPalette.ButtonText, Qt.white)
palette.setColor(QPalette.BrightText, Qt.red)
palette.setColor(QPalette.Link, QColor(42, 130, 218))
palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
palette.setColor(QPalette.HighlightedText, Qt.black)
app.setPalette(palette)
# The rest of the code is the same as for the "normal" text editor.
app.setApplicationName("Text Editor")
text = QPlainTextEdit()
class MainWindow(QMainWindow):
def closeEvent(self, e):
if not text.document().isModified():
return
answer = QMessageBox.question(
window, None,
"You have unsaved changes. Save before closing?",
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
)
if answer & QMessageBox.Save:
save()
elif answer & QMessageBox.Cancel:
e.ignore()
window = MainWindow()
window.setCentralWidget(text)
file_path = None
menu = window.menuBar().addMenu("&File")
open_action = QAction("&Open")
def open_file():
global file_path
path = QFileDialog.getOpenFileName(window, "Open")[0]
if path:
text.setPlainText(open(path).read())
file_path = path
open_action.triggered.connect(open_file)
open_action.setShortcut(QKeySequence.Open)
menu.addAction(open_action)
save_action = QAction("&Save")
def save():
if file_path is None:
save_as()
else:
with open(file_path, "w") as f:
f.write(text.toPlainText())
text.document().setModified(False)
save_action.triggered.connect(save)
save_action.setShortcut(QKeySequence.Save)
menu.addAction(save_action)
save_as_action = QAction("Save &As...")
def save_as():
global file_path
path = QFileDialog.getSaveFileName(window, "Save As")[0]
if path:
file_path = path
save()
save_as_action.triggered.connect(save_as)
menu.addAction(save_as_action)
close = QAction("&Close")
close.triggered.connect(window.close)
menu.addAction(close)
help_menu = window.menuBar().addMenu("&Help")
about_action = QAction("&About")
help_menu.addAction(about_action)
def show_about_dialog():
text = "<center>" \
"<h1>Text Editor</h1>" \
"&#8291;" \
"<img src=icon.svg>" \
"</center>" \
"<p>Version 31.4.159.265358<br/>" \
"Copyright &copy; Company Inc.</p>"
QMessageBox.about(window, "About Text Editor", text)
about_action.triggered.connect(show_about_dialog)
window.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,9 @@
# QPainter Python example
This example application demonstrates how you can use [`QPainter`](https://doc.qt.io/qt-5/qpainter.html) to perform custom rendering in a widget. It turns the text editor from [example 7](../07%20Qt%20Text%20Editor) into an action shooter: When you click inside the editor with the mouse, bullet holes appear.
<p align="center"><img src="qpainter-python-example.png" alt="QPainter Python Example"></p>
The crucial steps of this example are to [override `mousePressEvent(...)`](main.py#L13-L17) to handle the user's clicks, and [`paintEvent(...)`](main.py#L18-L22) to draw the bullets. See the top of [`main.py`](main.py) for how these features work in detail.
To run this example yourself, please follow [these instructions](https://github.com/1mh/pyqt-examples#running-the-examples).

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg id="svg11300" xmlns="http://www.w3.org/2000/svg" height="48" width="48" version="1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs id="defs">
<radialGradient id="R1" xlink:href="#G1" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(-2.7744 0 0 1.9697 112.76 -872.89)" r="117.14"/>
<linearGradient id="G1">
<stop id="s1" offset="0"/>
<stop id="s2" stop-opacity="0" offset="1"/>
</linearGradient>
<radialGradient id="R2" xlink:href="#G1" gradientUnits="userSpaceOnUse" cy="486.65" cx="605.71" gradientTransform="matrix(2.7744 0 0 1.9697 -1891.6 -872.89)" r="117.14"/>
<linearGradient id="G3" y2="609.51" gradientUnits="userSpaceOnUse" x2="302.86" gradientTransform="matrix(2.7744 0 0 1.9697 -1892.2 -872.89)" y1="366.65" x1="302.86">
<stop id="s3" stop-opacity="0" offset="0"/>
<stop id="s4" offset=".5"/>
<stop id="s5" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="G12" y2="6.8334" gradientUnits="userSpaceOnUse" x2="14.284" gradientTransform="matrix(1.1379 0 0 1 -2.6609 0)" y1="42.833" x1="21.043">
<stop id="s21" stop-color="#dfdfdf" offset="0"/>
<stop id="s22" stop-color="#fff" offset="1"/>
</linearGradient>
<linearGradient id="G13" y2="42.833" gradientUnits="userSpaceOnUse" x2="26.228" y1="28.083" x1="26.612">
<stop id="s17" stop-color="#939393" offset="0"/>
<stop id="s18" stop-color="#424242" offset="1"/>
</linearGradient>
<linearGradient id="G14" y2="7.5625" gradientUnits="userSpaceOnUse" x2="40.984" y1="7.5625" x1="6">
<stop id="s15" stop-color="#a3a4a0" offset="0"/>
<stop id="s16" stop-color="#888a85" offset="1"/>
</linearGradient>
<linearGradient id="G15" y2="22.251" gradientUnits="userSpaceOnUse" x2="50.988" gradientTransform="translate(-5.6693)" y1="17.376" x1="48.906">
<stop id="s12" stop-color="#ffd1d1" offset="0"/>
<stop id="s13" stop-color="#ff1d1d" offset=".5"/>
<stop id="s14" stop-color="#6f0000" offset="1"/>
</linearGradient>
<linearGradient id="G16" y2="22.625" gradientUnits="userSpaceOnUse" x2="47.688" gradientTransform="translate(-5.6693)" y1="19.812" x1="46">
<stop id="s10" stop-color="#c1c1c1" offset="0"/>
<stop id="s11" stop-color="#acacac" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient2990" gradientUnits="userSpaceOnUse" cy="27.641" cx="29.053" gradientTransform="matrix(2.9236 0 0 2.0297 -61.555 -27.884)" r="3.2409">
<stop id="s8" stop-color="#e7e2b8" offset="0"/>
<stop id="s9" stop-color="#e7e2b8" stop-opacity="0" offset="1"/>
</radialGradient>
<linearGradient id="G17" y2="30.703" gradientUnits="userSpaceOnUse" x2="25.515" gradientTransform="translate(-5.8255 .125)" y1="31.047" x1="25.719">
<stop id="s6" offset="0"/>
<stop id="s7" stop-color="#c9c9c9" offset="1"/>
</linearGradient>
<radialGradient id="R3" gradientUnits="userSpaceOnUse" cy="40.438" cx="23.562" gradientTransform="matrix(1 0 0 .34824 0 26.355)" r="19.562">
<stop id="s19" offset="0"/>
<stop id="s20" stop-opacity="0" offset="1"/>
</radialGradient>
</defs>
<g id="layer1">
<g id="g6707" transform="matrix(.024176 0 0 .020868 45.128 40.154)">
<rect id="rect6709" opacity=".4" height="478.36" width="1339.6" y="-150.7" x="-1559.3" fill="url(#G3)"/>
<path id="p1" opacity=".4" fill="url(#R2)" d="m-219.62-150.68v478.33c142.88 0.9 345.4-107.17 345.4-239.2 0-132.02-159.44-239.13-345.4-239.13z"/>
<path id="p2" opacity=".4" fill="url(#R1)" d="m-1559.3-150.68v478.33c-142.8 0.9-345.4-107.17-345.4-239.2 0-132.02 159.5-239.13 345.4-239.13z"/>
</g>
<path id="p3" fill="url(#G12)" d="m7.1639 4.5064h32.649c0.763 0 1.377 0.5324 1.377 1.1938l2.401 34.169 0.012 2.348c0 0.661-0.614 1.193-1.376 1.193h-37.477c-0.7625 0-1.3764-0.532-1.3764-1.193l-0.0112-2.167 2.425-34.35c0-0.6612 0.6139-1.1936 1.3765-1.1936z" stroke="url(#G13)"/>
<path id="p4" opacity=".31579" d="m43.125 40.438a19.562 6.8125 0 1 1 -39.125 0 19.562 6.8125 0 1 1 39.125 0z" transform="matrix(.61661 0 0 .44037 10.614 13.943)" fill="url(#R3)"/>
<rect id="rect2851" rx=".67938" ry=".67938" height="3.0715" width="39.048" y="39.868" x="3.977" fill="#a4a4a4"/>
<path id="p5" fill="#fff" d="m3.9268 40.443s0.1508-0.531 0.704-0.575h37.564c0.755 0 0.805 0.752 0.805 0.752s0.024-1.62-1.284-1.62h-36.412c-1.0055 0.088-1.3772 0.78-1.3772 1.443z"/>
<path id="p6" fill="url(#G14)" d="m6.25 5.7344l-0.25 4.3906s0.3125-1.125 1-1.125h33.125c0.703-0.0156 0.734 0.3125 0.859 0.8281l-0.25-3.875c-0.031-0.5469-0.218-0.9531-0.781-0.9531h-32.89c-0.4536 0-0.7661 0.3437-0.813 0.7344z"/>
<path id="p7" opacity=".43860" d="m7.8126 5.5405h31.132c0.722 0 1.303-0.1522 1.303 0.4741 0 0 2.274 33.008 2.274 33.008l0.1 2.709c0 0.626-0.139 0.644-0.861 0.644h-36.899c-0.4126 0-0.4194-0.106-0.4194-0.511l-0.0106-2.671 2.2961-33.148c0-0.6264 0.3625-0.5055 1.0845-0.5055z" stroke="#fff" fill="none"/>
<g id="g2950" stroke="#886f00" fill="#fce94f">
<rect id="rect2899" rx="1" ry="1" height="5" width="2" y="2.5" x="8.5"/>
<rect id="rect2901" rx="1" ry="1" height="5" width="2" y="2.5" x="12.5"/>
<rect id="rect2903" rx="1" ry="1" height="5" width="2" y="2.5" x="16.5"/>
<rect id="rect2905" rx="1" ry="1" height="5" width="2" y="2.5" x="20.5"/>
<rect id="rect2907" rx="1" ry="1" height="5" width="2" y="2.5" x="24.5"/>
<rect id="rect2909" rx="1" ry="1" height="5" width="2" y="2.5" x="28.5"/>
<rect id="rect2911" rx="1" ry="1" height="5" width="2" y="2.5" x="32.5"/>
<rect id="rect2913" rx="1" ry="1" height="5" width="2" y="2.5" x="36.5"/>
</g>
<g id="g2941">
<rect id="rect2927" opacity=".2807" height="1" width="29" y="12" x="9"/>
<rect id="rect2929" opacity=".2807" height="1" width="29" y="14.982" x="9"/>
<rect id="rect2931" opacity=".2807" height="1" width="13" y="18.004" x="9"/>
<rect id="rect2933" opacity=".2807" height="1" width="29" y="22.986" x="9"/>
<rect id="rect2935" opacity=".2807" height="1" width="29" y="26.008" x="9"/>
<rect id="rect2937" opacity=".2807" height="1" width="29" y="29.03" x="9"/>
<rect id="rect2939" opacity=".2807" height="1" width="8" y="32.052" x="9"/>
</g>
<path id="path2960" fill="#cb9022" d="m17.341 32.5l5.625-5.625 20.094-9.75c3.25-1.25 5.187 3.375 2.312 5l-20.031 9.375-8 1z" stroke="#5c410c"/>
<path id="p8" fill="url(#G15)" d="m38.331 20s1.437 0.094 2 1.344c0.579 1.288 0 2.656 0 2.656l5.031-2.469s1.452-0.881 0.656-2.843c-0.785-1.936-2.687-1.157-2.687-1.157l-5 2.469z"/>
<path id="p9" fill="url(#G16)" d="m38.331 20s1.437 0.094 2 1.344c0.579 1.288 0 2.656 0 2.656l2-1s0.827-1.319 0.218-2.688c-0.625-1.406-2.218-1.312-2.218-1.312l-2 1z"/>
<path id="p10" fill="url(#radialGradient2990)" d="m18.768 31.781l4.5-4.5c1.5 0.813 2.281 2.157 1.875 3.719l-6.375 0.781z"/>
<path id="p11" fill="url(#G17)" d="m20.112 30.375l-1.625 1.594 2.344-0.313c0.218-0.718-0.188-1.062-0.719-1.281z"/>
<path id="p12" fill-opacity=".36364" fill="#fff" d="m23.268 27.25l1.563 1.25 15.387-7.319c-0.444-0.856-1.242-1.084-1.903-1.162l-15.047 7.231z"/>
<path id="p13" fill-opacity=".36364" d="m25.143 31.062l0.188-0.75 15.231-7.129s-0.11 0.614-0.216 0.749l-15.203 7.13z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1,103 @@
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtMultimedia import QSound
class PlainTextEdit(QPlainTextEdit):
def __init__(self):
super().__init__()
self._holes = []
self._bullet = QPixmap("bullet.png")
size = self._bullet.size()
self._offset = QPoint(size.width() / 2, size.height() / 2)
def mousePressEvent(self, e):
self._holes.append(e.pos())
super().mousePressEvent(e)
self.viewport().update()
QSound.play("shot.wav")
def paintEvent(self, e):
super().paintEvent(e)
painter = QPainter(self.viewport())
for hole in self._holes:
painter.drawPixmap(hole - self._offset, self._bullet)
app = QApplication([])
text = PlainTextEdit()
text.setPlainText("Click with the mouse below to shoot ;-)")
# The rest of the code is as for the normal version of the text editor.
class MainWindow(QMainWindow):
def closeEvent(self, e):
if not text.document().isModified():
return
answer = QMessageBox.question(
window, None,
"You have unsaved changes. Save before closing?",
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
)
if answer & QMessageBox.Save:
save()
elif answer & QMessageBox.Cancel:
e.ignore()
app.setApplicationName("Text Editor")
window = MainWindow()
window.setCentralWidget(text)
file_path = None
menu = window.menuBar().addMenu("&File")
open_action = QAction("&Open")
def open_file():
global file_path
path = QFileDialog.getOpenFileName(window, "Open")[0]
if path:
text.setPlainText(open(path).read())
file_path = path
open_action.triggered.connect(open_file)
open_action.setShortcut(QKeySequence.Open)
menu.addAction(open_action)
save_action = QAction("&Save")
def save():
if file_path is None:
save_as()
else:
with open(file_path, "w") as f:
f.write(text.toPlainText())
text.document().setModified(False)
save_action.triggered.connect(save)
save_action.setShortcut(QKeySequence.Save)
menu.addAction(save_action)
save_as_action = QAction("Save &As...")
def save_as():
global file_path
path = QFileDialog.getSaveFileName(window, "Save As")[0]
if path:
file_path = path
save()
save_as_action.triggered.connect(save_as)
menu.addAction(save_as_action)
close = QAction("&Close")
close.triggered.connect(window.close)
menu.addAction(close)
help_menu = window.menuBar().addMenu("&Help")
about_action = QAction("&About")
help_menu.addAction(about_action)
def show_about_dialog():
text = "<center>" \
"<h1>Text Editor</h1>" \
"&#8291;" \
"<img src=icon.svg>" \
"</center>" \
"<p>Version 31.4.159.265358<br/>" \
"Copyright &copy; Company Inc.</p>"
QMessageBox.about(window, "About Text Editor", text)
about_action.triggered.connect(show_about_dialog)
window.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

View File

@ -0,0 +1,37 @@
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from requests import Session
name = input("Please enter your name: ")
chat_url = "https://build-system.fman.io/chat"
server = Session()
# GUI:
app = QApplication([])
text_area = QPlainTextEdit()
text_area.setFocusPolicy(Qt.NoFocus)
message = QLineEdit()
layout = QVBoxLayout()
layout.addWidget(text_area)
layout.addWidget(message)
window = QWidget()
window.setLayout(layout)
window.show()
# Event handlers:
def display_new_messages():
new_message = server.get(chat_url).text
if new_message:
text_area.appendPlainText(new_message)
def send_message():
server.post(chat_url, {"name": name, "message": message.text()})
message.clear()
# Signals:
message.returnPressed.connect(send_message)
timer = QTimer()
timer.timeout.connect(display_new_messages)
timer.start(1000)
app.exec_()

View File

@ -0,0 +1,49 @@
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from requests import Session
from threading import Thread
from time import sleep
name = input("Please enter your name: ")
chat_url = "https://build-system.fman.io/chat"
server = Session()
# GUI:
app = QApplication([])
text_area = QPlainTextEdit()
text_area.setFocusPolicy(Qt.NoFocus)
message = QLineEdit()
layout = QVBoxLayout()
layout.addWidget(text_area)
layout.addWidget(message)
window = QWidget()
window.setLayout(layout)
window.show()
# Event handlers:
new_messages = []
def fetch_new_messages():
while True:
response = server.get(chat_url).text
if response:
new_messages.append(response)
sleep(.5)
thread = Thread(target=fetch_new_messages, daemon=True)
thread.start()
def display_new_messages():
while new_messages:
text_area.appendPlainText(new_messages.pop(0))
def send_message():
server.post(chat_url, {"name": name, "message": message.text()})
message.clear()
# Signals:
message.returnPressed.connect(send_message)
timer = QTimer()
timer.timeout.connect(display_new_messages)
timer.start(1000)
app.exec_()

View File

@ -0,0 +1,43 @@
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from requests import Session
from threading import Thread
from threadutil import run_in_main_thread
from time import sleep
name = input("Please enter your name: ")
chat_url = "https://build-system.fman.io/chat"
server = Session()
# GUI:
app = QApplication([])
text_area = QPlainTextEdit()
text_area.setFocusPolicy(Qt.NoFocus)
message = QLineEdit()
layout = QVBoxLayout()
layout.addWidget(text_area)
layout.addWidget(message)
window = QWidget()
window.setLayout(layout)
window.show()
append_message = run_in_main_thread(text_area.appendPlainText)
def fetch_new_messages():
while True:
response = server.get(chat_url).text
if response:
append_message(response)
sleep(.5)
def send_message():
server.post(chat_url, {"name": name, "message": message.text()})
message.clear()
# Signals:
message.returnPressed.connect(send_message)
thread = Thread(target=fetch_new_messages, daemon=True)
thread.start()
app.exec_()

View File

@ -0,0 +1,15 @@
# PyQt Thread example
This example shows how you can use threads to make your PyQt application more responsive. It's a fully functional chat client.
<p align="center"><img src="pyqt-thread-example.png" alt="PyQt Thread Example"></p>
To run this example, please follow [the instructions in the README of this repository](https://github.com/1mh/pyqt-examples#running-the-examples). Instead of `python main.py`, use `python` to execute one of the scripts described below. Eg. `python 01_single_threaded.py`.
To demonstrate the utility of threads, this directory contains multiple implementations of the chat client:
* [`01_single_threaded.py`](01_single_threaded.py) does not use threads. Once per second, it fetches the latest messages from the server. It does this in the main thread. While fetching messages, it's unable to process your key strokes. As a result, it sometimes lags a little as you type.
* [`02_multithreaded.py`](02_multithreaded.py) uses threads to fetch new messages in the background. It is considerably more responsive than the single threaded version.
* [`03_with_threadutil.py`](03_with_threadutil.py) is a variation of the multithreaded version. It extracts the logic necessary for communicating between threads into a separate module that you can use in your own apps, [`threadutil.py`](threadutil.py). For an even more powerful implementation, see [`threadutil_blocking.py`](threadutil_blocking.py). This is the code which [fman](https://fman.io) uses.
Most of the added complexity of the multithreaded versions comes from having to synchronize the main and background threads. In more detail: The _main thread_ is the thread in which Qt draws pixels on the screen, processes events such as mouse clicks, etc. In the examples here, there is a single background thread which fetches messages from the server. But what should happen when a new message arrives? The background thread can't just draw the text on the screen, because Qt might just be in the process of drawing itself. The answer is that the background thread must somehow get Qt to draw the text in the main thread. The second and third examples presented here ([`02_multithreaded.py`](02_multithreaded.py) and [`03_with_threadutil.py`](03_with_threadutil.py)) use different ways of achieving this. In the former, the background thread appends messages to a list, which is then processed in the main thread. The latter uses a custom mechanism that lets the background thread execute arbitrary code in the main thread. In this case, the "arbitrary code" draws the text for the new message on the screen.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,22 @@
from PyQt5.QtCore import QObject, pyqtSignal
class CurrentThread(QObject):
_on_execute = pyqtSignal(object, tuple, dict)
def __init__(self):
super(QObject, self).__init__()
self._on_execute.connect(self._execute_in_thread)
def execute(self, f, args, kwargs):
self._on_execute.emit(f, args, kwargs)
def _execute_in_thread(self, f, args, kwargs):
f(*args, **kwargs)
main_thread = CurrentThread()
def run_in_main_thread(f):
def result(*args, **kwargs):
main_thread.execute(f, args, kwargs)
return result

View File

@ -0,0 +1,112 @@
"""
A more powerful, synchronous implementation of run_in_main_thread(...).
It allows you to receive results from the function invocation:
@run_in_main_thread
def return_2():
return 2
# Runs the above function in the main thread and prints '2':
print(return_2())
"""
from functools import wraps
from PyQt5.QtCore import pyqtSignal, QObject, QThread
from PyQt5.QtWidgets import QApplication
from threading import Event, get_ident
def run_in_thread(thread_fn):
def decorator(f):
@wraps(f)
def result(*args, **kwargs):
thread = thread_fn()
return Executor.instance().run_in_thread(thread, f, args, kwargs)
return result
return decorator
def _main_thread():
app = QApplication.instance()
if app:
return app.thread()
# We reach here in tests that don't (want to) create a QApplication.
if int(QThread.currentThreadId()) == get_ident():
return QThread.currentThread()
raise RuntimeError('Could not determine main thread')
run_in_main_thread = run_in_thread(_main_thread)
def is_in_main_thread():
return QThread.currentThread() == _main_thread()
class Executor:
_INSTANCE = None
@classmethod
def instance(cls):
if cls._INSTANCE is None:
cls._INSTANCE = cls(QApplication.instance())
return cls._INSTANCE
def __init__(self, app):
self._pending_tasks = []
self._app_is_about_to_quit = False
app.aboutToQuit.connect(self._about_to_quit)
def _about_to_quit(self):
self._app_is_about_to_quit = True
for task in self._pending_tasks:
task.set_exception(SystemExit())
task.has_run.set()
def run_in_thread(self, thread, f, args, kwargs):
if QThread.currentThread() == thread:
return f(*args, **kwargs)
elif self._app_is_about_to_quit:
# In this case, the target thread's event loop most likely is not
# running any more. This would mean that our task (which is
# submitted to the event loop via signals/slots) is never run.
raise SystemExit()
task = Task(f, args, kwargs)
self._pending_tasks.append(task)
try:
receiver = Receiver(task)
receiver.moveToThread(thread)
sender = Sender()
sender.signal.connect(receiver.slot)
sender.signal.emit()
task.has_run.wait()
return task.result
finally:
self._pending_tasks.remove(task)
class Task:
def __init__(self, fn, args, kwargs):
self._fn = fn
self._args = args
self._kwargs = kwargs
self.has_run = Event()
self._result = self._exception = None
def __call__(self):
try:
self._result = self._fn(*self._args, **self._kwargs)
except Exception as e:
self._exception = e
finally:
self.has_run.set()
def set_exception(self, exception):
self._exception = exception
@property
def result(self):
if not self.has_run.is_set():
raise ValueError("Hasn't run.")
if self._exception:
raise self._exception
return self._result
class Sender(QObject):
signal = pyqtSignal()
class Receiver(QObject):
def __init__(self, callback, parent=None):
super().__init__(parent)
self.callback = callback
def slot(self):
self.callback()

View File

@ -0,0 +1,23 @@
# QTreeView example in Python
A _tree view_ is what's classicaly used to display files and folders: A hierarchical structure where items can be expanded. This example application shows how PyQt5's [`QTreeView`](https://doc.qt.io/qt-5/qtreeview.html) can be used to display your local files.
<p align="center"><img src="qtreeview-example-in-python.png" alt="QTreeView example in Python"></p>
As for the other examples in this repository, the code lies in [`main.py`](main.py). The important steps are:
model = QDirModel()
view = QTreeView()
view.setModel(model)
view.setRootIndex(model.index(home_directory))
view.show()
Both [`QDirModel`](https://doc.qt.io/qt-5/qdirmodel.html) and [`QTreeView`](https://doc.qt.io/qt-5/qtreeview.html) are a part of Qt's [Model/View framework](https://doc.qt.io/qt-5/model-view-programming.html). The idea is that the model provides data to the view, which then displays it. As you can see above, we first instantiate the model and the view, then connect the two via `.setModel(...)`. The `.setRootIndex(...)` call instructs the view to display the files in your home directory.
The nice thing about the Model/View distinction is that it lets you visualize the same data in different ways. For instance, you could replace the line `view = QTreeView()` above by the following to display a flat _list_ of your files instead:
view = QListView()
The next example, [PyQt5 QListview](../13%20PyQt5%20QListView), shows another way of using `QListView`.
To run this example yourself, please follow [the instructions in the README of this repository](https://github.com/1mh/pyqt-examples#running-the-examples).

View File

@ -0,0 +1,12 @@
from os.path import expanduser
from PyQt5.QtWidgets import *
home_directory = expanduser('~')
app = QApplication([])
model = QDirModel()
view = QTreeView()
view.setModel(model)
view.setRootIndex(model.index(home_directory))
view.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,18 @@
# PyQt5 QListView
This example shows how you can use a PyQt5 [`QListView`](https://doc.qt.io/qt-5/qlistview.html) to display a list.
<p align="center"><img src="pyqt5-qlistview.png" alt="PyQt5 QListView"></p>
It simply shows a static list of strings. Technically, the data is managed by Qt's [`QStringListModel`](https://doc.qt.io/qt-5/qstringlistmodel.html). The important steps of the [code](main.py) are:
```
model = QStringListModel(["An element", "Another element", "Yay! Another one."])
view = QListView()
view.setModel(model)
view.show()
```
This is very similar to the [previous example](../12%20QTreeView%20example%20in%20Python), where we displayed a tree view of files. The reason for this similarity is that both examples use Qt's Model/View framework. As an exercise for yourself, you might want to try using `QListView` instead of `QTreeView` in the previous example.
To run this example, please follow [the instructions in the README of this repository](https://github.com/1mh/pyqt-examples#running-the-examples).

View File

@ -0,0 +1,11 @@
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QStringListModel
app = QApplication([])
model = QStringListModel([
"An element", "Another element", "Yay! Another one."
])
view = QListView()
view.setModel(model)
view.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,52 @@
# QAbstractTableModel example
This [`QAbstractTableModel`](https://doc.qt.io/qt-5/qabstracttablemodel.html) example shows how you can define a custom Qt _model_ to display tabular data.
<p align="center"><img src="qabstracttablemodel-example.png" alt="QAbstractTableModel example"></p>
The data is a table of famous scientists. In Python, it can be written as follows:
```
headers = ["Scientist name", "Birthdate", "Contribution"]
rows = [("Newton", "1643-01-04", "Classical mechanics"),
("Einstein", "1879-03-14", "Relativity"),
("Darwin", "1809-02-12", "Evolution")]
```
To make Qt display these data in a table, we need to answer the following questions:
1. How many rows are there?
2. How many columns?
3. What's the value of each cell?
4. What are the (column) headers?
We do this by subclassing `QAbstractTableModel`. This lets us answer each of the above questions by implementing a corresponding method:
```
class TableModel(QAbstractTableModel):
def rowCount(self, parent):
# How many rows are there?
return len(rows)
def columnCount(self, parent):
# How many columns?
return len(headers)
def data(self, index, role):
if role != Qt.DisplayRole:
return QVariant()
# What's the value of the cell at the given index?
return rows[index.row()][index.column()]
def headerData(self, section, orientation, role:
if role != Qt.DisplayRole or orientation != Qt.Horizontal:
return QVariant()
# What's the header for the given column?
return headers[section]
```
Once we have this model, we can instantiate it, connect it to a `QTableView` and show it in a window:
model = TableModel()
view = QTableView()
view.setModel(model)
view.show()
The full code is in [`main.py`](main.py). For instructions how to run it, please see [the instructions in the README of this repository](https://github.com/1mh/pyqt-examples#running-the-examples).

View File

@ -0,0 +1,28 @@
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
headers = ["Scientist name", "Birthdate", "Contribution"]
rows = [("Newton", "1643-01-04", "Classical mechanics"),
("Einstein", "1879-03-14", "Relativity"),
("Darwin", "1809-02-12", "Evolution")]
class TableModel(QAbstractTableModel):
def rowCount(self, parent):
return len(rows)
def columnCount(self, parent):
return len(headers)
def data(self, index, role):
if role != Qt.DisplayRole:
return QVariant()
return rows[index.row()][index.column()]
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole or orientation != Qt.Horizontal:
return QVariant()
return headers[section]
app = QApplication([])
model = TableModel()
view = QTableView()
view.setModel(model)
view.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1 @@
*.db

View File

@ -0,0 +1,40 @@
# PyQt database example
This example shows how you can connect to a database from a PyQt application.
<p align="center"><img src="pyqt-database-example.png" alt="PyQt database example"></p>
The screenshot shows a table of project data that comes from a SQL database. One of the projects is real ;-) (Though the income is made up.)
There are many different database systems: MySQL, PostgreSQL, etc. For simplicity, this example uses SQLite because it ships with Python and doesn't require separate installation.
The default way of connecting to a database in Python is the [Database API v2.0](https://www.python.org/dev/peps/pep-0249/). You can see an example of its use in [`initdb.py`](initdb.py). Essentially, you use `.connect(...)` to connect to a database, `.cursor()` to obtain a cursor for data querying / manipulation, and `.commit()` to save any changes you made:
import sqlite3
connection = sqlite3.connect("projects.db")
cursor = connection.cursor()
cursor.execute("CREATE TABLE projects ...")
cursor.execute("INSERT INTO projects ...")
connection.commit()
The above code creates the SQLite file `projects.db` with a copy of the data shown in the screenshot.
Qt also has its own facilities for connecting to a database. You can see this in [`main.py`](main.py), where we open the `projects.db` file created above and display its data:
```
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("projects.db")
db.open()
model = QSqlTableModel(None, db)
model.setTable("projects")
model.select()
view = QTableView()
view.setModel(model)
view.show()
```
As in [previous examples](../12%20QTreeView%20example%20in%20Python), this uses Qt's Model/View framework to separate the two concerns of obtaining and displaying the data: We use `model` to load the database, and `view` to display it.
To run this example yourself, first follow [these instructions](https://github.com/1mh/pyqt-examples#running-the-examples). Then invoke `python initdb.py` to initialize the database. After that, you can execute `python main.py` to start the sample application.
While we use SQLite here, you can easily use other database systems as well. For instance, you could use PostgreSQL via the [psycopg2](http://initd.org/psycopg/) library.

View File

@ -0,0 +1,13 @@
import sqlite3
connection = sqlite3.connect("projects.db")
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE projects
(url TEXT, descr TEXT, income INTEGER)
""")
cursor.execute("""INSERT INTO projects VALUES
('giraffes.io', 'Uber, but with giraffes', 1900),
('dronesweaters.com', 'Clothes for cold drones', 3000),
('hummingpro.io', 'Online humming courses', 120000)
""")
connection.commit()

View File

@ -0,0 +1,21 @@
from os.path import exists
from PyQt5.QtWidgets import *
from PyQt5.QtSql import *
import sys
if not exists("projects.db"):
print("File projects.db does not exist. Please run initdb.py.")
sys.exit()
app = QApplication([])
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("projects.db")
db.open()
model = QSqlTableModel(None, db)
model.setTable("projects")
model.select()
view = QTableView()
view.setModel(model)
view.show()
app.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

3
src/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
fbs==0.8.3
PyQt5==5.9.2
requests