序列化 Delphi 应用程序配置的最佳方法是什么?

发布于 2024-08-02 08:33:36 字数 545 浏览 2 评论 0原文

我会自己回答这个问题,但如果您比我更快或者您不喜欢我的解决方案,请随时提供您的答案。我刚刚提出这个想法,想对此发表一些意见。

目标:一个可读的配置类(如 INI 文件),但无需编写(并在添加新配置项后进行调整)加载和保存方法。

我想创建一个像

TMyConfiguration = class (TConfiguration)
  ...
  property ShowFlags : Boolean read FShowFlags write FShowFlags;
  property NumFlags : Integer read FNumFlags write FNumFlags;
end;

Calling TMyConfiguration.Save (继承自 TConfiguration) 这样的类,应该创建一个像

[Options]
ShowFlags=1
NumFlags=42

问题: 这样的文件,最好的方法是什么?

I will answer this question myself, but feel free to provide your answers if you are faster than me or if you don't like my solution. I just came up with this idea and would like to have some opinions on that.

Goal: a configuration class that is readable (like an INI-file) but without having to write (and adapt after a new configuration item has been added) the load and save methods.

I want to create a class like

TMyConfiguration = class (TConfiguration)
  ...
  property ShowFlags : Boolean read FShowFlags write FShowFlags;
  property NumFlags : Integer read FNumFlags write FNumFlags;
end;

Calling TMyConfiguration.Save (inherited from TConfiguration) should create a file like

[Options]
ShowFlags=1
NumFlags=42

Question: What is the best way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

墨小墨 2024-08-09 08:33:36

这是我提出的解决方案。

我有一个基类

TConfiguration = class
protected
  type
    TCustomSaveMethod = function  (Self : TObject; P : Pointer) : String;
    TCustomLoadMethod = procedure (Self : TObject; const Str : String);
public
  procedure Save (const FileName : String);
  procedure Load (const FileName : String);
end;

Load 方法如下所示(相应的 Save 方法):

procedure TConfiguration.Load (const FileName : String);
const
  PropNotFound = '_PROP_NOT_FOUND_';
var
  IniFile : TIniFile;
  Count : Integer;
  List : PPropList;
  TypeName, PropName, InputString, MethodName : String;
  LoadMethod : TCustomLoadMethod;
begin
  IniFile := TIniFile.Create (FileName);
  try
    Count := GetPropList (Self.ClassInfo, tkProperties, nil) ;
    GetMem (List, Count * SizeOf (PPropInfo)) ;
    try
      GetPropList (Self.ClassInfo, tkProperties, List);
      for I := 0 to Count-1 do
        begin
        TypeName  := String (List [I]^.PropType^.Name);
        PropName  := String (List [I]^.Name);
        InputString := IniFile.ReadString ('Options', PropName, PropNotFound);
        if (InputString = PropNotFound) then
          Continue;
        MethodName := 'Load' + TypeName;
        LoadMethod := Self.MethodAddress (MethodName);
        if not Assigned (LoadMethod) then
          raise EConfigLoadError.Create ('No load method for custom type ' + TypeName);
        LoadMethod (Self, InputString);
        end;
    finally
      FreeMem (List, Count * SizeOf (PPropInfo));
    end;
  finally
    FreeAndNil (IniFile);
  end;

基类可以为 delphi 默认类型提供加载和保存方法。然后,我可以为我的应用程序创建一个配置,如下所示:

TMyConfiguration = class (TConfiguration)
...
published
  function  SaveTObject (P : Pointer) : String;
  procedure LoadTObject (const Str : String);
published
  property BoolOption : Boolean read FBoolOption write FBoolOption;
  property ObjOption : TObject read FObjOption write FObjOption;
end;

自定义保存方法的示例:

function TMyConfiguration.SaveTObject (P : Pointer) : String;
var
  Obj : TObject;
begin
  Obj := TObject (P);
  Result := Obj.ClassName;  // does not make sense; only example;
end;       

This is my proposed solution.

I have a base class

TConfiguration = class
protected
  type
    TCustomSaveMethod = function  (Self : TObject; P : Pointer) : String;
    TCustomLoadMethod = procedure (Self : TObject; const Str : String);
public
  procedure Save (const FileName : String);
  procedure Load (const FileName : String);
end;

The Load methods look like this (Save method accordingly):

procedure TConfiguration.Load (const FileName : String);
const
  PropNotFound = '_PROP_NOT_FOUND_';
