代碼寫的久了,什麼功能都想搞點兒模式。不知道是否是隻有我一我的這麼想的,作功能時不在是隻爲了完成功能,而是在完成功能的同時會去考慮可擴展性怎麼樣?哪塊兒的代碼能夠複用?又或者哪裏須要留更多的接口?這兩個類之間是否是能夠加一個協做類?總之各類想法都出來了。app
假設說有這樣一種場景,咱們經過一個入口界面,上邊有不少種按鈕,當咱們點擊上邊的按鈕時,程序就會生成一個小窗口。這些小窗口他們的外在行爲基本一致,只是窗口裏邊展現的內容各不同。工具
總結一下應該是這樣的組件化
用過富途牛牛的同窗應該對這個功能比較清楚一點,富途中的交易模塊也是如此,工具箱裏支持各類小窗體生成,而且都有各自豐富的內容。佈局
這麼多繁雜的窗體,富途是怎麼管理的呢?測試
本篇文章就來仔細挖掘下這個功能,咱們也來實現一個小窗口管理功能。this
如效果圖所示,咱們以前所說的幾個功能點都已經有了,而且在配色上比以前的demo程序也有改善,固然要投入使用仍是須要設計師同窗通過專業的指點。spa
爲了實現這個窗口管理功能,思考的頭都快要炸掉了,不是由於功能有多複雜,而是做爲一個開發人員,我想的太多了,老是會出現一種幻覺,產品經理可能會提出這樣那樣的新需求,測試也可能會給你報一個莫名其妙的問題。設計
最最關鍵的仍是咱們要對本身寫的代碼有一個基本要求。結構必須合理、減小冗餘和耦合,讓其餘人閱讀代碼時使用最小的成本,code
爲了作這個窗口管理功能,我也是想了很久,老是想着設計的更合理一些,別人在使用時就會更簡單一些,使用成本也會更小。
爲此,我引入了好幾個關鍵類,都是輔助管理窗口的,下面先來一張類圖,這是我使用starUML畫的,不是特別規範,主要是爲了說明這些類的功能,以及他們之間的一個關係,圖上都用註釋寫出了每一個類的大概做用。
從圖上也能夠看出來,類中就是一個簡單的名字,我沒有把類的接口都貼上去,一個是時間問題,另外一個我也覺着不是特別有必要,由於我自己對這個畫圖工具使用就不是特別深刻,因此這裏就只提供了簡單的版本。你們湊合看吧,對理解設計思路仍是有很大的幫助。
本篇文章主要講的是小窗口管理,可是這裏主要是經過講述迷你報價
小窗口來講明怎麼去管理炒雞多的小窗口
從上邊圖中可知,關鍵類有如下這些
迷你報價
工具菜單按下時,彈出的視圖列表,主要是爲了修改顯示模式。仔細觀察效果展現中的gif圖,菜單'M'被點擊時,彈出了顯示模式
窗口,隨後在顯示模式窗口任意選項上單擊後,迷你報價
窗口上都出現了一行歡迎文本--感謝您的使用,您選擇了模式:X小窗口
管理上下文類,主要負責處理咱們定製好的外殼類SmallWidget和業務窗口MiniPriceSmall(其餘更多業務窗口)之間的消息通訊小窗口
工廠,負責構造各式各樣的小窗口首先,我本身作的是一個組件化的功能,我須要考慮的東西也是更多的,不只僅要把功能完成,更多的是要去考慮,別人用的時候代價怎麼能夠更小?能夠複用的代碼,我儘可能都提取出來,寫成共性的東西,其餘開發在作相應業務模塊時,只須要考慮真的和他們有直接關係的地方。
這裏咱們就拿迷你報價
這個功能來講事,看看怎麼去設計是合理的,或者說目前看起來是合理的,可能後期根據需求,咱們的結構或許會進行必定的調整。
單獨拿出一個迷你報價
功能來講,可能100%的人都會說很簡單,這有什麼難的,我剛參加工做那會兒百度百度也都能作出來。但是每每把一些簡單的東西,抽象抽來,讓程序變得更簡單這件事情自己就是一件比較難的事情。
迷你報價
這個窗口總的來講能夠分爲上下兩部分:上邊標題欄和下面內容展現區域
標題欄
標題欄是每個小窗口都有的,所以這裏咱們確定是要單獨提出來做爲一個公有的東西,當其餘開發作自選股、小時鐘、短線精靈這些小窗口時,標題欄的移動事件他們就根本不須要考慮了。
細心的同窗可能會發現了,標題欄上還有一些不認識的按鈕,這些按鈕時幹什麼的呢?
雖然標題欄已經被公有化,可是標題欄上的按鈕可不是這麼想的,他們的行爲要根據底部內容窗口的類型決定,所以這裏就有了必定的變化。
爲了適應這個變化,我本身定義了一個接口類ISmall,主要是讓內容窗口進行繼承的,也就是說內容窗口須要重寫這個接口類的一些接口,來完成運行時的一些設定。
好比說,迷你報價
標題欄按鈕顯示有哪些?按鈕顯示時都有哪些功能?按鈕點擊時該有什麼樣的相應?按鈕點擊後彈出的面板點擊被點擊了,內容窗口作何處理等等。
內容展現
內容展現窗口就比較簡單了,這裏有2個選擇,繼承ISmall接口類或者不繼承
事實上大多數的內容窗口都是須要繼承ISmall這個接口類的,純展現而沒有交互的功能幾乎沒有。
ISmall
ISmall接口類是毫無疑問的關鍵類,它是一個純虛類,繼承它的類都必須實現它的全部接口,以下代碼所示
/** * 簡介: 小窗口上的回調事件,須要通知中心窗體 */ struct ISmallCall { virtual ViewMode ViewType(SmallToolType) = 0; virtual void GetItems(QVector<DataItem> &) = 0; virtual QSize GetSize() const = 0; virtual void Notify(const QString &) = 0; };
GetSize()、GetItems()這些接口都是獲取數據接口,外殼類SmallWidget,會根據當前中心窗口返回的定製數據去初始化本身。
好比說效果圖中展現的顯示模式
面板,這個面板中的數據內容,就是迷你報價類MiniPriceSmall重寫了getItems接口以後,給外殼類提供的數據,而且制定了事件觸發時使用的顯示模式爲視圖。
迷你報價
類重寫的數據準備接口以下
void MiniPriceSmall::GetItems(QVector<DataItem> & items) { for (int i = 0; i < 9; ++i) { DataItem item; item.name = 'A' + i; item.type = item.name; item.img = "./image/mainWindow/tquant-btn_normal.png"; items.append(item); } }
目前接口類中的接口還不是特別完善,根據後續業務功能的變化,接口確定還會繼續增多。
MiniPriceSmall
這個類是迷你報價的數據展現類,他繼承自QWidget窗口類和ISmall接口類,界面搭建這裏我就不說了,兩個緣由:其一是這裏的界面自己就是空的,其二也是比較關鍵的,中心窗口的內容是根據業務類型決定的,後期根據不一樣開發負責的功能本身去搭建。一般狀況下這裏都是別人已經封裝好了的控件,有必要的時候在使用一個簡單外殼類包一層便可。
除過界面以外,就是ISmall的接口實現類了,一部分接口是給外部SmallWidget提供數據的,而另外一部分接口就是響應外部事件。
目前來講,響應外部接口事件就只有一個接口Notify,比較重要,消息響應時根據參數來區分消息類型
SmallWidget
小窗口類,爲全部小窗口的外殼類,提供了定製化的標題欄和邊界縮放。功能實現不難,須要的可自行百度。
ViewModePanel
顯示模式
面板,主要是一些小窗口能夠根據該面板的選項能夠進行顯示模式的重組,而且能夠切換窗體的大小等等。效果展現中咱們只是修改了顯示的問題,不過實現起來思路是同樣的,最主要的是內容窗體已經接收到咱們傳遞的消息。
SmallContext
這是一個比較關鍵的類,當時想了好久,到底要不要加。
這個類主要負責的功能就是同步小窗體外殼類SmallWidget和內容窗體類之間的消息傳遞。
當SunPanel面板經過工廠類SmallFactory進行構造小窗口時,會把SmallWidget和構造好的內容窗體進行註冊,並開始接收SmallWidget發送過來的事件請求。
void SmallContext::RegisterSmallWidget(SmallWidget * widget, ISmallCall * call) { m_itemMap[widget] = call; connect(widget, &SmallWidget::ClickedButton, this, &SmallContext::HandleEvent); }
當工具欄點擊了'M'按鈕時,SMallWidget就會給SmallContext發送事件請求,並附帶相關參數,這裏SmallWidget發送了顯示模式
選擇事件,下面是SmallContext的處理方式
void SmallContext::HandleEvent(int type, const QPoint & globalPos) { if (SmallWidget * widget = dynamic_cast<SmallWidget *>(sender())) { if (m_itemMap.contains(widget)) { if (ISmallCall * call = m_itemMap[widget]) { ViewMode view = call->ViewType(SmallToolType(type)); if (view == VM_VIEW) { ShowView(widget, call, globalPos); } } } } }
SmallFactory
最後就是工廠類了,一看名字就知道,他是一個工廠,專門負責生產小窗口的,對工具箱來講,他只知道給SubPanel發送顯示一個小窗口這個指令,可是SubPanel本身其實也不會去真正的構造一個小窗口,緣由其實很簡單,單個的構造一個小窗體其實沒問題,可是當小窗體種類多起來時,須要維護的消息就隨之增多。
重構以後的代碼看起來像這樣,當SubPanel類須要一個小窗口時,他只須要這麼幹就行
SmallWidget * smallWidget = m_pSmallFactory->CreateWidget(type, subContent); subContent->AddSmallWidget(smallWidget); smallWidget->move(m_pSmallFactory->GetPos(type, subContent)); smallWidget->show();
調用工程的CreateWidget方法,構造一個小窗口,而後加入到本身的佈局中
這裏還有一個比較關鍵的問題,就是窗口初始位置管理,咱們在工廠裏維護了一個位置偏移量,當連續請求同一類型的小窗口時,咱們會給這個偏移量增長一個固定值,使得連續請求時,同一類型的小窗口獲取到的位置是有規律的變化
void SmallFactory::UpdateOffset(bool change, QWidget * parent) { if (change) { m_offset += QPoint(13, 13); } else { m_offset = QPoint(0, 0); } QRect r = parent->rect(); if ( m_offset.y() > r.height() / 4 || m_offset.x() > r.width() / 4) { m_offset *= -1; m_offset.setX(m_offset.x() - 15); } }
很重要--轉載聲明