高仿富途牛牛-組件化(六)-炒雞牛逼的佈局記憶功能(序列化和反序列化)

1、佈局記憶

一款優秀的軟件,不只僅要求功能強健、穩定性高和可靠的精準率,每每不少時候咱們都須要去關注用戶界面是否友好,用戶操做是否順暢,軟件跨機器使用到底咋樣。windows

提及到怎麼讓用戶交互友好,這就是用戶體驗和視覺設計師的主場啦。這裏我就很少說了,今天主要是想說明一個問題--佈局記憶功能app

如今客戶端軟件各式各樣,種類多了去了,可是不知道你們有沒有注意到有這麼一些交互上的細節。less

  1. 使用過QQ的同窗應該都比較清楚。咱們在QQ使用時,除過第一次登陸QQ軟件,其他時間段登陸QQ時,QQ的初始位置每每會是上一次退出時的位置函數

  2. windows資源管理器咱們你們應該都常常在使用,不知道你們有沒有仔細觀察。咱們修改了資源管理器窗口大小後,再次打開資源管理器窗口時,新的窗口大小和咱們以前修改後的窗口大小同樣。工具

  3. firefox郵件客戶端,你們都用過吧,也是會記憶窗口最後組件化

  4. 還有一些工具軟件,好比說PicPick,選擇的使用模式會一直記錄佈局

  5. QQ飛車是一款騰訊出的客戶端遊戲,他支持多種顯示模式,設置一次後,會一直生效,直到咱們再次設置爲止。測試

以上是我隨便寫的幾個數據記憶的事例,相信你們都不陌生。除過這些簡單的數據持久化之外,其實還有不少其餘的事例,這裏就不一一例舉了。this

今天咱們主要是想給你們展現下咱們負責窗口布局是怎麼進行佈局記憶的。

2、效果展現

窗口布局記憶如效果圖所示。

當咱們經過主窗口關閉了軟件時,程序會自動把佈局信息序列化成字符串,而後進行保存。

再次啓動軟件時,咱們首先會去加載序列化的佈局信息,而後進行解析佈局信息,並構造咱們的窗口,這個工程稱之爲反序列化。

3、重點回顧

一、窗口管理

以前我已經寫了好幾天文章都是講組件化相關的東西,其中有一篇文章高仿富途牛牛-組件化(五)-如何去管理炒雞多的小窗口主要是講解怎麼去管理過多的小窗口,主要是把建立的過程進行了封裝,讓外界使用起來更加的接口化。

本篇文章主要是講述佈局怎麼去記憶?記憶後又是怎麼去恢復?關於窗口建立和消息通訊這裏我就不在去講解了。感興趣的同窗能夠翻看以前的文章,由於個人這個demo是在一直的維護,更新過程當中,所以講到這篇文章的時候,以前的一些主題中的方式、方法可能已經發現了變化,若是有問題的歡迎留言。

二、頁籤TabButton

一個組件窗口中同時只容許一個頁籤被選中,選中另外一個頁籤時,其餘的頁籤都會被重置爲非選中狀態。

TabButton是一個複雜的小窗口,支持同一個工具欄內拖拽,也支持多個工具欄之間拖拽。

三、子面板SubPanel

每個組件窗口都包含有多個頁籤和多個SubPanel,其中SubPanel和頁籤時一對一的關係。

咱們切換頁籤的時候,SubPanel也會跟隨者切換,而每個SubPanel上都包含有不一樣的小窗口,這些小窗口都是由工具箱進行建立的。

工具箱這裏就不在多說了,看展現的效果圖,上邊就有一個工具箱窗口,當咱們點擊其上的工具按鈕時,就會在當前的SubPanel上建立一個對應的小窗口。

4、佈局記憶內容

首先我一直強調的是高仿富途牛牛-組件化,所以這裏記憶的內容我也是根據福牛的交互行爲來記憶的,可能記憶的內容有下面這些,但也可能更多。

  1. 組件窗口個數
  2. 組件窗口位置和大小,包括層次關係
  3. 組件窗口關聯的工具箱是否顯示和其位置
  4. 工具欄的狀態,包括工具按鈕狀態,頁籤個數、順序、名稱和當前選中項
  5. 子面板上的小窗口
  6. 小窗口的層次關係、位置和大小

以上內容就是咱們序列化時會存儲的信息,但又不只限於這些。

5、佈局信息序列化

要讓佈局信息持久化,那麼佈局信息必然要被咱們存儲到硬盤上,所以電腦上的內存信息系統重啓後就會消息。

好,那麼接下來就是考慮把佈局信息寫入硬盤,這個時候咱們就得找個合適的實際寫入時機,目前我寫入的時機是在關閉軟件的時候,可是這裏不建議你們也這麼搞,所以這回致使關節關閉有延遲,當咱們有大量的數據須要寫入的時,可能會影響用戶體驗。

關於寫入時機選擇,不是本篇文章討論的主要內容,感興趣的能夠本身去研究。

數據寫入時須要注意,給讀取數據時寫入一些標誌,不然讀取數據時若是包含一些循環,則不知道循環應該何時結束。

