Giter Club home page Giter Club logo

ribbonuiframe's Introduction

基于Qt的模块化界面框架

这是一个界面框架模块,实现了Ribbon风格的样式,使用xml文件配置Ribbon标签页。框架完全使用原生Qt库,无第三方依赖,支持Windows和Linux系统。

此框架可以将Qt或MFC/Win32程序集成到同一个应用程序中。你可以使用Qt或MFC/Win32编写功能模块,并加载到主框架中,并在主框架中以Ribbon标签的方式显示。

工程中包含了一个框架测试程序、一个基于Qt的测试模块、一个基于MFC对话框的测试模块和一个基于Qt的主题模块。

界面框架支持更换主题,内置了十几种风格的主题。主题基于QSS实现,其中部分主题来自开源项目QSS-Skin-Builder

框架使用了xml文件配置Ribbon界面中的所有元素。框架具有以下特点:

  • 轻量化,无第三方依赖。使用Qt编写,只依赖Qt原生库。

  • 跨平台。支持支持Windows和Linux系统,在所有系统中均能获得一致的界面和功能。

  • 支持多种主题自由切换。支持系统原生主题、Office2010/Office2013/Office2016风格主题,以及其他十几种深色和浅色主题。

  • 提供完整的Ribbon界面。支持Ribbon界面的开始按钮、快速启动栏、标签、功能区等。

  • 方便的界面配置。界面全部通过xml配置,通过xml文件配置界面具有方便直观的优点。同时,将界面放到xml文件中,与C++代码分离,提高了程序的可维护性。

  • 模块化。支持将不同的软件作为模块加载,实现了模块化的软件设计。只需要在功能模块中实现指定的接口,并在xml文件中配置好模块的动态库,即可将模块加载到界面框架中。关于模块化的实现请参照二次开发说明章节。

界面截图▼

image-20230826193445586

环境说明

  • 开发环境: Windows 10 / Windows11
  • 开发工具 Qt Creator 10.0.2 / Visual Studio 2022
  • Qt 版本 5.15.2
  • 编译器版本 Microsoft Visual C++ 2022
  • 软件运行环境 Windows 10 / Windows 11

目录说明

目录 说明
RibbonFrame 界面框架核心动态库,包含一个RibbonFrameWindow类作为程序的主窗口。
MainApp 界面框架的示例程序,依赖RibbonFrame。
OfficeStyleApp 一个不使用模块的Office风格的示例程序。
TestModule Qt测试模块,RibbonFrame模块加载时会根据xml中配置的文件名加载该模块。
MFCModule MFC测试模块,RibbonFrame模块加载时会根据xml中配置的文件名加载该模块。
StylePlugin 主题模块,如果需要支持更换主题功能需要加载此模块。
bin 输出的二进制文件。
include 公共的头文件。

二次开发说明

二次开发步骤

要在你的项目中使用此界面框架,请遵循以下步骤:

  • 将RibbonFrame的依赖添加到你的主工程中。

LIBS +=-lRibbonFrame


* 并修改main.cpp中的代码:

* 添加`include/ribbonframewindow.h`头文件包含,使用`RibbonFrameWindow`类作为程序的主窗口。

* 你也可以自定义`RibbonFrameWindow`类的派生类作为程序主窗口,在派生类中重写`OnCommand`用于响应需要在主窗口中响应的命令。

* 编辑`MainApp/res/Mainframe.xml`文件定义框架中的命令、菜单、控件等。

* 关于此文件的编写规则请参照本文档中的“界面xml文件说明”章节。xml文件可以放在资源文件中,也可以独立于应用程序。xml文件的路径通过`RibbonFrameWindow`的构造函数传递,如果该路径为空,则默认读取应用程序所在目录下名为`Mainframe.xml`的文件。

* 添加你自己的功能模块。

* 在你的工程中添加一个动态库项目作为一个功能模块,并在xml文件中的`Page`节点中配置模块的名称。

