原文链接:Qt之股票组件-股票检索--支持搜索结果预览、鼠标、键盘操作

一、感慨一下

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

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

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

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

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

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

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

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

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

二、效果展示

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

  1. 搜索编辑框,支持股票代码和股票名称搜索
  2. 搜索预览框支持鼠标hover,并且可以使用键盘上下键进行当前项切换,单机时支持切换自选股
  3. 自选股列表,支持拖拽,拖拽时会有拖拽项映像,并示意将要拖拽到哪个位置
  4. 支持右键菜单,可以对某一项进行移动,删除等操作

如果觉着demo比较丑的话,可以看财联社-产品展示这篇文章中的效果图

三、搜索编辑框

首先出场的是搜索编辑框,如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右键菜单这篇文章,今天我们也使用其中的一种方式来实现右键菜单,那就是实现默认的contextMenuEvent函数,这个函数之所以会响应,也是有一定条件的,Qt之自定义QLineEdit右键菜单这篇文章中讲解的也很清楚,那就是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消息这篇文章

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("搜索股票代码/名称"));

由于篇幅原因,本篇文章就只先说搜索编辑框吧,本来想把自选股列表页一起加上,不过觉着内容太多,也不利于大家吸收,下一篇文章补上吧。。。

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

四、相关文章

财联社-产品展示

Qt之自定义QLineEdit右键菜单

qt捕获全局windows消息

高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具

高仿富途牛牛-组件化(二)-磁力吸附

高仿富途牛牛-组件化(三)-界面美化

高仿富途牛牛-组件化(四)-优秀的时钟

高仿富途牛牛-组件化(五)-如何去管理炒鸡多的小窗口

高仿富途牛牛-组件化(六)-炒鸡牛逼的布局记忆功能(序列化和反序列化)

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

很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords

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


Qt之股票组件-股票检索--支持预览框、鼠标、键盘操作的更多相关文章

  1. Windows Azure HDInsight 支持预览版 Hadoop 2.2 群集

     Windows Azure HDInsight 支持预览版 Hadoop 2.2 群集 继去年 10 月推出 Windows Azure HDInsight 之后,我们宣布 Windows Az ...

  2. HTML5 CSS3 经典案例:无插件拖拽上传图片 (支持预览与批量) (二)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/31513065 上一篇已经实现了这个项目的整体的HTML和CSS: HTML5 C ...

  3. Qt SD卡 文件系统挂载、文件预览

    /********************************************************************************** * Qt SD卡 文件系统挂载. ...

  4. Rider中Winform开发支持预览(5)

    1.Rider .netCore3.0 winform设计器支持预览,这点vs目前还不支持. 2.不过winform下控件选择工具栏都是没有图标的

  5. 解决pyspider框架web预览框过小问题

    解决pyspider框架web预览框过小问题 Chrome 使用pyspider框架时,web预览框只有一小条: 解决办法: 找到debug.min.css文件,替换为如下内容: body{margi ...

  6. 解决Android界面布局添加EditText组件后界面无法预览

    错误报告: Exception raised during rendering: java.lang.System.arraycopy([CI[CII)V Exception details are ...

  7. 模拟微信上传图片(带预览,支持预览gif)

    一.Html <style type="text/css"> #previewDiv{width:50px;height:50px;overflow:hidden;po ...

  8. MTK 平台上查询当前使用的摄像头模组及所支持预览分辨率

    1,MTK 平台如何查询当前使用的是哪颗摄像头及相关的模组信息? 在该目录下可以查到当前平台及相关项目的配置文件 ProjectConfig.mk \ALPS.JB.MP.V1_W_20120919\ ...

  9. js上传图片前预览方法(支持预览多个图片)

    运用js实现上传图片前的预览(支持多张图片),实现的例子如下: 1.源码例子: 1)Js脚本页面 <!doctype html> <html> <head> < ...

随机推荐

  1. WPF 界面实现多语言支持 中英文切换 动态加载资源字典

    1.使用资源字典,首先新建两个字典文件en-us.xaml.zh-cn.xaml.定义中英文的字符串在这里面[注意:添加xmlns:s="clr-namespace:System;assem ...

  2. HALCON学习之算子大全

    1.1 Gaussian-Mixture-Models 1.add_sample_class_gmm 功能:把一个训练样本添加到一个高斯混合模型的训练数据上. 2.classify_class_gmm ...

  3. 插入2D点,在WPF中使用Bezier曲线

    原文Interpolate 2D points, usign Bezier curves in WPF Interpolate 2D points, usign Bezier curves in WP ...

  4. UNITY VR 视频/图片 开发心得(二)

    上回说到了普通的全景图片,这回讲真正的VR. 由于这种图片分为两部分,所以我们需要两个Camera对象以及两个球体.首先新建一个Camera对象,并将其命名为RightEye(其它名字也无妨,只要你自 ...

  5. 喜迎Win10周年版,芒果TV UWP完善手机视频离线下载

    喜迎Win10周年版更新,湖南卫视旗下唯一官方视频平台<芒果TV>近日向Win10商店提交了芒果TV UWP V3.1.0版,这次不仅在上一版(V3.0.0)的基础上完善了用户呼声最高的手 ...

  6. Have You Tried Delphi on Amazon Linux? (就是AWS用的Linux)

    The new Delphi Linux compiler enables customers to take new or existing Windows server applications ...

  7. Win10商店芒果TV UWP版更新,新增后台视频下载

    湖南卫视旗下唯一官方视频平台<芒果TV>近日向Win10商店提交了芒果TV UWP V3.0.0版,这次为广大Win10平台用户带来了期待已久的重大功能和更新,可谓是良心厂商,值得鼓励和支 ...

  8. 基于X.509证书和SSL协议的身份认证过程实现(OpenSSL可以自己产生证书,有TCP通过SSL进行实际安全通讯的实际编程代码)good

    上周帮一个童鞋做一个数字认证的实验,要求是编程实现一个基于X.509证书认证的过程,唉!可怜我那点薄弱的计算机网络安全的知识啊!只得恶补一下了. 首先来看看什么是X.509.所谓X.509其实是一种非 ...

  9. hMailServer搭建简单邮件系统

    本文介绍的是搭建本地的邮件系统,至于互联网的还在研究之中. 1.需要一个邮件服务器软件,这里用的是hMailServer,其中会让你设置一个密码,记住这个密码,后面连接的时候回用到. 2.添加域名 因 ...

  10. QT 那些事

    一直做的是windows开发,有2个小项目想做到手机.看来看去,选择了QT.本文介绍自己的学习的一些心得,希望对你有帮助.1.选择QT2.安卓的一些事3.QML调用C++4.资源集成(比较有用,网上资 ...