QT tree that allows multiselection - c++

I'm making a simple file explorer and I ran into some problems with Qt. I want to show the user a tree view of files on his computer, but I also want to be able to select multiple files/directories and do something with them later on (by selecting checkboxes or multiple select using ctrl+left click or shift+left click). I've placed the QTreeView element and set up a model to it (QFileSystemModel). It gives me a good tree view, but I can't modify the headers (column names) or add my own column with checkbox in every row (for example). Qt is new to me, I've searched for few good hours for some tips/solutions, but nothing is working with QFileSystemModel. Is there anything I can do to get this working?
The code is short and simple:
QString lPath = "C:/";
QString rPath = "C:/";
leftTree_model = new QFileSystemModel(this);
rightTree_model = new QFileSystemModel(this);
leftTree_model->setRootPath(lPath);
rightTree_model->setRootPath(rPath);
//i have actually 2 tree views that work the same
ui->leftTree->setModel(leftTree_model); //ui->leftTree is the first tree view
ui->rightTree->setModel(rightTree_model); //the second

Use something of the following:
CheckStateRole to add checkboxes to your model. To do this, you inherit your custom item model (which you're going to use) from the QFileSystemModel, and reimplement the data() method, where you return bool values for CheckStateRole. You will also need the QAbstractItemModel::setData method to handle changes. You can also check the docs for QAbstractItemModel to see how to change header texts (headerData())
Change the selection mode of your view to allow multiple selections
EDIT:
here's a sample code to inherit from the model
class MyFancyModel : public QFileSystemModel
{
public:
MyFancyModel(QObject* pParent = NULL) : QFileSystemModel(pParent)
{
}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole ) const
{
if (role == Qt::CheckStateRole)
{
// stub value is true
return true; // here you will return real values
// depending on which item is currently checked
}
return QFileSystemModel::data(index, role);
}
};

Related

Applying two FileFilterProxyModel on one QFileSystemModel

The problem:
I am making a sort of file explorer, that has one view of the directories, and another view shows the files in the selected directory.
So the idea was to have one QFileSystemModel, and apply two FileFilterProxyModel's on it, one to show only folders, one to show only files (for selected folder, no sub folders)
The folders view works fine.
The files view works fine, for the first (root) folder, when the UI shows up.
After that, any selection on the folder view results in invalid source index (getting the index from the folder view and transforming it back to the source model index works fine, but when I try to translate the index from the source to the file filter model it always returns an invalid index, except for the first time , which means the file view always shows only the root folder content.)
Here is my code:
The models initialization:
void MainWindow::initFilesystemModel()
{
m_pFileSystemModel = new QFileSystemModel(this);
m_pFileSystemModel->setFilter(QDir::AllDirs|QDir::NoDotAndDotDot|QDir::Hidden|QDir::Drives|QDir::Files);
m_pFileSystemModel->setRootPath(QDir::rootPath());
m_pOnlyFoldersFilterModel = new FileFilterProxyModel(ui->pFolderTreeView);
m_pOnlyFoldersFilterModel->setSourceModel(m_pFileSystemModel);
QSortFilterProxyModel* onlyFilesModel = new OnlyFilesFileterModel(ui->pDetailViewWidget);
onlyFilesModel->setSourceModel(m_pFileSystemModel);
ui->pFolderTreeView->setModel(m_pOnlyFoldersFilterModel);
ui->pDetailViewWidget->setModel(onlyFilesModel);
connect(ui->pFolderTreeView, &QTreeView::pressed, this , &MainWindow::onFolderSelected);
}
And the reaction to selecting a folder:
Note the comments in the code:
void MainWindow::onFolderSelected(const QModelIndex &index)
{
QModelIndex sourceIndex = m_pOnlyFoldersFilterModel->mapToSource(index); //sourceIndex is valid and correct
QModelIndex rootIndex = dynamic_cast<QSortFilterProxyModel*>(ui->pDetailViewWidget->model())->mapFromSource(sourceIndex); //rootIndex is always invalid -1,-1
ui->pDetailViewWidget->setRootIndex(rootIndex);
}
I have found online few examples that did a very similar thing but they solved the issue by using two QFileSystemModel's, one for each view.
To me this counters the idea of Qt Mode/View Framework since it should be possible to show the same data in differently filtered in multiple views.
I must be missing something, or overlooking something, would very much appreciate any pointers, and things to try.
Thanks!
Presumably OnlyFilesFileterModel filters out everything that isn't a file, which will exclude subdirectories of your root directory.
mapFromSource will only return a valid index for a file.
You could add an intermediate model that presents only the direct children of a particular model's element, then you would set it's source root in onFolderSelected
class TreeToTableProxyModel : public QAbstractProxyModel
{
QModelIndex sourceRoot;
public:
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override
{
if (sourceIndex.parent() != sourceRoot) return QModelIndex();
return index(sourceIndex.row(), sourceIndex.column());
}
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
{
if (!proxyIndex.isValid()) return QModelIndex();
return sourceModel().index(proxyIndex.row(), proxyIndex.column(), sourceRoot);
}
void setSourceRoot(const QModelIndex &sourceIndex)
{
beginResetModel();
sourceRoot = sourceIndex;
endResetModel();
}
};

