点击项目外部取消在QTreeView中的选择,是否可能?

15

我希望能够通过在 QTreeView 中没有项目的部分点击来取消选择项目,但是我似乎找不到任何方法来实现这一点。我想拦截不在项上的单击,但是 QTreeView 没有 clicked 信号,因此我不知道该如何做。

9个回答

18

根据 @Eric 的解决方案,并且仅在单击的项目被选中时取消选择,这是我想出来的方法。 当你点击 QTreeView 的空白区域时,这个解决方案也会起作用。

#ifndef DESELECTABLETREEVIEW_H
#define DESELECTABLETREEVIEW_H
#include "QTreeView"
#include "QMouseEvent"
#include "QDebug"
class DeselectableTreeView : public QTreeView
{
public:
    DeselectableTreeView(QWidget *parent) : QTreeView(parent) {}
    virtual ~DeselectableTreeView() {}

private:
    virtual void mousePressEvent(QMouseEvent *event)
    {
        QModelIndex item = indexAt(event->pos());
        bool selected = selectionModel()->isSelected(indexAt(event->pos()));
        QTreeView::mousePressEvent(event);
        if ((item.row() == -1 && item.column() == -1) || selected)
        {
            clearSelection();
            const QModelIndex index;
            selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select);
        }
    }
};
#endif // DESELECTABLETREEVIEW_H

亚西尔


indexAt 可能会非常昂贵,您可以使用 item 代替第二个调用。 item.isValid() 可能比将行和列与 -1 进行比较更好。 此外,如果单击元素,似乎会更改选择? - Adrian Maire

12

这个其实很简单(在PyQt中):

class DeselectableTreeView(QtGui.QTreeView):
    def mousePressEvent(self, event):
        self.clearSelection()
        QtGui.QTreeView.mousePressEvent(self, event)

Qt使用mousePressEvent来发出clicked。如果在发送事件之前清除选择,则如果单击项,则将选择该项,否则将不选择任何内容。非常感谢Patrice帮我解决了这个问题:)


需要注意的是,这会从选择模型中发出相关信号,因此如果您正在监听它们,这通常会导致不必要的行为(即,如果您始终打开/关闭所选项目的视图,则会导致页面被快速关闭和打开)。您可以通过阻止选择模型的信号来避免这种情况。 - Folling
只有在使用单项选择模型时才有效,当您点击新项目时,您将失去多个选择。 - DirkR

10

clearSelection在我的情况下不起作用。我正在使用单选模式的treeviews。这是我的代码:

class DeselectableTreeView : public QTreeView
{
public:
    DeselectableTreeView(QWidget *parent) : QTreeView(parent) {}
    virtual ~DeselectableTreeView() {}

private:
    virtual void mousePressEvent(QMouseEvent *event)
    {
        QModelIndex item = indexAt(event->pos());
        bool selected = selectionModel()->isSelected(item);
        QTreeView::mousePressEvent(event);
        if (selected)
            selectionModel()->select(item, QItemSelectionModel::Deselect);
    }

};

这个很好地运行了。

Eric


7

QTreeView继承自QAbstractView (http://doc.qt.digia.com/4.6/qtreeview.html),它有一个clicked信号。问题在于,只有当索引有效时信号才会被发射,所以你无法通过这个信号来达到想要的效果。

尝试拦截mousePressEvent。在该函数中,您可以找到用户单击的位置,并在需要时取消选择已选定的项目。


谢谢。我已经添加了自己的答案,但是+1因为你帮助我到达那里! - Skilldrick

2
在@Skilldrick的答案中,我们可能会发送多余的事件。如果一个项目已经被选择,并且我们再次点击它,我们会引发取消选择和重新选择的事件。根据应用程序中的其他侦听器,这可能不是您想要的。
@eric-maeker的解决方案只有在我们再次单击已选择的项目时才取消选择该项目。严格来说,这不是原始问题的答案,原始问题是如何在单击其他地方时取消选择所选项目。
@yassir-ennazk接近了解决方案,但正如@adrian-maire指出的那样,解决方案并不理想。 event->pos()被评估两次。此外,鼠标事件总是通过调用QTreeView::mousePressEvent进行评估。
以下是我基于上述其他答案提出的解决方案。如果我们在存在另一个树视图项目的点处单击,则通过将事件转发到TreeView来选择新项目。如果没有,则清除选择。
请注意,这也适用于QTreeWidget
virtual void mousePressEvent(QMouseEvent* event)
{
    QModelIndex item = indexAt(event->pos());

    if (item.isValid())
    {
        QTreeView::mousePressEvent(event);
    }
    else
    {
        clearSelection();
        const QModelIndex index;
        selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select);
    }
}

1
默认实现的唯一问题是在视图的空白区域单击时它不会清除选择。因此,只需检查索引是否有效。如果无效,则调用 clearSelection()。然后无条件调用基类的 mousePressEvent - ekhumoro

1
为了补充@Skilldrick的回答,如果你需要将这个应用到已经被实例化的视图中,因为你正在使用Qt Designer,你可以像这样做:
import new
def mousePressEvent(self, event):
    self.clearSelection()
    QtGui.QTableView.mousePressEvent(self, event)
self.ui.tableView.mousePressEvent = new.instancemethod(mousePressEvent, self.ui.tableView, None)

这里假设你的视图是 self.ui.tableView
感谢这个答案:https://dev59.com/NHI-5IYBdhLWcg3w1sXi#1647616

0
你可以尝试为你的小部件设置不同的选择模式。我不确定它们中是否有任何一个完全符合你想要的(单选,但可取消选择)。

1
我在那里没有看到任何能实现我想要的东西,但还是谢谢。 - Skilldrick

0

由于问题特别涉及PyQt,我想根据Nick Pruehs的答案和ekhumoro的评论,提供最简单的实现方式:

class TreeView(QTreeView):
    def __init__(self, *args, **kwds):
        QTreeView.__init__(self, *args, **kwds)

    def mousePressEvent(self, event):
        item = self.indexAt(event.pos())

        if not item.isValid():
            self.clearSelection()
            
        QTreeView.mousePressEvent(self, event)

0

我选择使用事件过滤器而不是子类化。如果我有很多这样的情况,我会进行子类化,但对于一次性的情况,我认为修改我的窗口代码更容易。

一些注意事项:

  • 事件过滤器需要安装在树视图的视口中
  • 清除选择和清除选择模型都很重要。

下面的示例显示了对话框构造函数和事件过滤器:

MyWindow::MyWindow(QWidget *theParent) :
      QDialog(theParent),
      ui(new Ui::MyWindow)
{
      ui->setupUi(this);
      ui->MyTreeView->viewport()->installEventFilter(this);
}

bool MyWindow::eventFilter(QObject* theObject, QEvent* theEvent)
{
  if (theObject == ui->MyTreeView->viewport())
  {
    if(theEvent->type() == QEvent::MouseButtonPress)
    {
      QMouseEvent* aMouseEvent = static_cast<QMouseEvent*>(theEvent);
      if (aMouseEvent->button() == Qt::LeftButton)
      {
        auto anIndex =  ui->MyTreeView->indexAt(aMouseEvent->pos());
        if (anIndex.isValid() == false)
        {
          ui->MyTreeView->clearSelection();
          ui->MyTreeView->selectionModel()->clearSelection();
        }
      }
    }
  }
  return QObject::eventFilter(theObject, theEvent);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接