使用键盘在带有 QTreeView 的 QComboBox 中控制所选项目

huangapple go评论77阅读模式
英文:

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&#39;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 &lt;QApplication&gt;
    
    #include &quot;tree-combobox.h&quot;
    
    int main(int argc, char **argv)
    {
        QApplication app (argc, argv);
    
        TreeComboBox combo;
        combo.setGeometry(0, 0, 400, 50);
        combo.ShowFileList(&quot;/my/path/&quot;, &quot;*&quot;);
        combo.show();
    
        return app.exec();
    }


**tree-combobox.h:**

    #include &lt;QComboBox&gt;
    #include &lt;QTreeView&gt;
    #include &lt;QFileSystemModel&gt;
    #include &lt;QMouseEvent&gt;
    #include &lt;QAbstractItemView&gt;
    
    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-&gt;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-&gt;setReadOnly(true); // set it read-only
            fileModel-&gt;setFilter(QDir::AllDirs | QDir::AllEntries |QDir::NoDotAndDotDot); // all folders, all files, no file beginning with a dot
            fileModel-&gt;setOption(QFileSystemModel::DontUseCustomDirectoryIcons); // don&#39;t use icons from the files
            fileModel-&gt;setOption(QFileSystemModel::DontWatchForChanges); // the widget won&#39;t track changes on disk
            // set file filter for files model
            QStringList filter; // for files wildcard
            filter &lt;&lt; filesFilter;
            fileModel-&gt;setNameFilters(filter);
            // set root for files model
            fileModel-&gt;setRootPath(&quot;&quot;);
    
            //// create view
            // tree view
            QTreeView *view = new QTreeView;
            this-&gt;setView(view); // assign tree view to combobox
            // files model
            this-&gt;setModel(fileModel); // assign files model to combobox
            // remove columns in tree view, to keep only filenames
            QModelIndex index = fileModel-&gt;index(path);
            for (int i = 1; i &lt; fileModel-&gt;columnCount(); ++i) // all columns but first one
                view-&gt;hideColumn(i);
            // tree view options
            view-&gt;setAnimated(true); // animated
            view-&gt;setSortingEnabled(true); // sorting enabled by clicking on header
            view-&gt;sortByColumn(0, Qt::AscendingOrder); // sort values
            view-&gt;expand(index); // expand the view from the given path
            view-&gt;scrollTo(index); // set view from given path
            view-&gt;setRootIndex(index); // set root index to given path
    
            // allow signals again
            this-&gt;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()-&gt;currentIndex(); // current value index from QTree
    
            QString path = model()-&gt;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()-&gt;underMouse()) { // is mouse over QTreeView ?
                QComboBox::hidePopup(); // if not collapse the comboBox
                return;
            }
    
            QModelIndex index = view()-&gt;currentIndex(); // get current index of selected item
            if (!model()-&gt;hasChildren(index)) // if it doesn&#39;t have children (so it is not a folder)
                QComboBox::hidePopup(); // collapse the comboBox
        }
    
        virtual void keyPressEvent(QKeyEvent *keyboardEvent) // keyboard event
        {
            if (this-&gt;hasFocus()) { // widget has to be active to accept keyboard keys
                if (keyboardEvent-&gt;key() == Qt::Key_Up) { // up
                    //view()-&gt;setCurrentIndex(view()-&gt;currentIndex().sibling(view()-&gt;currentIndex().row() - 1, 0));
                    //view()-&gt;selectionModel()-&gt;select(view()-&gt;currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows);
    
                    QModelIndex index = view()-&gt;currentIndex();
                    int n = index.row() - 1;
                    QModelIndex sibling = index.siblingAtRow(n);
                    if (sibling.isValid()) {
                        view()-&gt;setCurrentIndex(sibling);
                        view()-&gt;scrollTo(sibling);
                        //view()-&gt;selectionModel()-&gt;setCurrentIndex(sibling, QItemSelectionModel::ClearAndSelect);
                        //view()-&gt;selectionModel()-&gt;select(sibling, QItemSelectionModel::Select | QItemSelectionModel::Rows);
                        //tree-&gt;selectionModel()-&gt;select(tree-&gt;currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
                        //this-&gt;setCurrentIndex(sibling.row());
                    }
    
                    keyboardEvent-&gt;accept(); // accept keyboard event
                }
                else if (keyboardEvent-&gt;key() == Qt::Key_Down) { // down
                    view()-&gt;setCurrentIndex(view()-&gt;currentIndex().sibling(view()-&gt;currentIndex().row() - 1, 0));
    
                    keyboardEvent-&gt;accept();
                }
            }
        }
    
    };

