利用INI文件实现界面无闪烁多语言切换
熊斌 landragon@tom.com
一、引言
越来越多的程序使用了多国语言切换,虽然DELPHI自带多语言包的添加和配置,但是那种方法在切换语言时界面会出现闪烁,而且实现起来很麻烦,这里我介绍给大家的是利用INI文件来读取界面的语种文字,用这种方法,不但简单易行,而且在切换的时候不会出现界面的闪烁。
二、InI文件格式说明
1、语种定义
[Language] //语言
DefaultLang = ChineseGB //界面的默认语种(InI文件名,不带扩展名)
Language = 语种 //菜单标题
ChineseGB = 简体中文 //相应语种文件名
ChineseBig = 繁体中文
English = 英文
⑴ 如果DefaultLang项没有设置,则为语种目录中所寻找到的InI的第一个文件,做为当前界面语种,所有Ini文件都要设置成一样;
⑵ Language为菜单界面上所显示的标题,不设置时,默认为‘Language’;
⑶ 如果Language以下项没有设置,则相应语种的菜单标题为文件名(不带扩展名)。
2、窗体界面定义
[FormName] //需要变换的窗口
.Caption = 演示窗体 //本窗口的属性,表示为:[窗口名].属性名 = 语言
Button1.Hint = 按钮说明 //控件的属性,表示为:控件名.属性名 = 语言
ComboBox1.Items = 简体中文||英文 //其中“||”为分隔符
注:⑴ 在InI文件中每行“//”开始的说明文字不要输入;
⑵ 对继承自TStrings的属性,其各个项目之间需用“||”分隔;
⑶ 设计思想来源于KeyZhang所编写的《多语言切换函数 For Delphi》组件。
三、实现原理
RTTI的基本设计思想是在运行时访问给定数据类型的信息,该类型可以是类也可以是简单的Pascal数据类型(int、string、char等)。本文便是通过RTTI在运行期,获取组件的相应属性,并进行动态设置,达到多语言切换功能。
利用INI文件实现界面无闪烁多语言切换
熊斌 landragon@tom.com
四、各个函数的定义及实现
1、程序运行时,我们查找语种目录下所有的语言配置文件(*.ini),为了达到这个目的,我编写了如下的函数搜索目录下所有的语言配置文件的文件名,然后将文件名去掉ini扩展名保存返回:
procedure SearchLanguagePack (lngStrings: TStrings);
var
DosError: Integer;
SearchRec: TsearchRec;
begin
DosError := FindFirst (ExtractFilePath (ParamStr (0)) 'Language\*.ini', faAnyFile, SearchRec);
while DosError = 0 do
begin
{ 返回的文件名并去掉末尾的.ini字符 }
lngStrings.Add (ChangeFileExt (SearchRec.Name, ''));
DosError := FindNext (SearchRec);
end;
FindClose(SearchRec);
end;
2、实现界面的多语言切换函数:
function ChangeLanguage (Form: TForm; Lngfile: string): Boolean;
var
INIF: TIniFile; //InI文件
SL: TStringList; // InI文件中读出的相应窗体的信息
TmpSL: TStringList; //将每一行信息,以“=”分隔的信息
Key: string; //“=”左边的信息
Value: string; //“=”右边的信息
CompName: string; //组件名
CompProp: string; //组件属性名
I: Integer;
AComponent: TComponent; //相应组件
procedure Split (S: string; SplitChar: string; R: TStrings); //将字符串分隔
var
P, L, C: Integer;
begin
R.Clear;
L := Length (S);
//确定分隔符的长度,以便删除字符串中的相应字符
C := Length (SplitChar);
//循环分隔字符串
repeat
P := Pos (SplitChar, S);
//如果在字符串中没有找到分隔符,说明为最后一个
if P = 0 then C := 1;
R.Add (Copy (S, P C, L));
Delete(S, P, L);
until P = 0;
end;
//设置相应属性值
procedure SetPropertyValue (AComponent: TComponent; sProperty, sValue: string);
var
PropInfo: PPropInfo;
AStrings: TStringList;
begin
if AComponent <> nil then
begin
PropInfo := GetPropInfo(AComponent, sProperty);
if PropInfo <> nil then
begin
case PropInfo.PropType^.Kind of
tkString, tkLString: //字符串属性
SetStrProp(AComponent, PropInfo, sValue);
tkInteger: //序数属性
SetOrdProp(AComponent, PropInfo, StrToInt(sValue));
else
if PropInfo.PropType^.Name = 'TStrings' then //列表属性
begin
AStrings := TStringList.Create;
try
Split (sValue, '||', AStrings);
SetOrdProp (AComponent, PropInfo, Integer (AStrings));
finally
AStrings.Free;
end; // end try
end; // end if PropInfo.PropType^.Name = 'TStrings'
end; // end case
end; // end if PropInfo <> nil
end; // end if AComponent <> ni
end;
begin
Result := True;
SL := TStringList.Create;
try
INIF := TIniFile.Create (LngFile); // 打开语种初始化文件
try
INIF.ReadSectionValues (Form.Name, SL); // 读取相应窗体信息
finally
INIF.Free;
end;
TmpSL := TStringList.Create;
try
for I := 0 to SL.Count - 1 do // 分隔每一行信息
begin
Split (SL.Strings[I], '=', TmpSL);
Key := Trim (TmpSL.Strings[1]);
CompName := Trim (Copy(Key, 1, Pos ('.', Key) - 1));
if CompName = '' then // 确定组件
AComponent := Form
else
AComponent := Form.FindComponent (CompName);
CompProp := Trim (Copy (Key, Pos ('.', Key) 1, Length (Key)));
Value := Trim (TmpSL.Strings[0]);
SetPropertyValue (AComponent, CompProp, Value); // 设置属性
end;
finally
TmpSL.Free;
end;
finally
SL.Free;
end;
end;
利用INI文件实现界面无闪烁多语言切换
熊斌 landragon@tom.com
3、在Form显示的事件中添加代码,将目录下所有的语言文件名加入菜单项:
procedure TForm1.FormShow(Sender: TObject);
var
lngStrings, tmpStrings: TStrings;
I: Integer;
lngMenu: TMenuItem;
Filename: string;
IniF: TIniFile;
begin
lngStrings := TString.Create;
try
SearchLanguagePack (lngStrings); // 查找语种文件
if lngStrings.Count = 0 then // 没有找到语种文件直接退出
exit;
Filename := ExtractFilePath (ParamStr (0)) 'Language\' lngStrings.Strings[0] '.ini';
IniF := TIniFile.Create (Filename);
Filename := IniF.ReadString('Language', ' DefaultLang ', '');// 设置为默认语种
if Filename <> '' then
begin
Filename := ExtractFilePath (ParamStr (0)) 'Language\' Filename '.ini';
if FileExists (Filename) then
begin
IniF.Free;
IniF := TIniFile.Create (Filename);
end;
end;
lngMenu := TMenuItem.Create (self);
lngMenu.Name := 'Language';
lngMenu.Caption := IniF.ReadString('Language', 'Language', 'Language');
MainMenu1.Items[MainMenu1.Items. Count].Add (lngMenu); // 语种菜单放到主菜单的最后
tmpStrings := TString.Create;
try
for I := 0 to lngStrings.Count – 1 do // 动态创建菜单项
begin
lngMenu := TMenuItem.Create (self);
lngMenu.Name := lngStrings.Strings[I]; // 将菜单项的名称赋予文件名
lngMenu.Caption := IniF.ReadString('Language', lngStrings.Strings[I], lngStrings.Strings[I]');
lngMenu.onClick := lngMenuClick; // 菜单事件
Language.Insert (Language.Items.Count, lngMenu); // 添加到语种菜单的最后一项上
end;
finally
tmpStrings.Free;
end;
finally
lngStrings.Free;
end;
ChangeLanguage (Form1, IniF); // 改变界面语种
IniF.Free;
end;
4、窗体中语种菜单的事件响应:
procedure TForm1.lngMenuClick (Sender: TObject);
var
Filename: string;
begin
if Sender is TMenuItem then
begin
// 确定语种文件,菜单项名称为语种文件名
Filename := ExtractFilePath (ParamStr (0)) 'Language\' (Sender as TMenuItem).Name '.ini';
ChangeLanguage (Form1, Filename);
end;
end;
五、结束语
通过本次学习,让读者初步了解Delphi编程中运行时类型信息的应用技巧,同时掌握如何在程序运行时动态创建菜单。