diff options
Diffstat (limited to 'sources/pyside6/doc/tutorials/basictutorial')
35 files changed, 1775 insertions, 0 deletions
diff --git a/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.png b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.png Binary files differnew file mode 100644 index 000000000..bfdc23fe0 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst new file mode 100644 index 000000000..c5464640b --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst @@ -0,0 +1,95 @@ +Using a Simple Button +===================== + +In this tutorial, we'll show you how to handle **signals and slots** +using Qt for Python. **Signals and slots** is a Qt feature that lets +your graphical widgets communicate with other graphical widgets or +your python code. Our application creates a button that logs the +`Button clicked, Hello!` message to the python console each time you +click it. + +Let's start by importing the necessary PySide6 classes and python +`sys` module: +:: + + import sys + from PySide6.QtWidgets import QApplication, QPushButton + from PySide6.QtCore import Slot + +Let's also create a python function that logs the message to the +console: +:: + + # Greetings + @Slot() + def say_hello(): + print("Button clicked, Hello!") + +.. note:: The `@Slot()` is a decorator that identifies a function as + a slot. It is not important to understand why for now, + but use it always to avoid unexpected behavior. + +Now, as mentioned in previous examples you must create the +`QApplication` to run your PySide6 code: +:: + + # Create the Qt Application + app = QApplication(sys.argv) + +Let's create the clickable button, which is a `QPushButton` instance. +To label the button, we pass a python string to the constructor: +:: + + # Create a button + button = QPushButton("Click me") + +Before we show the button, we must connect it to the `say_hello()` +function that we defined earlier. There are two ways of doing this; +using the old style or the new style, which is more pythonic. Let's +use the new style in this case. You can find more information about +both these styles in the +`Signals and Slots in PySide6 <https://wiki.qt.io/Qt_for_Python_Signals_and_Slots>`_ +wiki page. + +The `QPushButton` has a predefined signal called **clicked**, which +is triggered every time the button is clicked. We'll connect this +signal to the `say_hello()` function: +:: + + # Connect the button to the function + button.clicked.connect(say_hello) + +Finally, we show the button and start the Qt main loop: + +.. code-block:: python + + # Show the button + button.show() + # Run the main Qt loop + app.exec() + +Here is the complete code for this example: + +.. code-block:: python + + import sys + from PySide6.QtWidgets import QApplication, QPushButton + from PySide6.QtCore import Slot + + @Slot() + def say_hello(): + print("Button clicked, Hello!") + + # Create the Qt Application + app = QApplication(sys.argv) + # Create a button, connect it and show it + button = QPushButton("Click me") + button.clicked.connect(say_hello) + button.show() + # Run the main Qt loop + app.exec() + +After a few clicks, you will get something like this on your terminal: + +.. image:: clickablebutton.png + :alt: Clickable Button Example diff --git a/sources/pyside6/doc/tutorials/basictutorial/dialog.png b/sources/pyside6/doc/tutorials/basictutorial/dialog.png Binary files differnew file mode 100644 index 000000000..ad5690927 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/dialog.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/dialog.rst b/sources/pyside6/doc/tutorials/basictutorial/dialog.rst new file mode 100644 index 000000000..b7712672b --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/dialog.rst @@ -0,0 +1,149 @@ +Creating a Dialog Application +============================= + +This tutorial shows how to build a simple dialog with some +basic widgets. The idea is to let users provide their name +in a ``QLineEdit``, and the dialog greets them on click of a +``QPushButton``. + +Let us just start with a simple stub that creates and shows +a dialog. This stub is updated during the course of this +tutorial, but you can use this stub as is if you need to: +:: + + import sys + from PySide6.QtWidgets import QApplication, QDialog, QLineEdit, QPushButton + + class Form(QDialog): + + def __init__(self, parent=None): + super(Form, self).__init__(parent) + self.setWindowTitle("My Form") + + + if __name__ == '__main__': + # Create the Qt Application + app = QApplication(sys.argv) + # Create and show the form + form = Form() + form.show() + # Run the main Qt loop + sys.exit(app.exec()) + +The imports aren't new to you, the same for the creation of the +``QApplication`` and the execution of the Qt main loop. +The only novelty here is the **class definition**. + +You can create any class that subclasses PySide6 widgets. +In this case, we are subclassing ``QDialog`` to define a custom +dialog, which we name as **Form**. We have also implemented the +``init()`` method that calls the ``QDialog``'s init method with the +parent widget, if any. Also, the new ``setWindowTitle()`` method +just sets the title of the dialog window. In ``main()``, you can see +that we are creating a *Form object* and showing it to the world. + +Create the Widgets +------------------ + +We are going to create two widgets: a ``QLineEdit`` where users can +enter their name, and a ``QPushButton`` that prints the contents of +the ``QLineEdit``. +So, let's add the following code to the ``init()`` method of our Form: +:: + + # Create widgets + self.edit = QLineEdit("Write my name here..") + self.button = QPushButton("Show Greetings") + +It's obvious from the code that both widgets will show the corresponding +texts. + +Create a layout to organize the Widgets +--------------------------------------- + +Qt comes with layout-support that helps you organize the widgets +in your application. In this case, let's use ``QVBoxLayout`` to lay out +the widgets vertically. Add the following code to the ``init()`` method, +after creating the widgets: +:: + + # Create layout and add widgets + layout = QVBoxLayout(self) + layout.addWidget(self.edit) + layout.addWidget(self.button) + +So, we create the layout, add the widgets with ``addWidget()``. + +Create the function to greet and connect the Button +--------------------------------------------------- + +Finally, we just have to add a function to our custom **Form** +and *connect* our button to it. Our function will be a part of +the Form, so you have to add it after the ``init()`` function: +:: + + # Greets the user + def greetings(self): + print(f"Hello {self.edit.text()}") + +Our function just prints the contents of the ``QLineEdit`` to the +python console. We have access to the text by means of the +``QLineEdit.text()`` method. + +Now that we have everything, we just need to *connect* the +``QPushButton`` to the ``Form.greetings()`` method. To do so, add the +following line to the ``init()`` method: +:: + + # Add button signal to greetings slot + self.button.clicked.connect(self.greetings) + +Once executed, you can enter your name in the ``QLineEdit`` and watch +the console for greetings. + +Complete code +------------- + +Here is the complete code for this tutorial: + +.. code-block:: python + + import sys + from PySide6.QtWidgets import (QLineEdit, QPushButton, QApplication, + QVBoxLayout, QDialog) + + class Form(QDialog): + + def __init__(self, parent=None): + super(Form, self).__init__(parent) + # Create widgets + self.edit = QLineEdit("Write my name here") + self.button = QPushButton("Show Greetings") + # Create layout and add widgets + layout = QVBoxLayout() + layout.addWidget(self.edit) + layout.addWidget(self.button) + # Set dialog layout + self.setLayout(layout) + # Add button signal to greetings slot + self.button.clicked.connect(self.greetings) + + # Greets the user + def greetings(self): + print(f"Hello {self.edit.text()}") + + if __name__ == '__main__': + # Create the Qt Application + app = QApplication(sys.argv) + # Create and show the form + form = Form() + form.show() + # Run the main Qt loop + sys.exit(app.exec()) + + +When you execute the code, and write down your name, +the button will display messages on the terminal: + +.. image:: dialog.png + :alt: Simple Dialog Example diff --git a/sources/pyside6/doc/tutorials/basictutorial/greenapplication.png b/sources/pyside6/doc/tutorials/basictutorial/greenapplication.png Binary files differnew file mode 100644 index 000000000..29ea0a701 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/greenapplication.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons.png b/sources/pyside6/doc/tutorials/basictutorial/icons.png Binary files differnew file mode 100644 index 000000000..a5a554eba --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons.zip b/sources/pyside6/doc/tutorials/basictutorial/icons.zip Binary files differnew file mode 100644 index 000000000..e279e37b8 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons.zip diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/forward.png b/sources/pyside6/doc/tutorials/basictutorial/icons/forward.png Binary files differnew file mode 100644 index 000000000..c7a532dfe --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/forward.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/pause.png b/sources/pyside6/doc/tutorials/basictutorial/icons/pause.png Binary files differnew file mode 100644 index 000000000..d0beadb43 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/pause.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/play.png b/sources/pyside6/doc/tutorials/basictutorial/icons/play.png Binary files differnew file mode 100644 index 000000000..345685337 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/play.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/previous.png b/sources/pyside6/doc/tutorials/basictutorial/icons/previous.png Binary files differnew file mode 100644 index 000000000..979f18565 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/previous.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/stop.png b/sources/pyside6/doc/tutorials/basictutorial/icons/stop.png Binary files differnew file mode 100644 index 000000000..1e88ded3a --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/stop.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/player-new.png b/sources/pyside6/doc/tutorials/basictutorial/player-new.png Binary files differnew file mode 100644 index 000000000..8e45c757d --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/player-new.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/player.png b/sources/pyside6/doc/tutorials/basictutorial/player.png Binary files differnew file mode 100644 index 000000000..0563d3223 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/player.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/qml.rst b/sources/pyside6/doc/tutorials/basictutorial/qml.rst new file mode 100644 index 000000000..49cd3d94a --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/qml.rst @@ -0,0 +1,79 @@ +Your First QtQuick/QML Application +********************************** + +QML_ is a declarative language that lets you develop applications +faster than with traditional languages. It is ideal for designing the +UI of your application because of its declarative nature. In QML, a +user interface is specified as a tree of objects with properties. In +this tutorial, we will show how to make a simple "Hello World" +application with PySide6 and QML. + +A PySide6/QML application consists, at least, of two different files - +a file with the QML description of the user interface, and a python file +that loads the QML file. To make things easier, let's save both files in +the same directory. + +Here is a simple QML file called :code:`view.qml`: + +.. code-block:: javascript + + import QtQuick + + Rectangle { + id: main + width: 200 + height: 200 + color: "green" + + Text { + text: "Hello World" + anchors.centerIn: main + } + } + +We start by importing :code:`QtQuick`, which is a QML module. + +The rest of the QML code is pretty straightforward for those who +have previously used HTML or XML files. Basically, we are creating +a green rectangle with the size `200*200`, and adding a Text element +that reads "Hello World". The code :code:`anchors.centerIn: main` makes +the text appear centered within the object with :code:`id: main`, +which is the Rectangle in this case. + +Now, let's see how the code looks on the PySide6. +Let's call it :code:`main.py`: + +.. code-block:: python + + import sys + from PySide6.QtWidgets import QApplication + from PySide6.QtQuick import QQuickView + + if __name__ == "__main__": + app = QApplication() + view = QQuickView() + + view.setSource("view.qml") + view.show() + sys.exit(app.exec()) + +If you are already familiar with PySide6 and have followed our +tutorials, you have already seen much of this code. +The only novelties are that you must :code:`import QtQuick` and set the +source of the :code:`QQuickView` object to the URL of your QML file. +Then, similar to what you do with any Qt widget, you call +:code:`QQuickView.show()`. + +.. note:: If you are programming for desktop, you should consider + adding `view.setResizeMode(QQuickView.SizeRootObjectToView)` + before showing the view. + +When you execute the :code:`main.py` script, you will see the following +application: + + +.. image:: greenapplication.png + :alt: Simple QML and Python example + :align: center + +.. _QML: https://doc.qt.io/qt-6/qmlapplications.html diff --git a/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst new file mode 100644 index 000000000..858293beb --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst @@ -0,0 +1,173 @@ +.. _using_qrc_files: + +Using ``.qrc`` Files (``pyside6-rcc``) +************************************** + +The `Qt Resource System`_ is a mechanism for storing binary files +in an application. + +The files will be embedded into the application and be acessible for the +``QFile`` class and the constructors of the ``QIcon`` and ``QPixmap`` +classes taking a file name by using a special file name starting with ``:/``. + +The most common uses are for custom images, icons, fonts, among others. + +In this tutorial you will learn how to load custom images as button icons. + +For inspiration, we will try to adapt the multimedia player example +from Qt. + +As you can see on the following image, the ``QPushButton`` that are used +for the media actions (play, pause, stop, and so on) are using the +default icons meant for such actions. + +.. image:: player.png + :alt: Multimedia Player Qt Example + +You could make the application more attractive by designing the icons, +but in case you don't want to design them, you can download and use them. + +:download:`Download icons <icons.zip>` + +.. image:: icons.png + :alt: New Multimedia icons + +You can find more information about the ``rcc`` command, and ``.qrc`` file +format, and the resource system in general in the `Qt Resource System`_ +site. + + +The ``.qrc`` file +================= + +Before running any command, add information about the resources to a ``.qrc`` +file. +In the following example, notice how the resources are listed in ``icons.qrc`` + +:: + + <!DOCTYPE RCC><RCC version="1.0"> + <qresource> + <file>icons/play.png</file> + <file>icons/pause.png</file> + <file>icons/stop.png</file> + <file>icons/previous.png</file> + <file>icons/forward.png</file> + </qresource> + </RCC> + + +Generating a Python file +========================= + +Now that the ``icons.qrc`` file is ready, use the ``pyside6-rcc`` tool to generate +a Python class containing the binary information about the resources + +To do this, we need to run:: + + pyside6-rcc icons.qrc -o rc_icons.py + +The ``-o`` option lets you specify the output filename, +which is ``rc_icons.py`` in this case. + +To use the generated file, add the following import at the top of your main Python file:: + + import rc_icons + + +Changes in the code +=================== + +As you are modifying an existing example, you need to modify the following +lines: + +.. code-block:: python + + from PySide6.QtGui import QIcon, QKeySequence + playIcon = self.style().standardIcon(QStyle.SP_MediaPlay) + previousIcon = self.style().standardIcon(QStyle.SP_MediaSkipBackward) + pauseIcon = self.style().standardIcon(QStyle.SP_MediaPause) + nextIcon = self.style().standardIcon(QStyle.SP_MediaSkipForward) + stopIcon = self.style().standardIcon(QStyle.SP_MediaStop) + +and replace them with the following: + +.. code-block:: python + + from PySide6.QtGui import QIcon, QKeySequence, QPixmap + playIcon = QIcon(QPixmap(":/icons/play.png")) + previousIcon = QIcon(QPixmap(":/icons/previous.png")) + pauseIcon = QIcon(QPixmap(":/icons/pause.png")) + nextIcon = QIcon(QPixmap(":/icons/forward.png")) + stopIcon = QIcon(QPixmap(":/icons/stop.png")) + +This ensures that the new icons are used instead of the default ones provided +by the application theme. +Notice that the lines are not consecutive, but are in different parts +of the file. + +After all your imports, add the following + +.. code-block:: python + + import rc_icons + +Now, the constructor of your class should look like this: + +.. code-block:: python + + def __init__(self): + super(MainWindow, self).__init__() + + self.playlist = QMediaPlaylist() + self.player = QMediaPlayer() + + toolBar = QToolBar() + self.addToolBar(toolBar) + + fileMenu = self.menuBar().addMenu("&File") + openAction = QAction(QIcon.fromTheme("document-open"), + "&Open...", self, shortcut=QKeySequence.Open, + triggered=self.open) + fileMenu.addAction(openAction) + exitAction = QAction(QIcon.fromTheme("application-exit"), "E&xit", + self, shortcut="Ctrl+Q", triggered=self.close) + fileMenu.addAction(exitAction) + + playMenu = self.menuBar().addMenu("&Play") + playIcon = QIcon(QPixmap(":/icons/play.png")) + self.playAction = toolBar.addAction(playIcon, "Play") + self.playAction.triggered.connect(self.player.play) + playMenu.addAction(self.playAction) + + previousIcon = QIcon(QPixmap(":/icons/previous.png")) + self.previousAction = toolBar.addAction(previousIcon, "Previous") + self.previousAction.triggered.connect(self.previousClicked) + playMenu.addAction(self.previousAction) + + pauseIcon = QIcon(QPixmap(":/icons/pause.png")) + self.pauseAction = toolBar.addAction(pauseIcon, "Pause") + self.pauseAction.triggered.connect(self.player.pause) + playMenu.addAction(self.pauseAction) + + nextIcon = QIcon(QPixmap(":/icons/forward.png")) + self.nextAction = toolBar.addAction(nextIcon, "Next") + self.nextAction.triggered.connect(self.playlist.next) + playMenu.addAction(self.nextAction) + + stopIcon = QIcon(QPixmap(":/icons/stop.png")) + self.stopAction = toolBar.addAction(stopIcon, "Stop") + self.stopAction.triggered.connect(self.player.stop) + playMenu.addAction(self.stopAction) + + # many lines were omitted + +Executing the example +===================== + +Run the application by calling ``python main.py`` to checkout the new icon-set: + +.. image:: player-new.png + :alt: New Multimedia Player Qt Example + +.. _`Qt Resource System`: https://doc.qt.io/qt-5/resources.html diff --git a/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst new file mode 100644 index 000000000..0bfd9e276 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst @@ -0,0 +1,263 @@ +.. _signals-and-slots: + +Signals and Slots +================= + +Due to the nature of Qt, ``QObject``\s require a way to communicate, and that's +the reason for this mechanism to be a **central feature of Qt**. + +In simple terms, you can understand **Signal and Slots** in the same way you +interact with the lights in your house. When you move the light switch +(signal) you get a result which may be that your light bulbs are switched +on/off (slot). + +While developing interfaces, you can get a real example by the effect of +clicking a button: the 'click' will be the signal, and the slot will be what +happens when that button is clicked, like closing a window, saving a document, +etc. + +.. note:: + If you have experience with other frameworks or toolkits, it's likely + that you read a concept called 'callback'. Leaving the implementation + details aside, a callback will be related to a notification function, + passing a pointer to a function in case it's required due to the events + that happen in your program. This approach might sound similar, but + there are essential differences that make it an unintuitive approach, + like ensuring the type correctness of callback arguments, and some others. + +All classes that inherit from ``QObject`` or one of its subclasses, like +``QWidget`` can contain signals and slots. **Signals are emitted by objects** +when they change their state in a way that may be interesting to other objects. +This is all the object does to communicate. It does not know or care whether +anything is receiving the signals it emits. This is true information +encapsulation, and ensures that the object can be used as a software component. + +**Slots can be used for receiving signals**, but they are also normal member +functions. Just as an object does not know if anything receives its signals, +a slot does not know if it has any signals connected to it. This ensures that +truly independent components can be created with Qt. + +You can connect as many signals as you want to a single slot, and a signal can +be connected to as many slots as you need. It is even possible to connect +a signal directly to another signal. (This will emit the second signal +immediately whenever the first is emitted.) + +Qt's widgets have many predefined signals and slots. For example, +``QAbstractButton`` (base class of buttons in Qt) has a ``clicked()`` +signal and ``QLineEdit`` (single line input field) has a slot named +``clear()``. So, a text input field with a button to clear the text +could be implemented by placing a ``QToolButton`` to the right of the +``QLineEdit`` and connecting its ``clicked()`` signal to the slot +``clear()``. This is done using the ``connect()`` method of the signal: + +.. code-block:: python + + button = QToolButton() + line_edit = QLineEdit() + button.clicked.connect(line_edit.clear) + +``connect()`` returns a ``QMetaObject.Connection`` object, which can be +used with the ``disconnect()`` method to sever the connection. + +Signals can also be connected to free functions: + +.. code-block:: python + + import sys + from PySide6.QtWidgets import QApplication, QPushButton + + + def function(): + print("The 'function' has been called!") + + app = QApplication() + button = QPushButton("Call function") + button.clicked.connect(function) + button.show() + sys.exit(app.exec()) + +Connections can be spelled out in code or, for widget forms, +designed in the +`Signal-Slot Editor <https://doc.qt.io/qt-6/designer-connection-mode.html>`_ +of *Qt Widgets Designer*. + +The Signal Class +---------------- + +When writing classes in Python, signals are declared as class level +variables of the class ``QtCore.Signal()``. A QWidget-based button +that emits a ``clicked()`` signal could look as +follows: + +.. code-block:: python + + from PySide6.QtCore import Qt, Signal + from PySide6.QtWidgets import QWidget + + class Button(QWidget): + + clicked = Signal(Qt.MouseButton) + + ... + + def mousePressEvent(self, event): + self.clicked.emit(event.button()) + +The constructor of ``Signal`` takes a tuple or a list of Python types +and C types: + +.. code-block:: python + + signal1 = Signal(int) # Python types + signal2 = Signal(QUrl) # Qt Types + signal3 = Signal(int, str, int) # more than one type + signal4 = Signal((float,), (QDate,)) # optional types + +In addition to that, it can receive also a named argument ``name`` that defines +the signal name. If nothing is passed, the new signal will have the same name +as the variable that it is being assigned to. + +.. code-block:: python + + # TODO + signal5 = Signal(int, name='rangeChanged') + # ... + rangeChanged.emit(...) + +Another useful option of ``Signal`` is the arguments name, +useful for QML applications to refer to the emitted values by name: + +.. code-block:: python + + sumResult = Signal(int, arguments=['sum']) + +.. code-block:: javascript + + Connections { + target: ... + function onSumResult(sum) { + // do something with 'sum' + } + + +.. _slot-decorator: + +The Slot Class +-------------- + +Slots in QObject-derived classes should be indicated by the decorator +``@QtCore.Slot()``. Again, to define a signature just pass the types +similar to the ``QtCore.Signal()`` class. + +.. code-block:: python + + @Slot(str) + def slot_function(self, s): + ... + + +``Slot()`` also accepts a ``name`` and a ``result`` keyword. +The ``result`` keyword defines the type that will be returned and can be a C or +Python type. The ``name`` keyword behaves the same way as in ``Signal()``. If +nothing is passed as name then the new slot will have the same name as the +function that is being decorated. + +We recommend marking all methods used by signal connections with a +``@QtCore.Slot()`` decorator. Not doing causes run-time overhead due to the +method being added to the ``QMetaObject`` when creating the connection. This is +particularly important for ``QObject`` classes registered with QML, where +missing decorators can introduce bugs. + +Missing decorators can be diagnosed by setting activating warnings of the +logging category ``qt.pyside.libpyside``; for example by setting the +environment variable: + +.. code-block:: bash + + export QT_LOGGING_RULES="qt.pyside.libpyside.warning=true" + +.. _overloading-signals-and-slots: + +Overloading Signals and Slots with Different Types +-------------------------------------------------- + +It is actually possible to use signals and slots of the same name with different +parameter type lists. This is legacy from Qt 5 and not recommended for new code. +In Qt 6, signals have distinct names for different types. + +The following example uses two handlers for a Signal and a Slot to showcase +the different functionality. + +.. code-block:: python + + import sys + from PySide6.QtWidgets import QApplication, QPushButton + from PySide6.QtCore import QObject, Signal, Slot + + + class Communicate(QObject): + # create two new signals on the fly: one will handle + # int type, the other will handle strings + speak = Signal((int,), (str,)) + + def __init__(self, parent=None): + super().__init__(parent) + + self.speak[int].connect(self.say_something) + self.speak[str].connect(self.say_something) + + # define a new slot that receives a C 'int' or a 'str' + # and has 'say_something' as its name + @Slot(int) + @Slot(str) + def say_something(self, arg): + if isinstance(arg, int): + print("This is a number:", arg) + elif isinstance(arg, str): + print("This is a string:", arg) + + if __name__ == "__main__": + app = QApplication(sys.argv) + someone = Communicate() + + # emit 'speak' signal with different arguments. + # we have to specify the str as int is the default + someone.speak.emit(10) + someone.speak[str].emit("Hello everybody!") + + +.. _signals-and-slots-strings: + +Specifying Signals and Slots by Method Signature Strings +-------------------------------------------------------- + + +Signals and slots can also be specified as C++ method signature +strings passed through the ``SIGNAL()`` and/or ``SLOT()`` functions: + +.. code-block:: python + + from PySide6.QtCore import SIGNAL, SLOT + + button.connect(SIGNAL("clicked(Qt::MouseButton)"), + action_handler, SLOT("action1(Qt::MouseButton)")) + +This is not normally recommended; it is only needed +for a few cases where signals are only accessible via ``QMetaObject`` +(``QAxObject``, ``QAxWidget``, ``QDBusInterface`` or ``QWizardPage::registerField()``): + +.. code-block:: python + + wizard.registerField("text", line_edit, "text", + SIGNAL("textChanged(QString)")) + +The signature strings can be found by querying ``QMetaMethod.methodSignature()`` +when introspecting ``QMetaObject``: + +.. code-block:: python + + mo = widget.metaObject() + for m in range(mo.methodOffset(), mo.methodCount()): + print(mo.method(m).methodSignature()) + +Slots should be decorated using :ref:`@Slot <slot-decorator>`. diff --git a/sources/pyside6/doc/tutorials/basictutorial/signals_slots.png b/sources/pyside6/doc/tutorials/basictutorial/signals_slots.png Binary files differnew file mode 100644 index 000000000..0801cf16e --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/signals_slots.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/style.qss b/sources/pyside6/doc/tutorials/basictutorial/style.qss new file mode 100644 index 000000000..b84b98f05 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/style.qss @@ -0,0 +1,23 @@ +QListWidget { + color: #FFFFFF; + background-color: #33373B; +} + +QListWidget::item { + height: 50px; +} + +QListWidget::item:selected { + background-color: #2ABf9E; +} + +QLabel { + background-color: #FFFFFF; + qproperty-alignment: AlignCenter; +} + +QPushButton { + background-color: #2ABf9E; + padding: 20px; + font-size: 18px; +} diff --git a/sources/pyside6/doc/tutorials/basictutorial/tablewidget.png b/sources/pyside6/doc/tutorials/basictutorial/tablewidget.png Binary files differnew file mode 100644 index 000000000..e2549f7d0 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/tablewidget.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/tablewidget.rst b/sources/pyside6/doc/tutorials/basictutorial/tablewidget.rst new file mode 100644 index 000000000..5c04529fd --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/tablewidget.rst @@ -0,0 +1,97 @@ +Displaying Data Using a Table Widget +==================================== + +If you want to display data arranged in a table, use a ``QTableWidget`` to do +so, without dealing with much configuration. + +Notice that using a ``QTableWidget`` is not the only path to display +information in tables. You can also create a data model and display it using +a ``QTableView``, but that is not in the scope of this tutorial. + +.. note:: This Widget is a ready-to-use version of something you can customize + further on. To know more about the Model/View architecture in Qt, refer to + its `official documentation <https://doc.qt.io/qt-6/model-view-programming.html>`_. + +1. Import ``QTableWidget``, ``QTableWidgetItem``, and ``QColor`` to display + background colors: + + .. code-block:: python + + import sys + from PySide6.QtGui import QColor + from PySide6.QtWidgets import (QApplication, QTableWidget, + QTableWidgetItem) + +2. Create a simple data model containing the list of names and hex codes for + different colors: + + .. code-block:: python + + colors = [("Red", "#FF0000"), + ("Green", "#00FF00"), + ("Blue", "#0000FF"), + ("Black", "#000000"), + ("White", "#FFFFFF"), + ("Electric Green", "#41CD52"), + ("Dark Blue", "#222840"), + ("Yellow", "#F9E56d")] + +3. Define a function to translate the hex code into an RGB equivalent: + + .. code-block:: python + + def get_rgb_from_hex(code): + code_hex = code.replace("#", "") + rgb = tuple(int(code_hex[i:i+2], 16) for i in (0, 2, 4)) + return QColor.fromRgb(rgb[0], rgb[1], rgb[2]) + +4. Initialize the ``QApplication`` singleton: + + .. code-block:: python + + app = QApplication() + +5. Configure the ``QTableWidget`` to have a number of rows equivalent + to the amount of items from the ``colors`` structure, and a number of + columns with the members of one color entry, plus one. + You can set the column name using the ``setHorizontalHeaderLabels`` as + described below: + + .. code-block:: python + + table = QTableWidget() + table.setRowCount(len(colors)) + table.setColumnCount(len(colors[0]) + 1) + table.setHorizontalHeaderLabels(["Name", "Hex Code", "Color"]) + + .. note:: the reason of using ``+ 1`` is to include a new column where + we can display the color. + +6. Iterate the data structure, create the ``QTableWidgetItems`` instances, and + add them into the table using a ``x, y`` coordinate. Here the data is being + assigned row-per-row: + + .. code-block:: python + + for i, (name, code) in enumerate(colors): + item_name = QTableWidgetItem(name) + item_code = QTableWidgetItem(code) + item_color = QTableWidgetItem() + item_color.setBackground(get_rgb_from_hex(code)) + table.setItem(i, 0, item_name) + table.setItem(i, 1, item_code) + table.setItem(i, 2, item_color) + +7. Show the table and execute the ``QApplication``. + + .. code-block:: python + + table.show() + sys.exit(app.exec()) + + +The final application will look like this: + +.. image:: tablewidget.png + :alt: QTableWidget example + :align: center diff --git a/sources/pyside6/doc/tutorials/basictutorial/translations.png b/sources/pyside6/doc/tutorials/basictutorial/translations.png Binary files differnew file mode 100644 index 000000000..b9fc1ba17 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/translations.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/translations.rst b/sources/pyside6/doc/tutorials/basictutorial/translations.rst new file mode 100644 index 000000000..21c16cdcd --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/translations.rst @@ -0,0 +1,232 @@ +.. _translations: + +Translating Applications +======================== + +.. image:: translations.png + :alt: Translation Image + +Qt Linguist +----------- + +`Qt Linguist`_ and +its related tools can be used to provide translations for applications. + +The :ref:`qt-linguist-example` example illustrates this. The example is +very simple, it has a menu and shows a list of programming languages with +multiselection. + +Translation works by passing the message strings through function calls that +look up the translation. Each ``QObject`` instance provides a ``tr()`` +function for that purpose. There is also ``QCoreApplication.translate()`` +for adding translated texts to non-QObject classes. + +Qt ships its own translations containing the error messages and standard +dialog captions. + +The linguist example has a number of messages enclosed in ``self.tr()``. +The status bar message shown in response to a selection change uses +a plural form depending on a count: + +.. code-block:: python + + count = len(self._list_widget.selectionModel().selectedRows()) + message = self.tr("%n language(s) selected", "", count) + +The translation workflow for the example is as follows: +The translated messages are extracted using the ``lupdate`` tool, +producing XML-based ``.ts`` files: + +.. code-block:: bash + + pyside6-lupdate main.py -ts example_de.ts + +If ``example_de.ts`` already exists, it will be updated with the new +messages added to the code in-between. + +If there are form files (``.ui``) and/or QML files (``.qml``) in the project, +they should be passed to the ``pyside6-lupdate`` tool as well: + +.. code-block:: bash + + pyside6-lupdate main.py main.qml form.ui -ts example_de.ts + +The source files generated by ``pyside6-uic`` from the form files +should **not** be passed. + +The ``lupdate`` mode of ``pyside6-project`` can also be used for this. It +collects all source files and runs ``pyside6-lupdate`` when ``.ts`` file(s) +are given in the ``.pyproject`` file: + +.. code-block:: bash + + pyside6-project lupdate . + +``.ts`` files are translated using *Qt Linguist*. Once this is complete, +the files are converted to a binary form (``.qm`` files): + +.. code-block:: bash + + pyside6-lrelease example_de.ts -qm example_de.qm + +``pyside6-project`` will build the ``.qm`` file automatically when +``.ts`` file(s) are given in the ``.pyproject`` file: + +.. code-block:: bash + + pyside6-project build . + +To avoid having to ship the ``.qm`` files, it is recommend +to put them into a Qt resource file along with icons and other +applications resources (see :ref:`using_qrc_files`). +The resource file ``linguist.qrc`` provides the ``example_de.qm`` +under ``:/translations``: + +.. code-block:: xml + + <!DOCTYPE RCC><RCC version="1.0"> + <qresource prefix="translations"> + <file>example_de.qm</file> + </qresource> + </RCC> + +At runtime, the translations need to be loaded using the ``QTranslator`` class: + +.. code-block:: python + + path = QLibraryInfo.location(QLibraryInfo.TranslationsPath) + translator = QTranslator(app) + if translator.load(QLocale.system(), 'qtbase', '_', path): + app.installTranslator(translator) + translator = QTranslator(app) + path = ':/translations' + if translator.load(QLocale.system(), 'example', '_', path): + app.installTranslator(translator) + +The code first loads the translations shipped for Qt and then +the translations of the applications loaded from resources. + +The example can then be run in German: + +.. code-block:: bash + + LANG=de python main.py + +.. _Qt Linguist: https://doc.qt.io/qt-6/qtlinguist-index.html + +GNU gettext +----------- + +The `GNU gettext`_ module +can be used to provide translations for applications. + +The :ref:`gettext-example` example illustrates this. The example is +very simple, it has a menu and shows a list of programming languages with +multiselection. + +Translation works by passing the message strings through function calls that +look up the translation. It is common to alias the main translation function +to ``_``. There is a special translation function for sentences that contain +a plural form depending on a count ("{0} items(s) selected"). It is commonly +aliased to ``ngettext``. + +Those functions are defined at the top: + +.. code-block:: python + + import gettext + # ... + _ = None + ngettext = None + +and later assigned as follows: + +.. code-block:: python + + src_dir = Path(__file__).resolve().parent + try: + translation = gettext.translation('example', localedir=src_dir / 'locales') + if translation: + translation.install() + _ = translation.gettext + ngettext = translation.ngettext + except FileNotFoundError: + pass + if not _: + _ = gettext.gettext + ngettext = gettext.ngettext + +This specifies that our translation file has the base name ``example`` and +will be found in the source tree under ``locales``. The code will try +to load a translation matching the current language. + +Messages to be translated look like: + +.. code-block:: python + + file_menu = self.menuBar().addMenu(_("&File")) + +The status bar message shown in response to a selection change uses +a plural form depending on a count: + +.. code-block:: python + + count = len(self._list_widget.selectionModel().selectedRows()) + message = ngettext("{0} language selected", + "{0} languages selected", count).format(count) + +The ``ngettext()`` function takes the singular form, plural form and the count. +The returned string still contains the formatting placeholder, so it needs +to be passed through ``format()``. + +In order to translate the messages to say German, a template file (``.pot``) +is first created: + +.. code-block:: bash + + mkdir -p locales/de_DE/LC_MESSAGES + xgettext -L Python -o locales/example.pot main.py + +This file has a few generic placeholders which can be replaced by the +appropriate values. It is then copied to the ``de_DE/LC_MESSAGES`` directory. + +.. code-block:: bash + + cd locales/de_DE/LC_MESSAGES/ + cp ../../example.pot . + +Further adaptions need to be made to account for the German plural +form and encoding: + +.. code-block:: + + "Project-Id-Version: PySide6 gettext example\n" + "POT-Creation-Date: 2021-07-05 14:16+0200\n" + "Language: de_DE\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=2; plural=n != 1;\n" + +Below, the translated messages can be given: + +.. code-block:: + + #: main.py:57 + msgid "&File" + msgstr "&Datei" + +Finally, the ``.pot`` is converted to its binary form (machine object file, +``.mo``), which needs to be deployed: + +.. code-block:: bash + + msgfmt -o example.mo example.pot + +The example can then be run in German: + +.. code-block:: bash + + LANG=de python main.py + +.. _GNU gettext: https://docs.python.org/3/library/gettext.html diff --git a/sources/pyside6/doc/tutorials/basictutorial/treewidget.png b/sources/pyside6/doc/tutorials/basictutorial/treewidget.png Binary files differnew file mode 100644 index 000000000..990fe977b --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/treewidget.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst b/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst new file mode 100644 index 000000000..f431cb5c4 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst @@ -0,0 +1,79 @@ +Displaying Data Using a Tree Widget +=================================== + +If you want to display data arranged in a tree, use a ``QTreeWidget`` to do so. + +Notice that using a ``QTreeWidget`` is not the only path to display +information in trees. You can also create a data model and display it using a +``QTreeView``, but that is not in the scope of this tutorial. + +.. note:: This Widget is a ready-to-use version of something you can customize + further on. To know more about the Model/View architecture in Qt, refer to + its `official documentation <https://doc.qt.io/qt-6/model-view-programming.html>`_. + +#. Import ``QTreeWidget`` and ``QTreeWidgetItem`` for this application: + + .. code-block:: python + + import sys + from PySide6.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem + +#. Define a dictionary with project structures to display the information as a + tree, with files belonging to each project: + + .. code-block:: python + + data = {"Project A": ["file_a.py", "file_a.txt", "something.xls"], + "Project B": ["file_b.csv", "photo.jpg"], + "Project C": []} + +#. Initialize the ``QApplication`` singleton: + + .. code-block:: python + + app = QApplication() + +#. Configure the ``QTreeWidget`` to have two columns, one for the item name, + and the other for item type information of the files in the project + directories. + You can set the column name with the ``setHeaderLabels`` as described below: + + .. code-block:: python + + tree = QTreeWidget() + tree.setColumnCount(2) + tree.setHeaderLabels(["Name", "Type"]) + +#. Iterate the data structure, create the ``QTreeWidgetItem`` elements, and add + the corresponding children to each parent. + We also extract the extension name for only the files and add them + into the second column. + In the constructor, you can see that each element (``QTreeWidgetItem``) is + added to different columns of the tree (``QTreeWidget``). + + .. code-block:: python + + items = [] + for key, values in data.items(): + item = QTreeWidgetItem([key]) + for value in values: + ext = value.split(".")[-1].upper() + child = QTreeWidgetItem([value, ext]) + item.addChild(child) + items.append(item) + + tree.insertTopLevelItems(0, items) + +#. Show the tree and execute the ``QApplication``. + + .. code-block:: python + + tree.show() + sys.exit(app.exec()) + + +The final application will look like this: + +.. image:: treewidget.png + :alt: QTreeWidget example + :align: center diff --git a/sources/pyside6/doc/tutorials/basictutorial/uifiles.png b/sources/pyside6/doc/tutorials/basictutorial/uifiles.png Binary files differnew file mode 100644 index 000000000..918efec6d --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/uifiles.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst new file mode 100644 index 000000000..cb945908d --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst @@ -0,0 +1,304 @@ +.. _using_ui_files: + +Using ``.ui`` files from Designer or QtCreator with ``QUiLoader`` and ``pyside6-uic`` +************************************************************************************* + +This page describes the use of +`Qt Widgets Designer <https://doc.qt.io/qt-6/qtdesigner-manual.html>`_ to create +graphical interfaces based on Qt Widgets for your Qt for Python project. +*Qt Widgets Designer* is a graphical UI design tool which is available as a +standalone binary (``pyside6-designer``) or embedded into the +`Qt Creator IDE <https://doc.qt.io/qtcreator>`_. Its use within *Qt Creator* +is described at +`Using Qt Widgets Designer <https://doc.qt.io/qtcreator/creator-using-qt-designer.html>`_. + +.. image:: uifiles.png + :alt: Designer and the equivalent code + +The designs are stored in ``.ui`` files, which is an XML-based format. It will +be converted to Python or C++ code populating a widget instance at project build +time by the `pyside6-uic <https://doc.qt.io/qt-6/uic.html>`_ tool. + +To create a new Qt Design Form in *Qt Creator*, choose +``File/New File Or Project`` and "Main Window" for template. Save it as +``mainwindow.ui``. Add a ``QPushButton`` to the center of the centralwidget. + +Your file ``mainwindow.ui`` should look something like this: + +.. code-block:: xml + + <?xml version="1.0" encoding="UTF-8"?> + <ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralWidget"> + <widget class="QPushButton" name="pushButton"> + <property name="geometry"> + <rect> + <x>110</x> + <y>80</y> + <width>201</width> + <height>81</height> + </rect> + </property> + <property name="text"> + <string>PushButton</string> + </property> + </widget> + </widget> + <widget class="QMenuBar" name="menuBar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>20</height> + </rect> + </property> + </widget> + <widget class="QToolBar" name="mainToolBar"> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + </widget> + <widget class="QStatusBar" name="statusBar"/> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections/> + </ui> + +Now we are ready to decide how to use the **UI file** from Python. + +Option A: Generating a Python class +=================================== + +The standard way to interact with a **UI file** is to generate a Python +class from it. This is possible thanks to the ``pyside6-uic`` tool. +To use this tool, you need to run the following command on a console:: + + pyside6-uic mainwindow.ui -o ui_mainwindow.py + +We redirect all the output of the command to a file called ``ui_mainwindow.py``, +which will be imported directly:: + + from ui_mainwindow import Ui_MainWindow + +Now to use it, we should create a personalized class for our widget +to **setup** this generated design. + +To understand the idea, let's take a look at the whole code: + +.. code-block:: python + + import sys + from PySide6.QtWidgets import QApplication, QMainWindow + from PySide6.QtCore import QFile + from ui_mainwindow import Ui_MainWindow + + class MainWindow(QMainWindow): + def __init__(self): + super(MainWindow, self).__init__() + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + + if __name__ == "__main__": + app = QApplication(sys.argv) + + window = MainWindow() + window.show() + + sys.exit(app.exec()) + +What is inside the *if* statement is already known from the previous +examples, and our new basic class contains only two new lines +that are in charge of loading the generated python class from the UI +file: + +.. code-block:: python + + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + +.. note:: + + You must run ``pyside6-uic`` again every time you make changes + to the **UI file**. + +Option B: Loading it directly +============================= + +To load the UI file directly, we will need a class from the **QtUiTools** +module: + +.. code-block:: python + + from PySide6.QtUiTools import QUiLoader + +The ``QUiLoader`` lets us load the **ui file** dynamically +and use it right away: + +.. code-block:: python + + ui_file = QFile("mainwindow.ui") + ui_file.open(QFile.ReadOnly) + + loader = QUiLoader() + window = loader.load(ui_file) + window.show() + +The complete code of this example looks like this: + +.. code-block:: python + + # File: main.py + import sys + from PySide6.QtUiTools import QUiLoader + from PySide6.QtWidgets import QApplication + from PySide6.QtCore import QFile, QIODevice + + if __name__ == "__main__": + app = QApplication(sys.argv) + + ui_file_name = "mainwindow.ui" + ui_file = QFile(ui_file_name) + if not ui_file.open(QIODevice.ReadOnly): + print(f"Cannot open {ui_file_name}: {ui_file.errorString()}") + sys.exit(-1) + loader = QUiLoader() + window = loader.load(ui_file) + ui_file.close() + if not window: + print(loader.errorString()) + sys.exit(-1) + window.show() + + sys.exit(app.exec()) + +Then to execute it we just need to run the following on a +command prompt: + +.. code-block:: bash + + python main.py + +.. note:: + + ``QUiLoader`` uses ``connect()`` calls taking the function signatures as string + arguments for signal/slot connections. + It is thus unable to handle Python types like ``str`` or ``list`` from + custom widgets written in Python since these types are internally mapped + to different C++ types. + +.. _designer_custom_widgets: + +Custom Widgets in Qt Widgets Designer +===================================== + +*Qt Widgets Designer* is able to use user-provided (custom) widgets. +They are shown in the widget box and can be dragged onto the form just like +Qt's widgets (see +`Using Custom Widgets with Qt Widgets Designer <https://doc.qt.io/qt-6/designer-using-custom-widgets.html>`_ +). Normally, this requires implementing the widget as a plugin to +*Qt Widgets Designer* written in C++ implementing its +`QDesignerCustomWidgetInterface`_ . + +Qt for Python provides a simple interface for this which is similar to +:meth:`registerCustomWidget()<PySide6.QtUiTools.QUiLoader.registerCustomWidget>`. + +The widget needs to be provided as a Python module, as shown by +the :ref:`widgetbinding-example` (file ``wigglywidget.py``) or +the :ref:`task-menu-extension-example` (file ``tictactoe.py``). + +Registering this with *Qt Widgets Designer* is done by providing +a registration script named ``register*.py`` and pointing +the path-type environment variable ``PYSIDE_DESIGNER_PLUGINS`` +to the directory. + +The code of the registration script looks as follows: + +.. code-block:: python + + # File: registerwigglywidget.py + from wigglywidget import WigglyWidget + + import QtDesigner + + + TOOLTIP = "A cool wiggly widget (Python)" + DOM_XML = """ + <ui language='c++'> + <widget class='WigglyWidget' name='wigglyWidget'> + <property name='geometry'> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>200</height> + </rect> + </property> + <property name='text'> + <string>Hello, world</string> + </property> + </widget> + </ui> + """ + + QPyDesignerCustomWidgetCollection.registerCustomWidget(WigglyWidget, module="wigglywidget", + tool_tip=TOOLTIP, xml=DOM_XML) + + +QPyDesignerCustomWidgetCollection provides an implementation of +`QDesignerCustomWidgetCollectionInterface`_ +exposing custom widgets to *Qt Widgets Designer* with static convenience +functions for registering types or adding instances of +`QDesignerCustomWidgetInterface`_ . + +The function +:meth:`registerCustomWidget()<PySide6.QtDesigner.QPyDesignerCustomWidgetCollection.registerCustomWidget>` +is used to register a widget type with *Qt Widgets Designer*. In the simple case, it +can be used like ``QUiLoader.registerCustomWidget()``. It takes the custom widget +type and some optional keyword arguments passing values that correspond to the +getters of +`QDesignerCustomWidgetInterface`_ : + +When launching *Qt Widgets Designer* via its launcher ``pyside6-designer``, +the custom widget should be visible in the widget box. + +For advanced usage, it is also possible to pass the function an implementation +of the class QDesignerCustomWidgetInterface instead of the type to +:meth:`addCustomWidget()<PySide6.QtDesigner.QPyDesignerCustomWidgetCollection.addCustomWidget>`. +This is shown in taskmenuextension example, where a custom context menu +is registered for the custom widget. The example is a port of the +corresponding C++ +`Task Menu Extension Example <https://doc.qt.io/qt-6/qtdesigner-taskmenuextension-example.html>`_ . + +.. _QDesignerCustomWidgetCollectionInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetcollectioninterface.html +.. _QDesignerCustomWidgetInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetinterface.html + +Troubleshooting the Qt Widgets Designer Plugin +++++++++++++++++++++++++++++++++++++++++++++++ + +- The launcher ``pyside6-designer`` must be used. The standalone + *Qt Widgets Designer* will not load the plugin. +- The menu item **Help/About Plugin** brings up a dialog showing the plugins + found and potential load error messages. +- Check the console or Windows Debug view for further error messages. +- Due to the buffering of output by Python, error messages may appear + only after *Qt Widgets Designer* has terminated. +- When building Qt for Python, be sure to set the ``--standalone`` option + for the plugin to be properly installed. diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgets.png b/sources/pyside6/doc/tutorials/basictutorial/widgets.png Binary files differnew file mode 100644 index 000000000..de7a969f9 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgets.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgets.rst b/sources/pyside6/doc/tutorials/basictutorial/widgets.rst new file mode 100644 index 000000000..ef14c7e99 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgets.rst @@ -0,0 +1,52 @@ +Your First QtWidgets Application +********************************* + +As with any other programming framework, +you start with the traditional "Hello World" program. + +Here is a simple example of a Hello World application in PySide6: + +.. code-block:: python + + import sys + from PySide6.QtWidgets import QApplication, QLabel + + app = QApplication(sys.argv) + label = QLabel("Hello World!") + label.show() + app.exec() + + +When you execute it the code, the application will look like: + +.. image:: widgets.png + :alt: Simple Widget + + +For a widget application using PySide6, you must always start by +importing the appropriate class from the `PySide6.QtWidgets` module. + +After the imports, you create a `QApplication` instance. As Qt can +receive arguments from command line, you may pass any argument to +the QApplication object. Usually, you don't need to pass any +arguments so you can leave it as is, or use the following approach: + +.. code-block:: python + + app = QApplication([]) + +After the creation of the application object, we have created a +`QLabel` object. A `QLabel` is a widget that can present text +(simple or rich, like html), and images: + +.. code-block:: python + + # This HTML approach will be valid too! + label = QLabel("<font color=red size=40>Hello World!</font>") + +.. note:: After creating the label, we call `show()` on it. + +Finally, we call `app.exec()` to enter the Qt main loop and start +to execute the Qt code. In reality, it is only here where the label +is shown, but this can be ignored for now. + diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-no.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-no.png Binary files differnew file mode 100644 index 000000000..f8346533f --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-no.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.png Binary files differnew file mode 100644 index 000000000..d510a80cd --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.png Binary files differnew file mode 100644 index 000000000..e7a0c0ef7 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.png Binary files differnew file mode 100644 index 000000000..9b83b8267 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py new file mode 100644 index 000000000..106483b7b --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py @@ -0,0 +1,58 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys + +from PySide6.QtCore import Qt +from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QListWidget, + QListWidgetItem, QPushButton, QVBoxLayout, + QWidget) + +_placeholder = """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim +veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat. Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint +occaecat cupidatat non proident, sunt in culpa qui officia deserunt +mollit anim id est laborum +""" + + +class Widget(QWidget): + def __init__(self, parent=None): + super(Widget, self).__init__(parent) + + menu_widget = QListWidget() + for i in range(10): + item = QListWidgetItem(f"Item {i}") + item.setTextAlignment(Qt.AlignCenter) + menu_widget.addItem(item) + + text_widget = QLabel(_placeholder) + button = QPushButton("Something") + + content_layout = QVBoxLayout() + content_layout.addWidget(text_widget) + content_layout.addWidget(button) + main_widget = QWidget() + main_widget.setLayout(content_layout) + + layout = QHBoxLayout() + layout.addWidget(menu_widget, 1) + layout.addWidget(main_widget, 4) + self.setLayout(layout) + + +if __name__ == "__main__": + app = QApplication() + + w = Widget() + w.show() + + _style = None + with open("style.qss", "r") as f: + _style = f.read() + app.setStyleSheet(_style) + + sys.exit(app.exec()) diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst new file mode 100644 index 000000000..2fa51c0a8 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst @@ -0,0 +1,171 @@ +.. _widgetstyling: + +Styling the Widgets Application +=============================== + +Qt Widgets application use a default theme depending on the platform. +In some cases, there are system-wide configurations that modify the Qt theme, +and applications are displayed differently. + +However, you can take care of your own widgets and provide a custom style +to each component. As an example, look at the following simple snippet: + +.. code-block:: python + + import sys + from PySide6.QtCore import Qt + from PySide6.QtWidgets import QApplication, QLabel + + if __name__ == "__main__": + app = QApplication() + w = QLabel("This is a placeholder text") + w.setAlignment(Qt.AlignCenter) + w.show() + sys.exit(app.exec()) + +When you execute this code, you will see a simple ``QLabel`` aligned at the +center, and with a placeholder text. + +.. image:: widgetstyling-simple-no.png + :alt: Simple Widget with no style + +You can style your application using the CSS-like syntax. +For more information, see `Qt Style Sheets Reference`_. + +A ``QLabel`` can be styled differently by setting some of its CSS +properties, such as ``background-color`` and ``font-family``, +so let's see how does the code look like with these changes: + +.. code-block:: python + + import sys + from PySide6.QtCore import Qt + from PySide6.QtWidgets import QApplication, QLabel + + if __name__ == "__main__": + app = QApplication() + w = QLabel("This is a placeholder text") + w.setAlignment(Qt.AlignCenter) + w.setStyleSheet(""" + background-color: #262626; + color: #FFFFFF; + font-family: Titillium; + font-size: 18px; + """) + w.show() + sys.exit(app.exec()) + +Now when you run the code, notice that the ``QLabel`` looks different with your +custom style: + +.. image:: widgetstyling-simple-yes.png + :alt: Simple Widget with Style + + +.. note:: + + If you don't have the font ``Titillium`` installed, you can try with any + other you prefer. + Remember you can list your installed fonts using ``QFontDatabase``, + specifically the ``families()`` method. + + +Styling each UI element separately like you did in the previous snippet is a +lot of work. The easier alternative for this is to use Qt Style Sheets, +which is one or more ``.qss`` files defining the style for the UI elements in +your application. + +More examples can be found in the `Qt Style Sheet Examples`_ documentation +page. + + +.. _`Qt Style Sheets Reference`: https://doc.qt.io/qt-5/stylesheet-reference.html +.. _`Qt Style Sheet Examples`: https://doc.qt.io/qt-5/stylesheet-examples.html + +Qt Style Sheets +--------------- + +.. warning:: + + Before starting modifying your application, keep in mind that you will be + responsible for all the graphical details of the application. + Altering margins, and sizes might end up looking strange or incorrect, so you + need to be careful when altering the style. + It's recommended to create a full new Qt style to cover all the possible + corner cases. + +A ``qss`` file is quite similar to a CSS file, but you need to specify the Widget +component and optionally the name of the object:: + + QLabel { + background-color: red; + } + + QLabel#title { + font-size: 20px; + } + +The first style defines a ``background-color`` for all ``QLabel`` objects in your +application, whereas the later one styles the ``title`` object only. + +.. note:: + + You can set object names with the `setObjectName(str)` function to any Qt + object, for example: for a `label = QLabel("Test")`, you can write + `label.setObjectName("title")` + + +Once you have a ``qss`` file for your application, you can apply it by reading +the file and using the ``QApplication.setStyleSheet(str)`` function: + +.. code-block:: python + + if __name__ == "__main__": + app = QApplication() + + w = Widget() + w.show() + + with open("style.qss", "r") as f: + _style = f.read() + app.setStyleSheet(_style) + + sys.exit(app.exec()) + +Having a general ``qss`` file allows you to decouple the styling aspects of +the code, without mixing it in the middle of the general functionality, and you +can simply enable it or disable it. + +Look at this new example, with more widgets components: + +.. literalinclude:: widgetstyling.py + :linenos: + :lines: 22-44 + +This displays a two column widget, with a ``QListWidget`` on the left and a +``QLabel`` and a ``QPushButton`` on the right. It looks like this when you run the +code: + +.. image:: widgetstyling-no.png + :alt: Widget with no style + +If you add content to the previously described ``style.qss`` file, you can modify +the look-n-feel of the previous example: + +.. literalinclude:: style.qss + :linenos: + +The style changes mainly the color of the different widgets, alter the +alignment, and includes some spacing. +You can also use state-based styling on the QListWidget *items* for example, to +style them differently depending on whether they are *selected* or not. + +After applying all the styling alternatives you explored in this topic, notice +that the ``QLabel`` example looks a lot different now. +Try running the code to check its new look: + +.. image:: widgetstyling-yes.png + :alt: Widget with style + +You have the freedom to tune your style sheets and provide a really nice +look-n-feel to all your applications. |