In QT C++ while am editing Qtable model data i need to get a small box and in that i need to show the editing text how is it possible?

tmodel = new QSqlTableModel(this);
tmodel->setTable("trainee_info");
tmodel->select();
by this we will get a table model as output and it can be editable but while editing i need to show a label below the field in which am trying to edit and text should populate in it . so how can i do that.?
If you are using a QStandardItemModel you have to set the item editable before adding to the model, like the code below:
item->setFlags(item->flags() | Qt::ItemIsEditable);
Then, when you will double click on the item it would open a line edit by default for editing.
For finer controls you can also install a delegate on your table and override the createEditor function.
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem & /*inOption*/, const QModelIndex & inIndex ) const override
{
auto editor = new QLineEdit( parent );
return editor;
}
this will give you a line edit to edit your model.
But your case is pretty simple and the first approach will work for you!

How to clear a QTreeView / QFileSystemModel

I have a list of items displayed in a QTableWidget, each one corresponding to a specific folder.
Next to this, I have a QTreeView, with a QFileSystemModel. When I select an item from the QTableWidget, it calls a slot (shown below) in order to display the corresponding folder content.
void MyWidget::diplayFolder(int row)
{
if (auto item = table->item(row, 1))
{
QString correspondingDirectory = item->text();
if (QDir(correspondingDirectory).exists())
{
// treeModel => QFileSystemModel
// tree => QTreeView
treeModel->setRootPath("");
treeModel->setRootPath(correspondingDirectory);
tree->setRootIndex(treeModel->index(correspondingDirectory));
}
else
{
qDebug() << "Reset tree => do not display anything!";
// treeModel->setRootPath("");
// tree->reset();
}
}
}
If the directory does not exist, I don't want to display anything. However, when I try to set an empty root path or reset the view, it show all my computer drives.
How can I reset or clear the QTreeView ?
Had a similar issue. Im not quite sure how i solved it, because it was a long time ago. I think it should work if you set the QTreeview a nullptr as model. So when the QDir exists you set a new QFileSystemModel otherwise you call:
tree->setModel(nullptr);
Hope this helps you.
EDIT:
If your doing it this way the header is deleted too.

QSortFilterProxyModel how to handle QStandardItems correctly

