高仿富途牛牛-元件化(五)-如何去管理炒雞多的小視窗

朝十晚八發表於2019-06-21

一、概述

程式碼寫的久了,什麼功能都想搞點兒模式。不知道是不是隻有我一個人這麼想的,做功能時不在是隻為了完成功能,而是在完成功能的同時會去考慮可擴充套件性怎麼樣?哪塊兒的程式碼可以複用?又或者哪裡需要留更多的介面?這兩個類之間是不是可以加一個協作類?總之各種想法都出來了。

假設說有這樣一種場景,我們通過一個入口介面,上邊有很多種按鈕,當我們點選上邊的按鈕時,程式就會生成一個小視窗。這些小視窗他們的外在行為基本一致,只是視窗裡邊展示的內容各不一樣。

總結一下應該是這樣的

  • 窗體可以通過標題欄進行拖拽
  • 滑鼠移動到邊界可以進行放大縮小
  • 滑鼠選中時邊框高量顯示
  • 標題欄具有名稱、關閉和各種各樣的選單
  • 不同功能窗體的選單內容不一樣,展示風格也可能完全不同
  • 標題欄和中心窗體也有互動
  • 連續點選一個按鈕時,生成同型別的窗體不能完全覆蓋

用過富途牛牛的同學應該對這個功能比較清楚一點,富途中的交易模組也是如此,工具箱裡支援各種小窗體生成,並且都有各自豐富的內容。

這麼多繁雜的窗體,富途是怎麼管理的呢?

本篇文章就來仔細挖掘下這個功能,我們也來實現一個小視窗管理功能。

二、效果展示

如效果圖所示,我們之前所說的幾個功能點都已經有了,並且在配色上比之前的demo程式也有改善,當然要投入使用還是需要設計師同學經過專業的指點。

高仿富途牛牛-元件化(五)-如何去管理炒雞多的小視窗

三、功能類

為了實現這個視窗管理功能,思考的頭都快要炸掉了,不是因為功能有多複雜,而是作為一個開發人員,我想的太多了,總是會出現一種幻覺,產品經理可能會提出這樣那樣的新需求,測試也可能會給你報一個莫名其妙的問題。

最最關鍵的還是我們要對自己寫的程式碼有一個基本要求。結構必須合理、減少冗餘和耦合,讓其他人閱讀程式碼時使用最小的成本,

為了做這個視窗管理功能,我也是想了好久,總是想著設計的更合理一些,別人在使用時就會更簡單一些,使用成本也會更小。

為此,我引入了好幾個關鍵類,都是輔助管理視窗的,下面先來一張類圖,這是我使用starUML畫的,不是特別規範,主要是為了說明這些類的功能,以及他們之間的一個關係,圖上都用註釋寫出了每個類的大概作用。

高仿富途牛牛-元件化(五)-如何去管理炒雞多的小視窗

從圖上也可以看出來,類中就是一個簡單的名字,我沒有把類的介面都貼上去,一個是時間問題,另一個我也覺著不是特別有必要,因為我本身對這個畫圖工具使用就不是特別深入,所以這裡就只提供了簡單的版本。大家湊合看吧,對理解設計思路還是有很大的幫助。

本篇文章主要講的是小視窗管理,但是這裡主要是通過講述迷你報價小視窗來說明怎麼去管理炒雞多的小視窗

