# Model/View framework tutorial Qt's [Model/View framework](https://doc.qt.io/qt-6/modelview.html) is used to "separate data from views in widgets that handle data sets". It is an efficient solution when displaying data in containers such as lists, tables or trees. These containers do not use standard widgets to display the data. Instead, they handle the underlying data directly from the model. Most common containers are _QListView_, _QTableView_ and _QTreeView_. Qat can interact with Model/View objects in two ways: - by using virtual widgets to access each item like any other widget - by directly manipulating the data in the model ## Table of contents - [Virtual widgets](#virtual-widgets) - [QListView](#qlistview) - [QTableView](#qtableview) - [QTreeView](#qtreeview) - [Data model](#data-model) - [QModelIndex](#qmodelindex) - [Roles](#roles) - [Selection](#selection) ## Virtual widgets Virtual widgets are wrappers around model items that provide a widget-like interface to be used with API functions such as _wait_for_object()_, _mouse_click()_, _type_in()_ and others. To access a virtual widget, you need to provide a compatible object definition i.e a definition with its 'container' field set to the _Q*View_ container widget and its 'row' (and optionally 'column' for tables and trees) field set to the index of the data. > 'row' and 'column' fields are zero-based indexes Example: ```python item_definition = { 'container': {'objectName': 'myContainer'}, 'row': 5 } item = qat.wait_for_object(item_definition) # Select the item qat.mouse_click(item) # Take a screenshot of the item image = qat.grab_screenshot(item) assert image.width == item.globalBounds['width'] image.save('./screenshot.png') ``` The virtual widget returned by _wait_for_object()_ is of type __ModelIndexWrapper__ which provides the following custom functions and properties: - __row__ (read-only) : The row index of the item - __column__ (read-only) : The column index of the item - __text__ : The displayed text of this item (corresponds to Qt::DisplayRole) - __color__ : The text color of this item (corresponds to Qt::ForegroundRole) - __QVariant data(int role = Qt::DisplayRole)__ : Returns the value of this item for the given role - __bool setData(const QVariant& value, int role = Qt::DisplayRole)__ : Changes the value of this item for the given role - __void ScrollTo()__ : Automatically scrolls the container to make this item visible ### QListView A _QListView_ is a simple non-hierarchical list. By default, items can be edited by double-clicking on them: !["QListView"](./images/view_model/listView.png) ```python item_definition = { 'container': {'type': 'QListView'}, 'row': 15 } item = qat.wait_for_object(item_definition) # Automatically scroll to make the item visible item.ScrollTo() # Edit the item qat.double_click(item) qat.type_in(item, 'hello') # Close the editor qat.type_in(item, '') assert item.text == 'hello' ``` !["Edited QListView"](./images/view_model/listView_edited.png) ### QTableView A _QTableView_ is similar to a _QListView_ but can display multiple columns of data. The column index can be added to the item definition. If omitted, the first column (index = 0) is used. !["QTableView"](./images/view_model/tableView.png) ```python item_definition = { 'container': {'type': 'QTableView'}, 'row': 5, 'column': 1 } item = qat.wait_for_object(item_definition) assert item.row == 5 assert item.column == 1 # Edit the item qat.double_click(item) qat.type_in(item, 'hello') # Close the editor qat.type_in(item, '') assert item.text == 'hello' ``` !["Edited QTableView"](./images/view_model/tableView_edited.png) ### QTreeView _QTreeView_ is more complex as it displays hierarchical data as long as multiple columns. Parent/child relationship of the data is managed by the 'container' field of items' definition. !["QTreeView"](./images/view_model/treeView.png) ```python root_item_definition = { 'container': {'type': 'QTreeView'}, 'row': 2 } root_item = qat.wait_for_object(root_item_definition) assert root_item.text == 'Root item 2' # Third child of root_item is child item 10 child_item_definition = { 'container': root_item_definition, 'row': 2, 'column': 2 } child_item = qat.wait_for_object(child_item_definition) assert child_item.text == 'Value 10' ``` Clicking on the left side of a root item will activate its checkbox: ```python # Roles are defined in Qt::ItemDataRole checked_state_role = 10 unchecked_state = 0 checked_state = 2 assert root_item.data(checked_state_role) == unchecked_state # Assumes a square box on the left of the item offset = root_item.globalBounds['height'] / 2 qat.mouse_click(root_item, x=offset, y=offset) assert root_item.data(checked_state_role) == checked_state ``` Result: !["Checked QTreeView"](./images/view_model/treeView_checked.png) ## Data model Another way to interact with Model/View objects is to manipulate the underlying data model. The data model associated to a _Q*View_ can be accessed with their _model_ property. ### QModelIndex Each item of the model is identified by an instance of _QModelIndex_. These indexes are created by the _model.index()_ function and the associated data is accessed with the _model.data()_ function: ```python tree_definition = { 'type': 'QTreeView' } tree = qat.wait_for_object(tree_definition) model = tree.model # model.index() takes the row and column indexes as arguments root_item = model.index(2, 0) assert model.data(root_item) == 'Root item 2' # To access child items in a tree, the parent index is used child_item = model.index(3, 1, root_item) # Fourth child of Root item 2 is Child item 11 assert model.data(child_item) == 'Name 11' ``` ### Roles Each item of the model can contain data for multiple roles. The default role is the _DisplayRole_ which represents the text displayed in the container. Other roles include: - _FontRole_ (the font used to display the text of the item) - _ForegroundRole_ (the QBrush or the color used to render the text) - _CheckStateRole_ (indicating whether the item is checked or not) > Please refer to Qt documentation (Qt::ItemDataRole) for a complete list. Data for each role can be read with the _model.data()_ function and changed with the _model.setData()_ function. ```python # DisplayRole (default) model.setData(child_item, 'hello') assert model.data(child_item) == 'hello' # CheckStateRole check_role = 10 unchecked = 0 partially_checked = 1 checked = 2 # Items are unchecked by default assert model.data(root_item, check_role) == unchecked model.setData(root_item, checked, check_role) assert model.data(root_item, check_role) == checked ``` Result: !["QTreeView roles"](./images/view_model/treeView_roles.png) ### Selection Item selection is managed by a dedicated model, accessible with the _selectionModel_ property. ```python tree_definition = { 'type': 'QTreeView' } tree = qat.wait_for_object(tree_definition) model = tree.model # Create a model index root_item = model.index(2, 0) child_item = model.index(3, 1, root_item) selectionModel = tree.selectionModel # Items are not selected by default assert not selectionModel.isSelected(root_item) assert not selectionModel.isSelected(child_item) # Change selection selected_state = 3 selectionModel.select(child_item, selected_state) assert selectionModel.isSelected(child_item) assert selectionModel.currentIndex == child_item ``` Result: !["QTreeView selection"](./images/view_model/treeView_select.png) Other functions are available, please refer to the Qt documentation for the [QItemSelectionModel](https://doc.qt.io/qt-6/qitemselectionmodel.html) class.