目录

* 一、感慨一下 <https://www.cnblogs.com/swarmbees/p/11154821.html#一感慨一下>
* 二、效果展示 <https://www.cnblogs.com/swarmbees/p/11154821.html#二效果展示>
* 三、搜索编辑框 <https://www.cnblogs.com/swarmbees/p/11154821.html#三搜索编辑框>
* 1、编辑框 <https://www.cnblogs.com/swarmbees/p/11154821.html#编辑框>
* 2、预览框 <https://www.cnblogs.com/swarmbees/p/11154821.html#预览框>
* 四、相关文章 <https://www.cnblogs.com/swarmbees/p/11154821.html#四相关文章>
原文链接:Qt之股票组件-股票检索--支持搜索结果预览、鼠标、键盘操作
<https://www.cnblogs.com/swarmbees/p/11154821.html>

一、感慨一下

之前做过一款炒股软件,个人觉着是我职业生涯里做过的效果最好的一款产品,而且速度也不慢,效果可以参考财联社-产品展示
<https://www.cnblogs.com/swarmbees/p/6707798.html>
这篇文章,当然这篇文章只能显示有限的内容,其中整个代码的结构、一些好的方法和设计模式是没有机会展示的。


最近听到一个不好的消息,我们的产品夭折了。刚听到这个消息时心理还挺不是滋味的,毕竟这个产品我是从头参与到尾,后来因为种种原因离开了,产品功能也就此终结,但回想起那段开发的日子,真的是收获满满。更确切的说,这个产品应该是换了一种语言重新开始做。

不爽归不爽,可整个产品的代码还是不错的,因此 后续有时间我会慢慢的把一些好的代码抽离出来,编译成一个个可以单独运行的demo,方便有需要的朋友使用。

如果有需要的朋友可以加我好友,有偿提供源码、或者也可以进一步提供功能定制

封装的控件,或者demo都是没有样式的,所以看着会比较丑一些,不过加样式也是分分钟。。。这里咱可以先看功能,需要即可定制

本篇文章我们首先介绍的就是股票,该控件支持常用的股票检索功能,支持模糊匹配,键盘上下键切换当前检索项等

右键菜单包括复制、粘贴、剪贴、全选等

本篇文章中不包括的功能也可以提供定制,需求合理即可。

下面来具体说一说这个功能的实现思路,会公开大多数核心代码,有需要的同学可以根据思路自行完善整个代码。

二、效果展示

如下效果图所示,是自选股使用上的一个展示效果,具有如下功能

* 搜索编辑框,支持股票代码和股票名称搜索
* 搜索预览框支持鼠标hover,并且可以使用键盘上下键进行当前项切换,单机时支持切换自选股
* 自选股列表,支持拖拽,拖拽时会有拖拽项映像,并示意将要拖拽到哪个位置
* 支持右键菜单,可以对某一项进行移动,删除等操作
如果觉着demo比较丑的话,可以看财联社-产品展示 <https://www.cnblogs.com/swarmbees/p/6707798.html>
这篇文章中的效果图

三、搜索编辑框


首先出场的是搜索编辑框,如gif图中展示所示,搜索框支持预览数据,当我们输入了字符串后,就会出现过滤后的预览数据。这里由于我们的股票数据是我自己模拟的,因此只显示了5条数据。


实现搜索编辑框,有2个小的模块需要讲解,一个是编辑框本身,它用于输入文本的能力,并且支持复制、粘贴等交互操作;另一个就是预览框了,他会动态的展示当前搜索的内容。

1、编辑框

Qt已经帮我们实现了一种编辑框,但是他自带了很多菜单项,如果产品这个时候说,菜单项我需要自己定制,多余的项不要。那么我们是不是得重写这个控件呢?答案是肯定的

下面我们就来讲解这个控件的重写步骤

重写一个Qt控件还是很简单的,使用Qt超过半年的同学都会重写大量各种各样的控件,而我们的编辑框重写就会像下面这样,是一个简单的头文件展示
///***********************************/// /// 描述:自定义编辑框,重写鼠标右键事件
///***********************************/// class SearchEdit : public QLineEdit {
public: SearchEdit(QWidget * parent = nullptr); ~SearchEdit(){} protected:
virtual void contextMenuEvent(QContextMenuEvent * event) override; private:
void InitMenu(); private: QMenu * m_PopMenu = nullptr; };
这里我们主要是针对右键菜单进行了重写,Qt窗体实现右键菜单的方式多种多样,具体可以参考我很早以前写的Qt之自定义QLineEdit右键菜单
<https://www.cnblogs.com/swarmbees/p/6044361.html>
这篇文章,今天我们也使用其中的一种方式来实现右键菜单,那就是实现默认的contextMenuEvent函数,这个函数之所以会响应,也是有一定条件的,
Qt之自定义QLineEdit右键菜单 <https://www.cnblogs.com/swarmbees/p/6044361.html>
这篇文章中讲解的也很清楚,那就是contextMenuPolicy的值必须为默认的Qt::DefaultContextMenu属性。