從上邊圖中可知,關鍵類有以下這些

  1. ISmall:業務視窗介面類,負責和SmallWidget進行通訊
  2. MiniPriceSmall:迷你報價小視窗的中心視窗
  3. SmallWidget:小視窗模板類,這是所有小視窗的外殼類,包含了標題欄。迷你報價視窗就是構造了這麼一個類,然後設定了一箇中心視窗MiniPriceSmall。其他的業務視窗都是使用同樣的模式
  4. ViewModePanel:迷你報價工具選單按下時,彈出的檢視列表,主要是為了修改顯示模式。仔細觀察效果展示中的gif圖,選單'M'被點選時,彈出了顯示模式視窗,隨後在顯示模式視窗任意選項上單擊後,迷你報價視窗上都出現了一行歡迎文字--感謝您的使用,您選擇了模式:X
  5. SmallContext:小視窗管理上下文類,主要負責處理我們定製好的外殼類SmallWidget和業務視窗MiniPriceSmall(其他更多業務視窗)之間的訊息通訊
  6. SmallGroup:小視窗組管理者,主要負責磁力吸附這個功能,詳情參看高仿富途牛牛-元件化(二)-磁力吸附這篇文章
  7. SmallFactory:小視窗工廠,負責構造各式各樣的小視窗
  8. ToolBoxDialog:工具箱的實現沒有專門去寫文章講解,需要了解的可以參看高仿富途牛牛-元件化(三)-介面美化這篇文章,第三小節對工具箱做了簡單的講解

四、設計上的考慮

首先,我自己做的是一個元件化的功能,我需要考慮的東西也是更多的,不僅僅要把功能完成,更多的是要去考慮,別人用的時候代價怎麼可以更小?可以複用的程式碼,我儘量都提取出來,寫成共性的東西,其他開發在做相應業務模組時,只需要考慮真的和他們有直接關係的地方。

這裡我們就拿迷你報價這個功能來說事,看看怎麼去設計是合理的,或者說目前看起來是合理的,可能後期根據需求,我們的結構或許會進行一定的調整。

1、功能拆分

單獨拿出一個迷你報價功能來說,可能100%的人都會說很簡單,這有什麼難的,我剛參加工作那會兒百度百度也都能做出來。可是往往把一些簡單的東西,抽象抽來,讓程式變得更簡單這件事情本身就是一件比較難的事情。

迷你報價這個視窗總的來說可以分為上下兩部分:上邊標題欄和下面內容展示區域

標題欄

標題欄是每一個小視窗都有的,因此這裡我們肯定是要單獨提出來作為一個公有的東西,當其他開發做自選股、小時鐘、短線精靈這些小視窗時,標題欄的移動事件他們就根本不需要考慮了。

細心的同學可能會發現了,標題欄上還有一些不認識的按鈕,這些按鈕時幹什麼的呢?

雖然標題欄已經被公有化,但是標題欄上的按鈕可不是這麼想的,他們的行為要根據底部內容視窗的型別決定,因此這裡就有了一定的變化。

為了適應這個變化,我自己定義了一個介面類ISmall,主要是讓內容視窗進行繼承的,也就是說內容視窗需要重寫這個介面類的一些介面,來完成執行時的一些設定。

比如說,迷你報價標題欄按鈕顯示有哪些?按鈕顯示時都有哪些功能?按鈕點選時該有什麼樣的相應?按鈕點選後彈出的皮膚點選被點選了,內容視窗做何處理等等。

內容展示

內容展示視窗就比較簡單了,這裡有2個選擇,繼承ISmall介面類或者不繼承

  1. 繼承-意味著內容視窗需要和外殼視窗進行訊息通訊
  2. 不繼承-我們就只是展示資料的,不需要進行訊息通訊

事實上大多數的內容視窗都是需要繼承ISmall這個介面類的,純展示而沒有互動的功能幾乎沒有。

2、關鍵類

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和內容窗體類之間的訊息傳遞。

  • 當SmallWidget想要給內容窗體傳遞訊息時,會通過ISmall介面類進行傳送指令,因為內容窗體是繼承了這個ISmall介面類的
  • 當內容窗體想給外殼傳送訊息時,是通過ISmall的介面呼叫來到達目的的,嚴格來說其實是外殼窗體主動跟內容窗體要的訊息內容。

當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);
    }
}

五、相關文章

高仿富途牛牛-元件化(一)-支援頁籤拖拽、增刪、小工具

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

高仿富途牛牛-元件化(三)-介面美化

高仿富途牛牛-元件化(四)-優秀的時鐘




很重要--轉載宣告

  1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords

  2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。


相關文章