I have a problem using QFileSystemModel and Listview (qml).
I want to use 2 listviews:
- one to list directories of a directory
- one to list files available in the directory selected in the first view
At first, I am trying to list directories in a listview however, it seems the rootIndex is not well set because only "/" item is displayed:
Below my sample code:
main.cpp:
QFileSystemModel *lDirModel = new QFileSystemModel();
QDir lDir = QDir("/home/toto");
lDirModel->setRootPath("/home/toto");
lDirModel->setFilter(QDir::AllEntries | QDir::AllDirs);
qml_engine->rootContext()->setContextProperty("mediaModel", lDirModel);
qml_engine->rootContext()->setContextProperty("mediaRootModel", lDirModel->index("/home/toto"));
media.qml
DelegateModel {
id: visualModel
model: mediaModel
rootIndex: mediaRootModel
delegate: Rectangle {
color: "red"
height: 40
width: 500
Text { text: "Name: " + filePath}
}
}
ListView {
anchors.fill: parent
clip: true
model: visualModel
}
I have tested it also with TreeView (qml) it "works" even if rootIndex is not taken into account (it starts from "/")
The problem seems to be on the rootIndex definition in ListView. I don't know where I am wrong. Normally, it has to work .
I have read http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html but the example is for QAbstractListModel not QAbstractItemModel (QFileSystemModel inherits)
Do you know where I am wrong ?
Thank you for your help.
Edit:
the strange things is that using qlistview, it works and display $HOME content:
QFileSystemModel *lModel = new QFileSystemModel();
QModelIndex lIndex = lModel->setRootPath("/home/toto");
_ui->recordsListView->setModel(lModel);
_ui->recordsListView->setRootIndex(lIndex);
Somebody can help ?
Related
I have 2 static ListModel's in this example, in reality I use LocalStorage to fill the ListModel, but to keep it simple, I added 2 buttons to change the Models, but I want to tie it to the TableView's Header Column click event, and can not figure out how to do that from other examples of trying to sort, I do not know if it is possible to have a sort using ListModel, I could not find any example, so can someone explain this or show an example, of how to replace the buttons with column click events, I can then use this to pass the sort by argument to my LocalStorage sql statement to update the ListModel.
Update: I forgot to mention I was looking for a Qml / Qml JavaScript solution, for some reason I thought if I left off the C++ tag, I would avoid this issue, I will use this method as a last resort, since I decided to write this App using only Qml, with no C++ back end, but I do have that version now, since I had issues with how I was importing JavaScript written for the Web, as opposed to Qml JavaScript, which is not the same.
To be clear, I am trying to change Models and not Sort the rows, those are not the same question, the difference is in how the click event is used, and all I want to do is change the name of a query in the back end, which is Qml JavaScript, the reason I do not want C++ solutions is because I am doing this in Felgo, but this is not a Felgo quesiton, it works fine with C++, but you have to set up Live to work with it, and in reality this is going to be source I open up to github, and want it to be able to work without C++, and it seems there should be a way to hook this, and Mouse did work for me, capturing it in the even keep the header from loading, since it hooks at the beginning and waits for input, but if I have to, I am sure your solution will work, then I will accept it, sorry about that confusion, I get confused about what tags to use, so originally I only included qml, and qt was added to it, which I though was a great idea, because this really is a Qt question, that relates to Qml only, and not C++, that is another tag, this is a trend that Felgo is pushing, and they have good reasons, its easier for JavaScript or C/C++ Programmers to use, and Live Debugging works faster when used without a C++ back end, so now I gave more information, when Originally I though this was a simple question, that only related to Qml, if not then the answer has been given for C++, unless there is a better way, seeing how all I want to do is click on the header column the same way I would like on the button, can I embed the button into the column? If so how? I could not find an example of this, only ones that would effect text properties, and would sort the rows, which is not what I was trying to do, only update the model.
import QtQuick 2.11
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.3
import QtQuick.Window 2.11
Window {
visible: true
width: 640
height: 480
title: qsTr("TableView Sort")
Column {
id: column
spacing: 9
anchors.fill: parent
TableView {
id: tableView
anchors.left: column.left
anchors.leftMargin: 6
anchors.right: column.right
anchors.rightMargin: 273
highlightOnFocus: true
model: myListModel1
sortIndicatorVisible: true
TableViewColumn {
role: "title"
title: "Column 1"
//width: 133
}
TableViewColumn {
role: "description"
title: "Column 2"
//width: 166
}
}
Button {
id: button1
text: qsTr("Model 1")
anchors.left: column.left
anchors.leftMargin: 6
onClicked: {
tableView.model = myListModel1
}
}
Button {
id: button2
text: qsTr("Model 2")
anchors.left: column.left
anchors.leftMargin: 6
onClicked: {
tableView.model = myListModel2
}
}
}
ListModel {
id: myListModel1
ListElement {
title: "Orange"
description: "Orange is Orange"
}
ListElement {
title: "Banana"
description: "Yellow"
}
ListElement {
title: "Apple"
description: "Red"
}
}
ListModel {
id: myListModel2
ListElement {
title: "Apple"
description: "Red"
}
ListElement {
title: "Banana"
description: "Yellow"
}
ListElement {
title: "Orange"
description: "Orange is Orange"
}
}
}
Update: This worked
onSortIndicatorColumnChanged: tableView.model = (sortIndicatorColumn == 0) ? myListModel1 : myListModel2
onSortIndicatorOrderChanged: tableView.model = (sortIndicatorColumn == 0) ? myListModel1 : myListModel2
Thanks for any help.
You could use a proxy model to sort your model. But, there is no QML component and you have to use QSortFilterProxyModel.
It's quite easy to do. But, QSortFilterProxyModel is not made to be used with a QML tableview (your table uses role names to display the columns and the proxy model will attempt to sort on index).
A quick example:
class SortProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
SortProxyModel(): QSortFilterProxyModel ()
{
}
// Define the way you want to sort data
bool lessThan(const QModelIndex& left, const QModelIndex& right) const
{
int role = sourceModel()->roleNames().key(roleName.toLocal8Bit(), 0);
return left.data(role) < right.data(role);
}
Q_INVOKABLE void setSortRole(QString const& roleName) // Used to select the sort role
{
this->roleName = roleName;
}
Q_INVOKABLE virtual void sort(int /*column*/, Qt::SortOrder order = Qt::AscendingOrder)
{
QSortFilterProxyModel::sort(0, order); // Always use the first column.
}
private:
QString roleName; // Role used to sort the model
};
// main.cpp
// Declare your type to use it in QML
qmlRegisterType<SortProxyModel>("SortProxyModel", 0, 1, "SortProxyModel");
// Main.qml
import SortFilterProxyModel 0.1;
TableView {
id: tableView
model: proxy // Use the proxy model rather than the model itself
sortIndicatorVisible: true
onSortIndicatorColumnChanged: { // Called when you click on the header
if (sortIndicatorColumn == 0) // Set the role used to sort data
model.setSortRole("title");
else
model.setSortRole("description");
model.sort(sortIndicatorColumn, sortIndicatorOrder)
}
onSortIndicatorOrderChanged: { // Called when you click on the header
if (sortIndicatorColumn == 0) // Set the role used to sort data
model.setSortRole("title");
else
model.setSortRole("description");
model.sort(sortIndicatorColumn, sortIndicatorOrder)
}
SortProxyModel {
id: proxy
objectName: "proxy"
sourceModel: myListModel1
}
It's just a quick example and you should improve the code. But, I think it will be a good start...
I need to insert elements in a ListView inside another ListView (via JS code inside my QML file) but when I try to access the inner ListView I get the error :
TypeError: Cannot call method 'insert' of undefined
Here is an example code to show my problem :
Item{
id:list
width: parent.width-210
height: parent.height
x:105
Component{
id:listDelegate
Item {
id:elem
height: 100
width: parent.width
Item{
id:titre_liste
height: 50
width: parent.width
Text{
anchors.left: parent.left
color:"white"
text:titre_txt
font.pixelSize: 25
font.bold: false
}
}
Item{
id:listInList
width: parent.width-100
height: parent.height
Component{
id:listInListDelegate
Item{
id:element_liste
height: parent.height
width: parent.width/5
Text{
anchors.left: parent.left
color:"white"
text:element_txt
font.pixelSize: 25
font.bold: true
}
}
}
ListView {
id: viewin
anchors.fill: parent
model: ListModel{
id:listModel_in
}
delegate: listInListDelegate
}
}
}
}
ListView {
id: viewglobal
anchors.fill: parent
model: ListModel{
id:listModel
}
delegate: listDelegate
}
}
And here is my JS code, at the end of the QML file :
function addItem(){
var i;
var numListe = -1;
var liste = "titre"
var item = "item"
for(i = 0;i<listModel.count;i++)
{
if(listModel.get(i).titre_txt === liste)
{
numListe = i;
}
}
if(numListe === -1)//if the list doesn't exist
{
listModel.append({titre_txt:liste});
numListe = listModel.count-1;
}
listModel.get(numListe).listModel_in.insert(0,{element_txt:item});
}
The error come from the last line of the JS code, when I try to insert a new element in the inner list. I verified that the value of "numListe" is 0 so it is not just a problem of wrong index.
How can I add elements to the inner list ?
There is a lot of stuff wrong with that code.
For starters - it is a mess, which is a very bad idea for someone who is obviously new at this stuff. Keep it clean - that's always a good idea regardless of your level of expertise.
listModel_in is an id and as such cannot be accessed outside of the delegate component.
That object however happens to be bound to the view's model property, so as long as the model doesn't change, you can access listModel_in via the model property. However, the view itself doesn't look like it is the delegate root object, so you have to interface it, for example by using an alias.
However, the inner model doesn't exist in the outer model, it only exists in the outer model's delegate item.
So you cannot possibly get it from listModel. You can get it from the viewglobal view, however ListView doesn't provide access by index. So you will have to set currentIndex for every index and use currentItem.
So it will look like this:
viewglobal.currentItem.modelAlias.insert(0,{element_txt:item});
But it should go without saying, you are putting data in the GUI layer, which is conceptually wrong. But it gets worse than conceptually wrong - you might not be aware of this, but ListView only creates items that it needs to show, meaning that it creates and destroys delegates as necessary. Meaning if your item falls out of view, it will be destroyed, and when it comes back into view, a new one will be created, and all the data you had in the model of the old delegate item will be lost. The view should never store data, just show it.
The inner model should be inside the outer model. However, last time I checked, QMLs ListModel didn't support model nesting, neither using declarative nor imperative syntax. If you want to nest models, I have provided a generic object model QML type you can use.
I'm trying to create a correct Treeview with Qml Qt 5.5.
I succeed to have a Treeview with a global root.
But impossible to find how to add child for row item.
For the moment I got something like that :
TreeView {
id:listTree
anchors.fill: parent
anchors.leftMargin: 1
headerVisible: false
backgroundVisible: false
selection: ItemSelectionModel {
model: myModel
}
TableViewColumn {
role: "name"
}
itemDelegate: Item {
Text {
anchors.verticalCenter: parent.verticalCenter
color: styleData.textColor
elide: styleData.elideMode
text: styleData.value
}
}
Component.onCompleted: {
model.append({"name":"Never"})
model.append({"name":"gonna"})
model.append({"name":"give"})
model.append({"name":"you"})
model.append({"name":"up"})
model.append({"name":"Never"})
model.append({"name":"gonna"})
model.append({"name":"let"})
model.append({"name":"you"})
model.append({"name":"dow"})
}
}
And I would like something like that :
How can I do it ?
You can also create a TreeModel class that extends QStandardItemModel and overrides roleNames(), like done here. To add children to nodes in your tree, just use appendRow().
TreeModel::TreeModel(QObject *parent) : QStandardItemModel(parent)
{
QStandardItem *root = new QStandardItem("root");
QStandardItem *child = new QStandardItem("child");
this->appendRow(root);
root->appendRow(child);
}
Your model doesn't have any parent child relationships which is why its displayed like a list.
You'll want your "TreeModel" to be a collection of TreeItems. Each TreeItem will have knowledge of their own children and their parent item.
You can follow a fully implemented Qt example found here http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html. You'll want to (in C++) make a class for TreeItem, and a separate class for your TreeModel.
That example is working code, you can just copy and paste it and get a working model for your TreeView.
The part you'll be particularly interested in is the implementation of the method setupModelData(). That's where you'll want to parse through your wonderful dataset of 80's lyrics and assign each of them a TreeItem.
Each TreeItem (one for every row of data) should be given knowledge of its parent upon creation (in its constructor). Then as soon as its children are created, call parentTreeItem.appendChild(childTreeItem)
When your model is completed, you can assign it to your qml view in a few ways, registering it with qmlRegisterType is what I prefer (http://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType)
Once registered, it can be created in qml as though it were a ListView or any other qml object.
NOTE: You'll have this rootItem. This is something that isn't usable by the view, but all your "first indentation" parents are children of the rootItem.
Good luck!
Can you provide a code snippet of what line is causing your about failing to make a shortcut for QAbstractItemModel?
I'm going crazy. I have a ListView inside a ScrollView, hooked up to a model that inherits QAbstractListModel. When objects are added to the model, the ListView shows them using a delegate. So far, so good.
But I really want the view to stay scrolled to the bottom (like a chat window), and I'm having a very difficult time making that happen. Here is the relevant QML code:
Rectangle {
ScrollView {
[anchor stuff]
ListView {
id: messageList
model: textMessageFiltered
delegate: messageDelegate
}
}
TextField {
id: messageEditor
[anchor stuff]
onAccepted: {
controller.sendTextMessage(text)
text = ""
/* This works. */
//messageList.positionViewAtEnd();
}
}
Component {
id: messageDelegate
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
color: "white"
height: nameText.height + 4
Text {
id: nameText
wrapMode: Text.Wrap
text: "<b>" + authorName + " (" + authorId + ")</b> " + message
[anchor stuff]
}
ListView.onAdd: {
console.log("This prints just fine!")
messageList.positionViewAtEnd()
}
}
}
}
The really strange thing, is that messageList.positionViewAtEnd() (at the end of the file) actually jumps it to the beginning. Without the call, the view stays where it is, even as new entries appear in the list. And indeed, if you look at the Qt documentation for the ListView.positionViewAtEnd(), it says:
Positions the view at the beginning or end, taking into account ...
Is that a silly error in the documentation, or what? I've tried everything I can think of to make this work, particularly the positionViewAtIndex() method and using highlighters to force the scroll to happen. But nothing works. Note the /* This works. */ comment in the source code above. When that is enabled, it works totally fine! (except of course, it jumps to the ListView.count()-2 index, instead of the end of the list)
Does anyone have any idea what might be wrong here? Any examples I could try to prove that there's a terrible, terrible bug in QML?
I'm using Qt 5.3.1 with QtQuick 2.0 (or 2.1 or 2.2 fail too). I've tried many, many other configurations and code as well, so please ask if you need more info. I've completely exhausted my google-fu.
Thanks!
Edit 1
While the accepted answer does solve the above problem, it involves adding the Component.onCompleted to the delegate. This seems to cause problems when you scroll the list, because (I believe) the delegates are added to the view when you scroll up, causing the onCompleted trigger to be called even if the model item isn't new. This is highly undesirable. In fact, the application is freezing when I try to scroll up and then add new elements to the list.
It seems like I need a model.onAdd() signal instead of using the existence of a delegate instance to trigger the scroll. Any ideas?
Edit 2
And how does this NOT work?
ListView {
id: messageList
model: textMessageFiltered
delegate: messageDelegate
onCountChanged: {
console.log("This prints properly.")
messageList.positionViewAtEnd()
}
}
The text "This prints properly" prints, so why doesn't it position? In fact, it appears to reset the position to the top. So I tried positionViewAtBeginning(), but that did the same thing.
I'm totally stumped. It feels like a bug.
You need to set the currentIndex as well.
testme.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 300
height: 240
ScrollView {
anchors.fill: parent
ListView {
anchors.fill: parent
id: messageList
model: messageModel
delegate: Text { text: mytextrole }
highlight: Rectangle { color: "red" }
highlightMoveDuration: 0
onCountChanged: {
var newIndex = count - 1 // last index
positionViewAtEnd()
currentIndex = newIndex
}
}
}
ListModel {
id: messageModel
ListElement { mytextrole: "Dog"; }
ListElement { mytextrole: "Cat"; }
}
Timer {
property int counter: 0
running: true
interval: 500
repeat: true
onTriggered: {
messageModel.append({"mytextrole": "Line" + (counter++)})
}
}
}
There is still some jumping to the first element and jumping back down for a fraction of a second.
There is a note in documentation:
Note: methods should only be called after the Component has completed. To position the view at startup, this method should be called by Component.onCompleted.
Change your ListView.onAdd: to
Component.onCompleted: {
console.log("This prints just fine!")
messageList.positionViewAtEnd()
}
And it works well.
In your case, the ListView emits add signal before the new delegate is created and completed. The ListView is still working on something behind the scene, so positionViewAtEnd cannot work as expected. And /* This works. */ because it is called after the new delegate is completed. However, don't assume this always works. Simply follow the note, call positionViewAtEnd in Component.onCompleted, in documentation.
I created a ListView, which displays a couple of pages of content defined by the user (plain text). The page displayed is a delegate. Only one page is visible at a time. I decided to use it to get snapping to one item, in the same way the iOS' launcher works. The user simply flicks between the pages. (this is to be used on touch screens)
I need to have the index of the currently displayed page for some operation. currentIndex of the ListView always stays == 0. How can I get it?
For those who prefer code:
ListView
{
onCurrentIndexChanged: console.log(currentIndex) // this gets called only once - at startup
delegate: Column
{
// The page displayed, only one page at a time
}
}
Thanks
There are many ways to get the index of current item that is displayed in the screen. If you can get the x-y coordinate of current page, you can use indexAt method in ListView.
And in each delegate, you can find the index using index role within the scope of the delegate. The index is like a role you declared in your model, and is automatically assigned by ListView. For example,
ListView
{
delegate: Column
{
property int indexOfThisDelegate: index
//...
}
}
The index role is introduced here:
A special index role containing the index of the item in the model is also available to the delegate. Note this index is set to -1 if the item is removed from the model...
Another way is to explicitly assign value to the currentItem property in ListView, so the view can scroll by itself. Here is an simple example in Qt documentation, which is similar to your application.
I know this is quite old but I had the same problem and spend some time trying to find a way to get currentIndex that would work for me. In my case sometimes I need to change the width of my ListView so I have to recalculte currentIndex manualy every time I resize it.
But I found a highlightRangeMode property. When it's set to ListView.StrictlyEnforceRange then currentIndex is always updated automaticly and contains correct index of the currently visible item.
ListView {
highlightRangeMode: ListView.StrictlyEnforceRange
// ...
}
You can do like that:
QModelIndex index =this->indexAt(event->pos());
this ->setCurrentIndex(index);
You can use attached properties of ListView class (ListView). They are attached to each instance of the delegate.
See ListView.isCurrentItem or ListView.view example:
ListView {
width: 180; height: 200
Component {
id: contactsDelegate
Rectangle {
id: wrapper
width: 180
height: contactInfo.height
color: ListView.isCurrentItem ? "black" : "red"
Text {
id: contactInfo
text: name + ": " + number
color: wrapper.ListView.isCurrentItem ? "red" : "black"
}
}
}
model: ContactModel {}
delegate: contactsDelegate
focus: true
}