Difference between revisions of "Filling-and-reading-QML-UI-forms-from-Python"

From Qt Wiki
Jump to: navigation, search
(Rename category "LanguageBindings::PySide" -> "PySide")
 
(7 intermediate revisions by 4 users not shown)
Line 1: Line 1:
[[Category:LanguageBindings::PySide]]<br />[[Category:snippets]]<br />[[Category:Developing_with_Qt::Qt Quick]]<br />[[Category:Developing_with_Qt::Qt Quick::QML]]<br />[[Category:Developing_with_Qt::Qt Quick::Tutorial]]
 
  
= Filling and reading QML UI forms from Python =
+
[[Category:PySide]]
 +
[[Category:snippets]]
 +
[[Category:Developing_with_Qt::Qt Quick]]
 +
[[Category:Developing_with_Qt::Qt Quick::QML]]
 +
[[Category:Developing_with_Qt::Qt Quick::Tutorial]]
  
This [[PySide]] tutorial shows you how to create a &quot;classic&amp;quot; form-based UI with the Colibri QML Components and have it filled and controlled by Python code. There are several ways to do this, and depending on your use case, there might be a better method. Please also note that in this example, the controller code knows a bit about the UI (or rather: the UI has to inform the controller which widgets are to be filled), which might not be desired.
+
 
 +
This [[PySide]] tutorial shows you how to create a "classic" form-based UI with the Colibri QML Components and have it filled and controlled by Python code. There are several ways to do this, and depending on your use case, there might be a better method. Please also note that in this example, the controller code knows a bit about the UI (or rather: the UI has to inform the controller which widgets are to be filled), which might not be desired.
  
 
== CarAnalogy.py ==
 
== CarAnalogy.py ==
Line 11: Line 15:
 
We need the '''QtCore''' module for QObject, the '''QtGui''' module for QApplication and the '''QtDeclarative''' module for the QML View (QDeclarativeView):
 
We need the '''QtCore''' module for QObject, the '''QtGui''' module for QApplication and the '''QtDeclarative''' module for the QML View (QDeclarativeView):
  
<code><br />import sys
+
<code>
 +
import sys
  
from PySide import QtCore, QtGui, QtDeclarative<br /></code>
+
from PySide import QtCore, QtGui, QtDeclarative
 +
</code>
  
 
=== Define a Car as QObject ===
 
=== Define a Car as QObject ===
Line 24: Line 30:
 
* '''inStock''' (bool) - If the car is still in stock at the warehouse
 
* '''inStock''' (bool) - If the car is still in stock at the warehouse
  
<code><br />class Car(QtCore.QObject):<br /> def ''init''(self, model='', brand='', year=0, in_stock=False):<br /> QtCore.QObject.''init''(self)<br /> self._''model = model<br /> self.brand = brand<br /> self.year = year<br /> self.''_in_stock = in_stock
+
<code>
 +
class Car(QtCore.QObject):
 +
def ''init''(self, model='', brand='', year=0, in_stock=False):
 +
QtCore.QObject.''init''(self)
 +
self._''model = model
 +
self.brand = brand
 +
self.year = year
 +
self.''_in_stock = in_stock
  
 
changed = QtCore.Signal()
 
changed = QtCore.Signal()
  
def ''model(self): return self.''_model<br /> def ''brand(self): return self.''_brand<br /> def ''year(self): return self.''_year<br /> def ''inStock(self): return self.''_in_stock
+
def ''model(self): return self.''_model
 +
def ''brand(self): return self.''_brand
 +
def ''year(self): return self.''_year
 +
def ''inStock(self): return self.''_in_stock
 +
 
 +
def ''setModel(self, model):
 +
self.''_model = model
 +
self.changed.emit()
  
def ''setModel(self, model):<br /> self.''_model = model<br /> self.changed.emit()
+
def ''setBrand(self, brand):
 +
self.''_brand = brand
 +
self.changed.emit()
  
def ''setBrand(self, brand):<br /> self.''_brand = brand<br /> self.changed.emit()
+
def ''setYear(self, year):
 +
self.''_year = year
 +
self.changed.emit()
  
def ''setYear(self, year):<br /> self.''_year = year<br /> self.changed.emit()
+
def ''setInStock(self, in_stock):
 +
self.''_in_stock = in_stock
 +
