65.9K
CodeProject 正在变化。 阅读更多。
Home

设置存储 - 轻松处理程序设置

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (12投票s)

2004年4月13日

6分钟阅读

viewsIcon

96416

downloadIcon

2063

轻松处理程序设置,支持多种存储方案和多种框架。

引言

程序设置是许多人都实现过的功能。就我自己而言,我以多种方式实现了它,具体取决于框架(MFC、ATL 等)以及需要处理的数据类型。还有一个问题是设置存储在哪里?在注册表、文件中、XML 还是数据库中?

本文提供了一个解决方案,只需少量代码或零代码即可处理程序设置,同时考虑到您正在使用的框架以及您期望的存储方案。

背景

通常,我创建一个带有数据成员(设置)和一对 Load/Save 函数的类。它看起来像这样:

class CMySettings
{
public:
    bool Load(...);
    bool Save(...);

    CString WindowName;
    int Height;
    int Width;
    COLORREF BackColor;
};

当然,Load/Save 的内容需要手动编写。例如,可以使用注册表,调用 RegCreateKey()RegSetValue() 等函数。当处理集合(数组)时,Load/Save 函数会收到更多代码,每个集合都有一个循环。这会变得很麻烦。

在另一个程序中,甚至在同一个程序中使用不同的设置类时,我都会为新的数据成员重写 Load/Save 函数。事实上,我复制/粘贴以前函数中的代码并稍微修改一下。我就是不想再经历这个过程了。这既耗时又容易出错,因为需要复制/粘贴。

寻找设置类

在重新发明轮子之前,我想“肯定有人已经做了一个很棒的类了”。于是我开始了寻找。我在网上找到了一些类,有些功能和我上面描述的差不多,有些甚至更糟。最后,我在 CodeProject 上找到了 CRegSettings

CRegSettings 是 Magomed G. Abdurakhmanov 制作的。这是一个非常出色的类。它使用 MFC 或 ATL 映射(MSG、COM、DDX)之类的映射机制。这里的映射是为了避免编写 Load/Save 函数。这正是我想要的。

这是一个设置类的示例

class CMySettings : public CRegSettings
{
public:
    CString WindowName;
    int Height;
    int Width;
    COLORREF BackColor;

    BEGIN_REG_MAP(CMySettings)
        REG_ITEM(WindowName, "No Document")
        REG_ITEM_REQUIRE(Height)
        REG_ITEM_REQUIRE(Width)
        REG_ITEM(BackColor, 0xFF8800)
    END_REG_MAP()
};

在程序的某个地方,您会调用 Load/Save 函数。例如:m_Settings.Load();

您可能会问:“好吧,既然你找到了宝藏,那这篇文章还有什么意义?”因为注册表并非我总是希望我的设置存储的地方。

有什么新功能?

我的系统的主要思想是将设置类分成两个类。一个类负责数据成员和所有字段的加载/保存操作,第二个类负责针对特定存储机制的单个项的读写。

目标是让设置类与项的存储方式分离。这使您可以在运行时选择使用哪种存储机制。只需付出很小的努力,您也可以自己为系统添加新的存储方案。所有繁琐的工作都已经完成,因此您可以专注于真正的代码。

如何简单使用?

我将在这里解释定义一个管理某些设置的类的不同步骤。

步骤 1

让您的类从 CSettings 派生。请注意,CSettings 封装在 Mortimer 命名空间中。添加代表您设置的数据成员。

这是一个例子。

#include <MSettingsStorage.h>
using namespace Mortimer;

class CMySettings : public CSettings
{
public:
    CString WindowName;
    int Height;
    int Width;
    COLORREF BackColor;
    CDWordArray Measures;
};

第二步

为每个数据成员添加一个映射条目。

每种处理的数据类型或数据类型组都提供三个映射宏,以下是简单类型的宏:

  • SETTING_ITEM(<数据成员名称>)
  • SETTING_ITEM_REQUIRE(<数据成员名称>)
  • SETTING_ITEM_DEFAULT(<数据成员名称>, <默认值的表达式 for default value>)

标准宏只是将处理过的数据成员名称作为参数。如果在 CSettings::Load() 时项未能读取其数据,则整个 Load() 过程将静默继续,并可能返回成功(如果没有任何必需项生成错误)。

REQUIRE 宏会改变这种行为,因为它的名称暗示该项是必需的,因此读取失败将导致 Load() 过程返回失败。请注意,根据 CSettingsStorage::ContinueOnError(),其他项可能会被读取。

DEFAULT 宏的行为类似于标准宏,但当项读取失败时,项数据将被替换为给定的默认值。

在保存时(CSettings::Save()),任何这些宏都可能失败,并返回错误状态。根据 CSettingsStorage::ContinueOnError() 函数的值,其他项可能会进一步保存。

这是每个宏处理的类型列表。