至于菜单重写实现函数,这里就不展示了,就是比较常规的使用QMenu增加QAction的操作

2、预览框

大家仔细想一想,预览框是什么时候出现的?他显示的数据有什么样的特征?接下来我们来一一做以分析

首先是出现时机

预览框主要是展示我们模糊搜索后的股票数据,那么结论就很明显了。预览的出现时机就是搜索内容发现变化的时候,并且当编辑框失去焦点时,我们应该主动关闭预览框

编辑框内容发现变化时,显示预览框
connect(d_ptr->m_pSearchLineEdit, &QLineEdit::textChanged, this,
&SelfStocksWidget::TextChanged);
处理预览框数据,主要是使用了FilterModel来进行过滤所有股票后选项,注意我们过滤的条件就是搜索框中输入的内容
void SelfStocksWidget::TextChanged(const QString & text) { if
(d_ptr->m_pFilterModel) { d_ptr->m_pFilterModel->SetFilterContext(text); } if
(d_ptr->m_pStockPreviewWidget) { if (text.isEmpty()) {
d_ptr->m_pStockPreviewWidget->hide(); d_ptr->m_pPreviewError->hide();
d_ptr->m_pCloseButton->setIcon(QIcon()); } else {
d_ptr->m_pCloseButton->setIcon(QIcon(":/optional/Resources/optional/sotck_search_close_normal.png"));
d_ptr->m_pStockPreviewWidget->move(d_ptr->m_pTitleWidget->mapToGlobal(QPoint(0,
d_ptr->m_pTitleWidget->height()))); int rowHeight =
d_ptr->m_pStockPreview->rowHeight(0); int rowCount =
d_ptr->m_pFilterModel->rowCount(); ... } } }
当编辑框失去焦点时,关闭预览框
这里我们取了一个巧,接收了该App的原生Win32消息,当我们发现一些影响窗口焦点的事件被触发时,我们去判断是否需要关闭预览框。