self.changed.emit()
  
def ''setInStock(self, in_stock):<br /> self.''_in_stock = in_stock<br /> self.changed.emit()
+
model = QtCore.Property(str, _model, _setModel, notify=changed)
 +
brand = QtCore.Property(str, _brand, _setBrand, notify=changed)
 +
year = QtCore.Property(int, _year, _setYear, notify=changed)
 +
inStock = QtCore.Property(bool, _inStock, ''setInStock, notify=changed)
 +
</code>
  
model = QtCore.Property(str, _model, _setModel, notify=changed)<br /> brand = QtCore.Property(str, _brand, _setBrand, notify=changed)<br /> year = QtCore.Property(int, _year, _setYear, notify=changed)<br /> inStock = QtCore.Property(bool, _inStock, ''setInStock, notify=changed)<br /></code>
+
=== The controller to fill the form and react to events ===
<br />h3. The controller to fill the form and react to events
+
This is another QObject that takes a list of cars as constructor parameter. It also remembers the current position in the list of cars. There are three slots that are visible to the "outside" (QML in our case):
<br />This is another QObject that takes a list of cars as constructor parameter. It also remembers the current position in the list of cars. There are three slots that are visible to the &quot;outside&amp;quot; (QML in our case):
 
<br />* '''prev''' - Go to the previous item<br />* '''next''' - Go to the next item<br />* '''init''' - Show the first item
 
<br />All these slots take a QObject as parameter, and from the QML file, we will pass the root object there, which has a property '''widgets''' where we save a dictionary of mappings from name to QML component. The '''fill''' function takes care of filling in the data of the current car into the QML widgets.
 
<br /><code><br />class Controller(QtCore.QObject):<br /> definit<span class="lst self,">:<br /> QtCore.QObject.</span>init''_(self)<br /> self._lst = lst<br /> self._pos = 0
 
  
def fill(self, widgets):<br /> widgets['model'].setProperty('text', self._lst[self._pos].model)<br /> widgets['brand'].setProperty('text', self._lst[self._pos].brand)<br /> widgets['year'].setProperty('value', self._lst[self._pos].year)<br /> widgets['inStock'].setProperty('checked', self._lst[self._pos].inStock)<br /> widgets['position'].setProperty('text', 'd/%d' (self._pos+1, len(self._lst)))
+
* '''prev''' - Go to the previous item
 +
* '''next''' - Go to the next item
 +
* '''init''' - Show the first item
  
</code>QtCore.Slot(QtCore.QObject)<br /> def prev(self, root):<br /> print 'prev'<br /> self._pos = max(0, self._pos - 1)<br /> self.fill(root.property('widgets'))
+
All these slots take a QObject as parameter, and from the QML file, we will pass the root object there, which has a property '''widgets''' where we save a dictionary of mappings from name to QML component. The '''fill''' function takes care of filling in the data of the current car into the QML widgets.
  
<code>QtCore.Slot(QtCore.QObject)<br /> def next(self, root):<br /> print 'next'<br /> self._pos = min(len(self._lst) - 1, self.''pos + 1)<br /> self.fill(root.property('widgets'))
+
<code>
<br /> </code>QtCore.Slot(QtCore.QObject)<br /> def init(self, root):<br /> print 'init'<br /> self.fill(root.property('widgets'))<br /><code>
+
class Controller(QtCore.QObject):
<br />h3. Example data
+
definit<span class="lst self,">:
<br />Here is some example data, so that we can use our example and click through a list of cars:
+
QtCore.QObject.</span>init''_(self)
<br /></code><br />cars = [<br /> Car('Model T', 'Ford', 1908),<br /> Car('Beetle', 'Volkswagen', 1938, True),<br /> Car('Corolla', 'Toyota', 1966),<br /> Car('Clio', 'Renault', 1991, True),<br /> Car('Ambassador', 'Hindustan', 1958),<br /> Car('Uno', 'Fiat', 1983, True),<br /> Car('Ibiza', 'Seat', 1984, True),<br />]<br /><code>
+
self._lst = lst
<br />h3. Putting it all together
+
self._pos = 0
<br />We first need to create the '''controller''', which then also knows about our '''cars'''. Then, there is some housekeeping that we need to do - create a QApplication, create the QDeclarativeView and set its resizing mode (so that the root object in QML is always as big as the window).
+
 