* 模块类必须实现`IModule`接口,并实现以下必要的虚函数:

  | 虚函数            | 说明                                                         |
  | ----------------- | ------------------------------------------------------------ |
  | InitInstance      | 模块被加载后由框架调用,在这里添加一些初始化的代码。         |
  | UnInitInstance    | 模块被析构前由框架调用,在这里添加一些清理、保存的代码。     |
  | UiInitComplete    | 界面加载完成后由框架调用,并传递`IMainFrame`接口的指针,可以通过此指针调用框架接口。对框架命令、控件的初始化工作必须在此函数中进行。 |
  | GetMainWindowType | 返回主窗口的类型,如果模块是一个Qt工程,则返回`IModule::MT_QWIDGET`,如果是一个MFC工程,则返回`IModule::MT_HWND`。 |
  | GetMainWindow     | 根据GetMainWindowType的返回值返回一个QWidget对象的指针或HWND句柄。为框架提供一个主窗口作为此模块的主窗口。 |
  | GetModuleName     | 返回此模块的名称。此名称为框架区分不同模块的唯一标识,不同模块的名称不能相同。 |
  | OnCommand         | 当主窗口触发了一个命令时由框架调用。响应框架中命令的触发事件必须在此函数中。 |
  
* 模块导出一个名为`CreateInstance()`的函数,在函数中创建模块类的对象,并返回其指针。此对象在模块中创建,并由框架负责释放。

如果需要添加的模块不需要界面,并且不需要关联Ribbon页面,请在xml文件的`Plugins`节点下配置要添加的插件。例如本工程中的`StylePlugin`模块。

## 集成基于MFC的模块

你还可以将一个基于对话框的MFC工程集成到框架中,并在框架中显示MFC的主窗口。

要集成MFC模块,请参照`MFCModule`里的示例代码。

在集成基于MFC的模块时,有以下几点需要注意:

* 由于MFC窗口在初始化后且还未嵌入框架内时,会短暂地显示一段时间,导致程序时窗口会短暂地闪现一次,因此必须使得MFC窗口在初始化时保持隐藏状态,直到`IModule::UiInitComplete`函数被调用时再显示。具体做法是,响应对话框的`WM_WINDOWPOSCHANGING`消息,并在消息处理函数中添加如下代码:

