使 QComboBox 下拉一个带复选框的树形列表

背景

在项目开发过程中需要使 QComboBox 下拉一个树形列表,直接通过 setModel 和 setView 设置 combox 控件可以实现,但是在单击节点箭头按钮时也会隐藏下拉框的显示。因此需要重新实现 QComboBox 的方法来实现自己控制。

①.通过 QCheckTreeComboBoxView子类化 QTreeWidget 类,以重写 mousePressEvent 事件自己实现节点的展开和收缩,否则会有事件冲突。

②.通过 QCheckTreeComboBox子类化 QComboBox 类,以重写 hidePopup 方法,判断条件是否满足。

③.通过响应 QComboBox 类的 activated 信号,遍历获取当前的选择结果。

QCheckTreeComBox.h

#include <QComboBox>
#include "ui_QCheckTreeComboBox.h"
#include "QTreeWidget"
#include <functional>

class QCheckTreeComboBox : public QComboBox
{
  Q_OBJECT
public:
  QCheckTreeComboBox(QWidget *parent = Q_NULLPTR);
  ~QCheckTreeComboBox();
public:
  typedef std::function<QString()> getTreeSelectedFunc;//用于指定自定义方法
  class QCheckTreeComboBoxView : public QTreeWidget
  {
    public:
      QCheckTreeComboBoxView(QCheckTreeComboBox * cbx ,QWidget *parent = Q_NULLPTR) :QTreeWidget(parent),treeComboBox(cbx) {}
      ~QCheckTreeComboBoxView() {};
    protected:
      void mousePressEvent(QMouseEvent *event) override;
    private:
      void treeMousePressed(bool inItem);
      void setChildSelected(QTreeWidgetItem* root);
    private:
      //Qt 嵌套类不允许有信号槽,变为友元
      QCheckTreeComboBox * treeComboBox = nullptr;
    public :
      void setItemChecked();
  };
private:
  Ui::QCheckTreeComboBox ui;
protected:
  void hidePopup() override;//下拉列表收缩
  bool eventFilter(QObject *watched, QEvent *event) override;
private:
  bool canHidePopup = true;//允许收缩
  bool clickItem = false;//是否单击了节点
public:
  QCheckTreeComboBoxView * displayTreeWidget = nullptr;
  getTreeSelectedFunc getTreeSelectedText;//自定义的数据获取方法
  getTreeSelectedFunc getTreeSelectedData;
private:
  QString getCurrentSelectedText();//默认获取读取方法
  QString getCurrentSelectedData();
  void updateTreeWidgetSelected();//更新节点选择状态
  void updateEditText();//更新文本显示
public:
  void iniSelectText();//初始化文本显示
signals:
  void currentSelectedDataChange(QString & m_text);
  void currentSelectedTextChange(QString & m_text);
};

QCheckTreeComBox.cpp

#include "QCheckTreeComboBox.h"
#include "qevent.h"
#include "QLineEdit"


using namespace std::placeholders;

QCheckTreeComboBox::QCheckTreeComboBox(QWidget *parent)
  : QComboBox(parent)
{
  ui.setupUi(this);

  getTreeSelectedText = std::bind(&QCheckTreeComboBox::getCurrentSelectedText, this);
  getTreeSelectedData = std::bind(&QCheckTreeComboBox::getCurrentSelectedData, this);

  displayTreeWidget = new QCheckTreeComboBoxView(this);
  displayTreeWidget->setSelectionMode(QAbstractItemView::SingleSelection);
  displayTreeWidget->setHeaderHidden(true);

  QLineEdit * displayEdit = new QLineEdit(this);
  displayEdit->installEventFilter(this);
  displayEdit->setEnabled(false);
  this->setLineEdit(displayEdit);
  this->setModel(displayTreeWidget->model());
  this->setView(displayTreeWidget);
  
  connect(this, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), this, [&](int index) {
    if (clickItem) updateTreeWidgetSelected();
    updateEditText();
    canHidePopup = true;
  });
}

QCheckTreeComboBox::~QCheckTreeComboBox()
{
}

void QCheckTreeComboBox::hidePopup()
{
  if (canHidePopup) QComboBox::hidePopup();
}

bool QCheckTreeComboBox::eventFilter(QObject *watched, QEvent *event)
{
  if (watched == this->lineEdit() && event->type() == QEvent::MouseButtonPress)
  {
    if (((QMouseEvent*)event)->button() == Qt::LeftButton)
    {
      showPopup();
    }
  }
  return QComboBox::eventFilter(watched, event);
}

QString QCheckTreeComboBox::getCurrentSelectedText()
{
  QString result = "";
  QTreeWidgetItemIterator it(displayTreeWidget);
  while (*it)
  {
    if ((*it)->checkState(0) == Qt::Checked)
    {
      result += (*it)->text(0)+",";
    }  
    ++it;
  }
  return result;
}

QString QCheckTreeComboBox::getCurrentSelectedData()
{
  QString result = "";
  QTreeWidgetItemIterator it(displayTreeWidget);
  while ( (*it) && (*it)->data(0, Qt::UserRole).isValid() )
  {
    if ( (*it)->checkState(0) == Qt::Checked )
    {
      result += (*it)->data(0, Qt::UserRole).toString() + ",";
    }

    ++it;
  }
  return result;
}

void QCheckTreeComboBox::updateTreeWidgetSelected()
{
  displayTreeWidget->setItemChecked();
}

void QCheckTreeComboBox::updateEditText()
{
  QString selText = getTreeSelectedText();
  QString selData = getTreeSelectedData();
  lineEdit()->setText(selText);
  lineEdit()->setCursorPosition(0);

  emit currentSelectedTextChange(selText);
  emit currentSelectedDataChange(selData);
}

void QCheckTreeComboBox::iniSelectText()
{
  updateEditText();
}

void QCheckTreeComboBox::QCheckTreeComboBoxView::mousePressEvent(QMouseEvent *event)
{
  treeComboBox->canHidePopup = false;

  auto curIndex = currentIndex();
  auto rect = this->visualRect(curIndex);
  auto buttonRect = QRect(rect.left() - 20, rect.top(), 20, rect.height());
  if (buttonRect.contains(event->pos()))
  {
    if (isExpanded(curIndex)) setExpanded(curIndex, false);
    else setExpanded(curIndex, true);  

    treeMousePressed(false);
  }
  else
  {
    treeMousePressed(true);
  }
  
}

void QCheckTreeComboBox::QCheckTreeComboBoxView::treeMousePressed(bool inItem)
{
  treeComboBox->clickItem = inItem;
}

void QCheckTreeComboBox::QCheckTreeComboBoxView::setChildSelected(QTreeWidgetItem* root)
{
  int count = root->childCount();
  for (int i = 0; i < count; ++i)
  {
    root->child(i)->setCheckState(0,root->checkState(0));
  }
}

void QCheckTreeComboBox::QCheckTreeComboBoxView::setItemChecked()
{
  currentItem()->setCheckState(0, currentItem()->checkState(0) == Qt::Checked ? Qt::Unchecked : Qt::Checked);
  setChildSelected(currentItem());
}

代码示例

auto tree = ui.comboBox->displayTreeWidget;
  for (int i = 1;i < 4;i++ )
  {
    QTreeWidgetItem* item = new QTreeWidgetItem(tree, { QString::number(i) });
    item->setCheckState(0, Qt::Unchecked);

    for (int j = 1; j < 5; j++)
    {
      QTreeWidgetItem* sub = new QTreeWidgetItem(item, { QString("%1.%2").arg(i).arg(j) });
      sub->setCheckState(0, Qt::Unchecked);
    }
    
  }

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值