<br />We then get the root context and expose the controller and the cars list to it (if you look closely, we don't really need the cars themselves). Then, we load the QML file, show the view and start the application.
+
def fill(self, widgets):
<br /></code><br />controller = Controller(cars)
+
widgets['model'].setProperty('text', self._lst[self._pos].model)
<br />app = QtGui.QApplication(sys.argv)
+
widgets['brand'].setProperty('text', self._lst[self._pos].brand)
<br />view = QtDeclarative.QDeclarativeView()<br />view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)
+
widgets['year'].setProperty('value', self._lst[self._pos].year)
<br />ctx = view.rootContext()
+
widgets['inStock'].setProperty('checked', self._lst[self._pos].inStock)
<br />for name in ('controller', 'cars'):<br /> ctx.setContextProperty(name, locals()[name])
+
widgets['position'].setProperty('text', 'd/%d' (self._pos+1, len(self._lst)))
<br />view.setSource(file.replace('.py', '.qml'))<br />view.show()
+
 
<br />app.exec''()<br /><code>
+
QtCore.Slot(QtCore.QObject)
 +
def prev(self, root):
 +
print 'prev'
 +
self._pos = max(0, self._pos - 1)
 +
self.fill(root.property('widgets'))
 +
 
 +
QtCore.Slot(QtCore.QObject)
 +
def next(self, root):
 +
print 'next'
 +
self._pos = min(len(self._lst) - 1, self.''pos + 1)
 +
self.fill(root.property('widgets'))
 +
 
 +
QtCore.Slot(QtCore.QObject)
 +
def init(self, root):
 +
print 'init'
 +
self.fill(root.property('widgets'))
 +
</code>
 +
 
 +
=== Example data ===
 +
Here is some example data, so that we can use our example and click through a list of cars:
 +
 
 +
<code>
 +
cars = [
 +
Car('Model T', 'Ford', 1908),
 +
Car('Beetle', 'Volkswagen', 1938, True),
 +
Car('Corolla', 'Toyota', 1966),
 +
Car('Clio', 'Renault', 1991, True),
 +
Car('Ambassador', 'Hindustan', 1958),
 +
Car('Uno', 'Fiat', 1983, True),
 +
Car('Ibiza', 'Seat', 1984, True),
 +
]
 +
</code>
 +
 
 +
=== Putting it all together ===
 +
We first need to create the '''controller''', which then also knows about our '''cars'''. Then, there is some housekeeping that we need to do - create a QApplication, create the QDeclarativeView and set its resizing mode (so that the root object in QML is always as big as the window).
 +
 
 +
We then get the root context and expose the controller and the cars list to it (if you look closely, we don't really need the cars themselves). Then, we load the QML file, show the view and start the application.
 +
 
 +
<code>
 +
controller = Controller(cars)
 +
 
 +
app = QtGui.QApplication(sys.argv)
 +
 
 +
view = QtDeclarative.QDeclarativeView()
 +
view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)
 +
 
 +
ctx = view.rootContext()
 +
 
 +
for name in ('controller', 'cars'):
 +
ctx.setContextProperty(name, locals()[name])
 +
 
 +
view.setSource(file.replace('.py', '.qml'))
 +
view.show()
 +
 
 +
app.exec''()
 +
</code>
  
 
== CarAnalogy.qml ==
 
== CarAnalogy.qml ==
Line 69: Line 152:
 
This is the user interface of our application. We only use the controller in the UI, and we also only use it for initialization and when buttons are clicked.
 
This is the user interface of our application. We only use the controller in the UI, and we also only use it for initialization and when buttons are clicked.
  
</code>
+
<code>
  
import Qt 4.7<br />import &quot;colibri&amp;quot;
+
import Qt 4.7
 +
import "colibri"
  
