英文:
controling selected item with keyboard in a QCombobox with QTreeView
问题
使用Qt 6.2.4和Ubuntu环境,我从QComboBox
派生出一个控件,将QTreeView
设置为其视图。
它包含一个包含多个父项和子项的树(文件夹和文件),可能有多层文件夹。
一切都正常工作,表现如我所希望的那样。
现在,当选择一个项目时,我想使用上下箭头键来选择同一父项下的前一个或后一个项目。
我尝试了几种方法,我可以获取正确的兄弟项目,但无法相应地更新comboBox视图(comboBox始终显示之前选择的项目,没有更新)。
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); // 用于comboBox的树视图
setView(tree); // 将其分配给comboBox
}
void ShowFileList(QString path, QString filesFilter) // 用文件夹和文件填充comboBox,指定路径和通配符
{
// 阻止信号
this->blockSignals(true); // 填充控件时不发出信号
//// 创建文件模型
QFileSystemModel *fileModel = new QFileSystemModel(this); // 要使用的文件系统模型
// 设置文件模型选项
fileModel->setReadOnly(true); // 设置为只读
fileModel->setFilter(QDir::AllDirs | QDir::AllEntries | QDir::NoDotAndDotDot); // 所有文件夹,所有文件,不以点开头的文件
fileModel->setOption(QFileSystemModel::DontUseCustomDirectoryIcons); // 不使用文件的图标
fileModel->setOption(QFileSystemModel::DontWatchForChanges); // 控件不会跟踪磁盘上的更改
// 设置文件模型的文件过滤器
QStringList filter; // 用于文件通配符
filter << filesFilter;
fileModel->setNameFilters(filter);
// 设置文件模型的根目录
fileModel->setRootPath("");
//// 创建视图
// 树视图
QTreeView *view = new QTreeView;
this->setView(view); // 将树视图分配给comboBox
// 文件模型
this->setModel(fileModel); // 将文件模型分配给comboBox
// 删除树视图中的列,只保留文件名
QModelIndex index = fileModel->index(path);
for (int i = 1; i < fileModel->columnCount(); ++i) // 除第一列外的所有列
view->hideColumn(i);
// 树视图选项
view->setAnimated(true); // 启用动画
view->setSortingEnabled(true); // 通过单击标题启用排序
view->sortByColumn(0, Qt::AscendingOrder); // 排序值
view->expand(index); // 展开给定路径的视图
view->scrollTo(index); // 从给定路径设置视图
view->setRootIndex(index); // 设置根索引为给定路径
// 再次允许信号
this->blockSignals(false);
}
QString GetFile() // 获取列表中的选定值
// 每次单击项目时都会发出currentItemChanged()信号,即使是父项目
// 如果单击的项目无效(即文件夹),则此函数返回空QString
{
QModelIndex index = view()->currentIndex(); // 从QTree获取当前值索引
QString path = model()->data(index, QFileSystemModel::FilePathRole).toString(); // 获取完整的路径值
QFileInfo info(path); // 用于测试此值
if (info.isFile()) // 如果值确实是文件
return path; // ...返回其完整路径
else // 值是文件夹
return QString(); // ...所以什么都不返回
}
private:
virtual void hidePopup() // 控制弹出窗口隐藏行为
// 对于comboBox,每次单击项目时,弹出窗口都会消失...但这次不是为文件夹!
{
if (!view()->underMouse()) { // 鼠标是否位于QTreeView上?
QComboBox::hidePopup(); // 如果没有,折叠comboBox
return;
}
QModelIndex index = view()->currentIndex(); // 获取所选项目的当前索引
if (!model()->hasChildren(index)) // 如果没有子项目(因此不是文件夹)
QComboBox::hidePopup(); // 折叠comboBox
}
virtual void keyPressEvent(QKeyEvent *keyboardEvent) // 键盘事件
{
if (this->hasFocus()) { // 控件必须处于活动状态才能接受键盘按键
if (keyboardEvent->key() == Qt::Key_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(); // 接受键盘事件
<details>
<summary>英文:</summary>
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 !
</details>
# 答案1
**得分**: 3
以下是翻译好的代码部分:
```cpp
class TreeComboBox : public QComboBox
{
public:
// 添加一个新的成员来保存根索引
QModelIndex rootIndex;
TreeComboBox(QWidget* parent = 0) : QComboBox(parent)
{
QTreeView* tree = new QTreeView(this);
setView(tree);
}
void ShowFileList(QString path, QString filesFilter)
{
this->blockSignals(true);
QFileSystemModel *fileModel = new QFileSystemModel(this);
fileModel->setReadOnly(true);
fileModel->setFilter(QDir::AllDirs | QDir::AllEntries | QDir::NoDotAndDotDot);
fileModel->setOption(QFileSystemModel::DontUseCustomDirectoryIcons);
fileModel->setOption(QFileSystemModel::DontWatchForChanges);
QStringList filter;
filter << filesFilter;
fileModel->setNameFilters(filter);
fileModel->setRootPath("");
QTreeView *view = new QTreeView;
this->setView(view);
this->setModel(fileModel);
QModelIndex index = fileModel->index(path);
for (int i = 1; i < fileModel->columnCount(); ++i)
view->hideColumn(i);
view->setAnimated(true);
view->setSortingEnabled(true);
view->sortByColumn(0, Qt::AscendingOrder);
view->expand(index);
view->scrollTo(index);
view->setRootIndex(index);
// 保存根索引
rootIndex = index;
// 然后设置给你的 comboBox
setRootModelIndex(index);
this->blockSignals(false);
}
QString GetFile()
{
QModelIndex index = view()->currentIndex();
QString path = model()->data(index, QFileSystemModel::FilePathRole).toString();
QFileInfo info(path);
if (info.isFile())
return path;
else
return QString();
}
private:
virtual void hidePopup()
{
if (!view()->underMouse()) {
QComboBox::hidePopup();
return;
}
QModelIndex index = view()->currentIndex();
if (!model()->hasChildren(index))
QComboBox::hidePopup();
}
virtual void keyPressEvent(QKeyEvent *keyboardEvent)
{
if (this->hasFocus())
{
// 我只是用这个来避免必须通过点击来选择项目
// 如果对你没有用,可以删除它
if(!view()->currentIndex().isValid())
{
view()->setCurrentIndex(view()->indexAt(QPoint(0,0)));
}
if (keyboardEvent->key() == Qt::Key_Up)
{
QModelIndex index = view()->currentIndex();
int n = index.row() - 1;
QModelIndex sibling = index.siblingAtRow(n);
if (sibling.isValid())
{
// 更新视图的当前索引
view()->setCurrentIndex(sibling);
// 更新组合框
setRootModelIndex(sibling.parent());
setCurrentIndex(sibling.row());
// 这是保存根索引不丢失的地方
if(rootModelIndex() != rootIndex)
setRootModelIndex(rootIndex);
}
keyboardEvent->accept();
}
else if (keyboardEvent->key() == Qt::Key_Down)
{
QModelIndex index = view()->currentIndex();
int n = index.row() + 1;
QModelIndex sibling = index.siblingAtRow(n);
if(sibling.isValid())
{
// 更新视图的当前索引
view()->setCurrentIndex(sibling);
// 更新组合框
setRootModelIndex(sibling.parent());
setCurrentIndex(sibling.row());
if(rootModelIndex() != rootIndex)
setRootModelIndex(rootIndex);
}
keyboardEvent->accept();
}
}
}
};
希望这对你有所帮助。
英文:
Both the tree view and combobox need to be updated on key press, in order for the items to be visibly updated on the combobox.
And since it appears a rootModelIndex needs to be set, and the initial root getting lost on subfolders, I added a QModelIndex
as a member to store it in.
Modified class with explanatory comments:
class TreeComboBox : public QComboBox
{
public:
//add a new memeber to save the root index
QModelIndex rootIndex;
TreeComboBox(QWidget* parent = 0) : QComboBox(parent)
{
QTreeView* tree = new QTreeView(this);
setView(tree);
}
void ShowFileList(QString path, QString filesFilter)
{
this->blockSignals(true);
QFileSystemModel *fileModel = new QFileSystemModel(this);
fileModel->setReadOnly(true);
fileModel->setFilter(QDir::AllDirs | QDir::AllEntries |QDir::NoDotAndDotDot);
fileModel->setOption(QFileSystemModel::DontUseCustomDirectoryIcons);
fileModel->setOption(QFileSystemModel::DontWatchForChanges);
QStringList filter;
filter << filesFilter;
fileModel->setNameFilters(filter);
fileModel->setRootPath("");
QTreeView *view = new QTreeView;
this->setView(view);
this->setModel(fileModel);
QModelIndex index = fileModel->index(path);
for (int i = 1; i < fileModel->columnCount(); ++i)
view->hideColumn(i);
view->setAnimated(true);
view->setSortingEnabled(true);
view->sortByColumn(0, Qt::AscendingOrder);
view->expand(index);
view->scrollTo(index);
view->setRootIndex(index);
//save the root index
rootIndex=index;
//then set to your comboBox
setRootModelIndex(index);
this->blockSignals(false);
}
QString GetFile()
{
QModelIndex index = view()->currentIndex();
QString path = model()->data(index, QFileSystemModel::FilePathRole).toString();
QFileInfo info(path);
if (info.isFile())
return path;
else
return QString();
}
private:
virtual void hidePopup()
{
if (!view()->underMouse()) {
QComboBox::hidePopup();
return;
}
QModelIndex index = view()->currentIndex();
if (!model()->hasChildren(index))
QComboBox::hidePopup();
}
virtual void keyPressEvent(QKeyEvent *keyboardEvent)
{
if (this->hasFocus())
{
//I just used this to avoid having to select an item by clicking on it
//you can remove it if it's of no use to you
if(!view()->currentIndex().isValid())
{
view()->setCurrentIndex(view()->indexAt(QPoint(0,0)));
}
if (keyboardEvent->key() == Qt::Key_Up)
{
QModelIndex index = view()->currentIndex();
int n = index.row() - 1;
QModelIndex sibling = index.siblingAtRow(n);
if (sibling.isValid())
{
//update view's current index
view()->setCurrentIndex(sibling);
//update combobox
setRootModelIndex(sibling.parent());
setCurrentIndex(sibling.row());
//this is where you save the root index from being lost
if(rootModelIndex()!=rootIndex)
setRootModelIndex(rootIndex);
}
keyboardEvent->accept();
}
else
if (keyboardEvent->key() == Qt::Key_Down)
{
QModelIndex index = view()->currentIndex();
int n = index.row() + 1;
QModelIndex sibling = index.siblingAtRow(n);
if(sibling.isValid())
{
//update view's current index
view()->setCurrentIndex(sibling);
//update combobox
setRootModelIndex(sibling.parent());
setCurrentIndex(sibling.row());
if(rootModelIndex()!=rootIndex)
setRootModelIndex(rootIndex);
}
keyboardEvent->accept();
}
}
}
};
Result:
For more:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论