目錄
* 一、感慨一下 <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>
一、感慨一下
之前做過一款炒股軟件,個人覺著是我職業(yè)生涯里做過的效果最好的一款產(chǎn)品,而且速度也不慢,效果可以參考財聯(lián)社-產(chǎn)品展示
<https://www.cnblogs.com/swarmbees/p/6707798.html>
這篇文章,當然這篇文章只能顯示有限的內容,其中整個代碼的結構、一些好的方法和設計模式是沒有機會展示的。
最近聽到一個不好的消息,我們的產(chǎn)品夭折了。剛聽到這個消息時心理還挺不是滋味的,畢竟這個產(chǎn)品我是從頭參與到尾,后來因為種種原因離開了,產(chǎn)品功能也就此終結,但回想起那段開發(fā)的日子,真的是收獲滿滿。更確切的說,這個產(chǎn)品應該是換了一種語言重新開始做。
不爽歸不爽,可整個產(chǎn)品的代碼還是不錯的,因此 后續(xù)有時間我會慢慢的把一些好的代碼抽離出來,編譯成一個個可以單獨運行的demo,方便有需要的朋友使用。
如果有需要的朋友可以加我好友,有償提供源碼、或者也可以進一步提供功能定制
封裝的控件,或者demo都是沒有樣式的,所以看著會比較丑一些,不過加樣式也是分分鐘。。。這里咱可以先看功能,需要即可定制
本篇文章我們首先介紹的就是股票,該控件支持常用的股票檢索功能,支持模糊匹配,鍵盤上下鍵切換當前檢索項等
右鍵菜單包括復制、粘貼、剪貼、全選等
本篇文章中不包括的功能也可以提供定制,需求合理即可。
下面來具體說一說這個功能的實現(xiàn)思路,會公開大多數(shù)核心代碼,有需要的同學可以根據(jù)思路自行完善整個代碼。
二、效果展示
如下效果圖所示,是自選股使用上的一個展示效果,具有如下功能
* 搜索編輯框,支持股票代碼和股票名稱搜索
* 搜索預覽框支持鼠標hover,并且可以使用鍵盤上下鍵進行當前項切換,單機時支持切換自選股
* 自選股列表,支持拖拽,拖拽時會有拖拽項映像,并示意將要拖拽到哪個位置
* 支持右鍵菜單,可以對某一項進行移動,刪除等操作
如果覺著demo比較丑的話,可以看財聯(lián)社-產(chǎn)品展示 <https://www.cnblogs.com/swarmbees/p/6707798.html>
這篇文章中的效果圖
三、搜索編輯框
首先出場的是搜索編輯框,如gif圖中展示所示,搜索框支持預覽數(shù)據(jù),當我們輸入了字符串后,就會出現(xiàn)過濾后的預覽數(shù)據(jù)。這里由于我們的股票數(shù)據(jù)是我自己模擬的,因此只顯示了5條數(shù)據(jù)。
實現(xiàn)搜索編輯框,有2個小的模塊需要講解,一個是編輯框本身,它用于輸入文本的能力,并且支持復制、粘貼等交互操作;另一個就是預覽框了,他會動態(tài)的展示當前搜索的內容。
1、編輯框
Qt已經(jīng)幫我們實現(xiàn)了一種編輯框,但是他自帶了很多菜單項,如果產(chǎn)品這個時候說,菜單項我需要自己定制,多余的項不要。那么我們是不是得重寫這個控件呢?答案是肯定的
下面我們就來講解這個控件的重寫步驟
重寫一個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窗體實現(xiàn)右鍵菜單的方式多種多樣,具體可以參考我很早以前寫的Qt之自定義QLineEdit右鍵菜單
<https://www.cnblogs.com/swarmbees/p/6044361.html>
這篇文章,今天我們也使用其中的一種方式來實現(xiàn)右鍵菜單,那就是實現(xiàn)默認的contextMenuEvent函數(shù),這個函數(shù)之所以會響應,也是有一定條件的,
Qt之自定義QLineEdit右鍵菜單 <https://www.cnblogs.com/swarmbees/p/6044361.html>
這篇文章中講解的也很清楚,那就是contextMenuPolicy的值必須為默認的Qt::DefaultContextMenu屬性。
至于菜單重寫實現(xiàn)函數(shù),這里就不展示了,就是比較常規(guī)的使用QMenu增加QAction的操作
2、預覽框
大家仔細想一想,預覽框是什么時候出現(xiàn)的?他顯示的數(shù)據(jù)有什么樣的特征?接下來我們來一一做以分析
首先是出現(xiàn)時機
預覽框主要是展示我們模糊搜索后的股票數(shù)據(jù),那么結論就很明顯了。預覽的出現(xiàn)時機就是搜索內容發(fā)現(xiàn)變化的時候,并且當編輯框失去焦點時,我們應該主動關閉預覽框
編輯框內容發(fā)現(xiàn)變化時,顯示預覽框
connect(d_ptr->m_pSearchLineEdit, &QLineEdit::textChanged, this,
&SelfStocksWidget::TextChanged);
處理預覽框數(shù)據(jù),主要是使用了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消息,當我們發(fā)現(xiàn)一些影響窗口焦點的事件被觸發(fā)時,我們去判斷是否需要關閉預覽框。
具體可以參考我很早之前寫的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) { 同上... }
下面就是一個比較負責預覽數(shù)據(jù)環(huán)節(jié)了,幾千只股票,要準、要快,我們應該怎么技術選型呢?
預覽框到底怎么顯示數(shù)據(jù)的?他顯示的都是哪些數(shù)據(jù)?
Qt提供了QListView、QTableView和QTreeView這3種視圖模式,然后搭配Mode數(shù)據(jù)源,可以完成高效的大量數(shù)據(jù)展示,得知這個內容后是不是還有些小興奮呢!
乍一看,QListView和QTableView都可以作為我們的預覽框窗口,畢竟每一個Item項都是可以去重新定制的,看起來QListView還是更簡單一些,而且速度也會更快一些,但是仔細想想,好像不是這么回事,我們既然要支持股票代碼和名稱都進行搜索,那么自然不是一列數(shù)據(jù)就可以進行過濾的,方便起見我們還是使用QTableView作為我們的視圖窗口
既然視圖窗口選定了,接下來就是一堆的事件定制了
a、重寫QTableView
重寫QTableView時,我們得考慮一個很重要的事情,那就是鼠標hover事件了,鼠標移動時我們需要把當前行設置為鼠標hover狀態(tài),為了實現(xiàn)這個效果,我可謂是費勁腦汁,想出了一個辦法,寫了一個IView接口類,讓QTableView去繼承,當鼠標hover時,去調用這個接口類告知QTableView當前hover項。
class IView { public: virtual void SetMouseHover(int, bool forceChanged =
false) = 0; };
上邊的代碼是不是看著很簡單呢,就一個接口,就是當鼠標hover時告知表格當前hover項,那么什么實際通知合適呢?我這里是重寫了QStyledItemDelegate繪圖代理類,在paint函數(shù)中通知表格的,其他同學有好的辦法也可以留言。
預覽框的頭文件大致是下面這樣的,這里我只把公有的接口放出來了,其他的一些私有接口和成員變量沒有公開(放出來估計大家也不看)
///***********************************/// /// 描述:搜索預覽框
///***********************************/// 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、表格初始化
表格的數(shù)據(jù)內容在m_pListModel中存放,但是表格直接接收數(shù)據(jù)的是m_pFilterModel對象。
m_pFilterModel對象可以理解為是一個映像數(shù)據(jù)源,他沒有真正的去存儲數(shù)據(jù),他的數(shù)據(jù)都是來自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、表格填充數(shù)據(jù)
正常來說數(shù)據(jù)應該是網(wǎng)絡上拉取的,但是這里作為測試,我直接添加了5行模擬數(shù)據(jù)
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); } }
最終的數(shù)據(jù)被填充到了m_pListModel數(shù)據(jù)源中。
d、鍵盤操作
文章開始的地方也說過了,我們的搜索預覽框是支持鍵盤上下鍵來切換當前股票的,這個又是怎么完成的呢!
預覽框顯示時,編輯框一直處于鼠標輸入狀態(tài),并且具有鍵盤有限處理權限。
因此里我們是取了個巧,把編輯框的事件掛載在了他的父窗體上,當鍵盤按下時,父窗口拿到鍵盤按下事件,首先轉發(fā)給了預覽框,讓預覽框去換一個最新的當前股票,并選中。
代碼如下所示,是不是也很簡單。
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、過濾
前邊也講述過了,我們表格數(shù)據(jù)都是來自m_pFilterModel對象的,數(shù)據(jù)源中的數(shù)據(jù)m_pListModel基本沒有發(fā)生變化過,及時我們現(xiàn)實的內容變化了,那也僅僅是m_pFilterModel對象過濾到的內容發(fā)生了變化。
過濾接口Qt已經(jīng)幫我們寫好了,我們只需要實現(xiàn)其中的過濾方式即可。
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("搜索股票代碼/名稱"));
由于篇幅原因,本篇文章就只先說搜索編輯框吧,本來想把自選股列表頁一起加上,不過覺著內容太多,也不利于大家吸收,下一篇文章補上吧。。。
寫的手都酸了,其他內容自行腦補吧。。。
四、相關文章
財聯(lián)社-產(chǎn)品展示 <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>
高仿富途牛牛-組件化(四)-優(yōu)秀的時鐘 <https://www.cnblogs.com/swarmbees/p/11055495.html>
高仿富途牛牛-組件化(五)-如何去管理炒雞多的小窗口 <https://www.cnblogs.com/swarmbees/p/11062824.html>
高仿富途牛牛-組件化(六)-炒雞牛逼的布局記憶功能(序列化和反序列化)
<https://www.cnblogs.com/swarmbees/p/11094667.html>
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支持。您的支持是我最大的動力,謝謝?。?!
很重要--轉載聲明
*
本站文章無特別說明,皆為原創(chuàng),版權所有,轉載時請用鏈接的方式,給出原文出處。同時寫上原作者:朝十晚八
<https://www.cnblogs.com/swarmbees/> or Twowords
<https://www.jianshu.com/u/7673f8cfb4e6>
*
如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利于轉載者的目的。
熱門工具 換一換