Rectangle {<br /> id: page
+
Rectangle {
 +
id: page
  
 
property variant widgets
 
property variant widgets
  
width: 800<br /> height: 480
+
width: 800
 +
height: 480
  
Grid {<br /> id: grid<br /> columns: 2<br /> anchors.centerIn: parent<br /> spacing: 10
+
Grid {
 +
id: grid
 +
columns: 2
 +
anchors.centerIn: parent
 +
spacing: 10
  
Row {<br /> CLButton { text: &quot;&amp;quot;; onClicked: { controller.prev(page) } }<br /> CLButton { text: &quot;&amp;quot;; onClicked: { controller.next(page) } }<br /> }
+
Row {
 +
CLButton { text: ""; onClicked: { controller.prev(page) } }
 +
CLButton { text: ""; onClicked: { controller.next(page) } }
 +
}
  
Text { id: position; text: &quot; &quot; }
+
Text { id: position; text: " " }
  
Text { text: &quot;Model:&quot; }
+
Text { text: "Model:" }
  
 
CLLineEdit { id: model }
 
CLLineEdit { id: model }
  
Text { text: &quot;Brand:&quot; }
+
Text { text: "Brand:" }
  
 
CLLineEdit { id: brand }
 
CLLineEdit { id: brand }
  
Text { text: &quot;Year:&quot; }
+
Text { text: "Year:" }
  
Column {<br /> spacing: 10<br /> CLSlider {<br /> id: year<br /> minimum: 1900<br /> maximum: 2010<br /> }<br /> Text {<br /> text: year.value<br /> }<br /> }
+
Column {
 +
spacing: 10
 +
CLSlider {
 +
id: year
 +
minimum: 1900
 +
maximum: 2010
 +
}
 +
Text {
 +
text: year.value
 +
}
 +
}
  
Text { text: &quot; &quot; }
+
Text { text: " " }
  
Row {<br /> spacing: 10<br /> CLCheckBox { id: inStock }<br /> Text { text: &quot;In Stock&amp;quot; }<br /> }<br /> }
+
Row {
 +
spacing: 10
 +
CLCheckBox { id: inStock }
 +
Text { text: "In Stock" }
 +
}
 +
}
  
Component.onCompleted: {<br /> widgets = {<br /> 'position': position,<br /> 'model': model,<br /> 'brand': brand,<br /> 'year': year,<br /> 'inStock': inStock,<br /> }<br /> controller.init(page)<br /> }<br />}<br /><code>
+
Component.onCompleted: {
 +
widgets = {
 +
'position': position,
 +
'model': model,
 +
'brand': brand,
 +
'year': year,
 +
'inStock': inStock,
 +
}
 +
controller.init(page)
 +
}
 +
}
 +
</code>
  
 
== How the example app looks like ==
 
== How the example app looks like ==
  
 
Simply start the resulting app with '''python CarAnalogy.py''' and you should get something like this:
 
Simply start the resulting app with '''python CarAnalogy.py''' and you should get something like this:
 +
 +
[[Image:Screenshotfill.jpg]]

Latest revision as of 03:29, 5 June 2016


This PySide tutorial shows you how to create a "classic" form-based UI with the Colibri QML Components and have it filled and controlled by Python code. There are several ways to do this, and depending on your use case, there might be a better method. Please also note that in this example, the controller code knows a bit about the UI (or rather: the UI has to inform the controller which widgets are to be filled), which might not be desired.

CarAnalogy.py

Import the required modules

We need the QtCore module for QObject, the QtGui module for QApplication and the QtDeclarative module for the QML View (QDeclarativeView):

import sys

from PySide import QtCore, QtGui, QtDeclarative

Define a Car as QObject

This is simply the Python version of a normal QObject with 4 properties:

  • model (String) - The car name
  • brand (String) - The company who made the card
  • year (int) - The year it was first produced
  • inStock (bool) - If the car is still in stock at the warehouse
class Car(QtCore.QObject):
 def ''init''(self, model='', brand='', year=0, in_stock=False):
 QtCore.QObject.''init''(self)
 self._''model = model
 self.brand = brand
 self.year = year
 self.''_in_stock = in_stock

changed = QtCore.Signal()

def ''model(self): return self.''_model
 def ''brand(self): return self.''_brand
 def ''year(self): return self.''_year
 def ''inStock(self): return self.''_in_stock

def ''setModel(self, model):
 self.''_model = model
 self.changed.emit()

def ''setBrand(self, brand):
 self.''_brand = brand
 self.changed.emit()

def ''setYear(self, year):
 self.''_year = year
 self.changed.emit()

def ''setInStock(self, in_stock):
 self.''_in_stock = in_stock
 self.changed.emit()