```c++
if (!m_windowVisible)
    lpwndpos->flags &= ~SWP_SHOWWINDOW;

其中m_windowVisible为对话框的成员变量,初始时为fase,在IModule::UiInitComplete函数被调用时将其置为true。

  • 在MFC模块中实现IModuleInitInstance()UnInitInstance()接口时,请参MFCModule里的示例代码。

  • 编译MFC项目时,在Visual Studio的“项目”>“属性”>“高级”中,“MFC 的使用”一项应该设置为“在共享 DLL 中使用 MFC”,不能设置为“在静态库中使用 MFC”。

获取和设置功能区命令和控件的状态

  • 在模块中重写IModule接口中的虚函数UiInitComplete,保存此函数传递的IMainFrame接口的指针。

  • 通过IMainFrame接口可以获取和设置控件的状态。

下表列举了IMainFrame接口一些常用的函数。

虚函数 说明
GetAction 根据id获取功能区一个QAction对象。
GetWidget 根据id获取功能区一个控件。
GetMenu 根据id获取功能区一个菜单。
SetItemEnable/IsItemEnable 根据id设置/获取功能区一个控件的启用/禁用状态。
SetItemChecked/IsItemChecked 根据id设置/获取功能区一个Action、CheckBox或RadioButton的选中状态。
SetItemText/GetItemText 根据id设置/获取功能区一个Action或控件的文本。
SetItemCurIIndex/GetItemCurIndex 根据id设置/获取Combox或ListWidget当前选中项。

响应控件事件

在模块中重写IModule接口中的虚函数OnCommand,可以响应Action、CheckBox或RadioButton的点击事件。

在模块中重写IModule接口中的虚函数OnItemChanged,可以响应一些控件事件,例如Combox或ListWidget当前选中项的改变,或LineEdit/TextEdit文本的改变。

模块间通信

模块中重写IModule接口中的虚函数UiInitComplete,此函数会传递IMainFrame接口的指针,保存此指针。

向模块发送消息

  • 调用IMainFrame::SendModuleMessage向模块发送一个消息。

接收模块消息

  • 在模块实现类中重写IModule接口的OnMessage函数,使用SendModuleMessage函数向此模块发送了消息时,此函数会被调用。

重写RibbonFrameWindow中的SendModuleMessage函数也可以在主窗口中响应模块消息。注意,在RibbonFrameWindow的派生类中重写SendModuleMessage函数时,必须调用基类RibbonFrameWindow中的SendModuleMessage,以确保模块消息可以被发送到对应模块。

不使用模块

此框架也允许不使用任何模块,直接在exe工程中编写你的逻辑代码。

如果在使用此框架时不需要使用模块,请遵循以下步骤。

  • 编写界面xml文件,但是Page节点不配置modulePath属性。
  • 编写一个类继承于RibbonFrameWindow类作为程序的主窗口。
  • 在主窗口类的构造函数中调用基类的SetDefaultWidget设置一个默认的窗口,此窗口会代替模块主窗口显示在界面中。
  • 重写基类的OnCommand函数可以响应Ribbon标签中命令的触发,重写基类的OnItemChanged可以响应Ribbon标签中的控件消息。

界面xml文件说明

界面框架使用xml文件配置界面,xml文件的路径通过RibbonFrameWindow的构造函数传递,如果该路径为空,则默认读取应用程序所在目录下名为Mainframe.xml的文件。

xml文件节点可分为功能节点、容器节点和控件节点。

容器节点

root节点

xml文件唯一的根节点。

属性说明

  • appName:程序的名称。
  • font:字体名称。
  • fontSize:字体的大小。

MainWindow节点

MainWindow节点下包含所有主窗口元素,例如Page、SystemMenu、QuickAccessBar、Action。这些节点可以放在MainWindow节点下,也可以直接放在root节点(根节点)下。

MainWindow节点必须放在根节点下。

MainWindow节点或根节点下的控件节点将被添加到窗口的右上角。

属性说明

  • title:窗口标题

    如果有此属性,则用此属性的值作为窗口标题,否则使用root节点下appName属性的值作为窗口标题。

MainWindow节点不是必须的,所有放在MainWindow下的子节点都可以直接放在root节点(根节点)下面。

Page节点

Page节点为主界面中的一个标签页,对应一个模块。

在主界面中切换到一个标签页时,会显示对应模块的主窗口。

属性说明

  • name:模块名,用于显示在标签页上。
  • icon:显示在标签页上的图标。
  • modulePath:需要加载的模块的路径。(必须使用相对路径,不需要扩展名,框架会自动根据当前系统系统类型加载正确的动态库。如果动态库就放在可执行目录相同目录下,只需将modulePath属性配置为动态库的文件名即可。)

你也可以在多个Page节点中指定相同的modulePath属性,此时多个标签将对应同一个模块,切换到这些标签时,显示的都该模块的主窗口。

modulePath属性不是必须的,如果不指定modulePath属性,则该页面不关联模块。

ActionGroup节点

将若干个Action和其他控件添加到一个组中。

一个ActionGroup由若干个Action、组名称和选项按钮组成,如下图所示:

image-20231020104135873

其中组名称由ActionGroup的name属性指定。选项按钮由optionBtn属性指定,当点击选项按钮时,OnCommand会响应,传递的命令ID由ActionGrou的id属性指定。

属性说明

  • name:组的名称。
  • optionBtn:是否在命令组的右下角显示选项按钮。
  • id:当optionBtn属性为true时点击选项按钮,OnCommand函数会传递此ID。

Page节点下的Action节点可以放到ActionGroup节点下,也可以直接放到Page节点下。

如果不指定name属性,则组名称和选项按钮都不会显示。

QuickAccessBar节点

显示在界面左上角的快速启动栏,子节点可以是所有控件节点。

QuickAccessBar节点必须位于根节点或MainWindow节点下。

SystemMenu节点

显示在界面左上角按钮,点击后会弹出系统菜单。

SystemMenu节点必须位于根节点或MainWindow节点下。

属性说明

name:显示在左上角按钮上的文本。

icon:显示在左上角按钮上的图标。

WidgetGroup节点

将若干个Widget组合到一起。子节点可以是除了Action、Separator、Menu以外的所有控件节点,也可以是ToolBar节点。

属性说明

  • horizontalArrange:如果为true,则WidgetGroup中的项目为水平排列。未设置时为true。

ToolBar节点

一个工具栏,子节点可以是所有控件节点。子控件水平排列。图标大小固定为16x16。

StatusBar节点

状态栏。子节点可以是所有控件节点。控件将被添加到状态栏的左侧。如果将控件放在PermanentWidget节点下面,则将显示在状态栏的右侧。

StatusBar节点必须位于根节点或MainWindow节点下。

PermanentWidget节点

仅作为StatusBar节点的子节点,子节点可以是所有控件节点。控件将被添加到状态栏的右侧。

控件节点

Action节点

Action节点用于设置模块标签页下工具栏中的命令、菜单下的命令,以及标签栏右上角的命令。

属性说明

  • name:用于显示在工具栏中命令的名称。

  • icon:用于显示在工具栏中命令的图标的路径。

  • id / commandID:命令的ID,用于在程序中响应命令、设置命令状态时需要用到的ID。注意:即使在不同模块中,每个命令的ID也必须是唯一的。

  • checkable:设置此命令是否可以被选中。

  • checked:设置此命令是否默认选中。

  • enable / enabled:设置此命令是否处于启用状态

  • tip:设置鼠标指向此命令时的鼠标提示。

  • radioGroup:命令组号,用于设置此命令是否要和其他命令组成一组单选按钮,也就是说,处于同一个命令组的命令,一次只会有一个命令被选中。注意:即使在不同模块中,不同命令组的ID也不能相同。

  • shortcut:执行此命令的快捷键

  • smallIcon:是否为小图标,如果为true,则若干个连续的小图标会在工具栏中垂直排列以节省空间。

  • btnStyle:命令的风格,可以为以下值之一:

    • compact:紧凑按钮,文本显示在图标旁边,效果同smallIcon为true。
    • textOnly:仅文本,即使指定了icon属性也不显示图标。
    • iconOnly:仅图标,不显示文本。

Separator节点

显示在工具栏中的分隔符。

Menu节点

在工具栏中显示一个有下拉菜单的按钮

属性说明

  • name:菜单按钮的名称

  • icon:菜单按钮的图标

  • menuBtn:如果为true,此按钮显示为按钮和箭头两部分,点击按钮部分响应命令,点击箭头部分显示下拉菜单。如果为false,则此按钮只有一个部分,点击后直接显示下拉菜单。

    image-20231020104440439

  • id:命令id,当menuBtn为true时有效

  • smallIcon:同Action节点

  • btnStyle:同Action节点

Menu节点下面可以包含Action节点、Separator节点ToolBar节点和其他控件节点,也可以嵌套Menu节点。

属性说明

  • name:按钮上的文本
  • icon:按钮上的图标

SystemMenu节点下面可以包含Action节点、Separator节点和其他控件节点,也可以嵌套Menu节点。

Label节点

添加一个QLabel对象。

属性说明

  • icon:显示在QLabel左侧的图标。

**注意:**如果为Labe指定了图标,则使用IMainFrame::GetWidget函数获取到的对象将不再是QLabel,也就是说,使用以下代码将无法获取到QLabel的指针:

QLabel* pLabel = qobject_cast<QLabel*>(GetWidget(strId))

在这种情况下,可以使用IMainFrame::SetItemTextIMainFrame::GetItemText函数设置和获取Lable上的文本,还可以使用IMainFrame::SetItemIcon函数设置Label左侧的图标。

LineEdit节点

添加一个QLineEdit对象。

属性说明

  • editable:是否可以编辑。未指定时为true

TextEdit/Edit节点

添加一个QTextEdit对象。

属性说明

  • editable:是否可以编辑。未指定时为true

ComboBox节点

添加一个QComboBox对象。

属性说明

  • editable:是否可以编辑。未指定时为false

CheckBox节点

添加一个QCheckBox对象。

RadioButton节点

添加一个QRadioButton对象。

属性说明

  • radioGroup:设置QRadioButton所在组,具有相同radioGroup值的RadioButton为同一组。

ListWidget节点

添加一个QListWidget对象。

属性说明

  • horizontalArrange:如果为true,则ListWidget中的项目为水平排列。

UserWidget节点

添加一个用户自定义控件。

如果你需要添加其他控件到功能区,可以使用UserWidget节点。

当框架需要创建此控件时,会调用对应IModule接口中的CreateRibbonWidget函数,模块需要在此函数中根据id创建自定义控件,并返回控件的指针。

通用属性

所有控件节点都具有以下通用属性:

id:控件的id

name:控件的名称

width:控件的宽度度

height:控件的高度

enable / enabled:控件是否处于启用状态

功能节点

Plugins节点

Plugins节点用于配置需要加载的无界面模块。类似于Page节点,但是在这里配置的模块将不会显示主窗口。

Plugins节点必须位于根节点下。

Plugin节点

仅作为Plugins节点的子节点,每个模块在子节点Plugin中配置,path属性为插件的路径,同Page节点的modulePath属性。

界面搭建的一些技巧

  • Action节点可以放在ActionGroup里面,也可以直接放在Page节点下。

  • Action节点默认为大图标,若干个Action将水平排列,如果想要让Action显示为小图标,则可以设置smallIcon属性为true,此时Action将以两个一组显示,排列顺序如下图所示:

    image-20231020105002953

  • ToolBar节点可以在功能区添加一个子工具栏,如果你要实现两个子工具栏并排显示时可以使用ToolBar节点。如下图所示:

    image-20231020105039839

    在ActionGroup节点下添加两个ToolBar节点,在ToolBar节点中添加Action节点。要使两个ToolBar并排显示,必须为两个ToolBar节点指定smallIcon属性为true。

  • 若干个Action选中状态互斥。

    如果要使多个Action的选项状态互斥,即同时只有一个Action选中,可以使用RadioGroup属性,为同一组Action设置相同的RadioGroup值即可。RadioGroup属性可以对Action和RadioButton生效。示例xml代码如下:

<Action name="放大" icon="./Image/scaleUp.png" id="MapZoomIn" radioGroup="1"/>
<Action name="缩小" icon="./Image/scaleDown.png" id="MapZoomOut" radioGroup="1"/>
<Action name="平移" icon="./Image/roamTool.png" id="MapPan" radioGroup="1"/>
<Action name="选择" icon="./Image/selectTool.png" id="MapSelect" radioGroup="1"/>

上面代码中的4个Action设置了相同的radioGroup值,则这4个Action的选中状态将会是互斥的。

主题模块

框架支持更换主题,更换主题由主题模块提供。如果需要让框架支持更换主题,需要加载StylePlugin模块。

MainFrame.xml中通过Plugins加载主题插件:

<Plugins>
	<Plugin path="StylePlugin"/>
</Plugins>

然后在MainFrame.xml中添加如下代码:

<Menu name="主题" icon="./Image/color.png" id="Theme" btnStyle="compact">
	<Action name="默认主题" id="DefaultStyle" checkable="true"/>
</Menu>

其中“主题”菜单的id必须为Theme,菜单下包含一个id为DefaultStyle的Action。

StylePlugin模块会自动查找id为Theme的菜单,并在菜单下添加其他支持的主题。如果主题加载成功,“主题”菜单将如下图所示:

image-20230827182536244

主题颜色

框架的主题通过qss样式表实现,要使样式表支持主题颜色,在样式中指定颜色时请使用下表中的颜色占位符,而不是显式指定颜色值。

颜色占位符 说明
@themeColorOri 原始主题色
@themeColor0 亮度值为 128 的主题色
@themeColorLighter1 亮度值为 154 的主题色
@themeColorLighter2 亮度值为 180 的主题色
@themeColorLighter3 亮度值为 206 的主题色
@themeColorLighter4 亮度值为 232 的主题色
@themeColorDarker1 亮度值为 102 的主题色
@themeColorDarker2 亮度值为 76 的主题色
@themeColorDarker3 亮度值为 50 的主题色
@themeColorDarker4 亮度值为 24 的主题色

颜色占位符代表了当前主题色的不同亮度值,不同占位符代表的颜色亮度值参见下图:

image-20230822153012331

注意,目前仅Office2013和Office2016主题支持主题颜色。

主题模块消息

主题插件支持了一些模块消息,用于和其他模块或主窗口通信。消息类型在include/ribbonuipredefine.h中定义。

由主题模块发出的消息

以下消息由主题模块发送给其他所有模块。

消息类型(msgType) 说明 参数(msgData) 返回值
MODULE_MSG_StyleChanged 通知主题样式已改变。 样式名称,QString类型

由主题模块接收的消息

消息类型(msgType) 说明 参数(msgData) 返回值
MODULE_MSG_GetStyleType 获取当前主题样式类别。 CStyleManager::StyleType类型。
MODULE_MSG_GetStyleName 获取当前主题样式名称。 主题名称,QString类型。
MODULE_MSG_IsDarkTheme 获取当前主题是否为深色主题 bool类型。
MODULE_MSG_SetThemeColor 设置当前主题颜色 主题颜色,QString类型。主题颜色的十六进制值,如#557eef,可以通过QColor::name函数得到

配置数据

主题模块支持在程序退出后记住上次选择的主题和主题颜色,配置数据保存在注册表的以下位置中:

\HKEY_CURRENT_USER\SOFTWARE\Apps By ZhongYang\<应用程序名称>\

其中的<应用程序名称>qApp->applicationName()得到。

该位置保存了以下数据:

名称 类型 说明
style 字符串 上次选择的主题名称。如果为空则为默认主题。
themeColor 字符串 上次选择的主题颜色。颜色的十六进制值,由QColor::name得到。

RibbonUi工具

包含一些小工具。

资源ID定义生成器

一个用于从xml文件自动生成资源ID宏定义的小工具。

image-20231025091155934

点击“浏览”按钮选择资源xml文件,点击“生成”按钮,在下方文本框内会生成xml文件内所有id的宏定义,可以将这些宏定义复制到你的代码中使用。

ribbonuiframe's People

Contributors

zhongyang219 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.