很久沒有作業務相關的UI功能了,比較炫酷的交互效果也寫的少了,最近花了2天時間寫了一個簡易的高仿富途牛牛組件化的功能,固然了這只是一個初步的效果,並且沒有作貼圖、美化等工做,可是基本的功能已經有了。本篇文章只是做爲組件化的一個開始,後續還會陸續引入更多關於組件化的介紹,相信功能也會愈來愈豐富。除此以外,富途牛牛的一些其餘高級功能也會陸續引入,不乏有k線、分時、五日、指標、自選這樣的複雜功能。程序員
自選和k線這些東西我已經有成型的效果了,能夠參考效果展現,而且已經被我封裝成控件,來需求便可定製函數
說的再多,也不如貼一張效果圖來的實在,下邊的圖展現了附圖牛牛組件化的一個簡單demo,雖然沒有數據,可是該有的基礎交互流程已經都有了。用過富途牛牛的人應該都知道。工具
可能有一些人不是特別瞭解這個功能,這裏我簡單的說一下:組件化
寫這個demo,我基本上用了2天的時間,並且寫的過程當中,代碼結構也有一些變更,達到如今這個效果佈局
當時只考慮了一個主組件化界面TemplateLayout的實現,把這個頁面總共分了這麼幾個部門:主Frame、頂部工具條、頁籤窗體(支持拖拽)、中心內容窗體、工具箱、小窗體subWindow。優化
其中值得注意的有這麼幾個地方this
除過上述兩個問題之外,第一階段暫時沒有其餘比較棘手的問題,簡單的頁面編寫我這裏就不作過多介紹,主要分析下上邊兩個問題的處理過程,也是比較重要的處理過程spa
這個問題,說實話,當時真是想了好幾個小時,花費了比較長的事件。指針
自開始的想法就是重寫父窗口的moveEvent函數,當窗體移動時,我把移動的偏移量告知工具箱,這樣工具箱就能夠進行聯動了,後來進行實現,發現工具箱移動的老是很慢,哎!moveEvent確定是優化了一些調用,真是坑爹啊
想了一下子了,到了吃飯時間了,算了,仍是換換腦子,或許吃完飯回來就有方案了,哈哈哈,事實上還就是這樣,鎖屏去吃飯啦!!!
程序員便是閒不下來,去吃飯的路上又開始想了,不過此次我想到了方案,咱們以前都是考慮怎麼傳遞變化量到工具箱,那麼反過來咱們若是隻告訴工具箱主窗口位置發生變化了呢!!!,讓工具箱本身去移動,只要保證和以前的主窗口的相對位置不變便可。你說什麼?相對位置不變!,你們夥是否是也想到了呢,對了,就是這麼簡單了,咱們只須要在工具箱本身移動的時候更新這個相對位置便可,當主窗口移動時,咱們仍然是要保證相對位置不變。
就是這麼簡單!就是這麼任性!
void ToolBoxDialog::LinkMove() { if (QWidget * parent = parentWidget()) { QPoint globalPos = parent->mapToGlobal(m_relativePos); move(globalPos); } }
在作這個組件化拖拽以前,我也簡單的作過一些拖拽功能,所以這裏作這個東西也不困難。首先就是咱們的技術方案選擇了,Qt他爲咱們提供了一些拖拽上的功能實現,可是定製化不強,一旦咱們想作的更美觀,交互上更豐富一些,使用原有的操做邏輯就會遇到一些困難,所以這裏咱們徹底本身實現一個拖拽的效果
看這裏-咱們經過過濾鼠標的3種事件,來模擬拖拽。哪三種事件呢?鼠標按下、鼠標移動和鼠標擡起
下面咱們分析下具體的事件處理流程,既然咱們想要過濾這三種事件,那麼咱們就必需要找一個過濾對象,來過濾全部的頁籤按鈕事件,這裏很天然的我就想到了他們的父窗口DragTabWidget,瞭解Qt的事件循環的人應該都知道接下來我要幹什麼了,2個步驟:
代碼可能會像下面這樣
//按鈕把本身的事件先讓父類處理 this這裏表示父窗口 tabButton->installEventFilter(this); //父類重寫eventFilter函數 bool DragTabWidget::eventFilter(QObject * watched, QEvent * event) { TabButton * button = dynamic_cast<TabButton *>(watched); if (button && m_buttonMaps.contains(button->GetID())) { } return __super::eventFilter(watched, event); }
有了上面這個步驟以後,咱們接下來只須要專心的處理鼠標事件便可
一、鼠標按下
鼠標按下時,咱們這裏須要記錄鼠標按下的位置,方便後續移動咱們的頁籤。
if (event->type() == QEvent::MouseButtonPress) { QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event); m_pressGlobalPos = button->mapToGlobal(mouseEvent->pos()); m_pressButtonPos = button->pos(); }
二、鼠標移動
當鼠標被按下時,而且進行了移動,這個時候的處理過程就比較複雜了,這裏咱們涉及到2種效果
首先就是咱們的頁簽在自身的工具欄種移動,這樣的狀況比較好處理一些,咱們只須要把根據當前的鼠標位置移動咱們的佔用控件
和被拖拽的頁籤,
佔位控件:就是效果展現圖中,咱們看到的空白區域,主要是告訴使用者,當鼠標擡起時,控件將會在被移動到這個地方
這裏邊有一個小技巧:就是咱們拖拽頁籤的時候,咱們構造一個被拖拽頁籤的圖片,跟隨鼠標移動便可,這樣省時、省力又省心,最主要仍是bug少。
其次呢當咱們的鼠標移除工具欄時,咱們的頁籤也是要跟着被拖出原有的組件化頁面的,也就是原有的TemplateLayout。移除的過程可能像下面這樣
if (event->type() == QEvent::MouseMove) { if (IsMoveTab()) { //移動當前選中按鈕 MoveIn(button); MoveSnapSeat(button); } else { //把當前選中按鈕拖出佈局 MoveOut(button); } }
三、鼠標擡起
當咱們移動頁籤時,無非就三種狀況
處理代碼以下,這是第一個階段的代碼,有點兒問題,不支持第三種狀況。
仔細回想下咱們把按鈕的事件都安裝給了本身的父窗口DragTabWidget,可想而知不一樣DragTabWidget之間可能也不能進行交互了,由於他們走的都是本身的事件循環、和處理邏輯。
if (event->type() == QEvent::MouseButtonRelease) { if (IsMoveTab()) { //移動拖拽的按鈕到佔位位置 MoveDragButton(); } else { //添加新的獨立窗口 //button 添加到新的TemplateLayout佈局中 emit ButtonMoveOutCompleted(button->GetID()); //原有佈局中 若是隻剩下一個按鈕 關閉按鈕將不讓使用 if (m_buttonMaps.size() == 1) { m_buttonMaps.values().first()->SetCloseEanble(false); } } }
這不第一個版本有了解決不了的問題,這裏我才進行了適當的重構,引入第二個版本。
第二個版本的修改主要是,按鈕的事件不在交給父窗口去優先處理了,而是交給一個第三者,這個第三者接收了全部的按鈕事件,包括不一樣DragTabWidget窗體中的按鈕,這樣事件就達到了統一。
爲了解決不一樣工具欄之間能夠相互拖拽頁籤,這裏咱們引入了第三方類TabMoveManager,這個類專門負責處理全部的按鈕事件,處理過程和第一階段類型,這裏我把關鍵部分的代碼貼出來
bool TabMoveManager::eventFilter(QObject * watched, QEvent * event) { TabButton * button = dynamic_cast<TabButton *>(watched); if (button && m_tabButtonMap.contains(button->GetID())) { if (event->type() == QEvent::MouseButtonPress) { QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event); m_pressGlobalPos = mouseEvent->globalPos(); m_pressButtonPos = button->mapToParent(QPoint(0, 0)); //記錄從當前被拖拽的tab和模板 ResolveDragTab(button); } else if (event->type() == QEvent::MouseMove) { if (IsMoveTab()) { //移動當前選中按鈕 MoveIn(button); MoveSnapSeat(button); } else { //把當前選中按鈕拖出佈局 MoveOut(button); } } else if (event->type() == QEvent::MouseButtonRelease) { if (IsMoveTab()) { //移動拖拽的按鈕到候選模板佔位位置 MoveDragButton(); } else //空白處 釋放鼠標 須要新構造一個TemplateLayout { emit NeedNewTemplateLayout(button->GetID()); } //清理原有模板佈局 if (m_pDragTemplate != m_pCandidateTemplate && m_pCandidateTemplate != nullptr) { m_pDragTemplate->CleanUP(button->GetID()); } //原有佈局中 若是隻剩下一個按鈕 關閉按鈕將不讓使用 if (m_pDragTabWidget->GetButtonCount() == 1) { m_pDragTabWidget->GetToolButton()->SetCloseEanble(false); } ··· } } return __super::eventFilter(watched, event); }
除了處理拖拽事件之外,他還起到了一個容器的做用,全部的subPanel我都註冊到這個類中,其餘的地方均可以經過這個類來獲取指定Panel
void RegisterSubPanel(const QString &, SubContentWidget *); void UnregisterSybPanel(const QString &); SubContentWidget * GetSubPanel(const QString &) const;
前兩個階段已經把主要的功能基本都實現了,可是代碼結構上,包括一些類負責的功能上仍是有一些問題。
第三階段主要對代碼進行了細微的重構,工程代碼結構下圖所示
下面是工程中包含的全部類:
以上代碼基本把視線組件化功能的主要邏輯講完了,具體的代碼量比較大,並且是一個不成形的demo,暫時就不往外放了。
很重要--轉載聲明