[译]配置器
By robot-v1.0
本文链接 https://www.kyfws.com/applications/the-configurator-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 16 分钟阅读 - 7570 个词 阅读量 0[译]配置器
原文地址:https://www.codeproject.com/Articles/286898/The-Configurator
原文作者:nortee
译文由本站 robot-v1.0 翻译
前言
This will allow you to create a configuration file in either INI or XML format minus all the hard work in Delphi. 这样,您就可以创建INI或XML格式的配置文件,而无需在Delphi中进行所有繁琐的工作.
介绍(Introduction)
这个小库将允许您在Delphi中创建/更新/保存/加载配置文件.提供的示例将能够创建INI和XML配置文件.另外,我还提供了一些额外的编码技巧,可以帮助您理解编码原理和Delphi设计/编码原理的某些方面.由于有些人可能未安装Delphi,因此我提供了两个已编译源代码的可执行文件,一个用于INI配置,一个用于XML配置实现.(This little library will allow you to create/update/save/load configuration files in Delphi. The examples provided will be able to create INI and XML configuration files. As an extra, I have also included some extra bits of coding titbits that will help you understand certain aspects of coding principles and Delphi design/coding principles. Since some of you may not have Delphi installed, I provided two executables of the compiled source, one for the INI configuration and one for the XML configuration implementations.)
背景(Background)
我和你们大多数人一样,编写了很多软件.而且,如果您像我一样在游戏中待了足够长的时间,那么您就会发现有必要编写/读取应用程序在其功能中使用的配置设置.您还可以使用几个不同的配置文件格式选项:(I, like most of you, have written a lot of software. And if you’ve been in the game long enough like me, then you have found the need to write/read configuration settings that your application uses in its functionality. You would also have used several different configuration file format options at your disposal:)
-
INI文件(INI files)
-
XML文件(XML files)
-
Windows注册表(The Windows registry)
-
持久性的某种或其他形式,以您喜欢的邪恶方式起作用(一次接管一个可编程的蓝光播放器)(Some or other format of persistance that works in a way that you like in your own evil way (taking over the world one programmable Blu-Ray player at a time)) 考虑所有这些方法,我发现了两点很重要:(Considering all these methods, I have found two things that are important:)
-
Microsoft并不完全宽恕您使用Windows注册表.不是因为(显然)他们不允许您这样做,而是因为使用您的应用程序的用户可能实际上没有写或什至从注册表项中读取所需信息的权限.(Microsoft doesn’t exactly condone you using the Windows registry. Not because they don’t allow you to do it (obviously), but because of the fact that the user using your application may not actually have rights to write or even read the information you need from the registry entry.)您发现多少次(尤其是在诸如Vista和Windows 7等较新的平台上)无法访问某些东西,这仅仅是因为邪恶的网络管理员不允许您访问特定的数据.(How many times have you found that (especially on the newer platforms like Vista and Windows 7) that you cannot access certain things and that was only because of the fact that the evil network administrator doesn’t allow you to access that specific bit of data.)
-
您不想浪费时间弄清楚并一遍又一遍地写/读应用程序的配置设置所需的所有内容.(You don’t want to waste your time figuring out and writing out all the things that you need to write/read your application’s configuration settings over and over again.) 基于这些观察,我创建了一组文件(或小的库),希望在创建,保存,加载和更新Delphi应用程序的配置设置方面对这种情况有所帮助.我创建了一个基本配置对象和两个后代类,它们将创建一个INI配置文件或一个XML配置文件(巧合的是,与(Based on those observations, I have created a set (or little library) of files that I hope will help with this situation with regards to creating, saving, loading and updating your Delphi application’s configuration settings. I have created a base configuration object and two descendant classes which will create either an INI configuration file or an XML configuration file (which coincidentally, is very similar to the).config(.config)在Visual Studio中创建的文件).(file which is created in Visual Studio).) 重要说明:使用INI配置对象时,公开方法中的" AParentSections"和" ASectionName"参数合并在一起以形成实际的节名称.这是有意的,因为INI文件的局限性在于不能像XML文件中那样具有嵌套设置.就个人而言,我更喜欢使用XML配置对象.(IMPORTANT: When using the INI configuration object, the “AParentSections” and “ASectionName” parameter in the exposed methods are merged together to form the actual section name. This was intentional as the limitation of INI files is that there cannot be nested settings like you can have in XML files. Personally, I prefer using the XML configuration object.) 配置对象允许您为以下类型创建配置条目(The configuration object allows you to create configuration entries for the following types)(如果有人希望我添加任何其他类型的设置来保存,请告知我,我将相应地更新该文章)((If anyone would like me to add any other kind of settings to save, let me know and I will update the article accordingly)):(:)
-
串(string)
-
整数(integer)
-
布尔值(boolean)
-
真实(real)(注意:由于TDateTime实际上是实型的特殊类型,因此您也可以保存TDateTime值.)((NOTE: you can also save TDateTime values since a TDateTime is actually a special type of real type.))
该代码将向您显示什么(What This Code Will Show You)
当涉及Pascal/Delphi时,此代码将介绍许多不同的编码方案,包括如何使用(This code will introduce a lot of different coding scenarios when it comes to Pascal/Delphi, including how to work with) abstract
类.你们当中有些人会知道的.为了大家的利益,我将解释一些我所做的事情:(classes. Some of you people will know it. For the benefit of everyone, I will explain a few things about what I have done:)
- 遗产(Inheritance)和(and)多态性(Polymorphism):这是您定义一个类以及该类中的其他几个后代类的设计原理.在这里,您可以定义一个或多个后代类,并且这些类在调用相同的公共函数时会执行不同的操作.(: This is the design principle in which you define one class, and several other descendant classes from that one class. This is where you can define one or more descendant classes and those classes do different things when you call the same common function(s).)
- 类参考(Class References):您可以在其中实例化具体类,仅使用变量作为实例化对象.(: Where you can instantiate a concrete class, using only a variable as the instantiation object.)
- 方法重载(Method overloading)注意:参数列表是您可以在代码中用相同的方法名称定义多个方法的地方,而这些重载方法唯一不同的地方是参数列表.(: Where you can define several methods in your code with the same method name and the only thing that is different for these overloaded methods, is the parameter list.)
- 何时使用(When to use)虚拟(virtual)和(and)抽象(abstract)方法.(methods.)
- 什么时候使用(When to use the)受保护的(protected)关键词.(keyword.)
- 模板设计模式(The Template Design Pattern):基本上,这是简化创建基类和后代类的过程的许多面向对象设计原则之一.(: Which is basically one of many object-orientated design principles to simplify the process of creating base and descendant classes.)
- 自定义事件(Custom events):您将学习如何创建自己的事件(例如您自己的事件)(: You will learn how to create your own events (like your very own)
OnClick
要么(or)OnCreate
事件),但更酷.(events), but even more cooler.) - 关于Delphi VCL中的隐藏方法,您可能还不知道一些很酷的小技巧.(Some cool little tricks that you might not have known about hidden methods in the VCL of Delphi.) 好吧,那是一个大嘴巴,所以让我开始解释一下…(Okay, that was a mouthful, so let me get started by explaining…)
继承,多态,虚拟,抽象,模板设计,受保护,类引用和Delphi事件概念…(The inheritance, polymorphism, virtual, abstract, Template Design, protected, class references and Delphi event concepts…)
你们中的大多数人(如果不是所有人)都了解继承和多态性的核心概念,但是因为这是我的文章,并且可能有一些使用Delphi进行编码的新人,因此请继续.多态性的概念被描述为从同一祖先派生的两个(或多个)不同的类,其中一个或多个通用方法在被后代类调用时将产生不同的结果.“用英语,这意味着如果我定义以下祖先基类:(Most, if not all of you understand the core concept of what inheritance and polymorphism is, but because this is my article and there are probably some new people to coding in Delphi, here goes. The concept of polymorphism is described as two (or more) different classes derived from the same ancestor where one or more common methods will produce different results when called by the descendant classes”. In English, that means if I define the following ancestor base class:)
// in the interface section...
TSaw = class(TObject)
private
FBusy : Boolean;
procedure MyOnClickEvent(ASender : TObject);
protected
procedure Do_Cut; virtual; abstract;
procedure Initialise; virtual;
procedure Finalise; virtual;
public
procedure Cut;
public
property Busy : Boolean read FBusy;
end;
TMyRefSaw = class of TSaw;
// in the implementation section...
procedure TSaw.Cut;
begin
if not(FBusy) then
begin
FBusy := True;
try
Do_Cut;
finally
FBusy := False;
end;
end
else
begin
// Show some or other message to indicate that the TSaw is still busy cutting...
end;
end;
procedure TSaw.Initialise;
begin
FBusy := False;
end;
procedure TSaw.Finalise;
begin
// This, for the time being is still blank,
// because we don't need to clear up anything yet...
end;
procedure TSaw.MyOnClickEvent(ASender : TObject);
begin
// Perform some or other custom type of coding here...
end;
现在,我们在这里面临着自己之上的一切.这种类为您(首先?)提供了模板设计模式理想情况下想要实现的目标的一瞥.此类可以做某些事情.在这种情况下,对象会知道并可以设置对象何时忙于(Now, here we are faced with what we have above us. This kind of class provides you with your (first?) glimpse as to what a Template Design Pattern ideally would like to achieve. This class can do certain things. In this case, the object knows, and can set, when it is busy in the) Cut
方法.但是,它不知道如何削减.因此,我们有一个模板,但是我们需要在后代类中对其进行优化以告诉它如何(method. However, it does not know how to cut. So, we have a template, but we need to refine it in our descendant classes to tell it how to) Do_Cut
.这带给我为什么(. Which brings me to why the) Initialise
和(and) Finalise
只有(only has the) virtual;
定义旁边的关键字,以及(keyword next to the definition, and the) Do_Cut
有(has the) virtual; abstract;
在它的旁边.前两个表示将在基类中提供它们的"基本"实现.我们想要这样做,因为,例如(next to it. The first two mean that there will be a ‘base’ implementation of them that will be available in the base class. We want to do this, because, as in the case of) Initialise
我们需要确保(we need to ensure that) FBusy
设定为(is set to) False
.到那个时刻(. When it comes to) Do_Cut,
该基类对如何执行该方法一无所知,因此应始终在基类中实现它.这是两个后代类的两个示例:(this base class knows absolutely nothing about how to perform that method, so it should always be implemented within the base classes. Here are two samples of what two descendant classes would look like:)
// in the interface section...
TRipSaw = class(TSaw)
private
FWood : TWood;
protected
procedure Do_Cut; override;
procedure Initialise; override;
procedure Finalise; override;
end;
// in the implementation section...
procedure TRipSaw.Do_Cut;
begin
// Here, We will perform some or other actions that work with the FWood object...
end;
procedure TRipSaw.Initialise;
begin
inherited Initialise;
FWood := TWood.Create; // instantiate the TWood class...
end;
procedure TRipSaw.Finalise;
begin
FreeAndNil(FWood); // Free the TWood class...
end;
// in the interface section...
THackSaw = class(TSaw)
private
FMetal : TMetal;
protected
procedure Do_Cut; override;
procedure Initialise; override;
procedure Finalise; override;
end;
// in the implementation section...
procedure THackSaw.Do_Cut;
begin
// Here, We will perform some or other actions that world with the FMetal object...
end;
procedure THackSaw.Initialise;
begin
inherited Initialise;
FMetal := TMetal.Create; // instantiate the TWood class...
end;
procedure THackSaw.Finalise;
begin
FreeAndNil(FMEtal); // Free the TMeta=l class...
end;
由于以下事实,我选择了上面的示例:(I chose the example above because of the following facts:)
- 链锯和钢锯都是锯(A ripsaw and a hacksaw are both saws)
- 细锯可以切割木材,但不能切割金属(A ripsaw cuts wood but cannot cut metal)
- 钢锯切割金属,但不切割木材(A hacksaw cuts metal but not wood)
现在,我将这些方法放在(Now, I put those methods in the)
protected
关键字上下文,因为我希望能够重写后代类的方法,同时将方法的上下文保留在类中,但仍将其公开以访问后代类.就像一个比(keyword context, because I wanted to be able to override the descendant class' methods while keeping the context of the methods within the class, but still leaving them exposed for access to the descendant classes. It’s like a level higher than)private
,但比较酷,因为它只能在子孙类中访问,而在创建该类的实例时不能访问.(, but is cooler because it is accessible within only the descendant classes, and NOT when an instance of the class which is created.) 我们都用过(We’ve all used the)OnClick
要么(or)OnCreate
Delphi中的活动.我们通常只需双击事件查看器和BOOM!我们已经为此创建了代码,我们可以做我们需要做的事情.现在,假设我们有一个(events in Delphi. We usually just double click on the event viewer and BOOM! we have the code for this created for us and we can do what we need to do. Now, Let’s assume that we have a)TButton
在我们创建了(on a form where we have created the)TRipSaw
要么(or)THackSaw
对象.我们不想双击(objects. We don’t want to double click on the)OnClick
的事件(event of the)TButton
(在此示例中,我们将其称为((in this example, we will call it)MyButton
),因为我们已经定义了自己的事件(), because we already defined our own event)MyOnClickEvent
用于单击按钮时.在这种情况下,我们只需要这样做:(for when the button is clicked. In this case, we only need to do this:)
MyButton.OnClick := MySawInstance.MyOnClickEvent;
对于概念的最后一部分,我想介绍一下:类引用.这是Delphi中的小技巧,它允许您使用类类型的变量来创建类.所以,当我创建一个从(For the final part of the concepts, I want to outlay: Class references. This is little trick in Delphi that allows for you to create a class using a variable of the class type. So, when I am creating a class descended from) TSaw
,我将执行以下操作:(, I will do the following:)
var
Saw_Ref : TMyRefSaw;
MySaw : TSaw;
begin
SawRef := THackSaw; // or Saw := TRipSaw;
MySaw := Saw_Ref.Create; // which will create either of the THackSaw
// or the TRipSaw classes :)
end;
trick俩,嘿?与往常一样,如果任何人需要更深入地解释其工作原理,只需发布一条消息,我将在可行的地方进行解释.这带我去…(Cool trick, hey? As always, if anyone needs a deeper explanation of how this works, just post a message and I will explain where I can. Which brings me on to…)
使用代码(Using the Code)
使用代码实际上非常容易.要创建后代之一的实例,构造函数要求您为其提供文件扩展名和定界符类型(用于父节).对于文件扩展名参数,您只需要使用"(Using the code is actually very easy. To create an instance of one of the descendants, the constructor requires that you provide it with a file extension name and a delimiter type (for the parent sections). For the file extension parameter, you just need to use something like “) ini
" 要么 “(” or “) xml
“或者就我而言”(” or in my case “) config
“.您不得输入”(”. You must not put in the “) .
“值,因为配置程序已经为您完成了.对于delimiter参数,这将用于(” value as the configurator does that for you already. For the delimiter parameter, this will be for the) AParentSections
定界符.默认情况下,这是正常的”(delimiter. By default, it is the normal “) ,
“值,因此您无需更改该值.您需要做的就是添加基本文件((” value so you should not need to change that. All you need to do is include the base file ()uBaseConfigurator.pas(uBaseConfigurator.pas))以及包含您希望在其中包含的实际配置后代类的文件() and the file that contains the actual configuration descendant class that you would like to have in the) uses
要在其中使用的单位的子句(clause of the unit you want to use it in)
出于本文的目的,我创建了一个基本表单,该表单具有配置器的基本加载和读取(表单的位置,宽度和高度)以及两个派生表单,其中一个表单做了更多的加载和保存(主表单) )和其他(在触发事件时显示的内容).在包含以下内容的主表格中(For the purposes of this article, I created a base form which has basic loading and reading of the configurator (the position, width and height of the form) and two derived forms where the one does a bit more loading and saving (the main form) and the other nothing else (the one which displays when the events are fired). In the main form which contains the) string
网格,我用(grid, I used the) AParentSections
参数告诉配置对象我要保存的配置必须嵌套.的(parameter to tell the configuration object that the configuration that I want to save must be nested. The) AParentSections
参数基本上只是一个定界(parameter is basically just a delimited) string
值(或(value (or a) TStrings
目的).配置对象还会对传递给它的参数进行检查,因此如果您将其发送为空(object). The configuration object also performs a check on the parameters that you pass into it, so if you send in empty) string
的值(values for the) ASectionName
和(and) AValueName
参数,它将引发异常,并且由于您的节和/或值名称中不能包含空格,因此它将用下划线替换空格.这将确保配置对象将能够正确保留您的配置设置.这是使用XML配置对象时创建的配置文件的示例:(parameters, it will raise an exception and since you cannot have spaces in your section and/or value names, it will replace the spaces with underscores. This will ensure that the configuration object will be able to persist your configuration settings correctly. Here is an example of the configuration file created when using the XML configuration object:)
<Configurator>
<LogEventActionsForm>
<Left>873</Left>
<Top>259</Top>
<Width>551</Width>
<Height>346</Height>
</LogEventActionsForm>
<MainForm>
<Left>294</Left>
<Top>364</Top>
<Width>490</Width>
<Height>335</Height>
<Edit1>Edit1</Edit1>
<Edit2>0</Edit2>
<DateTimePicker1>40866.6819596412</DateTimePicker1>
<CheckBox1>True</CheckBox1>
<Edit3>0</Edit3>
<StringGrid1>
<Columns>
<Col0>64</Col0>
<Col1>64</Col1>
<Col2>64</Col2>
<Col3>64</Col3>
<Col4>64</Col4>
</Columns>
</StringGrid1>
</MainForm>
</Configurator>
这是使用INI配置对象时创建的配置文件的示例:(Here is an example of the configuration file created when using the INI configuration object:)
[LogEventActionsForm]
Left=655
Top=501
Width=687
Height=346
[MainForm]
Left=118
Top=415
Width=490
Height=335
Edit1=Edit1
Edit2=0
DateTimePicker1=40866.6819596412
CheckBox1=1
Edit3=0
[MainForm_StringGrid1_Columns]
Col0=64
Col1=64
Col2=64
Col3=64
Col4=64
兴趣点(Points of Interest)
我提供了自己的自定义事件,如果您感兴趣,可以尝试根据我的设计方式创建自己的自定义事件.请仔细阅读代码,以了解我如何实现这些代码,并随时提出与它们有关的任何问题.我将参加一个自定义事件,以使您对发生的事情有基本了解:(I included my own custom events, and if you are interested, you can try creating your own custom events based on how I designed mine. Please look through the code to see how I implemented these and feel free to ask any questions pertaining to them. I will go one of the custom events just to give you a base idea of what happens:)
TOnBeforeManipulateSection = procedure(
ASender : TConfigurator;
AParentSection : TStrings;
var AValueName : string) of object;
我将其定义为能够在对象添加,删除或检查配置文件中的某个部分之前触发的一种方法(如名称所示,以及在哪里实现此事件的最佳用法).因此,在我的(基础)课程中,我定义了…(Which I defined as a means of being able to fire (as the name indicates and the optimal use of where to implement this event) just before the object adds, removes or checks on a section in the configuration file. Hence in my (base) class I define…)
// Please check the code for the full implementation...
property OnBeforeAddSection : TOnBeforeManipulateSection read FOnBeforeAddSection
write FOnBeforeAddSection;
property OnBeforeRemoveSection : TOnBeforeManipulateSection read FOnBeforeRemoveSection
write FOnBeforeRemoveSection;
property OnBeforeHasSection : TOnBeforeManipulateSection read FOnBeforeHasSection
write FOnBeforeHasSection;
并实施(其中一个),我这样做:(and to implement (one of them), I do this:)
if Assigned(OnBeforeAddSection) then
OnBeforeAddSection(Self, LParentSections, LSectionName);
Do_AddSection(LParentSections, LSectionName);
伙计们,这就是您实现自己的自定义事件的方式.在我提供的示例中,我仅实现了一些内置于配置器中的自定义事件.(And that, folks, is how you implement your own custom events. In the example that I provide, I only implement a handful of the custom events built into the configurator.)
另外,配置类还将为服务应用程序正确创建配置文件.您问为什么这是额外的好处?答案很简单:服务应用程序的执行方式与普通应用程序不同,因此,当您使用(As an added bonus, the configuration class will correctly create the configuration file for service applications as well. Why is this an added bonus, you ask? The answer is simple: A service application is executed in a different way to normal applications and as such, when you use) ExtractFilePath(Application.ExeName)
,它不会给您期望的结果(应用程序的实际路径),而是(, it does NOT give you the result you expect (the actual path of the application), instead it will be the)**%Windows%\ System32(%Windows%\System32)**夹.这就是为什么在基本配置类的构造函数中,您会找到减轻此特定问题的代码的原因:(folder. Which is why in the constructor of the base configuration class, you will find the code which mitigates this particular problem:)
constructor TConfigurator.Create(
const AConfigurationFileExtension : string;
const AParentSectionsDelimiter : Char);
var
LApplicationFileName : array[0..MAX_PATH] of Char;
begin
inherited Create;
FConfigurationFileExtenstion := AConfigurationFileExtension;
FParentSectionsDelimiter := AParentSectionsDelimiter;
// This allows to get the executing module's full name whether
it's a normal or service application...
FillChar(LApplicationFileName, SizeOf(LApplicationFileName), #0);
GetModuleFileName(HInstance, LApplicationFileName, MAX_PATH);
FModuleName := LApplicationFileName;
FModulePath := ExtractFilePath(FModuleName);
FRootName := ChangeFileExt(ExtractFileName(FModuleName), '');
FSettingsFileName := ChangeFileExt(FModuleName,
'.'+ConfigurationFileExtenstion);
Initialise;
end;
您可以从基类创建自己的派生类((You can create your own derived class from the base class () TConfigurator
),如果您需要将配置选项保留为另一种可以满足您特定需求的格式.您需要做的就是重写以下方法:() if you need to persist your configuration options to another kind of format that meets your particular needs. All you need to do is override the following methods:)
// Initialization and finalization of your specific objects for
your implementation...
procedure Initialise; virtual;
procedure Finalise; virtual;
//For creating/adding/removing sections of the configuration
file...
procedure Do_AddSection(
const ASectionName : string;
const AParentSections : TStrings); overload; virtual;
abstract;
procedure Do_RemoveSection(
const ASectionName : string;
const AParentSections : TStrings); overload; virtual;
abstract;
function Do_HasSection(
const ASectionName : string;
const AParentSections : TStrings) : Boolean; overload;
virtual; abstract;
// For writing the associated values to the configuration
file...
procedure Do_WriteConfigurationValue(
const AParentSections : TStrings;
const ASectionName : string;
const AValueName : string;
const AValue : string); overload; virtual; abstract;
procedure Do_WriteConfigurationValue(
const AParentSections : TStrings;
const ASectionName : string;
const AValueName : string;
const AValue : Integer); overload; virtual; abstract;
procedure Do_WriteConfigurationValue(
const AParentSections : TStrings;
const ASectionName : string;
const AValueName : string;
const AValue : Boolean); overload; virtual; abstract;
procedure Do_WriteConfigurationValue(
const AParentSections : TStrings;
const ASectionName : string;
const AValueName : string;
const AValue : Real); overload; virtual; abstract;
// For reading the associated values from the configuration
file...
function Do_ReadConfigurationValue(
const AParentSections : TStrings;
const ASectionName : string;
const AValueName : string;
const ADefaultValue : string) : string; overload; virtual;
abstract;
function Do_ReadConfigurationValue(
const AParentSections : TStrings;
const ASectionName : string;
const AValueName : string;
const ADefaultValue : Integer) : Integer; overload; virtual;
abstract;
function Do_ReadConfigurationValue(
const AParentSections : TStrings;
const ASectionName : string;
const AValueName : string;
const ADefaultValue : Boolean) : Boolean; overload; virtual;
abstract;
function Do_ReadConfigurationValue(
const AParentSections : TStrings;
const ASectionName : string;
const AValueName : string;
const ADefaultValue : Real) : Real; overload; virtual;
abstract;
注意(NOTE):唯一不需要覆盖的方法是(: The only methods which do NOT need to get overridden are the) Initialise
和(and) Finalise
方法.如果您不覆盖其他方法,则会得到一个(methods. If you do not override the other methods, you will get an) EAbstractError
异常,当您尝试调用未重写的方法时.如果您对如何创建自己的实现有些困惑,只需看一下我派生的子类,您就可以从那里弄清楚它了:).(exception when you try to call the method(s) which you did not override. If you are a little confused as to how to create your own implementation, just look at the descendant classes which I derived and you should be able to figure it out from there :).)
要求(Requirements)
此示例要求您具有Delphi 7或更高版本(您可能将其与Delphi 6一起使用,但是由于我没有安装该版本,因此可能必须调整一两个步骤才能使此演示正常工作.有关此问题,请告诉我,我们将在可能的情况下为您提供帮助),尽管您可能需要更改它的一部分才能与AnsiString/UnicodeString一起使用,具体取决于您对Delphi 2009及更高版本的默认选择是什么,但我并不认为那将是一个问题.(This example requires that you have Delphi 7 or higher (you could probably wing it with Delphi 6, but since I don’t have that installed, you may have to tweak one or two things to get this demo to work. If you have any issues regarding this, let me know and I will assist where possible), although you might need to change part of it to work with AnsiString/UnicodeString depending on what your default options are for Delphi 2009 and higher, but I don’t really think that will be a problem.)如果有人可以确认这一点,请告诉我,我将适当地更新本文.(If anyone out there can confirm this, please let me know and I will update this article appropriately.)
历史(History)
- 20(20)日(th)2011年11月:初始版本(November, 2011: Initial version)
- 22(22)nd(nd)2011年11月:全面修改了基本代码,并对原始文章进行了改组/重写(November, 2011: Overhaul of the base code and reshuffle/rewrite of the initial article)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
Pascal Windows 新闻 翻译