目录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对象把布局信息以二级制的形式读入到内存中。
其余窗口的反序列化操做基本相似,这里就不一一列出。
以上的内容,基本上就是本篇文章的内容全部内容啦!序列化和反序列化功能基本完成,但愿能够帮到你们。
![]() |
![]() |
很重要--转载声明