技术专栏
Qt 自带树形表满足不了?教你自定义树形图
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
<
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包
1
1
评论
打赏
- 分享
- 举报
评论
2个
手气红包
相关专栏
-
浏览量:3873次2020-09-20 21:19:24
-
浏览量:15239次2020-11-12 21:55:56
-
浏览量:5965次2020-09-23 23:07:37
-
浏览量:3093次2020-05-06 15:52:54
-
浏览量:2360次2020-08-20 11:19:28
-
浏览量:2253次2020-08-03 12:02:37
-
浏览量:8223次2020-12-12 17:55:00
-
浏览量:8463次2020-12-12 17:47:04
-
浏览量:1898次2020-08-03 12:01:28
-
浏览量:4964次2021-06-28 15:59:34
-
浏览量:34437次2021-03-03 17:25:19
-
浏览量:2312次2020-08-14 18:33:44
-
浏览量:4508次2021-09-13 13:47:51
-
浏览量:5453次2017-11-23 12:40:31
-
浏览量:2117次2020-08-03 13:33:48
-
浏览量:2857次2020-08-14 18:40:18
-
浏览量:14205次2021-08-13 16:08:47
-
浏览量:4911次2021-09-08 16:03:36
-
浏览量:1188次2023-12-19 17:38:07
置顶时间设置
结束时间
删除原因
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
打赏作者

小王子🤴
您的支持将鼓励我继续创作!
打赏金额:
¥1

¥5

¥10

¥50

¥100

支付方式:

举报反馈
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明
审核成功
发布时间设置
发布时间:
请选择发布时间设置
是否关联周任务-专栏模块
审核失败
失败原因
请选择失败原因
备注
请输入备注
lohail