文章目录
一篇文章Qt之QComboBox定制讲到了qt实现自定义的下拉框,该篇文章主要实现了列表式的下拉框,这一节我还将继续讲解QComboBox的定制,而这一节我将会讲述更高级的用法,不仅仅是下拉列表框,而可以实现下拉框为表格,原理其实上一篇文章中的列表框类似,不过在这篇文章我将会重点讲述一下不同的地方,好了,下边我先截取一下demo中的运行效果图,如图1所示,效果并不是那么美观,不过确实有很大的用处。
图1 表格下拉框
![](https://www.cnblogs.com/images/cnblogs_com/swarmbees/1502901/o_21.png)
看了上图中的展示,是不是觉得很眼熟,是的,同学你说对了,其实这个界面时仿照铁道部的地区选组框做出来的,只不过是效果上有所差异,而功能上基本差不多,上图中的标题栏我是为了实现透明的表头而故意设置透明的,设置透明也是我后边重点要说的,怎么实现下拉框的背景色透明。
在文章的讲解开始前,我先截取下其他两个下拉框的截取,让有所期待的同学一睹为快。说实话,现在的下拉框并不是那么好看,不过用好了qss,这个就不是那么重要了,重要的是下拉框的交互功能,如图2是下拉框列表框,如图3是下拉框表格,不同于图1,图3是不支持根据表头切换内容的下拉框表格。
图2 下拉框列表
![](https://www.cnblogs.com/images/cnblogs_com/swarmbees/1502901/o_22.png)
图3 下拉框表格
![](https://www.cnblogs.com/images/cnblogs_com/swarmbees/1502901/o_23.png)
首先声明一下,在看这篇文章的时候我默认同学们已经看过Qt之QComboBox定制这篇文章,我的代码都是在这个demo的基础上重构出来的,如果有什么疑问可以去这篇文章中看看,或者直接私信我。
说起这个demo,我主要是按照两个路线来实现下拉框界面定制,列表和表格,所以我在实现的时候会分出这两个类来进行封装,然后在把他们的一些公有的操作提取出来,作为一层父类,也就是上文中所提到的博客中的combobox类。
接下来就是代码时刻,demo我会在文章最后给出下载链接,因此文章中我只贴出关键的代码段,
1、首先先来理解下文章中关键的类,理解了这几个类,这个demo的骨架就清楚了
CComboBox:下拉框父类,实现了大多数的数据添加接口
CListPopupComboBox:下拉框列表,如图2
CCheckBoxHeaderView:水平表头,主要是自绘表头
CTablePopupComboBox:下拉框表格,如图3
CTableRowHeaderView:列表头
CCityComboBox:城市选择下拉框,如图1
2、demo中的注释也是主要集中在接口中,不过在这里我还是要在不厌其烦的说一下接口相关的东西,毕竟接口就像是人的眼睛,接口弄明白了才能正确使用。
class CComboBox : public QWidget
{
public:
CComboBox(CustomPopupComboBox::ItemType type, QWidget * parent = nullptr);
~CComboBox(){}
public:
//设置分隔符 默认为'|'
void SetSeparatorSymbol(char symbol);
char GetSeparatorSymbol() const { return m_SeparatorSymbol; }
//新增数据
void AddText(const QString & text);
void AddTexts(const QVector<QString> & items);
//设置下拉框属性
void SetItemWidth(int width);
void SetItemHeight(int height);
//设置最多可见条目数
void SetMaxVisibleCount(int count);
//设置下拉框中项模式
void SetItemType(CustomPopupComboBox::ItemType type){ m_Type = type; }
CustomPopupComboBox::ItemType GetItemType() const { return m_Type; }
protected:
virtual bool eventFilter(QObject *, QEvent *) Q_DECL_OVERRIDE;
protected:
virtual void AddItem(const QString & text) = 0;
virtual void ResetItemWidth(int width) = 0;
virtual void ResetItemHeight(int height) = 0;
protected:
QWidget * NewItem(const QString & text);
protected:
CustomPopupComboBox::ItemType m_Type = CustomPopupComboBox::LabelItem;
char m_SeparatorSymbol = '|';//表格选择项分隔符
int m_ItemCount = 0;//表格总的项数
QString m_CurrentMemory;//当前选择字符串
QComboBox * m_ComboBox = nullptr;
QWidget * m_BottomWidget = nullptr;
QWidget * m_PopupWidget = nullptr;
//定制项信息
int m_ItemWidth = 150;
int m_ItemHeight = 45;//需要和css文件中的QComboBox QAbstractItemView::item{height:45px;}对应
private:
void InitializeUI();
};
上述代码中有几个保护的纯虚函数,这几个接口主要是在具体的下拉框类中实现,而该接口会在父类中被调用,其他public接口都是含有注释的,直接看代码应该也能看懂。
3、列表下拉框
/// 说明:combobox定制 下拉框为单列数据,支持文本、单选和复选
class CListPopupComboBox : public CComboBox
{
Q_OBJECT
public:
CListPopupComboBox(CustomPopupComboBox::ItemType type = CustomPopupComboBox::RadioItem, QWidget * parent = nullptr);
~CListPopupComboBox();
protected:
virtual bool eventFilter(QObject *, QEvent *) Q_DECL_OVERRIDE;
//新增数据 CComboBox
virtual void AddItem(const QString & text) Q_DECL_OVERRIDE;
virtual void ResetItemWidth(int width) Q_DECL_OVERRIDE;
virtual void ResetItemHeight(int height) Q_DECL_OVERRIDE;
private:
virtual void ConstructView();//列表定制
};
实现了CComboBox类中的3个纯虚接口,主要是重置下拉框项的高度和宽度,还有增加项等接口。列表框增加项代码如下:
void CListPopupComboBox::AddItem(const QString & text)
{
if (QListWidget * listWidget = dynamic_cast<QListWidget *>(m_PopupWidget))
{
QWidget * itemWidget = NewItem(text);
//itemWidget->setStyleSheet(QString("QCheckBox {background-color:lightgray;}"
// "QCheckBox:checked{background-color:white;}"));
itemWidget->setFixedSize(m_ItemWidth, m_ItemHeight);
int pos = listWidget->count() - 1 < 0 ? 0 : listWidget->count() - 1;
listWidget->insertItem(pos, new QListWidgetItem());
listWidget->setItemWidget(listWidget->item(pos), itemWidget);
}
}
4、表格行表头定制
/// 说明:table列表头定制
class CCheckBoxHeaderView : public QHeaderView
{
Q_OBJECT
public:
CCheckBoxHeaderView(int checkColumnIndex,
Qt::Orientation orientation,
QWidget * parent = 0) :
QHeaderView(orientation, parent)
{
m_checkColIdx = checkColumnIndex;
}
public:
void UpdateSelectColumn(int);
signals:
void SectionClicked(int);
protected:
virtual void paintSection(QPainter * painter, const QRect &rect, int logicalIndex) const;
virtual void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE;
private:
int m_checkColIdx;
};
主要实现了表格行表头自绘制,绘制代码在paintSection接口中实现,具体绘制方式如下:
void CCheckBoxHeaderView::paintSection(QPainter * painter, const QRect &rect, int logicalIndex) const
{
QRect r = rect;
r.setTop(r.top() + 20);
r.setLeft(r.left() - 35);
painter->fillRect(r, Qt::white);
if (logicalIndex == 0)
{
r.setTop(r.top() - 20);
r.setHeight(20);
painter->drawPixmap(rect, QPixmap(QStringLiteral(":/combobox/Resources/bg.png")));
}
QString text = model()->headerData(logicalIndex, this->orientation(),
Qt::DisplayRole).toString();
painter->setPen(QColor(239, 241, 241));
painter->drawLine(rect.bottomLeft(), rect.bottomRight());
painter->setPen(QPen(QColor(Qt::red), 2));
if (logicalIndex == m_checkColIdx)
{
QLine line(rect.bottomLeft() + QPoint(rect.width() / 3, 0), rect.bottomRight() - QPoint(rect.width() / 3, 0));
painter->drawLine(line);
QFont font = painter->font();
font.setBold(true);
painter->setFont(font);
}
painter->drawText(rect, Qt::AlignCenter, text);
}
5、表格下拉框
class CTablePopupComboBox : public CComboBox
{
Q_OBJECT
public:
CTablePopupComboBox(CustomPopupComboBox::ItemType type = CustomPopupComboBox::CheckBoxItem, QWidget * parent = 0);
~CTablePopupComboBox();
public:
//设置列数
void SetTableColumn(int column);
void SetHorizontalHeaderLabels(const QStringList & headerNames);
//设置下拉框属性
void SetItemWidth(int width);
void SetItemHeight(int height);
//设置表头是否可见
void SetHorizontalHeaderVisible(bool visible);
void CompletedData();
protected:
virtual bool eventFilter(QObject *, QEvent *) Q_DECL_OVERRIDE;
virtual void AddItem(const QString & text) Q_DECL_OVERRIDE;//新增数据
virtual void ResetItemWidth(int width) Q_DECL_OVERRIDE;
virtual void ResetItemHeight(int height) Q_DECL_OVERRIDE;
virtual void CompleteItems();//补齐没有填充完的行 防止combobox的view接手鼠标事件
virtual void ColumnHeaderClicked(int column);//列表头被点击处理
private slots:
void HeaderClicked(int index);
private:
virtual void ConstructView();//表格定制
private:
//定制项信息 //表格
int m_TableColumnCount = 2;
};
表格下拉框比列表下拉框复杂,它除了实现列表下拉框的接口外还实现了额外的接口,比如支持列表头和行表头的显示,并支持滚动条的显示等,这个表格下拉框实现起来没有那么完善,其中不乏有大量的bug,如果有同学发现了什么解决不了的问题可以私信我,或许这个bug我已经在项目中修正了,但是demo就没有时间修改。
6、行表头定制
/// 说明:table列表头定制
class CTableRowHeaderView : public QHeaderView
{
Q_OBJECT
public:
CTableRowHeaderView(QWidget * parent = 0) :
QHeaderView(Qt::Vertical, parent)
{
}
protected:
virtual void paintSection(QPainter * painter, const QRect &rect, int logicalIndex) const;
private:
};
7、城市列表下拉框
/// 说明:城市选择下拉框 支持行表头显示 不支持滚动条显示
class CCityComboBox : public CTablePopupComboBox
{
Q_OBJECT
public:
CCityComboBox(QWidget *parent);
~CCityComboBox();
public:
void SetVerticalHeaderLabels(const QStringList & headerNames);//设置表头列显示数据 不建议主动设置
void SetVerticalHeaderWidth(int width);//设置表头列宽度
void SetVerticalHeaderVisible(bool visible);//设置表头列是否可见
///***********************************///18 /// 说明:设置当前默认添加城市分组 配合AddText(AddTexts)一起使用 单次添加的数据 最后必须调用CompletedData接口
///***********************************///
void SetCurrentCityKey(char key){ m_CurrentCityKey = QChar(key).toLower().toLatin1(); }
void CompletedData();
///***********************************///26 /// 说明:设置当前城市分组
///***********************************///
void SetCistyMaps(const std::map < char, std::list<QString> > & citys);
protected:
virtual void AddItem(const QString & text);
virtual void ColumnHeaderClicked(int column);
private slots:
private:
void InitializeUI();
void FillData();//填充数据
int ResetTableData(int column);//根据点击列重置表格数据
private:
bool m_NeedFixedHeight = true;
char m_CurrentCityKey = 'a';
int m_CurrentDisplayColumnOrder = 0;
int m_VerticalHeaderWidth = 35;
QStringList m_VerticalHeaderName;
std::map< char, std::list<QString> > m_CitysMap;//按字母存储的城市列表
std::map < int, std::list<int> > m_RowsMap;//以列为键存储所在行
};
本篇文章的核心主要是想讲解这个类,该类可以实现上述列表下拉框和表格下拉框的功能,只需要调用相应的接口就可以,比如说列表下拉框,那么我只需要调用隐藏行和列表头,并把列设置为一列即可。
这个城市列表框其实也是在Qt之QComboBox定制文章中重构过来的,虽然接口类似,但是内部的实现细节有了不小的变化,就比如这个表格下拉框在列表头点击切换功能实现时,我改变了数据的存储模式,以前都是把数据删除重新添加,但是现在我是把所有数据都添加在表格中,而根据所需要显示的行进行show,而其他行直接hide,这样不仅实现起来方便而且不需要维护大量的缓存数据。
在这篇文章最开始的效果展示图中就能看到,下拉框有一部分是透明的了,这个其实也是我想实现的功能的一部分,主要是想实现一个带有小三角的下拉框,按照上述的方式应该也是能够实现,关于这个下拉框北京透明我也是搞了好久才搞明白,其实只要简单的几行代码就可以,代码如下:
if (QWidget * parent = m_ComboBox->view()->parentWidget())
{
// parent->installEventFilter(this);
parent->setAttribute(Qt::WA_TranslucentBackground);
parent->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);
}
只需要获取下拉框的顶层父类,然后设置其属性并设置其窗口风格即可,可是就这么几句话,不设置根本不能够实现背景色透明。所以看到这个信息的同学真是幸福。
城市表格下拉框的使用方式也比较简单,在添使用AddText加完数据之后需要调用CompletedData接口类告诉该类数据添加完毕;如果调用SetCistyMaps接口设置数据,则不需要,demo中使用方式如下:
CCityComboBox w3(&p);
w3.SetItemType(CustomPopupComboBox::LabelItem);
w3.SetHorizontalHeaderLabels(QStringList() << QStringLiteral("ABCDEF") << QStringLiteral("GHIJ")
<< QStringLiteral("KLMN")
<< QStringLiteral("PQRSTUVM")
<< QStringLiteral("XYZ"));
w3.SetVerticalHeaderLabels(QStringList() << QStringLiteral("A") << QStringLiteral("B")
<< QStringLiteral("C")
<< QStringLiteral("D")
<< QStringLiteral("E"));
w3.SetHorizontalHeaderVisible(true);
w3.SetVerticalHeaderVisible(true);
w3.setFixedSize(150, 30);
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.AddText(QStringLiteral("a"));
w3.SetCurrentCityKey('B');
w3.AddText(QStringLiteral("b"));
w3.AddText(QStringLiteral("b"));
w3.AddText(QStringLiteral("b"));
w3.AddText(QStringLiteral("b"));
w3.AddText(QStringLiteral("b"));
w3.SetCurrentCityKey('C');
w3.AddText(QStringLiteral("cC"));
w3.AddText(QStringLiteral("cC"));
w3.AddText(QStringLiteral("cC"));
w3.AddText(QStringLiteral("cC"));
w3.AddText(QStringLiteral("cC"));
w3.AddText(QStringLiteral("cC"));
w3.AddText(QStringLiteral("cC"));
w3.SetCurrentCityKey('M');
w3.AddText(QStringLiteral("m"));
w3.AddText(QStringLiteral("m"));
w3.AddText(QStringLiteral("m"));
w3.AddText(QStringLiteral("m"));
w3.AddText(QStringLiteral("m"));
w3.CompletedData();
本篇文章我只讲述了实现这样一个下拉框所需要的接口文件,而具体的实现我没有拉出来将,因为我觉着这个和上一篇文章中的实现类似,只是在接口上有比较大的重构。在写这篇文章的时候其实我就有一个想法,我应该还会写关于下拉框定制的第三篇文章,而这第三篇文章主要讲的还是怎么实现一个下拉框,但是走的完全是和现在不一样的路线。因为最近项目真的非常的紧张,根本没有时间去写和测试相关的demo,不过后边如果有时间我会尽快补上。
关于全新的下拉框定制,我的想法是完全定制一个下拉框,而不是重写QComboBox,这样的话可以省去很多的麻烦,比如:下拉框的大小,下拉框的视图位置等问题,说到这儿,我突然想起来这个demo的一个bug,那就是在表格下拉框出现下拉框滚动条的时候,如果在滚动了垂直滚动条之后隐藏下拉框,那么下次展示下拉框窗口时,视图会显示不正确,这个问题我自己已经解决了,但是没有在demo体现,解决办法是:在隐藏下拉框窗口时,把其视图滚动到顶端,实现代码如下:
bool CTablePopupComboBox::eventFilter(QObject * watched, QEvent * event)
{
if (m_BottomWidget && m_BottomWidget == watched)
{
if (QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event))
{
if (mouseEvent->type() == QEvent::MouseButtonPress
|| mouseEvent->type() == QEvent::MouseButtonRelease)
{
return true;
}
}
}
else if (watched == m_ComboBox->view()->parentWidget()
&& event->type() == QEvent::Hide)
{
if (QTableWidget * tableWidget = dynamic_cast<QTableWidget *>(m_PopupWidget))
{
tableWidget->scrollToTop();
}
}
return QWidget::eventFilter(watched, event);
}
注意:这个demo比较粗糙,如果有问题的同学可以私聊我,不管是建议还是问题我都会认真的回答
![]() | ![]() |
很重要–转载声明
-
本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords
-
如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。