Qt 自定义树形图
很多时候Qt自带的控件已经满足不了我们的需求,这个时候就应该自定义控件,本篇文章就来介绍下自定义树形表。 其实严格意义上来说这个不是真正的自定控件,而是使用mvc模式进行视图重绘。
MVC全称是 Model View Controller,是一种非常非常流行的架构模式,相知识网上一抓一大把,这里就不再阐述。
实际上Qt中没有MVC模式,Qt中的MVC并不叫MVC,而是叫“MVD”,Qt中没有Controller的说法,而是使用了另外一种抽象: Delegate (委托) ,其行为和传统的MVC是相同的。写过C#的同学肯定对delegate就不陌生了,这里delegate的用法就是负责协调Model和View之间的数据。
其思想如下图所示:
简单的介绍下Qt的MVC,因为重绘树形图使用的就是Qt的MVD,QStandardItemModel(M)、treeView(V)、QStyledItemDelegate(D),接下来废话少说,上代码,注释解释。
class QTreeWidgetEx : public QTreeView { Q_OBJECT public: explicit QTreeWidgetEx(QWidget *parent = nullptr); int addRow(int parentNode,QString text,QVariant v = QVariant()); QStandardItem* addParentNode(QString text,QString key);//添加父节点 QStandardItem* addChilderNode(QStandardItem *parentNode,QString name,int decimal,QString unit,QString key);//添加子节点 public slots: private: QStandardItemModel *mode = nullptr; }; TreeWidgetEx::QTreeWidgetEx::QTreeWidgetEx(QWidget *parent) : QTreeView(parent) { //创建mode mode = new QStandardItemModel(); //设置mode到视图 this->setModel(mode); //创建代理 ItemDelegate *itemDelegate = new ItemDelegate(this); //设置代理 this->setItemDelegate(itemDelegate); //视图禁止编辑 this->setEditTriggers(QAbstractItemView::NoEditTriggers); //禁止显示三角标 this->setRootIsDecorated(false); //子节点缩进 this->setIndentation(0); //隐藏滚动条 this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); //禁止双击展开 // this->setExpandsOnDoubleClick(false); } //添加一行 int TreeWidgetEx::QTreeWidgetEx::addRow(int parentNode, QString text,QVariant v) { QStandardItem *item = new QStandardItem(text); //设置节点高度 item->setSizeHint(QSize(0,80)); int index = -1; if(parentNode < 0) { mode->appendRow(item); index = mode->rowCount()-1; } else { mode->item(parentNode,0)->appendRow(item); index = mode->item(parentNode,0)->rowCount()-1; } return index; } //添加父节点 QStandardItem* TreeWidgetEx::QTreeWidgetEx::addParentNode(QString text, QString key) { QStandardItem *item = new QStandardItem(text); item->setData(key); //设置节点高度 item->setSizeHint(QSize(0,80)); mode->appendRow(item); parentNodeMap.insert(key,item); return item; } //添加子节点 QStandardItem* TreeWidgetEx::QTreeWidgetEx::addChilderNode(QStandardItem *parentNode, QString name, int decimal, QString unit, QString key) { QStandardItem *item = new QStandardItem(name); item->setData(key); //设置节点高度 item->setSizeHint(QSize(0,80)); parentNode->appendRow(item); return item; } //代理类,真正的自定义在这,重绘treeView class ItemDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit ItemDelegate(QWidget *parent = nullptr); ~ItemDelegate(); //重绘事件 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; //点击事件 bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); signals: //按钮点击信号 void pushButtonClick(const QModelIndex &index); private: QPoint m_mousePoint; // 鼠标位置 QScopedPointer<QPushButton> m_PushButton;//按钮 QStringList m_list; int m_nSpacing; // 按钮之间的间距 int m_nWidth; // 按钮宽度 int m_nHeight; // 按钮高度 int m_nType; // 按钮状态-1:划过 2:按下 QTreeView *tree = nullptr; }; TreeWidgetEx::ItemDelegate::ItemDelegate(QWidget *parent) : QStyledItemDelegate(parent), m_PushButton(new QPushButton()), m_nSpacing(5), m_nWidth(35), m_nHeight(35) { // 设置按钮正常、划过、按下样式 m_PushButton->setStyleSheet(“QPushButton {background-color: transparent;}”); tree = reinterpret_cast<QTreeView*>(parent); } TreeWidgetEx::ItemDelegate::~ItemDelegate() { } void TreeWidgetEx::ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { //保存按钮状态 painter->save(); QStyleOptionViewItem viewOption(option); initStyleOption(&viewOption, index); if (option.state.testFlag(QStyle::State_HasFocus)) viewOption.state = viewOption.state ^ QStyle::State_HasFocus; //抗锯齿绘图 painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); //绘制背景 QColor colorBg = QColor(53, 77, 159); //操作状态 选择、划过等 if (option.state & QStyle::State_Selected) { if(index.data(TreeWidgetEx::selectRole).isValid()) colorBg = QColor(53, 77, 100); } else if (option.state & QStyle::State_MouseOver) { } else { } painter->fillRect(option.rect, colorBg); int xPoint = 0; //绘制父节点 if(index.parent() == QModelIndex()) { if(static_cast<QStandardItemModel*>(tree->model())->rowCount(index) >= 1) { //绘制三角形图片 painter->setPen(QColor(255,255,255)); painter->setBrush(QColor(255,255,255)); QVector<QPointF> points; int margin = option.rect.width()/32/*25*/; //绘制的三角形所在的正方形框矩阵 QRect rect = option.rect; int rwh = qMin(option.rect.width()/20,option.rect.height()/4); rect.setX(option.rect.x() + margin); rect.setY(option.rect.y() + (option.rect.height() – rwh)/2/*/80*23*/); rect.setWidth(rwh); rect.setHeight(rwh); xPoint = rect.right(); if (!tree->isExpanded(index)) { //收起的三角形 points.append(QPointF(rect.x(), rect.y())); points.append(QPointF(rect.x(), rect.y()+rect.height())); points.append(QPointF(rect.x()+rect.width(), rect.y()+rect.height()/2)); } else { //展开的三角形 points.append(QPointF(rect.x(), rect.y())); points.append(QPointF(rect.x()+rect.width(), rect.y())); points.append(QPointF(rect.x()+rect.width()/2, rect.y()+rect.height())); } painter->drawPolygon(points); } //绘制分隔线条 painter->setPen(QPen(QColor(255,255,255) 1)); painter->drawLine(QPointF(option.rect.x(), option.rect.y()), QPointF(option.rect.x() + option.rect.width(), option.rect.y())); } // 绘制条目文字 QColor colorText = QColor(255, 255, 255); painter->setPen(QPen(colorText)); //绘制文字离左边的距离 int margin = static_cast<int>((static_cast<double>(option.rect.width())/400.0)*80.0); int textWidget = static_cast<int>((static_cast<double>(option.rect.width())/400.0)*200.0); int margin1 = static_cast<int>((static_cast<double>(option.rect.width())/400.0)*200.0); QRect rect = option.rect; rect.setX(option.rect.x() + margin); rect.setWidth(option.rect.width() – margin); QFont normalFont(“Segoe UI”, 24); QString text = index.data(Qt::DisplayRole).toString(); for(int i = 24;i > 0;i–){ normalFont.setPointSize(i); QFontMetrics fontMetrics(normalFont); if(fontMetrics.boundingRect(text).width() < textWidget) break; } painter->setFont(normalFont); painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, text); // 计算按钮显示区域 int nTop = static_cast<int>(static_cast<double>(option.rect.height())/80.0*23); // 绘制按钮 QStyleOptionButton button; button.rect = QRect(option.rect.right() – static_cast<int>(static_cast<double>(option.rect.width())/400.0*19) – static_cast<int>(static_cast<double>(option.rect.height())/80.0*35), option.rect.top() + nTop, static_cast<int>(static_cast<double>(option.rect.height())/80.0*35), static_cast<int>(static_cast<double>(option.rect.height())/80.0*35)); button.state |= QStyle::State_Enabled; if(index.data(TreeWidgetEx::nodeState).toBool()) { button.icon = QIcon(QString(“:/new/prefix1/icon/*.png”)); button.iconSize = QSize(button.rect.width(),button.rect.height()); }else{ button.icon = QIcon(QString(“:/new/prefix1/*”)); button.iconSize = QSize(button.rect.width(),button.rect.height()); } //划过按下 if (button.rect.contains(m_mousePoint)) { if (m_nType == 0) { button.state |= QStyle::State_MouseOver; } else if (m_nType == 1) { button.state |= QStyle::State_Sunken; button.iconSize = QSize(button.rect.width()/2,button.rect.height()/2); }else if (m_nType == -1) { } } QWidget *pWidget = m_PushButton.data(); pWidget->style()->drawControl(QStyle::CE_PushButton, &button, painter,pWidget); painter->restore();//恢复painter } //点击事件 bool TreeWidgetEx::ItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { Q_UNUSED(model); m_nType = -1; bool bRepaint = false; QMouseEvent *pEvent = static_cast<QMouseEvent *> (event); m_mousePoint = pEvent->pos(); int nTop = static_cast<int>(static_cast<double>(option.rect.height())/80.0*23); // 还原鼠标样式 { QStyleOptionButton button; //按钮位置 button.rect = QRect(option.rect.right() – static_cast<int>(static_cast<double>(option.rect.width())/400.0*19) – static_cast<int>(static_cast<double>(option.rect.height())/80.0*35), option.rect.top() + nTop, static_cast<int>(static_cast<double>(option.rect.height())/80.0*35), static_cast<int>(static_cast<double>(option.rect.height())/80.0*35)); // 鼠标位于按钮之上 if (!button.rect.contains(m_mousePoint)) { if(event->type() == QEvent::MouseButtonRelease) tree->setExpanded(index,!tree->isExpanded(index)); return bRepaint; } bRepaint = true; switch (event->type()) { // 鼠标滑过 case QEvent::MouseMove: { // 设置鼠标样式为手型 m_nType = 0; break; } // 鼠标按下 case QEvent::MouseButtonPress: { m_nType = 1; break; } // 鼠标释放 case QEvent::MouseButtonRelease: { emit pushButtonClick(index); break; } default: break; } } return bRepaint; }<
免责声明:文章内容来自互联网,本站不对其真实性负责,也不承担任何法律责任,如有侵权等情况,请与本站联系删除。
转载请注明出处:Qt 自带树形表满足不了?教你自定义树形图 https://www.yhzz.com.cn/a/14965.html