文章目录

  • 一、布局记忆
  • 二、效果展示
  • 三、重点回顾
    • 1、窗口管理
    • 2、页签TabButton
    • 3、子面板SubPanel
  • 四、布局记忆内容
  • 五、布局信息序列化
    • 1、流程
    • 2、主流程写入
    • 3、标签页写入
    • 4、小窗口写入
    • 5、其他
  • 六、布局信息反序列化
    • 1、流程
    • 2、反序列化主流程
    • 3、工具栏按钮
    • 4、初始化标签页
    • 5、子面板初始化
    • 6、其他
  • 七、相关文章

一、布局记忆

一款优秀的软件,不仅仅要求功能强健、稳定性高和可靠的精准率,往往很多时候我们都需要去关注用户界面是否友好,用户操作是否顺畅,软件跨机器使用到底咋样。

说起到怎么让用户交互友好,这就是用户体验和视觉设计师的主场啦。这里我就不多说了,今天主要是想说明一个问题–布局记忆功能

现在客户端软件各式各样,种类多了去了,但是不知道大家有没有注意到有这么一些交互上的细节。

  1. 使用过QQ的同学应该都比较清楚。我们在QQ使用时,除过第一次登录QQ软件,其余时间段登录QQ时,QQ的初始位置往往会是上一次退出时的位置

  2. windows资源管理器我们大家应该都经常在使用,不知道大家有没有仔细观察。我们修改了资源管理器窗口大小后,再次打开资源管理器窗口时,新的窗口大小和我们之前修改后的窗口大小一样。

  3. firefox邮件客户端,大家都用过吧,也是会记忆窗口最后

  4. 还有一些工具软件,比如说PicPick,选择的使用模式会一直记录

  5. QQ飞车是一款腾讯出的客户端游戏,他支持多种显示模式,设置一次后,会一直生效,直到我们再次设置为止。

以上是我随便写的几个数据记忆的事例,相信大家都不陌生。除过这些简单的数据持久化以外,其实还有很多其他的事例,这里就不一一例举了。

今天我们主要是想给大家展示下我们负责窗口布局是怎么进行布局记忆的。

二、效果展示

窗口布局记忆如效果图所示。

当我们通过主窗口关闭了软件时,程序会自动把布局信息序列化成字符串,然后进行保存。

再次启动软件时,我们首先会去加载序列化的布局信息,然后进行解析布局信息,并构造我们的窗口,这个工程称之为反序列化。

三、重点回顾

1、窗口管理

之前我已经写了好几天文章都是讲组件化相关的东西,其中有一篇文章高仿富途牛牛-组件化(五)-如何去管理炒鸡多的小窗口主要是讲解怎么去管理过多的小窗口,主要是把创建的过程进行了封装,让外界使用起来更加的接口化。

本篇文章主要是讲述布局怎么去记忆?记忆后又是怎么去恢复?关于窗口创建和消息通信这里我就不在去讲解了。感兴趣的同学可以翻看之前的文章,因为我的这个demo是在一直的维护,更新过程中,因此讲到这篇文章的时候,之前的一些主题中的方式、方法可能已经发现了变化,如果有问题的欢迎留言。

2、页签TabButton

一个组件窗口中同时只允许一个页签被选中,选中另一个页签时,其他的页签都会被重置为非选中状态。

TabButton是一个复杂的小窗口,支持同一个工具栏内拖拽,也支持多个工具栏之间拖拽。

3、子面板SubPanel

每一个组件窗口都包含有多个页签和多个SubPanel,其中SubPanel和页签时一对一的关系。

我们切换页签的时候,SubPanel也会跟随者切换,而每一个SubPanel上都包含有不同的小窗口,这些小窗口都是由工具箱进行创建的。

工具箱这里就不在多说了,看展示的效果图,上边就有一个工具箱窗口,当我们点击其上的工具按钮时,就会在当前的SubPanel上创建一个对应的小窗口。

四、布局记忆内容

首先我一直强调的是高仿富途牛牛-组件化,因此这里记忆的内容我也是根据福牛的交互行为来记忆的,可能记忆的内容有下面这些,但也可能更多。

  1. 组件窗口个数
  2. 组件窗口位置和大小,包括层次关系
  3. 组件窗口关联的工具箱是否显示和其位置
  4. 工具栏的状态,包括工具按钮状态,页签个数、顺序、名称和当前选中项
  5. 子面板上的小窗口
  6. 小窗口的层次关系、位置和大小

以上内容就是我们序列化时会存储的信息,但又不仅限于这些。

五、布局信息序列化

要让布局信息持久化,那么布局信息必然要被我们存储到硬盘上,因此电脑上的内存信息系统重启后就会消息。