一、流程

  1. 主組件窗口關閉時,開始序列化佈局信息
  2. 首先寫入組件窗口個數,方便後期讀數據
  3. 工具欄按鈕狀態寫入
  4. 工具欄頁籤個數寫入
  5. 工具欄頁籤循環寫入
  6. 工具欄頁籤選中項index寫入
  7. 工具箱大小和位置寫入
  8. 循環子面板SubPanel
  9. 寫入SubPanel中全部小窗體信息
  10. 小窗體信息吸入:標題欄名稱、所屬組、窗口大小、位置等

二、主流程寫入

窗口信息使用二進制的方式寫入文件,因爲如今是demo階段,所以這裏爲了方便測試,隨手寫了一個文件路徑。

void TemplateLayout::SaveMainLayout()
{
    Q_ASSERT(m_pToolBar);

    QString path = "d:\\main.ttlayout";
    QFile file(path);
    if (file.open(QIODevice::WriteOnly | QIODevice::Truncate))
    {
        QDataStream in(&file);

        int count = templates.size();
        in << count;//存儲組件窗口個數

        //從最下面一級的窗體開始序列化
        for (int i = templates.size() - 1; i >= 0; --i)
        {
            TemplateLayout * widget = templates.at(i);
            widget->m_pToolBar->SaveLayout(in);
            in << QString("toolBar");//toolBar結束標誌

            widget->SaveToolBox(in);

            widget->m_pPanel->SaveLayout(in);
            in << QString("panels");//panel結束標誌
        }
    }
}

從最下面一級的組件窗體開始序列化,主要建立的時候,就是自下而上建立,窗口的z值就不存在問題。

序列化代碼主體流程看起來就像上邊這樣,咱們使用QDataStream來進行二進制信息的寫入。

在整個寫入的過程當中,咱們使用了一個QDataStream對象,並把文件做爲他的輸入設備。

這裏須要注意一點,咱們不能在函數調用過程當中使用多個QDataStream,把每一個窗口的佈局信息都存儲到一個QByteArray中去。由於QDataStream內部在存儲數據時,會在末尾加上4個字節的結束符,這樣咱們在多層嵌套寫數據時,雖然沒有問題,可是讀數據時就會出現問題,這個問題我也是查了很久就經過調試代碼發現的

三、標籤頁寫入

前邊咱們也說了,咱們整個的寫入過程都使用了一個QDataStream,內部窗口的寫入都是使用了最外層的QDataStream,這裏從參數咱們也能夠看得出來。

標籤頁寫入方式和以前的模式差很少,主要是存儲的數據不一樣,這裏主要存放了3種信息:標籤頁數量、標籤頁名稱和選中項下標

void DragTabWidget::SaveLayout(QDataStream & in) const
{
    Q_ASSERT(m_pTabLayout);
    in <<  m_buttonMaps.size();//記錄button個數
    int selectedIndex = 0;
    int buttonIndex = 0;
    for (int index = 0; index < m_pTabLayout->count(); ++index)
    {
        if (TabButton * desButton = dynamic_cast<TabButton *>(m_pTabLayout->itemAt(index)->widget()))
        {
            in << desButton->Text();
            if (desButton->IsSelected())
            {
                selectedIndex = buttonIndex;
            }
            ++buttonIndex;
        }
    }
    in << selectedIndex;//記錄選中按鈕
}

四、小窗口寫入

小窗口寫入時,首先寫入了的是標題欄的信息,而後在寫入窗口自身的位置、大小和窗口類型

這裏須要重點提下窗口類型,這個信息很重要。當咱們反序列化的時候,須要根據這個類型來進行建立窗口

void SmallWidget::SaveLayout(QDataStream & in) const
{
    QPoint pos = this->pos();//保存位置
    QSize size = this->size();//保存大小
    SubWindowNormalType type = GetSmallType();//保存窗口類型

    m_pTitle->SaveLayout(in);
    
    in << pos;
    in << size;
    in << (int)type;
}

五、其餘

序列化的整個過程基本都是同樣的套路,主要就是使用QDataStream對象把佈局信息以二級制的形式寫入到硬盤文件中。

其餘的佈局信息寫入方式大豆差很少,這裏就不一一列出。

6、佈局信息反序列化

說完序列化後,接下來就是咱們的反序列化的過程了。

反序列化就是序列化的相反過程,主要是咱們須要寫入正確的信息,而後按寫入時的順序進行讀取佈局信息便可

一、流程

  1. 啓動程序時,打開佈局文件
  2. 讀出組件窗口個數
  3. 讀取工具欄按鈕狀態
  4. 初始化頁籤,這個時候SubPanel也會被初始化
  5. 初始化頁籤選中項
  6. 讀取工具箱大小和位置
  7. 初始化各子面板上的小窗口
  8. 循環第三步

二、反序列化主流程

反序列化就是序列化的逆序,不過這裏須要注意的一個地方就是,咱們序列化的時候,主窗口時最後保存的,所以反序列化的時候,主窗口也是最後才進行初始化的。