具体可以参考我很早之前写的qt捕获全局windows消息 <https://www.cnblogs.com/swarmbees/p/5632765.html>
这篇文章
bool SelfStocksWidget::nativeEventFilter(const QByteArray & eventType, void *
message, long * result) { if (eventType == "windows_generic_MSG" || eventType
== "windows_dispatcher_MSG") { MSG * pMsg = reinterpret_cast<MSG *>(message);
if (pMsg->message == WM_MOVE) { NativeParentWindowMove(); } else if
(pMsg->message == WM_ACTIVATEAPP) { if (bool(pMsg->wParam) == false) { if
(!d_ptr->m_pStockPreviewWidget->rect().contains(d_ptr->m_pStockPreview->mapFromGlobal(QPoint(pMsg->pt.x,
pMsg->pt.y)))) { d_ptr->m_pStockPreviewWidget->hide(); } if
(!d_ptr->m_pPreviewError->rect().contains(d_ptr->m_pPreviewError->mapFromGlobal(QPoint(pMsg->pt.x,
pMsg->pt.y)))) { d_ptr->m_pPreviewError->hide(); } } } else if (pMsg->message
== WM_NCMBUTTONDOWN || pMsg->message == WM_LBUTTONDOWN || pMsg->message ==
WM_RBUTTONDOWN || pMsg->message == WM_NCLBUTTONDOWN || pMsg->message ==
WM_NCRBUTTONDOWN || pMsg->message == WM_MBUTTONDOWN) { 同上... }
下面就是一个比较负责预览数据环节了,几千只股票,要准、要快,我们应该怎么技术选型呢?

预览框到底怎么显示数据的?他显示的都是哪些数据?


Qt提供了QListView、QTableView和QTreeView这3种视图模式,然后搭配Mode数据源,可以完成高效的大量数据展示,得知这个内容后是不是还有些小兴奋呢!


乍一看,QListView和QTableView都可以作为我们的预览框窗口,毕竟每一个Item项都是可以去重新定制的,看起来QListView还是更简单一些,而且速度也会更快一些,但是仔细想想,好像不是这么回事,我们既然要支持股票代码和名称都进行搜索,那么自然不是一列数据就可以进行过滤的,方便起见我们还是使用QTableView作为我们的视图窗口

既然视图窗口选定了,接下来就是一堆的事件定制了

a、重写QTableView


重写QTableView时,我们得考虑一个很重要的事情,那就是鼠标hover事件了,鼠标移动时我们需要把当前行设置为鼠标hover状态,为了实现这个效果,我可谓是费劲脑汁,想出了一个办法,写了一个IView接口类,让QTableView去继承,当鼠标hover时,去调用这个接口类告知QTableView当前hover项。
class IView { public: virtual void SetMouseHover(int, bool forceChanged =
false) = 0; };

上边的代码是不是看着很简单呢,就一个接口,就是当鼠标hover时告知表格当前hover项,那么什么实际通知合适呢?我这里是重写了QStyledItemDelegate绘图代理类,在paint函数中通知表格的,其他同学有好的办法也可以留言。

预览框的头文件大致是下面这样的,这里我只把公有的接口放出来了,其他的一些私有接口和成员变量没有公开(放出来估计大家也不看)
///***********************************/// /// 描述:搜索预览框
///***********************************/// class StockTableView : public
QTableView, public IView { Q_OBJECT signals : void RowClicked(const QString &
code); void RowDbClicked(const QString & code); public:
StockTableView(QStandardItemModel * model, QWidget * parent = 0); public: void
SetMouseHover(int, bool forceChanged = false); void SetMouseChecked(int); void
SetDbClickedEnable(bool enable); void SetHoverColor(const QColor & color); void
SetCheckedColor(const QColor & color); void CheckedMoveUp(); void
CheckedMoveDown(); void EnterPressed(); protected: ... private: ... };
代码中的接口都比较好理解,看名字应该都知道是干嘛的,这里就不做过多解释。

b、表格初始化

表格的数据内容在m_pListModel中存放,但是表格直接接收数据的是m_pFilterModel对象。

m_pFilterModel对象可以理解为是一个映像数据源,他没有真正的去存储数据,他的数据都是来自m_pListModel类。
//初始化搜索个股列表 d_ptr->m_pStockPreview = new StockTableView(d_ptr->m_pListModel);
d_ptr->m_pFilterModel = new StockSortFilterProxyModel;
d_ptr->m_pPreviewError->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
d_ptr->m_pPreviewError->setText(QStringLiteral("未搜索到相关股票"));
d_ptr->m_pStockPreview->horizontalHeader()->setVisible(false);
d_ptr->m_pStockPreview->verticalHeader()->setVisible(false);
d_ptr->m_pStockPreview->setShowGrid(false);
d_ptr->m_pStockPreview->horizontalHeader()->setStretchLastSection(true);
d_ptr->m_pStockPreview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
d_ptr->m_pStockPreview->setMouseTracking(true);
previewLayout->addWidget(d_ptr->m_pStockPreview);
d_ptr->m_pStockPreviewWidget->setLayout(previewLayout); StockItemDelegate *
itemDelegate = new StockItemDelegate(d_ptr->m_pStockPreview);
d_ptr->m_pStockPreview->setItemDelegate(itemDelegate);
itemDelegate->setView(d_ptr->m_pStockPreview);
d_ptr->m_pPreviewError->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::Tool |
Qt::FramelessWindowHint);
d_ptr->m_pStockPreviewWidget->setWindowFlags(Qt::WindowStaysOnTopHint |
Qt::Tool | Qt::FramelessWindowHint);
d_ptr->m_pFilterModel->setSourceModel(d_ptr->m_pListModel);
d_ptr->m_pStockPreview->setModel(d_ptr->m_pFilterModel);
d_ptr->m_pStockPreview->setColumnHidden(2, true);
d_ptr->m_pStockPreview->setSortingEnabled(true);
d_ptr->m_pPreviewError->setFixedSize(DropWidgetMaxWidth, 26);
d_ptr->m_pStockPreviewWidget->setFixedWidth(DropWidgetMaxWidth);
c、表格填充数据
正常来说数据应该是网络上拉取的,但是这里作为测试,我直接添加了5行模拟数据
void SelfStocksWidget::InitiAStock() { std::vector<BaseStockInfoItem>
sotckLists; BaseStockInfoItem item; for (int i = 1; i <= 5; ++i) {
item.wstrSymbol = QString("0h000%1").arg(i).toStdWString(); item.wstrName =
QString("%1%1%1").arg(i).toStdWString(); item.wstrSymbol =
QString("pingyin%1").arg(i).toStdWString(); sotckLists.push_back(item); } for
each (BaseStockInfoItem stock in sotckLists) { QList<QStandardItem *> rows;
QStandardItem * symbol = new
QStandardItem(QString::fromStdWString(stock.wstrSymbol).toUpper());
symbol->setData(QColor(28, 30, 34), Qt::BackgroundRole);
symbol->setData(QColor(204, 204, 204), Qt::ForegroundRole);
symbol->setSelectable(false); rows << symbol; QStandardItem * name = new
QStandardItem(QString::fromStdWString(stock.wstrName));
name->setData(QColor(28, 30, 34), Qt::BackgroundRole);
name->setData(QColor(204, 204, 204), Qt::ForegroundRole);
name->setSelectable(false); rows << name; QStandardItem * pinyin = new
QStandardItem(QString::fromStdWString(stock.wstrShortPinYin));
pinyin->setData(QColor(28, 30, 34), Qt::BackgroundRole);
pinyin->setData(QColor(204, 204, 204), Qt::ForegroundRole);
pinyin->setSelectable(false); rows << pinyin; //QStandardItem * type = new
QStandardItem(QString::number(stock.m_stockType)); //type->setData(QColor(28,
30, 34), Qt::BackgroundRole); //type->setData(QColor(204, 204, 204),
Qt::ForegroundRole); //type->setSelectable(false); //rows << type;
d_ptr->m_pListModel->appendRow(rows); } }
最终的数据被填充到了m_pListModel数据源中。