好,那么接下来就是考虑把布局信息写入硬盘,这个时候我们就得找个合适的实际写入时机,目前我写入的时机是在关闭软件的时候,但是这里不建议大家也这么搞,因此这回导致关节关闭有延迟,当我们有大量的数据需要写入的时,可能会影响用户体验。

关于写入时机选择,不是本篇文章讨论的主要内容,感兴趣的可以自己去研究。

数据写入时需要注意,给读取数据时写入一些标志,否则读取数据时如果包含一些循环,则不知道循环应该什么时候结束。

1、流程

  1. 主组件窗口关闭时,开始序列化布局信息
  2. 首先写入组件窗口个数,方便后期读数据
  3. 工具栏按钮状态写入
  4. 工具栏页签个数写入
  5. 工具栏页签循环写入
  6. 工具栏页签选中项index写入
  7. 工具箱大小和位置写入
  8. 循环子面板SubPanel
  9. 写入SubPanel中所有小窗体信息
  10. 小窗体信息吸入:标题栏名称、所属组、窗口大小、位置等

2、主流程写入

窗口信息使用二进制的方式写入文件,由于现在是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个字节的结束符,这样我们在多层嵌套写数据时,虽然没有问题,但是读数据时就会出现问题,这个问题我也是查了好久就通过调试代码发现的

3、标签页写入

前边我们也说了,我们整个的写入过程都使用了一个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;//记录选中按钮
}

4、小窗口写入

小窗口写入时,首先写入了的是标题栏的信息,然后在写入窗口自身的位置、大小和窗口类型

这里需要重点提下窗口类型,这个信息很重要。当我们反序列化的时候,需要根据这个类型来进行创建窗口

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

5、其他

序列化的整个过程基本都是一样的套路,主要就是使用QDataStream对象把布局信息以二级制的形式写入到硬盘文件中。

其他的布局信息写入方式大豆差不多,这里就不一一列出。

六、布局信息反序列化

说完序列化后,接下来就是我们的反序列化的过程了。

反序列化就是序列化的相反过程,主要是我们需要写入正确的信息,然后按写入时的顺序进行读取布局信息即可

1、流程

  1. 启动程序时,打开布局文件
  2. 读出组件窗口个数
  3. 读取工具栏按钮状态
  4. 初始化页签,这个时候SubPanel也会被初始化
  5. 初始化页签选中项
  6. 读取工具箱大小和位置
  7. 初始化各子面板上的小窗口
  8. 循环第三步

2、反序列化主流程

反序列化就是序列化的逆序,不过这里需要注意的一个地方就是,我们序列化的时候,主窗口时最后保存的,因此反序列化的时候,主窗口也是最后才进行初始化的。

注意代码中的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");}}
}

3、工具栏按钮

读取工具栏按钮的信息,并进行初始化。

工具栏按钮主要是有两个

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

4、初始化标签页

加载工具栏上标签页,分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());}
}

5、子面板初始化

在布局信息序列化小结中,我们讲述了子面板中的小窗口在写入信息时,写入了窗口的类型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();}
}

6、其他

反序列化的整个过程基本都是一样的套路,主要就是使用QDataStream对象把布局信息以二级制的形式读入到内存中。

其他窗口的反序列化操作基本类似,这里就不一一列出。

七、相关文章

高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具

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

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

高仿富途牛牛-组件化(四)-优秀的时钟

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

C++序列化对象

以上的内容,基本上就是本篇文章的内容所有内容啦!序列化和反序列化功能基本完成,希望可以帮到大家。

如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!

很重要–转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords

  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。


