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