高仿富途牛牛-组件化-布局记忆功能(序列化和反序列化)

2024-01-14 09:10

本文主要是介绍高仿富途牛牛-组件化-布局记忆功能(序列化和反序列化),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、布局记忆

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

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

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

  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对象把布局信息以二级制的形式读入到内存中。

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

 

这篇关于高仿富途牛牛-组件化-布局记忆功能(序列化和反序列化)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/604644

相关文章

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

Olingo分析和实践之EDM 辅助序列化器详解(最佳实践)

《Olingo分析和实践之EDM辅助序列化器详解(最佳实践)》EDM辅助序列化器是ApacheOlingoOData框架中无需完整EDM模型的智能序列化工具,通过运行时类型推断实现灵活数据转换,适用... 目录概念与定义什么是 EDM 辅助序列化器?核心概念设计目标核心特点1. EDM 信息可选2. 智能类

Olingo分析和实践之OData框架核心组件初始化(关键步骤)

《Olingo分析和实践之OData框架核心组件初始化(关键步骤)》ODataSpringBootService通过初始化OData实例和服务元数据,构建框架核心能力与数据模型结构,实现序列化、URI... 目录概述第一步:OData实例创建1.1 OData.newInstance() 详细分析1.1.1

Java实现预览与打印功能详解

《Java实现预览与打印功能详解》在Java中,打印功能主要依赖java.awt.print包,该包提供了与打印相关的一些关键类,比如PrinterJob和PageFormat,它们构成... 目录Java 打印系统概述打印预览与设置使用 PageFormat 和 PrinterJob 类设置页面格式与纸张

MySQL 8 中的一个强大功能 JSON_TABLE示例详解

《MySQL8中的一个强大功能JSON_TABLE示例详解》JSON_TABLE是MySQL8中引入的一个强大功能,它允许用户将JSON数据转换为关系表格式,从而可以更方便地在SQL查询中处理J... 目录基本语法示例示例查询解释应用场景不适用场景1. ‌jsON 数据结构过于复杂或动态变化‌2. ‌性能要

SpringSecurity整合redission序列化问题小结(最新整理)

《SpringSecurity整合redission序列化问题小结(最新整理)》文章详解SpringSecurity整合Redisson时的序列化问题,指出需排除官方Jackson依赖,通过自定义反序... 目录1. 前言2. Redission配置2.1 RedissonProperties2.2 Red

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

mysql表操作与查询功能详解

《mysql表操作与查询功能详解》本文系统讲解MySQL表操作与查询,涵盖创建、修改、复制表语法,基本查询结构及WHERE、GROUPBY等子句,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随... 目录01.表的操作1.1表操作概览1.2创建表1.3修改表1.4复制表02.基本查询操作2.1 SE

Golang如何用gorm实现分页的功能

《Golang如何用gorm实现分页的功能》:本文主要介绍Golang如何用gorm实现分页的功能方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录背景go库下载初始化数据【1】建表【2】插入数据【3】查看数据4、代码示例【1】gorm结构体定义【2】分页结构体