var
  IniFile : TIniFile;
  Count : Integer;
  List : PPropList;
  TypeName, PropName, InputString, MethodName : String;
  LoadMethod : TCustomLoadMethod;
begin
  IniFile := TIniFile.Create (FileName);
  try
    Count := GetPropList (Self.ClassInfo, tkProperties, nil) ;
    GetMem (List, Count * SizeOf (PPropInfo)) ;
    try
      GetPropList (Self.ClassInfo, tkProperties, List);
      for I := 0 to Count-1 do
        begin
        TypeName  := String (List [I]^.PropType^.Name);
        PropName  := String (List [I]^.Name);
        InputString := IniFile.ReadString ('Options', PropName, PropNotFound);
        if (InputString = PropNotFound) then
          Continue;
        MethodName := 'Load' + TypeName;
        LoadMethod := Self.MethodAddress (MethodName);
        if not Assigned (LoadMethod) then
          raise EConfigLoadError.Create ('No load method for custom type ' + TypeName);
        LoadMethod (Self, InputString);
        end;
    finally
      FreeMem (List, Count * SizeOf (PPropInfo));
    end;
  finally
    FreeAndNil (IniFile);
  end;

The base class could provide load and save methods for the delphi default types. I can then create a configuration for my application like this:

TMyConfiguration = class (TConfiguration)
...
published
  function  SaveTObject (P : Pointer) : String;
  procedure LoadTObject (const Str : String);
published
  property BoolOption : Boolean read FBoolOption write FBoolOption;
  property ObjOption : TObject read FObjOption write FObjOption;
end;

Example of a custom save method:

function TMyConfiguration.SaveTObject (P : Pointer) : String;
var
  Obj : TObject;
begin
  Obj := TObject (P);
  Result := Obj.ClassName;  // does not make sense; only example;
end;       
不奢求什么 2024-08-09 08:33:36

我的所有应用程序都使用 XML 作为配置方式。它是:

  • 灵活的
  • 未来功能证明
  • 易于使用任何文本阅读器阅读
  • 非常易于在应用程序中扩展。无需类修改

我有一个 XML 库,可以非常轻松地读取或修改配置,甚至无需注意缺失值。现在,如果速度是问题或需要不断读取某些值,您还可以将 XML 映射到应用程序内部的类,以便更快地访问。

我发现其他配置方法的可选性要差得多:

  • Ini 文件:没有深入的结构,
  • 注册表的灵活性要差得多:远离它。

I use XML for all my application as means of configuration. It is:

  • flexible
  • future feature proof
  • easy to read with any text reader
  • very easy to extend in application. No class modifications needed

I have an XML library that makes it extremely easy to read or modify configuration, without even having to watch for missing values. Now you can also map the XML to a class inside application for faster access if speed is the issue, or certain values are read constantly.

I find other configuration methods far less optional:

  • Ini file: no in depth structure, far less flexible
  • registry: just keep away from that.
没有伤那来痛 2024-08-09 08:33:36

我的首选方法是在我的全局接口单元中创建一个接口:

type
  IConfiguration = interface
    ['{95F70366-19D4-4B45-AEB9-8E1B74697AEA}']
    procedure SetConfigValue(const Section, Name,Value:String);
    function GetConfigValue(const Section, Name:string):string;
  end;

然后该接口在我的主窗体中“公开”:

type
  tMainForm = class(TForm,IConfiguration)
  ...
  end;

大多数时候实际实现不在主窗体中,它只是一个占位符,我使用实现关键字将界面重定向到主窗体拥有的另一个对象。这样做的要点是配置的责任被委托了。每个单元并不关心配置是否存储在表、ini 文件、xml 文件,甚至(喘息)注册表中。这允许我在使用全局接口单元的任何单元中执行如下调用:

var
  Config : IConfiguration;
  Value : string;
begin
  if Supports(Application.MainForm,IConfiguration,Config) then
    value := Config.GetConfiguration('section','name');
  ...      
end;

所需要做的就是将 FORMS 和我的全局接口单元添加到我正在处理的单元中。而且因为它不使用主窗体,如果我决定稍后将其重用于另一个项目,我不必做任何进一步的更改......它就可以工作,即使配置存储方案完全不同。

我的一般偏好是创​​建一个表(如果我正在处理数据库应用程序)或一个 XML 文件。如果是多用户数据库应用程序,那么我会创建两个表。一个用于全局配置,另一个用于用户配置。

