1QML, SQL and PySide Integration Tutorial 2######################################## 3 4This tutorial is very similar to the `Qt Chat Tutorial`_ one but it focuses on explaining how to 5integrate a SQL database into a PySide2 application using QML for its UI. 6 7.. _`Qt Chat Tutorial`: https://doc.qt.io/qt-5/qtquickcontrols-chattutorial-example.html 8 9sqlDialog.py 10------------ 11 12We import the pertinent libraries to our program, define a global variable that hold the 13name of our table, and define the global function ``createTable()`` that creates a new table if it 14doesn't already exist. 15The database contains a single line to mock the beginning of a conversation. 16 17 .. literalinclude:: sqlDialog.py 18 :linenos: 19 :lines: 40-77 20 21The ``SqlConversationModel`` class offers the read-only data model required for the non-editable 22contacts list. It derives from the :ref:`QSqlQueryModel` class, which is the logical choice for 23this use case. 24Then, we proceed to create the table, set its name to the one defined previously with the 25:meth:`~.QSqlTableModel.setTable` method. 26We add the necessary attributes to the table, to have a program that reflects the idea 27of a chat application. 28 29 .. literalinclude:: sqlDialog.py 30 :linenos: 31 :lines: 80-91 32 33In ``setRecipient()``, you set a filter over the returned results from the database, and 34emit a signal every time the recipient of the message changes. 35 36 .. literalinclude:: sqlDialog.py 37 :linenos: 38 :lines: 93-103 39 40The ``data()`` function falls back to ``QSqlTableModel``'s implementation if the role is not a 41custom user role. 42If you get a user role, we can subtract :meth:`~.QtCore.Qt.UserRole` from it to get the index of 43that field, and then use that index to find the value to be returned. 44 45 .. literalinclude:: sqlDialog.py 46 :linenos: 47 :lines: 105-112 48 49 50In ``roleNames()``, we return a Python dictionary with our custom role and role names as key-values 51pairs, so we can use these roles in QML. 52Alternatively, it can be useful to declare an Enum to hold all of the role values. 53Note that ``names`` has to be a hash to be used as a dictionary key, 54and that's why we're using the ``hash`` function. 55 56 .. literalinclude:: sqlDialog.py 57 :linenos: 58 :lines: 114-128 59 60The ``send_message()`` function uses the given recipient and message to insert a new record into 61the database. 62Using :meth:`~.QSqlTableModel.OnManualSubmit` requires you to also call ``submitAll()``, 63since all the changes will be cached in the model until you do so. 64 65 .. literalinclude:: sqlDialog.py 66 :linenos: 67 :lines: 130-146 68 69chat.qml 70-------- 71 72Let's look at the ``chat.qml`` file. 73 74 .. literalinclude:: chat.qml 75 :linenos: 76 :lines: 40-42 77 78First, import the Qt Quick module. 79This gives us access to graphical primitives such as Item, Rectangle, Text, and so on. 80For a full list of types, see the `Qt Quick QML Types`_ documentation. 81We then add QtQuick.Layouts import, which we'll cover shortly. 82 83Next, import the Qt Quick Controls module. 84Among other things, this provides access to ``ApplicationWindow``, which replaces the existing 85root type, Window: 86 87Let's step through the ``chat.qml`` file. 88 89 .. literalinclude:: chat.qml 90 :linenos: 91 :lines: 44-49 92 93``ApplicationWindow`` is a Window with some added convenience for creating a header and a footer. 94It also provides the foundation for popups and supports some basic styling, such as the background 95color. 96 97There are three properties that are almost always set when using ApplicationWindow: ``width``, 98``height``, and ``visible``. 99Once we've set these, we have a properly sized, empty window ready to be filled with content. 100 101There are two ways of laying out items in QML: `Item Positioners`_ and `Qt Quick Layouts`_. 102 103- Item positioners (`Row`_, `Column`_, and so on) are useful for situations where the size of items 104 is known or fixed, and all that is required is to neatly position them in a certain formation. 105- The layouts in Qt Quick Layouts can both position and resize items, making them well suited for 106 resizable user interfaces. 107 Below, we use `ColumnLayout`_ to vertically lay out a `ListView`_ and a `Pane`_. 108 109 .. literalinclude:: chat.qml 110 :linenos: 111 :lines: 50-53 112 113Pane is basically a rectangle whose color comes from the application's style. 114It's similar to `Frame`_, but it has no stroke around its border. 115 116Items that are direct children of a layout have various `attached properties`_ available to them. 117We use `Layout.fillWidth`_ and `Layout.fillHeight`_ on the `ListView`_ to ensure that it takes as 118much space within the `ColumnLayout`_ as it can, and the same is done for the Pane. 119As `ColumnLayout`_ is a vertical layout, there aren't any items to the left or right of each child, 120so this results in each item consuming the entire width of the layout. 121 122On the other hand, the `Layout.fillHeight`_ statement in the `ListView`_ enables it to occupy the 123remaining space that is left after accommodating the Pane. 124 125.. _Item Positioners: https://doc.qt.io/qt-5/qtquick-positioning-layouts.html 126.. _Qt Quick Layouts: https://doc.qt.io/qt-5/qtquicklayouts-index.html 127.. _Row: https://doc.qt.io/qt-5/qml-qtquick-row.html 128.. _Column: https://doc.qt.io/qt-5/qml-qtquick-column.html 129.. _ColumnLayout: https://doc.qt.io/qt-5/qml-qtquick-layouts-columnlayout.html 130.. _ListView: https://doc.qt.io/qt-5/qml-qtquick-listview.html 131.. _Pane: https://doc.qt.io/qt-5/qml-qtquick-controls2-pane.html 132.. _Frame: https://doc.qt.io/qt-5/qml-qtquick-controls2-frame.html 133.. _attached properties: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html 134.. _Layout.fillWidth: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html#fillWidth-attached-prop 135.. _Layout.fillHeight: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html#fillHeight-attached-prop 136.. _ListView: https://doc.qt.io/qt-5/qml-qtquick-listview.html 137.. _Qt Quick QML Types: https://doc.qt.io/qt-5/qtquick-qmlmodule.html 138 139Let's look at the ``Listview`` in detail: 140 141 .. literalinclude:: chat.qml 142 :linenos: 143 :lines: 53-99 144 145After filling the ``width`` and ``height`` of its parent, we also set some margins on the view. 146 147 148Next, we set `displayMarginBeginning`_ and `displayMarginEnd`_. 149These properties ensure that the delegates outside the view don't disappear when you 150scroll at the edges of the view. 151To get a better understanding, consider commenting out the properties and then rerun your code. 152Now watch what happens when you scroll the view. 153 154We then flip the vertical direction of the view, so that first items are at the bottom. 155 156Additionally, messages sent by the contact should be distinguished from those sent by a contact. 157For now, when a message is sent by you, we set a ``sentByMe`` property, to alternate between 158different contacts. 159Using this property, we distinguish between different contacts in two ways: 160 161* Messages sent by the contact are aligned to the right side of the screen by setting 162 ``anchors.right`` to ``parent.right``. 163* We change the color of the rectangle depending on the contact. 164 Since we don't want to display dark text on a dark background, and vice versa, we also set the 165 text color depending on who the contact is. 166 167At the bottom of the screen, we place a `TextArea`_ item to allow multi-line text input, and a 168button to send the message. 169We use Pane to cover the area under these two items: 170 171 .. literalinclude:: chat.qml 172 :linenos: 173 :lines: 101-125 174 175The `TextArea`_ should fill the available width of the screen. 176We assign some placeholder text to provide a visual cue to the contact as to where they should begin 177typing. 178The text within the input area is wrapped to ensure that it does not go outside of the screen. 179 180Lastly, we have a button that allows us to call the ``send_message`` method we defined on 181``sqlDialog.py``, since we're just having a mock up example here and there is only one possible 182recipient and one possible sender for this conversation we're just using strings here. 183 184.. _displayMarginBeginning: https://doc.qt.io/qt-5/qml-qtquick-listview.html#displayMarginBeginning-prop 185.. _displayMarginEnd: https://doc.qt.io/qt-5/qml-qtquick-listview.html#displayMarginEnd-prop 186.. _TextArea: https://doc.qt.io/qt-5/qml-qtquick-controls2-textarea.html 187 188 189main.py 190------- 191 192We use ``logging`` instead of Python's ``print()``, because it provides a better way to control the 193messages levels that our application will generate (errors, warnings, and information messages). 194 195 .. literalinclude:: main.py 196 :linenos: 197 :lines: 40-50 198 199``connectToDatabase()`` creates a connection with the SQLite database, creating the actual file 200if it doesn't already exist. 201 202 .. literalinclude:: main.py 203 :linenos: 204 :lines: 53-72 205 206 207 208A few interesting things happen in the ``main`` function: 209 210- Declaring a :ref:`QGuiApplication`. 211 You should use a :ref:`QGuiApplication` instead of :ref:`QApplication` because we're not 212 using the **QtWidgets** module. 213- Connecting to the database, 214- Declaring a :ref:`QQmlApplicationEngine`. 215 This allows you to access the QML context property to connect Python 216 and QML from the conversation model we built on ``sqlDialog.py``. 217- Loading the ``.qml`` file that defines the UI. 218 219Finally, the Qt application runs, and your program starts. 220 221 .. literalinclude:: main.py 222 :linenos: 223 :lines: 75-85 224 225.. image:: example_list_view.png 226