EDIT: 26.08.2014 08:20 - Completely reworked question!
What I want to do is:
Fill a qml-listview with data from a cpp-listmodel (QAbstractListModel).
Open a Dialog that show's more data from the cpp-listmodel by click on a listview-item.
I have two cpp-Classes:
DataModelItem with two attributes (listData (displayed in the listview) and detailsData (displayed in the Dialog))
DataModel which inherits QAbstractListModel with an attribut QList itemList.
DataModel.cpp:
QVariant DataModel::data(const QModelIndex &index, int role) const
{
DataModelItem *item = m_itemList.at(index.row());
switch (role) {
case ListDataRole:
return QString().sprintf("%.2f", item->listData());
break;
case DetailsDataRole:
return QString().sprintf("%.4f", item->detailsData());
break;
default:
qDebug () << "role not handled";
}
return QVariant();
}
What I now wanna do is, to display the listData in the ListView. When I click on one ListItem a dialog should appear with the detailsData.
I figured out, that I can't write model.detailsData in my main application, but just detailsData works (I also tried listview.model.detailsData with no effect). Probably someone know why this does not work.
Anyway I found a solution.
Here's the working example:
main.qml
import QtQuick 1.1
Rectangle {
width: 200
height: 400
ListView {
id: listView
model: dataModel
delegate: listDelegate
}
Component {
id: listDelegate
Item {
id: delegateItem
width: listDataText.width
height: listDataText.height
Text {
id: listDataText
text: listData
}
MouseArea {
anchors.fill: parent
onClicked: {
console.log(detailsData)
itemDetails.details = model.detailsData
itemDetails.visible = true
}
}
}
}
DetailsDialog {
id: itemDetails
visible: false
anchors.centerIn: parent
}
}
DetailsDialog.qml
import QtQuick 1.1
Rectangle {
property alias details: detailsText.text
width: 100
height: 62
Text {
id: detailsText
}
}
Related
There's a simple QStandardItemModel defined in c++ which I am displaying in a QML ListView via custom Delegates and a DelegateModel. The ListView can be reordered via Drag'n Drop:
// The DropArea is part of the delegate `comp_container`
DropArea{
anchors{fill: parent}
keys: ["pageitem"]
onEntered: {
let from = drag.source.DelegateModel.itemsIndex
let to = dragAreaPage.DelegateModel.itemsIndex
if ( pageItemDragOperationStartIndex === -1 ){
pageItemDragOperationStartIndex = from
}
pageItemDragOperationFinalIndex = to
console.log(from + "->" + to)
visualModel.items.move(from,to)
}
}
Here is the delegate model and pageproxymodel is the c++ model.
DelegateModel {
id: visualModel
model: pageproxymodel
delegate: comp_container
}
How do I want to update the c++ model?
The delegate's top level item is a MouseArea and I handle the reordering in the release handler:
onReleased: {
if ( pageItemDragOperationStartIndex !== -1 && pageItemDragOperationFinalIndex !== -1 ){
console.log("Page item final drag operation: " + pageItemDragOperationStartIndex + "->" + pageItemDragOperationFinalIndex)
pageproxymodel.move(pageItemDragOperationStartIndex, pageItemDragOperationFinalIndex)
pageItemDragOperationStartIndex = -1
pageItemDragOperationFinalIndex = -1
}
}
The c++ model's move function forwards the call to this handler:
bool PageModel::moveRow(const QModelIndex &sourceParent,
int sourceRow,
const QModelIndex &destinationParent,
int destinationChild)
{
if ( sourceRow < 0 || sourceRow > rowCount()-1 ||
destinationChild < 0 || destinationChild > rowCount() )
{
return false;
}
beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationChild);
QList<QStandardItem*> rowItems = takeRow(sourceRow);
insertRow(destinationChild, rowItems);
endMoveRows();
return true;
}
With the above c++ model code, it crashes at the release handler in QML:
I've tried other things to see the effect, no crashes, but also not the expected behaviour.
deleting a single row (which deletes 2 (!) rows in the QML ListView)
deleting a single row without begin/end calls (deletes 1 rows in the QML ListView, but can't be right)
remove and insert a single row without begin/end calls (QML ListView looks fine for a while but comes out of sync after a few moves)
Basically all I want to do is to save the ListView state via the c++ model, after all that is a standard use case and something simple must be wrong on my side, yet I can't see it.
One thing I like to do with DelegateModel makes use of DelegateModelGroup. By declaring a group named "all", it introduces an attached property allIndex which is useful for tracking an item after it has been reordered. The following example implements a DelegateModel with both MouseArea and DropArea. When in dragging mode, I disable all MouseArea so that the DropArea can have a chance at responding.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
property int activeMouseArea: -1
ListView {
id: listView
width: 420
height: parent.height
model: SampleDelegateModel { }
ScrollBar.vertical: ScrollBar {
width: 20
policy: ScrollBar.AlwaysOn
}
}
footer: Text { id: dbg }
}
// SampleData.qml
import QtQuick
import QtQuick.Controls
ListModel {
ListElement { name: "Steve Jobs" }
ListElement { name: "Jeff Bezos" }
ListElement { name: "Bill Gates" }
ListElement { name: "Elon Musk" }
}
// SampleDelegateModel.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models
DelegateModel {
id: delegateModel
model: SampleData { }
delegate: SampleDelegate { }
groups: [
DelegateModelGroup {
id: allItems
name: "all"
includeByDefault: true
}
]
filterOnGroup: "all"
function moveItem(from, to) {
dbg.text = `Debugging: moveItem(${from},${to})`;
allItems.move(from, to);
}
}
// SampleDelegate.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models
Rectangle {
property int allIndex: DelegateModel.allIndex
width: 400
height: labelText.height + 20
border.color: "grey"
z: mouseArea.drag.active || mouseArea.pressed ? 2 : 1
property int dragTo: -1
Drag.active: mouseArea.drag.active
Text {
id: labelText
anchors.centerIn: parent
text: allIndex + ": [" + index + "] " + name
}
DropArea {
anchors.fill: parent
onEntered: drag.source.dragTo = allIndex
}
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: parent
property point startPoint
enabled: activeMouseArea === -1
onPressed: {
activeMouseArea = allIndex;
dragTo = -1;
startPoint = Qt.point(parent.x, parent.y);
}
onReleased: {
activeMouseArea = -1;
[parent.x,parent.y] = [startPoint.x, startPoint.y];
Qt.callLater(delegateModel.moveItem, allIndex, dragTo);
}
}
}
You can Try it Online!
Found the mistake:
The pageItemDragOperationStartIndex and pageItemDragOperationFinalIndex variables where part of each delegate, but not of the page.
Also, as was pointed out in the comments, using a QStandardItemModel it is not necessary to call the begin/end functions. Now it works like a charm.
Background
I have a tree-like QStandardItemModel, whose items I would like to access from QML.
Here is how the model is defined on the C++ side:
backend.h
class Backend : public QObject
{
Q_OBJECT
Q_PROPERTY(QStandardItemModel *model READ model CONSTANT)
public:
explicit Backend(QObject *parent = nullptr);
QStandardItemModel *model() const;
private:
QStandardItemModel *m_model;
};
backend.cpp
Backend::Backend(QObject *parent) :
QObject(parent),
m_model(new QStandardItemModel(this))
{
auto *itemFirst = new QStandardItem(tr("First"));
auto *itemSecond = new QStandardItem(tr("Second"));
auto *subItem = new QStandardItem(tr("First_02"));
subItem->appendRow(new QStandardItem("First_02_01"));
itemFirst->appendRow(new QStandardItem(tr("First_01")));
itemFirst->appendRow(subItem);
itemFirst->appendRow(new QStandardItem(tr("First_03")));
itemSecond->appendRow(new QStandardItem(tr("Second_00")));
itemSecond->appendRow(new QStandardItem(tr("Second_01")));
m_model->appendRow(itemFirst);
m_model->appendRow(itemSecond);
}
QStandardItemModel *Backend::model() const
{
return m_model;
}
The model is exported to QML in main.cpp like this:
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
qmlRegisterUncreatableType<QStandardItemModel>("QStandardItemModel", 1, 0, "QStandardItemModel", "The model should be created in C++");
Using TreeView from QtQuick.Controls 1.4 in main.qml like this:
TreeView {
anchors.fill: parent
model: backend.model
TableViewColumn {
title: "Name"
role: "display"
}
}
I get the desired results, i.e. all items nested correctly:
Problem
When I try to manually iterate over the nested items using Repeater and DelegateModel like this:
ColumnLayout {
anchors.fill: parent
Repeater {
model: backend.model
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "blue"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "green"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Text {
color: "red"
text: model.display
}
}
}
}
}
}
}
}
}
}
}
the main branches (marked with blue) and the items on the first nesting level (marked with green) are the right ones, but I get the wrong items on the second nesting level (marked with red):
How to fix the code to correctly iterate over the QStandardItemModel's items on each nesting level?
The problem is in these two lines: rootIndex: modelIndex(index).
index is the index of the 'parent' model, but modelIndex(...) is the method of the current model.
I've tried it with this (slightly modified) piece of code and it worked:
Repeater {
model: DelegateModel {
id: model1
model: backend.model
delegate: ColumnLayout{
Text {
text: "Data: " + display
}
Repeater {
model: DelegateModel {
id: model2
model: backend.model
// 'index' comes from 'model1', so use the 'modelIndex' method from 'model1'
rootIndex: model1.modelIndex(index)
delegate: ColumnLayout{
Text {
text: "- Data: " + display
}
Repeater {
model: DelegateModel {
id: model3
model: backend.model
// 'index' comes from 'model2', so use the 'modelIndex' method from 'model2'
rootIndex: model2.modelIndex(index)
delegate: Text {
text: "-- Data: " + display
}
}
}
}
}
}
}
}
}
I am creating a Table using the new qml tableview (Qt 5.12).
I am able to create a model in C++ and able to populate the model in tabular format along with scrollbar.How do I add column headers to this table?
Code:
import QtQuick 2.12
import QtQuick.Controls 2.5
import Qt.labs.qmlmodels 1.0
//import QtQuick.Controls.Styles 1.4
import TableModel 0.1
Rectangle {
id:table
border.width: 3
border.color: 'dark blue'
QtObject{
id:internals
property int rows:0
property int col:0
property int colwidth:0
property var columnName:[]
}
function setRows(num){ internals.rows = num}
function setCols(num){ internals.col = num}
function setColWidth(num){internals.colwidth = num}
function setColNames(stringlist){
if(stringlist.length > 1)
internals.col = stringlist.length
dataModel.setColumnName(stringlist);
}
function addRowData(stringlist){
var len = stringlist.length
if(len >0)
{
dataModel.addData(stringlist)
}
}
TableModel {
id:dataModel
}
TableView{
id:tbl
anchors.top: headerCell
anchors.fill: parent
//columnSpacing: 1
//rowSpacing: 1
clip: true
ScrollBar.horizontal: ScrollBar{}
ScrollBar.vertical: ScrollBar{}
model:dataModel
Component{
id:datacell
Rectangle {
implicitWidth: 100
implicitHeight: 20
color: 'white'
border.width: 1
border.color: 'dark grey'
Text {
id:txtbox
anchors.fill: parent
wrapMode: Text.NoWrap
clip: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: display
}
}
}
}
function init(){
console.log("Calling init")
tbl.delegate= datacell
}
}
Currently TableView does not have headers so you should create it, in this case use Row, Column and Repeater.
On the other hand you must implement the headerData method and you must do it Q_INVOKABLE.
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
// ...
Q_INVOKABLE QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// ...
TableView {
id: tableView
model: table_model
// ...
Row {
id: columnsHeader
y: tableView.contentY
z: 2
Repeater {
model: tableView.columns > 0 ? tableView.columns : 1
Label {
width: tableView.columnWidthProvider(modelData)
height: 35
text: table_model.headerData(modelData, Qt.Horizontal)
color: '#aaaaaa'
font.pixelSize: 15
padding: 10
verticalAlignment: Text.AlignVCenter
background: Rectangle { color: "#333333" }
}
}
}
Column {
id: rowsHeader
x: tableView.contentX
z: 2
Repeater {
model: tableView.rows > 0 ? tableView.rows : 1
Label {
width: 60
height: tableView.rowHeightProvider(modelData)
text: table_model.headerData(modelData, Qt.Vertical)
color: '#aaaaaa'
font.pixelSize: 15
padding: 10
verticalAlignment: Text.AlignVCenter
background: Rectangle { color: "#333333" }
}
}
}
The complete example you find here.
If you're using Qt 5.15, you can use a HorizontalHeaderView for column labels.
https://doc.qt.io/qt-5/qml-qtquick-controls2-horizontalheaderview.html
There's also VerticalHeaderView for row labeling.
https://doc.qt.io/qt-5/qml-qtquick-controls2-verticalheaderview.html
I'm new to the QML. I came to the answer of eyllanesc so many times through my struggle with the new TableView (qt 5.12+), so I wanna thank you and share what helped me even more.
It's this video:
Shawn Rutledge - TableView and DelegateChooser: new in Qt 5.12
part of Qt Virtual Tech Summit 2019
The discussed code
It's a bit long but he covers
the differences between old and new TableView
how to create universal model for the views
resizable headers
different representation per column type - DelegateChooser
sortable columns
column reorder
I have a tree model derived from a QAbstractItemModel. And I can display the data in a tree like way.
What I want is to display teh data by the layers. To display only one level of a layer at a time AND put each layer on a stack and navigate backwards by poping the layer from the stack.
I guess I have to implement a custom delegate? Any advice would be highly appreciated. Thank you.
I recently implemented something similar, based on a QFileSystemModel, set as a qml contextProperty, named treeModel in the example below.
The idea was to keep track of the current QModelIndex, and to use the data() & rowCount() functions of the QAbstractItemModel to get the actual model data, and to use a recursive stack view for the navigation
General layout
ApplicationWindow {
id: main
visible: true
width: 640
height: 480
ColumnLayout
{
anchors.fill: parent
// Breadcrumb
SEE BELOW
// View
StackView
{
id: stackView
Layout.fillHeight: true
Layout.fillWidth: true
initialItem: TreeSlide {}
}
}
}
TreeSlide
The view itself is pretty simple. I didn't used anything fancy here, and it displays only one role, but you could extend it without trouble. Note that the view's model is NOT your treeModel, but instead just the rowCount for the rootIndex.
ListView
{
Layout.fillHeight: true
Layout.fillWidth: true
model: treeModel.rowCount(rootIndex)
clip: true
snapMode: ListView.SnapToItem
property var rootIndex
// I used a QFileSytemModel in my example, so I had to manually
// fetch data when the rootIndex changed. You may not need this though.
onRootIndexChanged: {
if(treeModel.canFetchMore(rootIndex))
treeModel.fetchMore(rootIndex)
}
Connections {
target: treeModel
onRowsInserted: {
rootIndexChanged()
}
}
delegate: ItemDelegate {
property var modelIndex: treeModel.index(index,0, rootIndex)
property bool hasChildren: treeModel.hasChildren(modelIndex)
width: parent.width
text: treeModel.data(modelIndex)
onClicked: {
if(hasChildren)
{
// Recursively add another TreeSlide, with a new rootIndex
stackView.push("TreeSlide.qml", {rootIndex: modelIndex})
}
}
}
}
Breadcrumb
To navigate the model, instead of a simple back button, I used a kind of dynamic breadcrumb
// Breadcrumb
RowLayout
{
Repeater
{
id: repeat
model: {
var res = []
var temp = stackView.currentItem.rootIndex
while(treeModel.data(temp) != undefined)
{
res.unshift(treeModel.data(temp))
temp = temp.parent
}
res.unshift('.')
return res
}
ItemDelegate
{
text : modelData
onClicked: {
goUp(repeat.count - index-1)
}
}
}
}
the goUp function simply goes up the stack by poping items
function goUp(n)
{
for(var i=0; i<n; i++)
stackView.pop()
}
To to do it completely by guides we should use DelegateModel and DelegateModel.rootIndex
DelegateModel {
id: delegateSupportPropConfigModel
model: supportModel
delegate: SupportPropConfigListItem {
id: currentItem
width: scrollRect2.width - 60
fieldName: model.fieldName
fieldValue: model.value
onClick:{
delegateSupportPropConfigModel.rootIndex = supportPropConfigModel.index(0, 0, supportPropConfigModel)
}
}
}
Column {
id: columnSettings
spacing: 2
Repeater {
model: delegateSupportPropConfigModel
}
}
I am binding a ListView with values passed from the cpp.
Issue: Listview displays only one row, mean first value, The rest of the rows are not appeared.
Checked:
I created an ListModel/ListElement in main.qml as test and bind with ListView, Now the Listview just working fine, display all values
I suspect after the signal emit, the error occurs.
Code snippet:
main.qml
ListView {
id: idListView
anchors {
left: parent.left
leftMargin: 10 * scaleFactor
right: parent.right
rightMargin: 10 * scaleFactor
top: rectangleToolBar.bottom
topMargin: 10 * scaleFactor
bottom: rectangleStatusBar.top
bottomMargin: 10 * scaleFactor
}
// model: objHomeController.detailsModel // Display only one row
//model: idListmodel //Working fine
delegate: comsearchDelegate
spacing: 10 * scaleFactor
clip: true
highlight: Rectangle {
color: 'grey'
Text {
anchors.centerIn: parent
color: 'white'
}
}
focus: true
}
Component {
id: comsearchDelegate
Row {
spacing: 10 * scaleFactor
Column {
Layout.alignment: Qt.AlignTop
Text { text: title; font { pixelSize: 14 * scaleFactor; bold: true } }
Text { text: description; font { pixelSize: 14 * scaleFactor; bold: true } }
}
}
}
ListModel {
id: idListModel
ListElement{
title : "sdfsdf";
description:"sdfsdfs";
}
ListElement {
title : "sdfsdf";
description:"sdfsdfs";
}
ListElement {
title : "sdfsdf";
description:"sdfsdfs";
}
ListElement {
title : "sdfsdf";
description:"sdfsdfs";
}
}
HomeController.h
Q_PROPERTY(Model* detailsModel READ get_detailsModel WRITE set_detailsModel NOTIFY detailsModelChanged )
HomeController.cpp
void HomeController::set_detailsModel(Model* value)
{
m_detailsModel = value;
//value has correct values - checked.
emit detailsModelChanged(value);
}
Model* HomeController::get_detailsModel(void)
{
return m_detailsModel;
}
void HomeController::getAllData()
{
m_detailsModel->clear();
m_detailsModel->updateModel(eveReadXML());
set_detailsModel(m_detailsModel);
}
Model.cpp
void Model::updateModel(const QList<Details> & details)
{
if(this->rowCount() > 0) {
this->clear();
}
beginInsertRows(QModelIndex(),rowCount(),rowCount());
m_modelData.append(details);
endInsertRows();
}
Since I came from .Net background, I would like to understand binding a Listview/GridView to a DataTable or an XML. Here I followed, Created class called Details [Details.h] and created Model.h/Model.cpp and fetching the value from there and binding to ListView. Am I doing right, Or do we have other flow. Any tutorial/Codesnippet/Link for projects highly appreciated.
To define ListModel from c++, you need to subclass QAbstractListModel
https://doc.qt.io/qt-5/qabstractlistmodel.html
You can take example on QQmlObjectListModel in this project : http://gitlab.unique-conception.org/qt-qml-tricks/qt-qml-models
Or clone it and use it in your project as follow :
Q_PROPERTY(QQmlObjectListModel<Details>* detailsModel READ get_detailsModel WRITE set_detailsModel NOTIFY detailsModelChanged)