编辑者注释 - 为防止滚动,一些单词被连字符分隔
SETTING_ITEM 所有整数类型
LONGLONG, ULONGLONG
long, unsigned long
int, unsigned int
short, unsigned short
char (signed), unsigned char (作为数字处理)
简单类型
float, double, bool
字符串
CString, std::string, std::wstring
SETTING_ITEM_SZ 零终止字符串
SETTING_ITEM_BINARY 任何数据

SETTING_IT-EM_BINARYPTR

指针后面的任何数据
SETTING_ITEM_ARRAY CSettings 子类的 CArray
ATL CSimpleArray<>
MFC CArray<>
MFC CxxxArray
SETTING_ITEM_STL CSettings 子类的 STL 集合
std::vector
std::deque
std::list
SETTING_IT-EM_ALONE_ARRAY 独立类型的 CArray
ATL CSimpleArray<>
MFC CArray<>
MFC CxxxArray
SETTING_ITEM_ALONE_STL 独立类型的 STL 集合
std::vector
std::deque
std::list

独立类型是 SETTING_ITEM 处理的所有类型。

带映射的示例

class CMySettings : public CSettings
{
public:
    CString WindowName;
    int Height;
    int Width;
    COLORREF BackColor;
    CDWordArray Measures;

    BEGIN_SETTING_MAP(CMySettings)
        SETTING_ITEM_DEFAULT(WindowName, "MyApp")
        SETTING_ITEM_REQUIRE(Height)
        SETTING_ITEM_REQUIRE(Width)
        SETTING_ITEM(BackColor)
        SETTING_ITEM_ALONE_ARRAY(Measures, DWORD)
    END_SETTING_MAP()
};

请注意 SETTING_ITEM_ALONE_ARRAY 的附加参数,您必须传递元素的类型。

步骤 3

现在您已经定义了您的设置类,您必须加载和保存这些设置。这在您的代码中的某个位置通过调用设置类的 Load()Save() 函数来完成。

为了能够调用它们,您必须传递一个 CSettingsStorage 对象。该对象与底层存储机制建立连接。

让我们用一个使用注册表的示例

#include <MSettingsStorageRegistry.h>
using Mortimer::CSettingsStorageRegistry;

#define APP_REGISTRY_KEY "Software\\Company\\MyApp"

class CMyApp : public CSomeFrameworkApp
{
public:

    ...

    void OnInit()
    {
        CSettingsStorageRegistry Stg(HKEY_CURRENT_USER, APP_REGISTRY_KEY);
        m_MySettings.Load(Stg);
    }

    void OnChangeSettings()
    {
        CSettingsDialog Dlg;
        if (Dlg.DoModal() == IDOK)
        {
            CSettingsStorageRegistry Stg(HKEY_CURRENT_USER, APP_REGISTRY_KEY);
            m_MySettings.Save(Stg);
        }
    }

    ...

protected:
    CMySettings m_MySettings;

};

为清晰起见,已省略所有错误处理。嗯,就是这么简单。不需要别的了。

如何实现新的存储方案?

我创建了三个管理存储方案的类

  • CSettingsStorageRegistry 用于处理注册表。
  • CSettingsStorageIniFile 用于处理 .ini 文件。
  • CSettingsStorageMSXMLFile 用于处理 XML 文件(通过使用 msxml.dll)。

您可以通过从抽象类 CSettingsStorage 派生新类并为每种处理的类型实现 SaveLoadItem() 函数来添加自己的存储方案。有关详细信息,请参阅该类的文档。您也可以研究提供的类。

结论

您现在应该可以更轻松地处理您的设置,即使是您自己的类型或集合。要获得真实示例,请阅读演示项目,它向您展示了一些高级用法。提供了三个示例:一个用于 MFC 框架,一个用于 ATL/WTL,最后一个是用于纯 Win32 使用 STL 的简单示例。

请随意使用和修改此软件。也欢迎提出建议和评论。

敬请期待下一篇文章。我将介绍如何使用此设置系统与对话框结合使用来编辑它们,当然是以最少的代码。

历史

  • 2004 年 5 月 28 日
    • 添加了处理 XML 的类:CSettingsStorageMSXML*
  • 2004 年 5 月 14 日
    • 错误修复。
    • CSettingsStorage 已重构以添加标准行为。

      警告:INI 文件存在兼容性问题。集合的“Count”值现在存储在“Root.KeyName”-“Count”下,而不是“Root”-“KeyName.Count”。

    • 添加了 SETTING_ITEM_ALONE_* 宏。

      感谢 CoolVini 先生提出这个想法。

  • 2004 年 4 月 16 日
    • 更新了源代码。
  • 2004 年 4 月 5 日
    • 文章已提交。
  • 2003 年 12 月 31 日
    • 首次发布。
© . All rights reserved.