What&#39;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-&gt;blockSignals(true); 
QFileSystemModel *fileModel = new QFileSystemModel(this); 
fileModel-&gt;setReadOnly(true);
fileModel-&gt;setFilter(QDir::AllDirs | QDir::AllEntries |QDir::NoDotAndDotDot);
fileModel-&gt;setOption(QFileSystemModel::DontUseCustomDirectoryIcons); 
fileModel-&gt;setOption(QFileSystemModel::DontWatchForChanges); 
QStringList filter; 
filter &lt;&lt; filesFilter;
fileModel-&gt;setNameFilters(filter);
fileModel-&gt;setRootPath(&quot;&quot;);
QTreeView *view = new QTreeView;
this-&gt;setView(view); 
this-&gt;setModel(fileModel);
QModelIndex index = fileModel-&gt;index(path);
for (int i = 1; i &lt; fileModel-&gt;columnCount(); ++i) 
view-&gt;hideColumn(i);
view-&gt;setAnimated(true); 
view-&gt;setSortingEnabled(true);
view-&gt;sortByColumn(0, Qt::AscendingOrder); 
view-&gt;expand(index); 
view-&gt;scrollTo(index); 
view-&gt;setRootIndex(index); 
//save the root index
rootIndex=index;
//then set to your comboBox
setRootModelIndex(index);
this-&gt;blockSignals(false);
}
QString GetFile()
{
QModelIndex index = view()-&gt;currentIndex();
QString path = model()-&gt;data(index, QFileSystemModel::FilePathRole).toString(); 
QFileInfo info(path); 
if (info.isFile()) 
return path; 
else 
return QString(); 
}
private:
virtual void hidePopup() 
{
if (!view()-&gt;underMouse()) { 
QComboBox::hidePopup(); 
return;
}
QModelIndex index = view()-&gt;currentIndex();
if (!model()-&gt;hasChildren(index))
QComboBox::hidePopup();
}
virtual void keyPressEvent(QKeyEvent *keyboardEvent)
{
if (this-&gt;hasFocus())
{
//I just used this to avoid having to select an item by clicking on it
//you can remove it if it&#39;s of no use to you
if(!view()-&gt;currentIndex().isValid())
{
view()-&gt;setCurrentIndex(view()-&gt;indexAt(QPoint(0,0)));
}
if (keyboardEvent-&gt;key() == Qt::Key_Up)
{
QModelIndex index = view()-&gt;currentIndex();
int n = index.row() - 1;
QModelIndex sibling = index.siblingAtRow(n);
if (sibling.isValid())
{
//update view&#39;s current index
view()-&gt;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-&gt;accept();
}
else
if (keyboardEvent-&gt;key() == Qt::Key_Down)
{
QModelIndex index = view()-&gt;currentIndex();
int n = index.row() + 1;
QModelIndex sibling = index.siblingAtRow(n);
if(sibling.isValid())
{
//update view&#39;s current index
view()-&gt;setCurrentIndex(sibling);
//update combobox
setRootModelIndex(sibling.parent());
setCurrentIndex(sibling.row());
if(rootModelIndex()!=rootIndex)
setRootModelIndex(rootIndex);
}
keyboardEvent-&gt;accept();
}
}
}
};

Result:

使用键盘在带有 QTreeView 的 QComboBox 中控制所选项目

For more:

huangapple
  • 本文由 发表于 2023年7月7日 06:27:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76632873.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定