I have QTreeView with some items and search QLineEdit with connected slot on textEdited signal.
With this code:
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(messagesModel);
proxyModel->setFilterFixedString(text);
ui.treeView->setModel(proxyModel);
text filtering is ok, but when I clicked on QTreeView QStandardItems checkboxes (after proxy model assigned to QTreeView), I have the program crashes in slot, that connected to this QTreeView original model (before proxy was assigned).
What is the right way to processing item checkbox clicks? Need I use new connect/slot to processing model changes, or I can use the same code for original model with some changes? I just need to hide filtered items in QTreeView. In QTreeWidget is hide() method, does QTreeView has something like this, or QSortFilterProxyModel - is what I need? Thx!
UPD crashed in slot, connected to treeView:
auto item = messagesModel->itemFromIndex(index); // item is NULL because proxyModel is set for TreeView now
if(item->whatsThis().isEmpty()) return; // error below
#ifndef QT_NO_WHATSTHIS
inline QString whatsThis() const {
return qvariant_cast<QString>(data(Qt::WhatsThisRole));
}
inline void setWhatsThis(const QString &whatsThis);
#endif
because I set proxyModel to treeView, but messagesModel have whatsThis...
I changed my code with that:
QStandardItem* item;
if(ui.leFilter->text().isEmpty())
item = messagesModel->itemFromIndex(index);
else
item = messagesModel->itemFromIndex(proxyModel->mapToSource(index));
if(item->whatsThis().isEmpty()) return;
and it works. Is that correct way? Proxy model is member of my UI class ... not local.
UPD how can I update source model when checkbox checked in proxyModel?
UPD 2 I have load "original" model for QtreeView and show it. When I edit text in QListEdit, I use proxyModel (code from 1st post). When text edited, I have check checkboxes in QtreeView (now proxyModel is active) and at this step all is ok. But when I do some changes in UI, in QTreeView set the original model and it has no changes that was made for proxyModel. How can I notify and update items in source Model with new data from proxyModel?
UPD3 Yes, source model is also modified ... I have just clear it)

QTreeWidget editItem fails with "edit: editing failed"

I have a QTreeWidgetItem added to a QTreeWidget:
QTreeWidgetItem* item = new QTreeWidgetItem(ui->trwPairs);
item->setFlags(item->flags() | Qt::ItemIsEditable);
If the item is edited, I want to do a few checks on the new value:
Pairs::Pairs(QWidget *parent) :
QWidget(parent),
ui(new Ui::Pairs)
{
ui->setupUi(this);
connect(this->ui->trwPairs, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(Validate(QTreeWidgetItem*,int)));
}
void Pairs::Validate(QTreeWidgetItem* item, int column)
{
if (item->text(column).toInt() < 1)
{
QMessageBox::critical(this, "Error", QString("Node ID ") + item->text(column) + " is invalid.");
ui->trwPairs->editItem(item, column);
}
}
Naturally, if it's less than 1, it catches it, and gives me the message box. However, printed to cerr is edit: editing failed and the item is not in edit mode. What am I missing?
Stepping through it in the debugger reveals the following:
In quabstractitemview.cpp line false is returned on line 3953. Somehow it looks like your item is still in editing state and you are trying to edit it again or something.
bool QAbstractItemViewPrivate::shouldEdit(QAbstractItemView::EditTrigger trigger,
const QModelIndex &index) const
{
// ..
if (state == QAbstractItemView::EditingState)
return false;
}
IIRC I had a similar problem with tables with multiple lines per cell. Check out the classes QAbstractItemDelegate views have item delegates which allow you to control which editor is used and how it behaves. I believe by default the QLineEdit is used. Editors like QLineEdit can have validators which control how the data is validated, in your case reject it if the numerical value is < 0. But I think you have to use the model / view classes and implement your own model for that. The Qt documentation for QTreeWidget::setItemWidget(..) says:
This function should only be used to display static content in the place of a tree widget item. If you want to display custom dynamic content or implement a custom editor widget, use QTreeView and subclass QItemDelegate instead.
I am not sure however if there is a simpler way to do this using the widget classes.
The problem could be, that you are setting the flags for your items in a very strange way.
Simply enable both item-selection, and edit:
item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
I had a similar issue where I was attempting to edit the subsequent column upon receiving the itemChanged signal. Based on Nils' analysis that the item was still in the edit state, I changed the signal connection type to QueuedConnection, which allowed the item to leave the state before re-entering it.
I had a similar problem where I'd get the 'edit: editing failed' error when invoking edit() via a shortcut key. I was passing currentIndex() to edit(), but I wasn't checking that the correct column of the selected row was current. I only had the first column editable, so if I had clicked the row (but in any other column) and then invoked my edit key I'd get the error.
I was able to solve my problem by passing the result of sibling(currentIndex().row(), 0) to edit() instead.