利用INI文件實現界面無閃爍多語言切換

一、引言

越來越多的程序使用了多國語言切換,雖然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文件實現界面無閃爍多語言切換

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編程中運行時類型信息的應用技巧,同時掌握如何在程序運行時動態創建菜單。