高仿富途牛牛-组件化(六)-炒鸡牛逼的布局记忆功能(序列化和反序列化)相关推荐

  1. 高仿富途牛牛-组件化(三)-界面美化

    文章目录 一.概述 二.效果展示 三.工具箱 1.布局 a.标题栏 b.客户区 2.功能详解 四.组件模板工具栏 五.其他界面美化 六.使用qss文件 七.相关文章 一.概述 今天是组件化的第三篇文章 ...

  2. 高仿富途牛牛-组件化(五)-如何去管理炒鸡多的小窗口

    目录 一.概述 二.效果展示 三.功能类 四.设计上的考虑 1.功能拆分 2.关键类 五.相关文章 一.概述 代码写的久了,什么功能都想搞点儿模式.不知道是不是只有我一个人这么想的,做功能时不在是只为 ...

  3. 高仿富途牛牛-组件化-如何去管理超级多的小窗口

    一.概述 代码写的久了,什么功能都想搞点儿模式.不知道是不是只有我一个人这么想的,做功能时不在是只为了完成功能,而是在完成功能的同时会去考虑可扩展性怎么样?哪块儿的代码可以复用?又或者哪里需要留更多的 ...

  4. Qt (高仿Visio)流程图组件开发(二) 基本图元绘制 图元间连线绘制

    文章目录 本系列目录 前言 一.如何绘制图元 二.两图元之间如何连线 三.如何实现线跟随图元移动 四.线的位置判断 总结 本系列目录 Qt (高仿Visio)流程图组件开发(一) 效果展示及基本开发框 ...

  5. Qt (高仿Visio)流程图组件开发(三) 图元基类如何定义,流程图多种图元类型实现

    文章目录 本系列目录 前言 一.图元基类的定义 1.图元信息基类结构体 2.图元位置 3.父子对象关系 二.自定义图元实现 1.自定义图元基类(FlowchartGraphicsItem)与Qt原生图 ...

  6. Qt (高仿Visio)流程图组件开发(九) 流程图 代码展示

    文章目录 本系列目录 本系列目录 Qt (高仿Visio)流程图组件开发(一) 效果展示及基本开发框架构思 Qt (高仿Visio)流程图组件开发(二) 基本图元绘制 图元间连线绘制 Qt (高仿Vi ...

  7. Android之高仿墨迹天气桌面组件(AppWidgetProvider) .

    Android之高仿墨迹天气桌面组件(AppWidgetProvider) . 点击:382 发布时间:2012-10-03 更多0 相信墨迹天气,大家都见过,他在时间显示和天气界面上,很吸引人,今天 ...

  8. 纯html+css+js高仿苹果手机时钟小组件

    话不多数说,这是一款用html+css+js开发的高仿苹果手机时钟组件, 作者将苹果的小组件移植到浏览器插件中,可以在BdTab插件安装该组件 HTML <div style="wid ...

  9. (4.0.24.2)Android之桌面组件App Widget案例之高仿墨迹天气桌面组件

    相信墨迹天气,大家都见过,他在时间显示和天气界面上,很吸引人,今天我就来模仿一下墨迹天气的桌面组件,但是由于谷歌在天朝频频被墙的缘故,所以我在今天测试的时候,解析xml文件的网页打不开,所以天气显示出 ...

  10. Android高仿微信/支付宝 扫一扫(弱光检测扫一扫自动放大功能)

    原文地址 前言  目前市面上App携带的扫一扫功能大多是乞丐版,怎么说,就是只有扫一扫.而目前来说扫一扫做的最好的还是微信,微信有弱光环境的检测(可以自动提示用户打开闪光灯),同时,当发现扫描目标距离 ...

最新文章

  1. python及pip中常用命令,经常总结
  2. 风控策略和模型的区别_风控模型之产品赢利分析与策略优化
  3. boost::math::interpolators::cardinal_quadratic_b_spline用法的测试程序
  4. 13 | 答疑(一):无法模拟出 RES 中断的问题,怎么办?
  5. PHP Collection 类
  6. 01-08-02【Nhibernate (版本3.3.1.4000) 出入江湖】二级缓存:NHibernate自带的HashtableProvider...
  7. 拜托!程序员的工作不能用时间来衡量
  8. 给Win32 GUI程序增加控制台窗口的方法
  9. 白皮书丨关于工业互联网,你想知道的都在这儿
  10. php替换指定函数,PHP替换指定字符函数str_replace()的用法
  11. 基于JAVA+SpringMVC+MYSQL的ktv预订管理系统
  12. ubuntu下opencv和qt的安装配置
  13. 使用slf4j和log4j记录日志
  14. 如何批量修改网页 更新网站 一键保存 windows查看和排序
  15. 漫画:如何实现大整数相乘?
  16. 计算机应用基础教材提纲,《计算机应用基础》课程教学大纲
  17. npm下载和使用(超详细)
  18. Win10查看笔记本电池损耗和健康度
  19. 一文读懂各种分布式机器学习框架的区别与联系
  20. 识别中文_文字的效率:中英韩日四国文字识别效率对比,中文遥遥领先

热门文章

  1. 你在找mp3音频格式转换器有哪些吗?分享3个实用的软件
  2. php exec 命令,概述PHP命令行shell_exec()使用
  3. 学习笔记 - 正态分布
  4. vue 引入字体图标显示方块
  5. 用 strcoll 实现中文按拼音排序
  6. sam卡和sim卡区别_SAM卡概述
  7. 湘潭大学 Hurry Up 三分,求凹函数的最小值问题
  8. 新点互联互通_新点驱动(江苏省互联互通版)
  9. Long-tail Relation Extraction via Knowledge Graph Embeddings and Graph Convolution Networks
  10. OpenDaylight(ODL)学习笔记