注意代碼中的if (i == count - 1)這個if判斷,就是處理主窗口初始化。

void TemplateLayout::RestoreLayout()
{
    QString path = "d:\\main.ttlayout";
    QFile file(path);
    if (file.open(QIODevice::ReadOnly))
    {
        QDataStream out(&file);

        int count;
        out >> count;//存儲組件窗口個數
        
        for (int i = 0; i < count; ++i)
        {
            TemplateLayout * widget = nullptr; 
            if (i == count - 1)//最後一個是主窗口
            {
                widget = this; 
            }
            else
            {
                widget = new TemplateLayout;
                widget->setWindowFlags(Qt::FramelessWindowHint);
                widget->m_pToolBar->SetMoveable(true);
                widget->SetIsMajor(false);
                widget->show();
            }
            widget->m_pToolBar->LoadLayout(out);

            QString toolSign;
            out >> toolSign;//toolBar結束標誌
            Q_ASSERT(toolSign == "toolBar");

            widget->LoadToolBox(out);

            widget->m_pPanel->LoadLayout(out);

            QString panelSign;
            out >> panelSign;//panel結束標誌
            Q_ASSERT(panelSign == "panels");
        }
    }
}

三、工具欄按鈕

讀取工具欄按鈕的信息,並進行初始化。

工具欄按鈕主要是有兩個

  1. 小工具窗口是否打開
  2. 磁力吸附特性是否啓用。

代碼中toolBoxChecked就是表示工具箱按鈕是否被選中,magneticChecked表示吸力吸附按鈕是否被選中

void DragToolBar::LoadLayout(QDataStream & out)
{
    bool toolBoxChecked, magneticChecked;
    out >> toolBoxChecked;
    out >> magneticChecked;

    Q_ASSERT(m_pToolBoxAct);
    m_pToolBoxAct->setChecked(toolBoxChecked);
    m_pToolBoxAct->triggered(toolBoxChecked);

    Q_ASSERT(m_pMagneticAct);
    m_pMagneticAct->setChecked(magneticChecked);
    m_pMagneticAct->triggered(magneticChecked);

    Q_ASSERT(m_pDragTab);
    m_pDragTab->LoadLayout(out);
}

四、初始化標籤頁

加載工具欄上標籤頁,分3個步驟

  1. 讀取標籤頁個數
  2. 循環讀取全部標籤頁
  3. 讀取選中的標籤頁下標

根據讀取到的信息初始化工具欄。

void DragTabWidget::LoadLayout(QDataStream & out)
{
    int count;
    out >> count;
    QStringList titles;

    while (count-- > 0)
    {
        QString title;
        out >> title;
        titles.append(title);
    }
    
    int selectedIndex = 0;
    out >> selectedIndex;

    TabButton * selected = nullptr;
    for (int i = 0; i < titles.size(); ++i)
    {
        QString title = titles.at(i);
        UpdateMaxOrder(title);
        TabButton * button = AddNewButton(title);
        if (i == selectedIndex)
        {
            selected = button;
        }
    }

    if (selected)
    {
        ButtonClicked(selected->GetID());
    }
}

五、子面板初始化

在佈局信息序列化小結中,咱們講述了子面板中的小窗口在寫入信息時,寫入了窗口的類型type,這個時候咱們就會發現這個type真的過重要了

看以下代碼,咱們讀出了小窗口的type值,而後使用SmallFactory工廠的CreateWidget方法建立了小窗口,代碼看起來是否是仍是比較流暢的。

除過窗口類型外,還包括了窗口標題欄名稱、所屬組、位置、大小等信息

void SubContentWidget::LoadeLayout(QDataStream & out)
{
    QString titleName, groupName;
    QPoint pos;
    QSize size;
    int type;
    int count;
    out >> count;

    while (count-- > 0)
    {
        out >> titleName;
        out >> groupName;

        out >> pos;//保存位置

        out >> size;//保存大小

        out >> (int)type;//保存窗口類型

        SmallWidget * smallWidget = SmallFactory::GetInstance()->CreateWidget(SubWindowNormalType(type), this);
        AddSmallWidget(smallWidget);

        smallWidget->SetWindowTitle(titleName);
        if (groupName.isEmpty() == false)
        {
            smallWidget->SetToolText(STT_GROUP, groupName);
        }
        smallWidget->move(pos);
        smallWidget->resize(size);
        smallWidget->show();
    }
}

六、其餘

反序列化的整個過程基本都是同樣的套路,主要就是使用QDataStream對象把佈局信息以二級制的形式讀入到內存中。

其餘窗口的反序列化操做基本相似,這裏就不一一列出。

7、相關文章

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

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

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

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

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

C++序列化對象


以上的內容,基本上就是本篇文章的內容全部內容啦!序列化和反序列化功能基本完成,但願能夠幫到你們。

若是您以爲文章不錯,不妨給個 打賞,寫做不易,感謝各位的支持。您的支持是我最大的動力,謝謝!!!




很重要--轉載聲明

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

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

相關文章
相關標籤/搜索