d、键盘操作

文章开始的地方也说过了,我们的搜索预览框是支持键盘上下键来切换当前股票的,这个又是怎么完成的呢!

预览框显示时,编辑框一直处于鼠标输入状态,并且具有键盘有限处理权限。

因此里我们是取了个巧,把编辑框的事件挂载在了他的父窗体上,当键盘按下时,父窗口拿到键盘按下事件,首先转发给了预览框,让预览框去换一个最新的当前股票,并选中。

代码如下所示,是不是也很简单。
bool SelfStocksWidget::eventFilter(QObject * watched, QEvent * event) { if
(d_ptr->m_pSearchLineEdit == watched) { if (event->type() == QEvent::KeyPress)
{ if (QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event)) { switch
(keyEvent->key()) { case Qt::Key_Up: d_ptr->m_pStockPreview->CheckedMoveUp();
break; case Qt::Key_Down: d_ptr->m_pStockPreview->CheckedMoveDown(); break;
case Qt::Key_Enter: case Qt::Key_Return:
d_ptr->m_pStockPreview->EnterPressed(); break; default: break; } } } } return
__super::eventFilter(watched, event); }
e、过滤


前边也讲述过了,我们表格数据都是来自m_pFilterModel对象的,数据源中的数据m_pListModel基本没有发生变化过,及时我们现实的内容变化了,那也仅仅是m_pFilterModel对象过滤到的内容发生了变化。

过滤接口Qt已经帮我们写好了,我们只需要实现其中的过滤方式即可。
bool StockSortFilterProxyModel::filterAcceptsRow(int source_row , const
QModelIndex & source_parent) const { QRegExp regExp = filterRegExp(); if
(regExp.isEmpty()) { return true; } bool result = false; for (int i = 0; i <
sortColumn; ++i) { QModelIndex index = sourceModel()->index(source_row, i,
source_parent); QString context = sourceModel()->data(index).toString();
QString regExpStr = regExp.pattern(); result = regExp.exactMatch(context); if
(result) { break; } } return result; }
以上就是搜索股票编辑框的大致内容了,至于一些细微的设置,大家自行去完善即可。

比如说预览框的窗口属性应该是这样的:
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::Tool | Qt::FramelessWindowHint);
未输入任务内容时,编辑框的holderText应该是这样的:
setPlaceholderText(QStringLiteral("搜索股票代码/名称"));
由于篇幅原因,本篇文章就只先说搜索编辑框吧,本来想把自选股列表页一起加上,不过觉着内容太多,也不利于大家吸收,下一篇文章补上吧。。。

写的手都酸了,其他内容自行脑补吧。。。

四、相关文章

财联社-产品展示 <https://www.cnblogs.com/swarmbees/p/6707798.html>

Qt之自定义QLineEdit右键菜单 <https://www.cnblogs.com/swarmbees/p/6044361.html>

qt捕获全局windows消息 <https://www.cnblogs.com/swarmbees/p/5632765.html>

高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具 <https://www.cnblogs.com/swarmbees/p/11027429.html>

高仿富途牛牛-组件化(二)-磁力吸附 <https://www.cnblogs.com/swarmbees/p/11042704.html>

高仿富途牛牛-组件化(三)-界面美化 <https://www.cnblogs.com/swarmbees/p/11048378.html>

高仿富途牛牛-组件化(四)-优秀的时钟 <https://www.cnblogs.com/swarmbees/p/11055495.html>

高仿富途牛牛-组件化(五)-如何去管理炒鸡多的小窗口 <https://www.cnblogs.com/swarmbees/p/11062824.html>

高仿富途牛牛-组件化(六)-炒鸡牛逼的布局记忆功能(序列化和反序列化)
<https://www.cnblogs.com/swarmbees/p/11094667.html>






如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!



















很重要--转载声明

*
本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八
<https://www.cnblogs.com/swarmbees/> or Twowords
<https://www.jianshu.com/u/7673f8cfb4e6>

*
如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信