1、效果展示
今天这篇文章主要讲解的是自定义搜索框,不仅仅支持搜索,而且可以支持搜索预览,具体请看效果图1。网上也有一些比较简单明了的自定义搜索框,比如Qt之自定义搜索框,讲的也比较详细,不过本文的侧重点不仅仅是搜索,而且包括了检索功能。有兴趣的小伙伴可以看下步骤3的思路讲解。
图1 自定义搜索框
2、功能分析
这个自定义搜索框支持输入一定的数据源,然后通过检索窗口进行搜索数据,匹配到的数据会优先展示到预览下拉框,预览窗口支持hover高亮整行。仔细阅读demo源码的同学可能就会发现其实这个搜搜框的左侧又一个按钮是可以点击的,但是目前还没有添加具体的点击功能。了解了这个控件的功能之后,如果觉得对你有用,那么就可以接着继续往下看实现流程。
3、思路讲解
问题分析:
1、数据源存储在哪儿?怎么实现检索
2、弹出式下拉框显示和隐藏控制?位置同步?
3、鼠标hover状态的颜色设置?
首先在讲解源码之前,我抛出了3个问题,有精力的同学可以先思考下这几个问题,然后在接着往下看,下边我也会逐一说明这个些问题。
源码讲解:
1、使用到的类:
StockSortFilterProxyModel:过滤数据源,该model上的数据索引直接供视图展示
StockTableView:自定义视图,用于显示预览数据
StockListWidget:自定义搜索框
StockItemDelegate:自定义委托,提供自定义绘图
上边4个类是完成自定义搜索框的自定义类,当然除了上述4个类以外,还用到了qt自带的一些类,更好的理解这些类,那么这个自定义控件的思路也就显得异常好理解。
2、头文件说明
- 委托:负责视图绘制
1 class IView; 2 3 struct StockItemDelegatePrivate 4 { 5 int column = 1;//进度条所在列,下标从0开始 6 QTableView * parent = nullptr; 7 IView * view = nullptr; 8 }; 9 10 class StockItemDelegate : public QStyledItemDelegate 11 { 12 Q_OBJECT 13 14 public: 15 StockItemDelegate(QTableView * parent = nullptr); 16 ~StockItemDelegate(){}; 17 18 public: 19 void setView(IView * view); 20 21 protected: 22 virtual void paint(QPainter * painter 23 , const QStyleOptionViewItem & option 24 , const QModelIndex & index) const Q_DECL_OVERRIDE; 25 26 virtual QSize sizeHint(const QStyleOptionViewItem &option, 27 const QModelIndex &index) const Q_DECL_OVERRIDE; 28 29 private: 30 QScopedPointer<StockItemDelegatePrivate> d_ptr; 31 };
- 窗口布局:StockTableView是专门用来展示检索后的数据,StockListWidget是窗口布局
1 class IView 2 { 3 public: 4 virtual void SetMouseOver(int) = 0; 5 }; 6 7 class StockTableView : public QTableView, public IView 8 { 9 Q_OBJECT 10 public: 11 StockTableView(QStandardItemModel * model, QWidget * parent = 0); 12 13 public: 14 void SetMouseOver(int); 15 16 protected: 17 virtual void mouseMoveEvent(QMouseEvent * event) override; 18 virtual void leaveEvent(QEvent * event) override; 19 virtual void mousePressEvent(QMouseEvent * event) override; 20 21 private: 22 int currHovered; 23 void disableMouseOver(); 24 25 private: 26 QStandardItemModel * m_pSourceModel; 27 }; 28 29 struct StockListWidgetPrivate; 30 31 class StockListWidget : public QWidget, public QAbstractNativeEventFilter 32 { 33 Q_OBJECT 34 35 public: 36 StockListWidget(QWidget * parent = nullptr); 37 ~StockListWidget(); 38 39 public slots: 40 void NativeParentWindowMove(); 41 42 protected: 43 virtual void moveEvent(QMoveEvent * event) override; 44 virtual bool nativeEventFilter(const QByteArray & eventType, void * message, long * result) override; 45 46 private: 47 void InitializeUI(); 48 49 private: 50 QScopedPointer<StockListWidgetPrivate> d_ptr; 51 };
- 数据源:根据模式串匹配检索后的数据,并负责通知视图进行更新展示
1 class StockSortFilterProxyModel : public QSortFilterProxyModel 2 { 3 Q_OBJECT 4 5 public: 6 StockSortFilterProxyModel(QObject *parent = nullptr); 7 ~StockSortFilterProxyModel(); 8 9 void SetFilterContext(const QString & pattern); 10 11 protected: 12 bool lessThan(const QModelIndex &left 13 , const QModelIndex &right) const; 14 bool filterAcceptsRow(int source_row 15 , const QModelIndex & source_parent) const; 16 private: 17 size_t sortColumn = 0; 18 };
3、窗口布局:
窗口布局:也就是这个检索框长什么样子,效果展示图1中就可以看到,在这个dmeo中,也就是StockListWidget类,该类使用了一个水平布局添加了按钮和文本输入框。当文本输入框内容发生变化时,启动检索,然后刷新视图上的数据,具体看源码
1 connect(d_ptr->m_pSearchLineEdit, &QLineEdit::textChanged, this, [this](const QString & text){ 2 if (d_ptr->m_pFilterModel) 3 { 4 d_ptr->m_pFilterModel->SetFilterContext(text);//根据检索内容刷新model 5 } 6 if (d_ptr->m_pStockPreviewWidget) 7 { 8 d_ptr->m_pStockPreviewWidget->move(d_ptr->m_pTitleWidget->mapToGlobal(QPoint(0, d_ptr->m_pTitleWidget->height()))); 9 int rowHeight = d_ptr->m_pStockPreview->rowHeight(0); 10 int rowCount = d_ptr->m_pFilterModel->rowCount(); 11 d_ptr->m_pStockPreviewWidget->setFixedHeight(rowHeight * rowCount > DropWidgetMaxHeight ? DropWidgetMaxHeight : rowHeight * rowCount); 12 d_ptr->m_pStockPreviewWidget->show();//修正view高度,挪动位置并显示 13 } 14 });
4、数据存储
qt提供了一些列的model来供我们使用,有可以存放数据的,也有一些只供我们使用接口的,在这个demo中我使用的是QStandardItemModel,他可以存储我们所需要检索的源数据,然后qt还提供了一个检索model,QSortFilterProxyModel,我继承该model可以做自己想做的检索实现,然后把检索到的数据索引通知到视图,这样就完成了数据更新,具体关联代码如下:
1 StockItemDelegate * itemDelegate = new StockItemDelegate(d_ptr->m_pStockPreview); 2 d_ptr->m_pStockPreview->setItemDelegate(itemDelegate); 3 itemDelegate->setView(d_ptr->m_pStockPreview);//委托关联到视图上,负责数据绘制 4 5 d_ptr->m_pStockPreviewWidget->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::Popup); 6 7 d_ptr->m_pFilterModel->setSourceModel(d_ptr->m_pListModel);//检索model检索的数据源设置 8 d_ptr->m_pStockPreview->setModel(d_ptr->m_pFilterModel);//视图展示的model为检索后的model
数据检索实现,我匹配的是每一列是否为需要的值,也就是如果这一行中某一列满足要求都认为该行满足要求
1 bool StockSortFilterProxyModel::filterAcceptsRow(int source_row 2 , const QModelIndex & source_parent) const 3 { 4 QRegExp regExp = filterRegExp(); 5 bool result = false; 6 for (int i = 0; i < sortColumn; ++i)//循环匹配所有的列,有一列满足要求就返回true 7 { 8 QModelIndex index = sourceModel()->index(source_row, i, source_parent); 9 QString context = sourceModel()->data(index).toString(); 10 11 if (regExp.isEmpty() == false) 12 { 13 QString regExpStr = regExp.pattern(); 14 result = regExp.exactMatch(context); 15 } 16 17 if (result) 18 { 19 break; 20 } 21 } 22 23 return result; 24 }
5、视图显示
关于视图显示,主要是视图显示位置和显示时机
显示时机:编辑框内容发现变化的时候显示,编辑框失去焦点的时候隐藏,这样也就存在一个问题,当主窗口拖动时,视图位置更新怎么做?
显示位置:当主窗口位置移动时,更新视图位置,这个方法看一参考qt捕获全局windows消息。
6、视图背景色
视图背景色在添加数据源的时候设置了默认背景色,在后hover的时候重新设置背景色,hover失效后再恢复默认背景色,实现行hover代码如下:
1 void StockTableView::SetMouseOver(int row) 2 { 3 if (row == currHovered) 4 { 5 return; 6 } 7 8 StockSortFilterProxyModel * sortModel = static_cast<StockSortFilterProxyModel *>(model()); 9 if (sortModel->rowCount() <= row) 10 { 11 return; 12 } 13 for (int col = 0; col < sortModel->columnCount(); col++)//循环遍历,设置指定行中每一个item的背景色 14 { 15 QModelIndex index = sortModel->index(row, col); 16 if (index.isValid()) 17 { 18 if (QStandardItem * item = m_pSourceModel->itemFromIndex(sortModel->mapToSource(index))) 19 { 20 item->setBackground(QBrush(QColor(43, 92, 151))); 21 } 22 } 23 } 24 25 if (currHovered != -1) 26 { 27 disableMouseOver();//恢复之前hover的行 28 } 29 currHovered = row; 30 }