目錄html
一款優秀的軟件,不只僅要求功能強健、穩定性高和可靠的精準率,每每不少時候咱們都須要去關注用戶界面是否友好,用戶操做是否順暢,軟件跨機器使用到底咋樣。windows
提及到怎麼讓用戶交互友好,這就是用戶體驗和視覺設計師的主場啦。這裏我就很少說了,今天主要是想說明一個問題--佈局記憶功能app
如今客戶端軟件各式各樣,種類多了去了,可是不知道你們有沒有注意到有這麼一些交互上的細節。less
使用過QQ的同窗應該都比較清楚。咱們在QQ使用時,除過第一次登陸QQ軟件,其他時間段登陸QQ時,QQ的初始位置每每會是上一次退出時的位置函數
windows資源管理器咱們你們應該都常常在使用,不知道你們有沒有仔細觀察。咱們修改了資源管理器窗口大小後,再次打開資源管理器窗口時,新的窗口大小和咱們以前修改後的窗口大小同樣。工具
firefox郵件客戶端,你們都用過吧,也是會記憶窗口最後組件化
還有一些工具軟件,好比說PicPick,選擇的使用模式會一直記錄佈局
QQ飛車是一款騰訊出的客戶端遊戲,他支持多種顯示模式,設置一次後,會一直生效,直到咱們再次設置爲止。測試
以上是我隨便寫的幾個數據記憶的事例,相信你們都不陌生。除過這些簡單的數據持久化之外,其實還有不少其餘的事例,這裏就不一一例舉了。this
今天咱們主要是想給你們展現下咱們負責窗口布局是怎麼進行佈局記憶的。
窗口布局記憶如效果圖所示。
當咱們經過主窗口關閉了軟件時,程序會自動把佈局信息序列化成字符串,而後進行保存。
再次啓動軟件時,咱們首先會去加載序列化的佈局信息,而後進行解析佈局信息,並構造咱們的窗口,這個工程稱之爲反序列化。
以前我已經寫了好幾天文章都是講組件化相關的東西,其中有一篇文章高仿富途牛牛-組件化(五)-如何去管理炒雞多的小窗口主要是講解怎麼去管理過多的小窗口,主要是把建立的過程進行了封裝,讓外界使用起來更加的接口化。
本篇文章主要是講述佈局怎麼去記憶?記憶後又是怎麼去恢復?關於窗口建立和消息通訊這裏我就不在去講解了。感興趣的同窗能夠翻看以前的文章,由於個人這個demo是在一直的維護,更新過程當中,所以講到這篇文章的時候,以前的一些主題中的方式、方法可能已經發現了變化,若是有問題的歡迎留言。
一個組件窗口中同時只容許一個頁籤被選中,選中另外一個頁籤時,其餘的頁籤都會被重置爲非選中狀態。
TabButton是一個複雜的小窗口,支持同一個工具欄內拖拽,也支持多個工具欄之間拖拽。
每個組件窗口都包含有多個頁籤和多個SubPanel,其中SubPanel和頁籤時一對一的關係。
咱們切換頁籤的時候,SubPanel也會跟隨者切換,而每個SubPanel上都包含有不一樣的小窗口,這些小窗口都是由工具箱進行建立的。
工具箱這裏就不在多說了,看展現的效果圖,上邊就有一個工具箱窗口,當咱們點擊其上的工具按鈕時,就會在當前的SubPanel上建立一個對應的小窗口。
首先我一直強調的是高仿富途牛牛-組件化,所以這裏記憶的內容我也是根據福牛的交互行爲來記憶的,可能記憶的內容有下面這些,但也可能更多。
以上內容就是咱們序列化時會存儲的信息,但又不只限於這些。
要讓佈局信息持久化,那麼佈局信息必然要被咱們存儲到硬盤上,所以電腦上的內存信息系統重啓後就會消息。
好,那麼接下來就是考慮把佈局信息寫入硬盤,這個時候咱們就得找個合適的實際寫入時機,目前我寫入的時機是在關閉軟件的時候,可是這裏不建議你們也這麼搞,所以這回致使關節關閉有延遲,當咱們有大量的數據須要寫入的時,可能會影響用戶體驗。
關於寫入時機選擇,不是本篇文章討論的主要內容,感興趣的能夠本身去研究。
數據寫入時須要注意,給讀取數據時寫入一些標誌,不然讀取數據時若是包含一些循環,則不知道循環應該何時結束。
窗口信息使用二進制的方式寫入文件,因爲如今是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對象把佈局信息以二級制的形式寫入到硬盤文件中。
其餘的佈局信息寫入方式大豆差很少,這裏就不一一列出。
說完序列化後,接下來就是咱們的反序列化的過程了。
反序列化就是序列化的相反過程,主要是咱們須要寫入正確的信息,而後按寫入時的順序進行讀取佈局信息便可
反序列化就是序列化的逆序,不過這裏須要注意的一個地方就是,咱們序列化的時候,主窗口時最後保存的,所以反序列化的時候,主窗口也是最後才進行初始化的。
注意代碼中的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"); } } }
讀取工具欄按鈕的信息,並進行初始化。
工具欄按鈕主要是有兩個
代碼中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個步驟
根據讀取到的信息初始化工具欄。
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對象把佈局信息以二級制的形式讀入到內存中。
其餘窗口的反序列化操做基本相似,這裏就不一一列出。
以上的內容,基本上就是本篇文章的內容全部內容啦!序列化和反序列化功能基本完成,但願能夠幫到你們。
![]() |
![]() |
很重要--轉載聲明