model = QtCore.Property(str, _model, _setModel, notify=changed)
 brand = QtCore.Property(str, _brand, _setBrand, notify=changed)
 year = QtCore.Property(int, _year, _setYear, notify=changed)
 inStock = QtCore.Property(bool, _inStock, ''setInStock, notify=changed)

The controller to fill the form and react to events

This is another QObject that takes a list of cars as constructor parameter. It also remembers the current position in the list of cars. There are three slots that are visible to the "outside" (QML in our case):

  • prev - Go to the previous item
  • next - Go to the next item
  • init - Show the first item

All these slots take a QObject as parameter, and from the QML file, we will pass the root object there, which has a property widgets where we save a dictionary of mappings from name to QML component. The fill function takes care of filling in the data of the current car into the QML widgets.

class Controller(QtCore.QObject):
 definit<span class="lst self,">:
 QtCore.QObject.</span>init''_(self)
 self._lst = lst
 self._pos = 0

def fill(self, widgets):
 widgets['model'].setProperty('text', self._lst[self._pos].model)
 widgets['brand'].setProperty('text', self._lst[self._pos].brand)
 widgets['year'].setProperty('value', self._lst[self._pos].year)
 widgets['inStock'].setProperty('checked', self._lst[self._pos].inStock)
 widgets['position'].setProperty('text', 'd/%d' (self._pos+1, len(self._lst)))

QtCore.Slot(QtCore.QObject)
 def prev(self, root):
 print 'prev'
 self._pos = max(0, self._pos - 1)
 self.fill(root.property('widgets'))

QtCore.Slot(QtCore.QObject)
 def next(self, root):
 print 'next'
 self._pos = min(len(self._lst) - 1, self.''pos + 1)
 self.fill(root.property('widgets'))

QtCore.Slot(QtCore.QObject)
 def init(self, root):
 print 'init'
 self.fill(root.property('widgets'))

Example data

Here is some example data, so that we can use our example and click through a list of cars:

cars = [
 Car('Model T', 'Ford', 1908),
 Car('Beetle', 'Volkswagen', 1938, True),
 Car('Corolla', 'Toyota', 1966),
 Car('Clio', 'Renault', 1991, True),
 Car('Ambassador', 'Hindustan', 1958),
 Car('Uno', 'Fiat', 1983, True),
 Car('Ibiza', 'Seat', 1984, True),
]

Putting it all together

We first need to create the controller, which then also knows about our cars. Then, there is some housekeeping that we need to do - create a QApplication, create the QDeclarativeView and set its resizing mode (so that the root object in QML is always as big as the window).

We then get the root context and expose the controller and the cars list to it (if you look closely, we don't really need the cars themselves). Then, we load the QML file, show the view and start the application.

controller = Controller(cars)

app = QtGui.QApplication(sys.argv)

view = QtDeclarative.QDeclarativeView()
view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)

ctx = view.rootContext()

for name in ('controller', 'cars'):
 ctx.setContextProperty(name, locals()[name])

view.setSource(file.replace('.py', '.qml'))
view.show()

app.exec''()

CarAnalogy.qml

This is the user interface of our application. We only use the controller in the UI, and we also only use it for initialization and when buttons are clicked.

import Qt 4.7
import "colibri"

Rectangle {
 id: page

property variant widgets

width: 800
 height: 480

Grid {
 id: grid
 columns: 2
 anchors.centerIn: parent
 spacing: 10

Row {
 CLButton { text: "←"; onClicked: { controller.prev(page) } }
 CLButton { text: "→"; onClicked: { controller.next(page) } }
 }

Text { id: position; text: " " }

Text { text: "Model:" }

CLLineEdit { id: model }

Text { text: "Brand:" }

CLLineEdit { id: brand }

Text { text: "Year:" }

Column {
 spacing: 10
 CLSlider {
 id: year
 minimum: 1900
 maximum: 2010
 }
 Text {
 text: year.value
 }
 }

Text { text: " " }

Row {
 spacing: 10
 CLCheckBox { id: inStock }
 Text { text: "In Stock" }
 }
 }

Component.onCompleted: {
 widgets = {
 'position': position,
 'model': model,
 'brand': brand,
 'year': year,
 'inStock': inStock,
 }
 controller.init(page)
 }
}

How the example app looks like

Simply start the resulting app with python CarAnalogy.py and you should get something like this:

Screenshotfill.jpg