My preferred method is to create an interface in my global interfaces unit:

type
  IConfiguration = interface
    ['{95F70366-19D4-4B45-AEB9-8E1B74697AEA}']
    procedure SetConfigValue(const Section, Name,Value:String);
    function GetConfigValue(const Section, Name:string):string;
  end;

This interface is then "exposed" in my main form:

type
  tMainForm = class(TForm,IConfiguration)
  ...
  end;

Most of the time the actual implementation is not in the main form, its just a place holder and I use the implements keyword to redirect the interface to another object owned by the main form. The point of this is that the responsibility of configuration is delegated. Each unit doesn't care if the configuration is stored in a table, ini file, xml file, or even (gasp) the registry. What this DOES allow me to do in ANY unit which uses the global interfaces unit is make a call like the following:

var
  Config : IConfiguration;
  Value : string;
begin
  if Supports(Application.MainForm,IConfiguration,Config) then
    value := Config.GetConfiguration('section','name');
  ...      
end;

All that is needed is adding FORMS and my global interfaces unit to the unit I'm working on. And because it doesn't USE the mainform, if I decide to later reuse this for another project, I don't have to do any further changes....it just works, even if the configuration storage scheme is completely different.

My general preference is to create a table (if I'm dealing with a database application) or an XML file. If it is a multi-user database application, then I will create two tables. One for global configuration, and another for user configuration.

欢烬 2024-08-09 08:33:36

基本上,您需要一个解决方案来序列化给定的对象(在您的情况下是 ini 文件的配置)。有现成的组件,您可以开始查找 此处此处

Basically you are asking for a solution to serialize a given object (in your case a configurations to ini files). There are ready made components for that and you can start looking here and here.

瞳孔里扚悲伤 2024-08-09 08:33:36

不久前,我为同一任务编写了一个小单元 - 在 xml 文件中保存/加载应用程序的配置。

检查我们的免费软件 SMComponent 库中的 Obj2XML.pas 单元:
http://www.scalabium.com/download/smcmpnt.zip

Sometime ago I wrote small unit for same task - to save/load the configuration of application in xml-file.

Check the Obj2XML.pas unit in our freeware SMComponent library:
http://www.scalabium.com/download/smcmpnt.zip

夜未央樱花落 2024-08-09 08:33:36

这适用于 Java。

我喜欢使用 java.util.Properties 用于读取配置文件或属性文件的类。我喜欢的是,您可以按照上面显示的相同方式将文件与行一起放置(键=值)。
此外,它使用 #(井号)作为注释行,有点像许多脚本语言。

所以你可以使用:

ShowFlags=true
# this line is a comment    
NumFlags=42

等等

那么你就只有这样的代码:

Properties props = new Properties();
props.load(new FileInputStream(PROPERTIES_FILENAME));
String value = props.getProperty("ShowFlags");
boolean showFlags = Boolean.parseBoolean(value);

就这么简单。

This would be for Java.

I like to use the java.util.Properties class for reading in config files or properties files. What I like is that you put your file with lines in the same way you showed above (key=value).
Also, it uses a # (pound sign) for a line thats a comment, kind of like a lot of scripting languages.

So you could use:

ShowFlags=true
# this line is a comment    
NumFlags=42

etc

Then you just have code like:

Properties props = new Properties();
props.load(new FileInputStream(PROPERTIES_FILENAME));
String value = props.getProperty("ShowFlags");
boolean showFlags = Boolean.parseBoolean(value);

Easy as that.

眼前雾蒙蒙 2024-08-09 08:33:36

Nicks 的回答(使用 Java Properties)有一个观点:这种在应用程序各部分之间读取和传递配置的简单方法不会引入对特殊配置类的依赖。简单的键/值列表可以减少应用程序模块之间的依赖关系,并使代码重用更加容易。

在 Delphi 中,简单的基于 TStrings 的配置是实现配置的简单方法。例子:

mail.smtp.host=192.168.10.8    
mail.smtp.user=joe    
mail.smtp.pass=*******

Nicks answer (using Java Properties) has a point: this simple way to read and pass configuration around between parts of the application does not introduce dependencies on a special configuration class. A simple key/value list can reduce the dependencies between application modules and make code reuse easier.

In Delphi, a simple TStrings-based configuration is an easy way to implement a configuration. Example:

mail.smtp.host=192.168.10.8    
mail.smtp.user=joe    
mail.smtp.pass=*******
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文