Using Qt 6.2.4, Ubuntu environment, I derived from QComboBox
to set a QTreeView
as its view.
It contains a tree (folders and files) with multiple parents and children, several levels of folders is possible.
All is working fine and behaves as I want.
Now, when one item is selected I'd like to use up and down arrow keys to select the previous or next item of the same parent.
I tried several things, I get the right sibling but I am not able to update the comboBox view accordingly (the comboBox always shows the item selected before, nothing is updated).
main.cpp:
#include <QApplication>
#include "tree-combobox.h"
int main(int argc, char **argv)
{
QApplication app (argc, argv);
TreeComboBox combo;
combo.setGeometry(0, 0, 400, 50);
combo.ShowFileList("/my/path/", "*");
combo.show();
return app.exec();
}
tree-combobox.h:
#include <QComboBox>
#include <QTreeView>
#include <QFileSystemModel>
#include <QMouseEvent>
#include <QAbstractItemView>
class TreeComboBox : public QComboBox
{
public:
TreeComboBox(QWidget* parent = 0) : QComboBox(parent)
{
QTreeView* tree = new QTreeView(this); // tree view for combobox
setView(tree); // assign it to combobox
}
void ShowFileList(QString path, QString filesFilter) // fill combobox with folders and files, specify path and wildcard(s)
{
// block signals
this->blockSignals(true); // no signals emitted while stuffing the widget
//// create files model
QFileSystemModel *fileModel = new QFileSystemModel(this); // file system model to use
// set options to file model
fileModel->setReadOnly(true); // set it read-only
fileModel->setFilter(QDir::AllDirs | QDir::AllEntries |QDir::NoDotAndDotDot); // all folders, all files, no file beginning with a dot
fileModel->setOption(QFileSystemModel::DontUseCustomDirectoryIcons); // don't use icons from the files
fileModel->setOption(QFileSystemModel::DontWatchForChanges); // the widget won't track changes on disk
// set file filter for files model
QStringList filter; // for files wildcard
filter << filesFilter;
fileModel->setNameFilters(filter);
// set root for files model
fileModel->setRootPath("");
//// create view
// tree view
QTreeView *view = new QTreeView;
this->setView(view); // assign tree view to combobox
// files model
this->setModel(fileModel); // assign files model to combobox
// remove columns in tree view, to keep only filenames
QModelIndex index = fileModel->index(path);
for (int i = 1; i < fileModel->columnCount(); ++i) // all columns but first one
view->hideColumn(i);
// tree view options
view->setAnimated(true); // animated
view->setSortingEnabled(true); // sorting enabled by clicking on header
view->sortByColumn(0, Qt::AscendingOrder); // sort values
view->expand(index); // expand the view from the given path
view->scrollTo(index); // set view from given path
view->setRootIndex(index); // set root index to given path
// allow signals again
this->blockSignals(false);
}
QString GetFile() // get selected value from list
// currentItemChanged() is emitted each time an item is clicked, even a parent item
// this function returns an empty QString if the clicked item is not valid (i.e. a folder)
{
QModelIndex index = view()->currentIndex(); // current value index from QTree
QString path = model()->data(index, QFileSystemModel::FilePathRole).toString(); // get full path value
QFileInfo info(path); // to test this value
if (info.isFile()) // if the value is really a file
return path; // ... return its full path
else // value is a folder
return QString(); // ... so return nothing
}
private:
virtual void hidePopup() // control popup hiding behaviour
// for a combobox, each time an item is clicked the popup disappears... but not for a folder this time !
{
if (!view()->underMouse()) { // is mouse over QTreeView ?
QComboBox::hidePopup(); // if not collapse the comboBox
return;
}
QModelIndex index = view()->currentIndex(); // get current index of selected item
if (!model()->hasChildren(index)) // if it doesn't have children (so it is not a folder)
QComboBox::hidePopup(); // collapse the comboBox
}
virtual void keyPressEvent(QKeyEvent *keyboardEvent) // keyboard event
{
if (this->hasFocus()) { // widget has to be active to accept keyboard keys
if (keyboardEvent->key() == Qt::Key_Up) { // up
//view()->setCurrentIndex(view()->currentIndex().sibling(view()->currentIndex().row() - 1, 0));
//view()->selectionModel()->select(view()->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows);
QModelIndex index = view()->currentIndex();
int n = index.row() - 1;
QModelIndex sibling = index.siblingAtRow(n);
if (sibling.isValid()) {
view()->setCurrentIndex(sibling);
view()->scrollTo(sibling);
//view()->selectionModel()->setCurrentIndex(sibling, QItemSelectionModel::ClearAndSelect);
//view()->selectionModel()->select(sibling, QItemSelectionModel::Select | QItemSelectionModel::Rows);
//tree->selectionModel()->select(tree->currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
//this->setCurrentIndex(sibling.row());
}
keyboardEvent->accept(); // accept keyboard event
}
else if (keyboardEvent->key() == Qt::Key_Down) { // down
view()->setCurrentIndex(view()->currentIndex().sibling(view()->currentIndex().row() - 1, 0));
keyboardEvent->accept();
}
}
}
};
What's working so far in keyPressEvent()
: the sibling
index (QModelIndex
) is the right one.
What I tried: the lines commented out with //
.
Desired result: the comboBox selects and shows the previous or next item in the list, for the same parent (no need to go up or down to another parent).
Thanks in advance !