选项模块实现
接口设计
消息类型的定义
- 子页面消息定义:
- 用于通知主框架,子页面的创建状态(主框架监听的消息处理)
#define MSG_PLUGIN_OPTIONS_PAGE_CREATED “msg.plugin.options.page_created”
扩展数据:{[“pageName”:”Avatar”,”pageHandle”:6878868], [“pageName”:”Avatar”,”pageHandle”:6878868]}
- 用于通知主框架,子页面的状态改变
#define MSG_PLUGIN_OPTIONS_PAGE_STATE_CHANGED “msg.plugin.options.page_statechanged”
扩展数据:{[“pageName”:”Avatar”,”pageHandle”:6878868, ”state”:1] }
-
主框架消息定义(各个选项插件监听消息:)
通知子页面,主框架页面创建完成
#define MSG_PLUGIN_OPTIONS_FRAMEWND_CREATED “msg.plugin.options.framewnd_created”
扩展数据:{”FrameWndHandle”:7878456469}
通知子页面,主框架页面状态改变
#define MSG_PLUGIN_OPTIONS_FRAMEWND_STATE_CHANGED “msg.plugin.options.framewnd_statechanged”
扩展数据:{”state”:1}
流程用例图
交互流程
- 主窗体创建处理
选项主窗口创建完毕后,SendMessage(MSG_PLUGIN_OPTIONS_FRAMEWND_CREATED) 给各个选项插件,等待选项插件创建完毕后,发送消息通知选项主窗口自身创建完毕,然后显示选项对话框。
- 子页面创建处理
各个选项插件监听 MSG_PLUGIN_OPTIONS_FRAMEWND_CREATED 消息,当监听到此消息时,解析出主窗口的窗口句柄,并以此窗口句柄作为父窗口,创建各自选项页面,页面创建完毕后,向选项窗口SendMessage (MSG_PLUGIN_OPTIONS_PAGE_CREATED,extData),将自身窗口加入到主窗口中。
-
子页面状态改变处理
当子页面状态改变时,通知主窗口自身状态改变SendMessage(MSG_PLUGIN_OPTIONS_PAGE_STATE_CHANGED),方便主窗口激活到不同的状态。
-
主窗口状态改变处理
当主窗口状态改变时(比如进行了保存、取消等操作),则向各个插件 SendMessage(MSG_PLUGIN_OPTIONS_FRAMEWND_STATE_CHANGED),子页面进行相应的操作。
子页面注册使用
在框架实现类中需要实现,注册容器定义:
COptionDlg* m_pOptionDlg=nullptr;//主窗体框架指针
std::map<const std::wstring, CMFCPropertyPage*> m_optionMap;//存储生成的属性子页面
设置子界面注册管理实现
/**
* @brief 设置界面属性子页面使用前需注册
* @author shixun.zhang
* @param const std::wstring& optionName : 注册的子属性页面标识名
* @param CMFCPropertyPage* propertyPage : 注册的子属性页面指针
* @return bool : 注册分页面管理是否成功( true / false )
*/
virtual bool RegisterMFCOption(const std::string& optionName, CMFCPropertyPage* propertyPage);
/**
* @brief 设置界面属性子页面查询
* @author shixun.zhang
* @param const std::wstring& optionName : 注册的子属性页面标识名
* @return CMFCPropertyPage* : 子页面指针
*/
virtual CMFCPropertyPage* GetMFCOptionInterface(const std::string& optionName);
//存储注册的设置界面分页面
std::map<std::string, CMFCPropertyPage*> m_optionMap;
//源码实现
bool MFCOption::RegistorPage(const std::wstring& optionName, CMFCPropertyPage* propertyPage)
{
if (propertyPage == nullptr)
{
return false;
}
auto finder = m_optionMap.find(optionName);
if (finder == m_optionMap.end())
{
m_optionMap.insert(std::make_pair(optionName, propertyPage));
return true;
}
return false;
}
CMFCPropertyPage* MFCOption::GetOptionPage(const std::wstring& optionName)
{
auto finder = m_optionMap.find(optionName);
if (finder == m_optionMap.end())
{
return nullptr;
}
return finder->second;
}
选项对话框的构建过程
选项主对话框考虑按如下顺序进行初始化:
创建非模态对话框
对话框初始化时,尝试加载树结构
-
如果安装文件夹下存在选项树配置文件$(INSTALLDIR)\ Resource\OptionsTree.xml,则从配置文件中加载
-
如果配置文件不存在,则构建一颗最基本的选项树(或者提示软件被破坏,请重新安装)
-
树结构加载类:
选项配置文件格式:
文件格式
- xml文件存储路径:C:\WorkSpace\1005NewPlatCode\src\bin64\Resource
- xml文件名:OptionSet.xml
- xml文件格式:
<?xml version="1.0" encoding="utf-8"?>
<Define>
<Actions Name="General Setting">
<Action Name="Basic" DependOnUi= "Elemental Setup"></Action>
<Action Name="Effect" DependOnUi= "Effect Setup"></Action>
<Action Name="Display" DependOnUi= "Display Setup"></Action>
<Action Name="Context" DependOnUi= "Context Setup"></Action>
</Actions>
<Actions Name="Avatar Setting">
</Actions>
<Actions Name="Conver Setting">
</Actions>
</Define>
树结构不限制树的深度,只有叶子节点存在关联的页面。
xml文件解析处理
- 处理方式:xml文件内容转成Json格式数据。
- Json格式数据,传入到界面类COptionDlg中,用来组织生成目录树方式
主对话框内的动作:
- 任意时刻均需要显示一个页面。当选择非叶子节点时,实际显示的页面为第一个子(孙)节点的页面(递归向下寻找)
- 恢复默认:还原到软件默认的配置:从软件的安装文件夹下拷贝配置信息到用户目录后,加载相应配置信息
- 应用:应用配置到当前场景中(可以考虑删除)
- 确定:应用配置到当前场景中,并保存配置后退出
- 取消:取消所有修改的配置,并退出
存在问题:
为避免出现某一个节点无孩子节点的情况,可以选择一种方式处理(可以当成特殊情况以后考虑处理或者不予处理):
a. 当某一个父节点下所有页面均不存在时,向上递归删除此节点
b. 在创建节点前,预先遍历子页面是否存在
树结构代码实现
头文件
#pragma once
//选项树结构加载类
class TiXmlElement;
class TiXmlDocument;
class COptionsTreeCfg final
{
public:
COptionsTreeCfg() {};
~COptionsTreeCfg() {};
/**
* @brief 构建选项树组织
* @author shixun.zhang
* @return bool:创建成功或失败。
*/
bool BuildOptionTree();
private:
/**
* @brief 设置xml文件路径,带文件是否存在检查
* @param const string& sPath:xml文件全路径
* @author shixun.zhang
* @return bool:设置xml文件路径成功与否。
*/
bool SetOptionCfgPath(const string sPath);
/**
* @brief 获取选项树结构是否加载成功
* @author shixun.zhang
* @return bool:获取树结构加载是否成功状态。
*/
bool IsSuccessLoadCfg() { return m_IsSuccessLoad; };
/**
* @brief 创建默认的选项树组织结构(选项xml文件不存在时)
* @author shixun.zhang
* @return bool:创建默认选项树组织是否成功状态。
*/
bool CreateDefaultTree();
/**
* @brief 加载选项树xml配置文件
* @param const string& sPath:xml文件全路径
* @author shixun.zhang
* @return bool:创建选项树组织是否成功状态。
* @note - 采用递归的方式深度遍历整颗树,如果节点不存在则不创建此节点
*/
bool BeginLoadTreeCfg();
/**
* @brief 根据xml文件节点,创建选项树组织
* @param const TiXmlElement* elment:传入解析的xml文件节点(默认采用的是xml文件的根节点)
* @author shixun.zhang
*/
bool ParseOptionNode(const TiXmlElement* elment);
private:
string m_strPath = "";
TiXmlElement* m_pRoot = nullptr;
TiXmlDocument* m_doc = nullptr;
bool m_IsSuccessLoad = false;
};
源文件实现
#include "pch.h"
#include <filesystem>
#include <tinyxml.h>
#include "OptionsTreeCfg.h"
#include "MFCOption.h"
bool COptionsTreeCfg::SetOptionCfgPath(const string sPath)
{
if (sPath.empty() || !std::filesystem::exists(sPath))
{
return false;
}
m_strPath = sPath;
return true;
}
bool COptionsTreeCfg::BeginLoadTreeCfg()
{
string strFilePath = m_strPath;
m_doc = new TiXmlDocument();
m_pRoot = nullptr;
if (m_doc->LoadFile(strFilePath.c_str()) && (m_pRoot = m_doc->RootElement()))
{
return ParseOptionNode(m_pRoot);
}
return false;
}
bool COptionsTreeCfg::ParseOptionNode(const TiXmlElement* elment)
{
if (elment == nullptr) return false;
//通过TiXmlElement* 创建树节点
std::function<void(const TiXmlElement * elment, HTREEITEM tree, COptionDlg * pDld)> createOptionsTreeFucCall;
createOptionsTreeFucCall = [&createOptionsTreeFucCall] (const TiXmlElement* rootNode, HTREEITEM parentTreeNode, COptionDlg* pDld){
if (nullptr != rootNode)
{
const TiXmlElement* pParentNode = rootNode->FirstChildElement("Node");//孩子节点
HTREEITEM currentTreeNode = nullptr;
bool isCreateNotRefPageNode = false;
if (nullptr != pParentNode)
{
while (pParentNode)
{
const char* sName = pParentNode->Attribute("Name");
const char* sRefPage = pParentNode->Attribute("RefPage");
//创建孩子树节点
//判断是否有子节点
const TiXmlElement* pChildNode = pParentNode->FirstChildElement("Node");//孩子节点
if (nullptr != pChildNode)
{
isCreateNotRefPageNode = true;
pDld->CreateOptionTree(sName, sRefPage, parentTreeNode, currentTreeNode, isCreateNotRefPageNode);
//当前节点包含孩子节点
createOptionsTreeFucCall(pParentNode, currentTreeNode, pDld);
//非叶子节点,则绑定第一个叶子节点属性页
pDld->BindParentTreeNodeToFirstLeafNodeRefPage(currentTreeNode);
}
else
{
isCreateNotRefPageNode = false;
bool isCreateTreeNodeSuccess = pDld->CreateOptionTree(sName, sRefPage, parentTreeNode, currentTreeNode, isCreateNotRefPageNode);
if (!isCreateTreeNodeSuccess)//创建子节点失败,当前节点仍为父节点
currentTreeNode = parentTreeNode;
}
pParentNode = pParentNode->NextSiblingElement("Node");
}
//树节点创建失败,则递归查找删除父节点,父节点无孩子节点
pDld->DeleteNonleafTreeNode(currentTreeNode);
}
}
};
/*IOptionInterface* pOptionInterface = dynamic_cast<IOptionInterface*>(eZPlat::PlatformFramework::GetSingleton()->GetPluginsManager()->GetPluginInterface("ui.MFCOption"));
assert(pOptionInterface);*/
COptionDlg* pDld = (COptionDlg*)MFCOption::GetSingleton()->GetWnd();
if (nullptr == pDld) return false;
createOptionsTreeFucCall(elment, nullptr, pDld);//root
m_IsSuccessLoad = true;
return true;
}
bool COptionsTreeCfg::CreateDefaultTree()
{
// 定义一个TiXmlDocument类指针
TiXmlElement* RootElement = new TiXmlElement("OptionRoot");
/************************************************************************
General Setting参数
************************************************************************/
TiXmlElement* GeneralElement = new TiXmlElement("Node");
GeneralElement->SetAttribute("Name", "General Setting");
RootElement->LinkEndChild(GeneralElement);
//Basic
TiXmlElement* BasicElement = new TiXmlElement("Node");
GeneralElement->LinkEndChild(BasicElement);
BasicElement->SetAttribute("Name", "Basic");
BasicElement->SetAttribute("RefPage", "Option.PageBasic");
//Effect
TiXmlElement* EffectElement = new TiXmlElement("Node");
GeneralElement->LinkEndChild(EffectElement);
EffectElement->SetAttribute("Name", "Effect");
EffectElement->SetAttribute("RefPage", "Option.PageEffect");
//Display
TiXmlElement* DisplayElement = new TiXmlElement("Node");
GeneralElement->LinkEndChild(DisplayElement);
DisplayElement->SetAttribute("Name", "Display");
DisplayElement->SetAttribute("RefPage", "Option.PageDisplay");
//Camera
TiXmlElement* CameraElement = new TiXmlElement("Node");
GeneralElement->LinkEndChild(CameraElement);
CameraElement->SetAttribute("Name", "Camera");
CameraElement->SetAttribute("RefPage", "Option.PageCamera");
//Context
TiXmlElement* ContextElement = new TiXmlElement("Node");
GeneralElement->LinkEndChild(ContextElement);
ContextElement->SetAttribute("Name", "Context");
ContextElement->SetAttribute("RefPage", "Option.PageContext");
//Cache
TiXmlElement* CacheElement = new TiXmlElement("Node");
GeneralElement->LinkEndChild(CacheElement);
CacheElement->SetAttribute("Name", "Cache");
CacheElement->SetAttribute("RefPage", "Option.PageCache");
//Save
TiXmlElement* SaveElement = new TiXmlElement("Node");
GeneralElement->LinkEndChild(SaveElement);
SaveElement->SetAttribute("Name", "Save");
SaveElement->SetAttribute("RefPage", "Option.PageSave");
//Undo
TiXmlElement* UndoElement = new TiXmlElement("Node");
GeneralElement->LinkEndChild(UndoElement);
UndoElement->SetAttribute("Name", "Undo");
UndoElement->SetAttribute("RefPage", "Option.PageUndo");
/************************************************************************
Avatar Setting参数
************************************************************************/
TiXmlElement* AvatarElement = new TiXmlElement("Node");
AvatarElement->SetAttribute("Name", "Avatar Setting");
RootElement->LinkEndChild(AvatarElement);
//Param
TiXmlElement* ParamElement = new TiXmlElement("Node");
AvatarElement->LinkEndChild(ParamElement);
ParamElement->SetAttribute("Name", "Param");
ParamElement->SetAttribute("RefPage", "Avatar.PageParam");
/************************************************************************
Conver Setting参数
************************************************************************/
TiXmlElement* ConverElement = new TiXmlElement("Node");
ConverElement->SetAttribute("Name", "Conver Setting");
RootElement->LinkEndChild(ConverElement);
//Common
TiXmlElement* CommonElement = new TiXmlElement("Node");
ConverElement->LinkEndChild(CommonElement);
CommonElement->SetAttribute("Name", "Common");
CommonElement->SetAttribute("RefPage", "Conver.PageConverCommon");
//Dgn
TiXmlElement* DgnElement = new TiXmlElement("Node");
ConverElement->LinkEndChild(DgnElement);
DgnElement->SetAttribute("Name", "Dgn");
DgnElement->SetAttribute("RefPage", "Conver.PageConverDgn");
//Caita
TiXmlElement* CaitaElement = new TiXmlElement("Node");
ConverElement->LinkEndChild(CaitaElement);
CaitaElement->SetAttribute("Name", "Caita");
CaitaElement->SetAttribute("RefPage", "Conver.PageConverCaita");
//Dwg
TiXmlElement* DwgElement = new TiXmlElement("Node");
ConverElement->LinkEndChild(DwgElement);
DwgElement->SetAttribute("Name", "Dwg");
DwgElement->SetAttribute("RefPage", "Conver.PageConverDwg");
//PointCloud
TiXmlElement* PointCloudElement = new TiXmlElement("Node");
ConverElement->LinkEndChild(PointCloudElement);
PointCloudElement->SetAttribute("Name", "PointCloud");
PointCloudElement->SetAttribute("RefPage", "Conver.PageConverPointCloud");
/************************************************************************
Simulation Setting参数
************************************************************************/
TiXmlElement* SimulationElement = new TiXmlElement("Node");
SimulationElement->SetAttribute("Name", "Simulation Setting");
RootElement->LinkEndChild(SimulationElement);
//Common
TiXmlElement* PositionElement = new TiXmlElement("Node");
SimulationElement->LinkEndChild(PositionElement);
PositionElement->SetAttribute("Name", "Position");
PositionElement->SetAttribute("RefPage", "Simulation.PagePosition");
ParseOptionNode(RootElement);
return true;
}
bool COptionsTreeCfg::BuildOptionTree()
{
COptionDlg* pDld = (COptionDlg*)MFCOption::GetSingleton()->GetWnd();
if (nullptr == pDld) return false;
//根据xml文件创建选项树组织
if (!IsSuccessLoadCfg())
{
//指定Option配置文件路径
std::filesystem::path menuPath(eZPlat::PlatformFramework::GetSingleton()->GetResourceFolderPath());
menuPath /= "OptionsTree.xml";
if (!SetOptionCfgPath(menuPath.string()))
CreateDefaultTree();
else
{
BeginLoadTreeCfg();
if (!IsSuccessLoadCfg())
CreateDefaultTree();
}
}
//显示指定树节点
pDld->ShowTreeItemRefPage((void*)"General Setting", 0);
return true;
}
参数读取与保存
-
在安装目录下存储有参数的原始配置数据;当软件第一次启动时,自动将配置数据拷贝到用户目录下
-
参数数据统一保存到一个配置文件中;主框架提供数据目录,用于访问不同分组的参数数据;
const wchar_t* GetConfigFolderPath() const { return m_strConfigFolderPath.c_str(); }
-
当点击恢复默认功能时,将安装文件夹下的配置数据拷贝到用户目录下形成默认数据。
-
启动多个客户端时,以最后一次保存的数据为准。
-
参数存储格式均为json,所以所有参数均为u8
-
对于所有参数,请创建一个描述表,且在默认参数配置中进行配置,以方便后续查询使用
访问参数时:
ConfigKit* configKit = ConfigKit::Using();
go.m_bForceRawMode = configKit->GetBoolean("ForceRawMode", false, ”xre_setting”);
(为方便兼容原有代码,将分组数据放在接口的末尾)
访问子结构时:
Json::*Value* jItem = configKit–>GetObject(”PartItem”, ”xre_setting”);
子页面实现对数据的读写
//配置文件与ui界面进行想换交互
bool CPropRollBack::ConfigKitDataToUi(bool bMode)
{
ConfigKit* pSettingPtr = ConfigKit::Using();
if (!pSettingPtr)
return false;
if (bMode)//读操作 配置文件到ui界面
{
m_spinRollBackCnt.SetBuddy(GetDlgItem(IDC_EDIT_ROLLBACK_CNT));
m_spinRollBackCnt.SetRange(0, 100);
CString str;
str = StringAnsiToWide(pSettingPtr->GetString("RollbackCnt", "50")).c_str();
m_editRollBackCnt.SetWindowTextW(str);
}
else
{
CString cstr;
std::string str;
m_editRollBackCnt.GetWindowTextW(cstr);
str = StringWideToAnsi(cstr.GetBuffer());
pSettingPtr->Set("RollbackCnt", str);
}
return true;
}
Dll资源切换
问题原因:在MFC使用过程中,遇到DLL资源与主EXE资源冲突问题。
基于RAII 技术 ,MFC DLL资源动态切换实现
#pragma once
#include "afxwin.h"
class ModualResource
{
public:
ModualResource(LPCWSTR strModualName) :m_hOldRes(NULL)
{
m_hCurHandle = ::GetModuleHandle(strModualName);
if (m_hCurHandle != NULL)
{
m_hOldRes = ::AfxGetResourceHandle();
::AfxSetResourceHandle(m_hCurHandle);
}
}
~ModualResource()
{
if (m_hOldRes != NULL)
{
::AfxSetResourceHandle(m_hOldRes);
m_hOldRes = NULL;
}
}
HMODULE GetCurModuleHandle() { return m_hCurHandle; }
private:
HINSTANCE m_hOldRes;
HMODULE m_hCurHandle;
};
//使用例子
CString strModul(_T("MFCAvatar.dll"));
CModualResource CurrentModualResource(strModul);
主窗体界面代码实现
头文件
#pragma once
#include "CPropElement.h"
#include "CPropCamera.h"
#include "CPropContext.h"
#include "CPropDisplay.h"
#include "CPropEffect.h"
#include "CPropSave.h"
#include "CPropRollBack.h"
#include "CPropCache.h"
#include "CPageScenes.h"
#include "MFCOption.h"
// COptionDlg 对话框
class IOptionPropertyPage;
class COptionDlg : public CDialogEx, public AutoRegMessageListener
{
DECLARE_DYNAMIC(COptionDlg)
public:
COptionDlg(CWnd* pParent = nullptr); // 标准构造函数
virtual ~COptionDlg();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_MAINDLG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
DECLARE_MESSAGE_MAP()
public:
virtual BOOL OnInitDialog();
virtual BOOL DestroyWindow();
afx_msg void OnBnClickedButtonApply();//应用
afx_msg void OnBnClickedOk();//确定
afx_msg void OnBnClickedButtonDefault();//恢复
afx_msg void OnNMClickListtree(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnMove(int x, int y);
virtual void OnCancel();//取消
bool HandlerMessage(Message& msg);
public:
void SetModify(CMFCPropertyPage* propPtr);
//保存“属性页面”对话框的指针
void SetPtr(CString name, CMFCPropertyPage* preptr, CMFCPropertyPage* ptr);
/**
* @brief 绑定父节点(非叶子节点)到第一个叶子节点关联子页面
* @author shixun.zhang
* @param HTREEITEM parentNode : 需要绑定的父节点(非叶子节点)
* @return bool : 绑定成功与否( true / false )
* @note - 递归查找第一个叶子节点
*/
bool BindParentTreeNodeToFirstLeafNodeRefPage(HTREEITEM parentNode);
/**
* @brief 创建选项界面树组织
* @author shixun.zhang
* @param const char* nodeName: 树节点名称
* @param const char* sRefPage: 关联子页面标识名
* @param HTREEITEM parentNode: 创建树节点依赖的父级树节点
* @param HTREEITEM& currentNode: 当前树节点
* @param bool isCreateNotRefPageNode:是否创建非关联子页面树节点
* @return bool : 绑定树节点成功与否( true / false )
*/
bool CreateOptionTree(const char* nodeName, const char* sRefPage, HTREEITEM parentNode, HTREEITEM& currentNode, bool isCreateNotRefPageNode);
/**
* @brief 删除无子节点的非叶子节点
* @author shixun.zhang
* @param HTREEITEM parentNode : 需要删除的非叶子节点
*/
void DeleteNonleafTreeNode(HTREEITEM hItem);
/**
* @brief 恢复安装目录中的Option配置文件,拷贝到用户目录中。
* @author shixun.zhang
*/
void ReserveConfigFolderOptionDataToAppRootFolder();
/**
* @brief 显示树节点关联的子页面
* @param void* data : 显示树节点关联的子页面依赖条件
* @param int nType : 0:(const char* 类型,即树节点名称);1:(HTREEITEM类型,即树节点)
* @return bool : 显示树节点关联的子页面成功与否( true / false )
* @author shixun.zhang
*/
bool ShowTreeItemRefPage(void* data, int nType);
protected:
/**
* @brief 根据指定的树节点名称,查找树节点
* @param HTREEITEM hroot : 从此树节点进行遍历查找子节点()
* @param CString strtext : 查找的树节点名称
* @author shixun.zhang
*/
HTREEITEM FindTreeItemByName(HTREEITEM hroot, CString strtext);
private:
CPropSave* m_pPropSave = nullptr;
CPropEffect* m_pPropEffect = nullptr;
CPropDisplay* m_pPropDisplay = nullptr;
CPropContext* m_pPropContext = nullptr;
CPropCamera* m_pPropCamera = nullptr;
CPropElement* m_pPropElemental = nullptr;
CPropCache* m_pPropCache = nullptr;
CPropRollBack* m_pPropRollBack = nullptr;
CPageScenes* m_pPageScenes = nullptr;
private:
bool m_bEdit = false;
std::vector<CMFCPropertyPage*> m_editPtrVec;//修改了的页面的指针数组
friend class MFCOption;
public:
std::map<HTREEITEM, CMFCPropertyPage*> m_ptrMap;//属性对话框指针字典
public:
CTreeCtrl m_tree;
CButton m_butOK;
CButton m_butApply;
CButton m_butDefault;
};
源文件
// COptionDlg.cpp: 实现文件
//
#include "pch.h"
#include <filesystem>
#include <fstream>
#include "COptionDlg.h"
#include "afxdialogex.h"
// COptionDlg 对话框
IMPLEMENT_DYNAMIC(COptionDlg, CDialogEx)
COptionDlg::COptionDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MAINDLG, pParent)
{
PlatformFramework::GetSingleton()->GetMessageBus()->RegisterMsgListener(MSG_PLUGIN_OPTIONS_FRAMEWND_CREATED, GetMessageListener());
PlatformFramework::GetSingleton()->GetMessageBus()->RegisterMsgListener(MSG_PLUGIN_OPTIONS_PAGE_STATE_CHANGED, GetMessageListener());
PlatformFramework::GetSingleton()->GetMessageBus()->RegisterMsgListener(MSG_PLUGIN_OPTIONS_FRAMEWND_STATE_CHANGED, GetMessageListener());
}
COptionDlg::~COptionDlg()
{
}
void COptionDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LISTTREE, m_tree);
DDX_Control(pDX, IDOK, m_butOK);
DDX_Control(pDX, IDC_BUTTON_APPLY, m_butApply);
DDX_Control(pDX, IDC_BUTTON_DEFAULT, m_butDefault);
}
BEGIN_MESSAGE_MAP(COptionDlg, CDialogEx)
ON_BN_CLICKED(IDC_BUTTON_APPLY, &COptionDlg::OnBnClickedButtonApply)
ON_BN_CLICKED(IDOK, &COptionDlg::OnBnClickedOk)
ON_BN_CLICKED(IDC_BUTTON_DEFAULT, &COptionDlg::OnBnClickedButtonDefault)
ON_WM_MOVE()
ON_NOTIFY(NM_CLICK, IDC_LISTTREE, &COptionDlg::OnNMClickListtree)
END_MESSAGE_MAP()
// COptionDlg 消息处理程序
void COptionDlg::SetPtr(CString name, CMFCPropertyPage* preptr, CMFCPropertyPage* ptr)
{
if (ptr)
{
HTREEITEM parentitem = nullptr;
if (preptr)//有父节点
{
std::map<HTREEITEM, CMFCPropertyPage*>::iterator it;
for (it = m_ptrMap.begin(); it != m_ptrMap.end(); it++)
{
if ((*it).second == preptr)
{
parentitem = (*it).first;//父节点在目录树中的句柄
}
}
}
HTREEITEM item = m_tree.InsertItem(name, parentitem);
m_tree.SetItemData(item, (DWORD_PTR)ptr);
m_ptrMap.insert(std::pair<HTREEITEM, CMFCPropertyPage*>(item, ptr));
ptr->ShowWindow(SW_HIDE);
CRect rec;
ptr->GetClientRect(&rec);
rec.left += 140;
rec.right += 140;
rec.top += 25;
rec.bottom += 25;
ClientToScreen(&rec);
ptr->MoveWindow(rec);
m_bEdit = false;
m_editPtrVec.clear();
m_butOK.EnableWindow(m_bEdit);
m_butApply.EnableWindow(m_bEdit);
}
}
bool COptionDlg::BindParentTreeNodeToFirstLeafNodeRefPage(HTREEITEM parentNode)
{
std::function<void(HTREEITEM parentNode, COptionDlg * pDld)> blendFucCall;
blendFucCall = [&blendFucCall](HTREEITEM parentNode, COptionDlg* pDld) mutable->bool{
if (parentNode != NULL)
{
//判断当前选中的节点是否有子节点
if (pDld->m_tree.ItemHasChildren(parentNode))
{
//如果有子节点
HTREEITEM hChildItem = pDld->m_tree.GetChildItem(parentNode);
if (hChildItem)
{
CMFCPropertyPage* pPropertyPage = (CMFCPropertyPage*)pDld->m_tree.GetItemData(hChildItem);
if (pPropertyPage)
{
pDld->m_tree.SetItemData(parentNode, (DWORD_PTR)pPropertyPage);
pDld->m_ptrMap.insert(std::pair<HTREEITEM, CMFCPropertyPage*>(parentNode, pPropertyPage));
return true;
}
else
{
blendFucCall(hChildItem, pDld);
}
return false;
}
}
}
};
blendFucCall(parentNode, this);
return true;
}
bool COptionDlg::CreateOptionTree(const char* nodeName, const char* sRefPage, HTREEITEM parentNode, HTREEITEM & currentNode, bool isCreateNotRefPageNode)
{
if (nodeName == nullptr) return false;
if(isCreateNotRefPageNode)
{
currentNode = m_tree.InsertItem(TR(Xre::StringAnsiToWide(nodeName)), parentNode);
}
else
{
if (sRefPage == nullptr) return false;
CMFCPropertyPage* pPropertyPage = MFCOption::GetSingleton()->GetOptionPage(Xre::StringAnsiToWide(sRefPage).c_str());
if (nullptr == pPropertyPage) return false;
if (pPropertyPage)
{
currentNode = m_tree.InsertItem(TR(Xre::StringAnsiToWide(nodeName)), parentNode);
//设置树结构体与分页面相关联
m_tree.SetItemData(currentNode, (DWORD_PTR)pPropertyPage);
m_ptrMap.insert(std::pair<HTREEITEM, CMFCPropertyPage*>(currentNode, pPropertyPage));
}
}
return true;
}
void COptionDlg::DeleteNonleafTreeNode(HTREEITEM hItem)
{
if (hItem != NULL)
{
//判断当前选中的节点是否有子节点,非叶子节点不包含数据
if (!m_tree.GetItemData(hItem) && !m_tree.ItemHasChildren(hItem))
{
m_tree.DeleteItem(hItem);
}
}
}
void COptionDlg::ReserveConfigFolderOptionDataToAppRootFolder()
{
//获取安装目录和用户目录的配置文件路径
string sFileName = "setting.json";
std::filesystem::path configPath = eZPlat::PlatformFramework::GetSingleton()->GetConfigFolderPath();
std::filesystem::path setupConfigPath(eZPlat::PlatformFramework::GetSingleton()->GetAppRootFolderPath()/*GetResourceFolderPath()*/);
//configPath /= "RINVR";
std::filesystem::path configFolderPath = configPath / "config/";
std::filesystem::path reloadPath = configFolderPath;
configFolderPath /= sFileName;
configFolderPath.make_preferred();
std::filesystem::path rootFolderPath = setupConfigPath / "config/";
rootFolderPath /= sFileName;
rootFolderPath.make_preferred();
char content[512];//一次获取字节数
std::ifstream ifs(rootFolderPath, std::fstream::in);//读取文件
std::ofstream ofs(configFolderPath, std::fstream::trunc | std::fstream::out);//写方式打开
if (ifs.is_open() && ofs.is_open())
{
while (!ifs.eof()) {
ifs.getline(content, 256);
ofs << content << endl;
}
ifs.close();
ofs.close();
}
//配置文件重加载,否则内存里还是启动时加载的数据
ConfigKit::Initialize(reloadPath);
}
bool COptionDlg::ShowTreeItemRefPage(void* data, int nType)
{
HTREEITEM treeItem = nullptr;
if (0 == nType)//name 树节点名称
{
const char* sName = nullptr;
try
{
sName = reinterpret_cast<const char*>(data);
}
catch (const std::exception&)
{
}
if (nullptr == sName) return false;
treeItem = FindTreeItemByName(m_tree.GetRootItem(), TR(Xre::StringAnsiToWide(sName)));
}
else if (1 == nType)//HTREEITEM 树节点
{
try
{
treeItem = reinterpret_cast<HTREEITEM>(data);
}
catch (const std::exception&)
{
}
}
if (treeItem)
{
std::map<HTREEITEM, CMFCPropertyPage*>::iterator it;
for (it = m_ptrMap.begin(); it != m_ptrMap.end(); it++)
{
(*it).second->ShowWindow(SW_HIDE);//全部属性窗口隐藏
}
CMFCPropertyPage* propPage = (CMFCPropertyPage*)m_tree.GetItemData(treeItem);
if (propPage)
{
propPage->ShowWindow(SW_SHOW);//显示点击的窗口
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_FRAMEWND_STATE_CHANGED, "OnReset", propPage);
}
return true;
}
return false;
}
HTREEITEM COptionDlg::FindTreeItemByName(HTREEITEM hroot, CString strtext)
{
HTREEITEM hfind;
//空树,直接返回NULL
if (NULL == hroot)
return NULL;
//否则遍历查找
while (hroot != NULL)
{
//当前节点即所需查找节点
if (m_tree.GetItemText(hroot) == strtext)
return hroot;
//查找当前节点的子节点
if (m_tree.ItemHasChildren(hroot))
{
hroot = m_tree.GetChildItem(hroot);
//递归调用查找子节点下节点
hfind = FindTreeItemByName(hroot, strtext);
if (hfind)
return hfind;
else //子节点中未发现所需节点,继续查找兄弟节点
hroot = m_tree.GetNextSiblingItem(m_tree.GetParentItem(hroot));
}
else {
//若无子节点,继续查找兄弟节点
hroot = m_tree.GetNextSiblingItem(hroot);
}
}
return hroot;
}
BOOL COptionDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// TODO: 在此添加额外的初始化
//发消息通知模块注册设置对话框
//初始时“确定、应用、默认”按钮不可用
m_bEdit = false;
m_editPtrVec.clear();
m_butOK.EnableWindow(m_bEdit);
m_butApply.EnableWindow(m_bEdit);
SetDialogLanguage(m_hWnd, IDD_MAINDLG, _T("MFCOption"));
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
void COptionDlg::SetModify(CMFCPropertyPage* propPtr)
{
m_bEdit = true;
bool hasPtr = false;
for (int i=0;i<m_editPtrVec.size();i++)
{
if (m_editPtrVec[i] == propPtr)
{
hasPtr = true;
break;
}
}
if (!hasPtr)
m_editPtrVec.push_back(propPtr);
m_butOK.EnableWindow(m_bEdit);
m_butApply.EnableWindow(m_bEdit);
}
BOOL COptionDlg::DestroyWindow()
{
// TODO: 在此添加专用代码和/或调用基类
if (m_pPropEffect)
{
delete m_pPropEffect;
m_pPropEffect = nullptr;
}
if (m_pPropContext)
{
delete m_pPropContext;
m_pPropContext = nullptr;
}
if (m_pPropCamera)
{
delete m_pPropCamera;
m_pPropCamera = nullptr;
}
if (m_pPropSave)
{
delete m_pPropSave;
m_pPropSave = nullptr;
}
if (m_pPropDisplay)
{
delete m_pPropDisplay;
m_pPropDisplay = nullptr;
}
if (m_pPropElemental)
{
delete m_pPropElemental;
m_pPropElemental = nullptr;
}
if (m_pPropCache)
{
delete m_pPropCache;
m_pPropCache = nullptr;
}
if (m_pPropRollBack)
{
delete m_pPropRollBack;
m_pPropRollBack = nullptr;
}
if (m_pPageScenes)
{
delete m_pPageScenes;
m_pPageScenes = nullptr;
}
return CDialogEx::DestroyWindow();
}
void COptionDlg::OnBnClickedButtonApply()
{
// TODO: 在此添加控件通知处理程序代码
//传apply消息
//数据交互由各页面自己完成
for (int i=0;i<m_editPtrVec.size();i++)
{
//m_editPtrVec[i]->OnApply();
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_FRAMEWND_STATE_CHANGED, "OnApply", m_editPtrVec[i]);
}
m_editPtrVec.clear();//所有修改了的页面交换数据完成后数组重置;
ConfigKit* ptr = ConfigKit::Using();//这里只保存setting.json文件,其他配置文件在发生修改后自行保存!!
if (ptr)
ptr->Save();
}
void COptionDlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
OnBnClickedButtonApply();
std::map<HTREEITEM, CMFCPropertyPage*>::iterator it;
for (it = m_ptrMap.begin(); it != m_ptrMap.end(); it++)
{
(*it).second->ShowWindow(SW_HIDE);
}
CDialogEx::OnOK();
}
void COptionDlg::OnBnClickedButtonDefault()
{
// TODO: 在此添加控件通知处理程序代码
//当点击恢复默认功能时,将安装文件夹下的配置数据拷贝到用户目录下形成默认数据。
ReserveConfigFolderOptionDataToAppRootFolder();
std::map<HTREEITEM, CMFCPropertyPage*>::iterator it;
for (it = m_ptrMap.begin(); it != m_ptrMap.end(); it++)
{
//(*it).second->OnReset();//恢复默认
//界面恢复默认
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_FRAMEWND_STATE_CHANGED, "OnReset", (*it).second);
//应用默认设置
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_FRAMEWND_STATE_CHANGED, "OnApply", (*it).second);
}
}
void COptionDlg::OnCancel()
{
// TODO: 在此添加专用代码和/或调用基类
if (m_editPtrVec.size())
{
int ret = MessageBox(TR(_T("Wish to waive this amendment?")), TR(L"Tips"),MB_YESNO);
if (ret == IDYES)
{
for (int i=0;i<m_editPtrVec.size();i++)
{
//发还原消息
//m_editPtrVec[i]->OnCancel();
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_FRAMEWND_STATE_CHANGED, "OnCancel", m_editPtrVec[i]);
}
m_bEdit = false;
m_editPtrVec.clear();//数组重置
m_butOK.EnableWindow(m_bEdit);
m_butApply.EnableWindow(m_bEdit);
}
else if (ret == IDNO)
return;
}
std::map<HTREEITEM, CMFCPropertyPage*>::iterator it;
for (it = m_ptrMap.begin(); it != m_ptrMap.end(); it++)
{
(*it).second->ShowWindow(SW_HIDE);
}
CDialogEx::OnCancel();
}
bool COptionDlg::HandlerMessage(Message& msg)
{
if (MSG_PLUGIN_OPTIONS_FRAMEWND_CREATED == msg.msgName)
{
auto parentPtr = msg.GetDataValue<void*>(0);
CWnd*& pWnd = *(CWnd**)(*parentPtr);
if (nullptr == pWnd) return false;
//创建属性页
CMFCPropertyPage* pPropPage = nullptr;
auto CreatePropPage = [&pPropPage](UINT nID, CWnd* pParentWnd) mutable->bool{
if (nullptr != pPropPage) return false;
switch (nID)
{
case IDD_PROPPAGE_ELEMENT://基础设置
{
pPropPage = new CPropElement();
pPropPage->Create(nID, pParentWnd);
break;
}
case IDD_PROPPAGE_SAVE://保存设置
{
pPropPage = new CPropSave();
pPropPage->Create(nID, pParentWnd);
break;
}
case IDD_PROPPAGE_EFFECT://效果设置
{
pPropPage = new CPropEffect();
pPropPage->Create(nID, pParentWnd);
break;
}
case IDD_PROPPAGE_DISPLAY://显示设置
{
pPropPage = new CPropDisplay();
pPropPage->Create(nID, pParentWnd);
break;
}
case IDD_PROPPAGE_CONTEXT://环境设置
{
pPropPage = new CPropContext();
pPropPage->Create(nID, pParentWnd);
break;
}
case IDD_PROPPAGE_CAMERA://相机设置
{
pPropPage = new CPropCamera();
pPropPage->Create(nID, pParentWnd);
break;
}
case IDD_PROPPAGE_CACHE://缓存设置
{
pPropPage = new CPropCache();
pPropPage->Create(nID, pParentWnd);
break;
}
case IDD_PROPPAGE_ROLLBACK://回退设置
{
pPropPage = new CPropRollBack();
pPropPage->Create(nID, pParentWnd);
break;
}
case IDD_PROPPAGE_SCENES://模拟设置
{
pPropPage = new CPageScenes();
pPropPage->Create(nID, pParentWnd);
break;
}
default:
break;
}
if (nullptr == pPropPage) return false;
CRect rec;
pPropPage->GetClientRect(&rec);
rec.left += 140;
rec.right += 140;
rec.top += 25;
rec.bottom += 25;
pParentWnd->ClientToScreen(&rec);
pPropPage->MoveWindow(rec);
pPropPage->ShowWindow(SW_HIDE);
return true;
};
if (!m_pPropElemental)//基础设置
{
pPropPage = nullptr;
if (CreatePropPage(IDD_PROPPAGE_ELEMENT, pWnd) && pPropPage)
{
m_pPropElemental = (CPropElement*)pPropPage;
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_PAGE_CREATED, "Option.PageBasic", pPropPage);
}
}
if (!m_pPropSave)//保存设置
{
pPropPage = nullptr;
if (CreatePropPage(IDD_PROPPAGE_SAVE, pWnd) && pPropPage)
{
m_pPropSave = (CPropSave*)pPropPage;
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_PAGE_CREATED, "Option.PageSave", pPropPage);
}
}
if (!m_pPropEffect)//效果设置
{
pPropPage = nullptr;
if (CreatePropPage(IDD_PROPPAGE_EFFECT, pWnd) && pPropPage)
{
m_pPropEffect = (CPropEffect*)pPropPage;
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_PAGE_CREATED, "Option.PageEffect", pPropPage);
}
}
if (!m_pPropDisplay)//显示设置
{
pPropPage = nullptr;
if (CreatePropPage(IDD_PROPPAGE_DISPLAY, pWnd) && pPropPage)
{
m_pPropDisplay = (CPropDisplay*)pPropPage;
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_PAGE_CREATED, "Option.PageDisplay", pPropPage);
}
}
if (!m_pPropContext)//环境设置
{
pPropPage = nullptr;
if (CreatePropPage(IDD_PROPPAGE_CONTEXT, pWnd) && pPropPage)
{
m_pPropContext = (CPropContext*)pPropPage;
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_PAGE_CREATED, "Option.PageContext", pPropPage);
}
}
if (!m_pPropCamera)//相机设置
{
pPropPage = nullptr;
if (CreatePropPage(IDD_PROPPAGE_CAMERA, pWnd) && pPropPage)
{
m_pPropCamera = (CPropCamera*)pPropPage;
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_PAGE_CREATED, "Option.PageCamera", pPropPage);
}
}
if (!m_pPropCache)//缓存设置
{
pPropPage = nullptr;
if (CreatePropPage(IDD_PROPPAGE_CACHE, pWnd) && pPropPage)
{
m_pPropCache = (CPropCache*)pPropPage;
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_PAGE_CREATED, "Option.PageCache", pPropPage);
}
}
if (!m_pPropRollBack)//回退设置
{
pPropPage = nullptr;
if (CreatePropPage(IDD_PROPPAGE_ROLLBACK, pWnd) && pPropPage)
{
m_pPropRollBack = (CPropRollBack*)pPropPage;
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_PAGE_CREATED, "Option.PageUndo", pPropPage);
}
}
if (!m_pPageScenes)//模拟设置
{
pPropPage = nullptr;
if (CreatePropPage(IDD_PROPPAGE_SCENES, pWnd) && pPropPage)
{
m_pPageScenes = (CPageScenes*)pPropPage;
PlatformFramework::GetSingleton()->GetMessageBus()->SendMessage(MSG_PLUGIN_OPTIONS_PAGE_CREATED, "Simulation.PagePosition", pPropPage);
}
}
return false;
}
if (MSG_PLUGIN_OPTIONS_PAGE_STATE_CHANGED == msg.msgName)//子页面状态改变
{
if (msg.msgDatas.empty()) return false;
CMFCPropertyPage* pProp = msg.GetDataObj<CMFCPropertyPage>(0);
if (nullptr == pProp) return false;
SetModify(pProp);
}
if (MSG_PLUGIN_OPTIONS_FRAMEWND_STATE_CHANGED == msg.msgName)//主窗体页面改变
{
const char* sAction = msg.GetDataObj<const char>(0);
if (nullptr == sAction) return false;
CMFCPropertyPage* pProp = msg.GetDataObj<CMFCPropertyPage>(1);
if (nullptr == pProp) return false;
if (0 == strcmp(sAction, "OnApply"))
{
pProp->OnApply();
}
else if (0 == strcmp(sAction, "OnCancel"))
{
pProp->OnCancel();
}
else if (0 == strcmp(sAction, "OnReset"))//OnReset
{
pProp->OnReset();
}
return false;
}
return true;
}
void COptionDlg::OnMove(int x, int y)
{
CDialogEx::OnMove(x, y);
// TODO: 在此处添加消息处理程序代码
std::map<HTREEITEM, CMFCPropertyPage*>::iterator it;
for (it = m_ptrMap.begin(); it != m_ptrMap.end(); it++)
{
CRect rec;
(*it).second->GetClientRect(&rec);
(*it).second->MoveWindow(x + 140, y + 25, rec.Width(), rec.Height());
}
}
void COptionDlg::OnNMClickListtree(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: 在此添加控件通知处理程序代码
CPoint lpt;//记录左键点击位置
GetCursorPos(&lpt);
m_tree.ScreenToClient(&lpt);
UINT UFlags;
HTREEITEM CurItem = m_tree.HitTest(lpt, &UFlags);
ShowTreeItemRefPage(CurItem, 1);
*pResult = 0;
}