作者:成曉旭
(需要完整源代碼請與作者聯系)
最近在做一個小程序,要求實現對多語言界面顯示支持功能,並且,界面顯示內容用戶能夠自己設置。
初步設計用INI文件來配置顯示內容,換一種語言的配置文件,就能夠更換整個系統的顯示語言。考慮到系統規模很小,周期又短,不想用太復雜的方案來解決這個問題,當參考了很多網上類似的設計和代碼,發現都不是很滿意。
主要問題在於:絕大多數基於INI文件配置這種簡單應有實現的代碼,都是針對組件ID固定加載,寫死了組件的ID號,比如:
strCaption = fileManager.GetString(section,"IDC_Stc_ListStudent","");
SetDlgItemText(IDC_Stc_ListStudent,strCaption);
strCaption = fileManager.GetString(section,"IDC_Stc_AllContent","");
SetDlgItemText(IDC_Stc_AllContent,strCaption);
這樣:界面組件越多,加載代碼越長;每新增一個顯示窗口,又必須復制、粘貼類似的代碼,根據組件ID常量值來修改相關的加載項。很是不爽!
初步設想是:設計統一、通用的窗口組件Caption設置方法,對給定的Frame或Dialog等Window容器組件內的所以組件進行遍歷,當增、減顯示組件不對語言包加載代碼產生影響,達到自適應界面組件語言包加載效果。
這樣就產生一個新問題:語言包配置文件中的Caption值如何跟相關的組件正確地一一對應?
好友文國慶建議:用XML文件來定義這種對應關系。這個想法觸動了我:反正就是一個[Key,Value]的數據,就用已經實現的INI配置文件也可以啊。於是所有問題解決!
具體設計是:語言包配置文件就直接設置成組件ID與組件顯示信息的Hash表,Key = Value的形式,比如:BtnOK組件的ControlID為“1003”,中文顯示Caption為“登錄”,語言包配置內容就是“1003=登錄”。
語言包的加載過程為2步實現:
首先,從語言包配置文件中,讀取所有配置的ID、Caption條目到Vector或者Array中。
其次,在遍歷指定窗口中所有組件時,每發現一個組件,就用其ID在已經加載的語言包數組中查找,找到就用配置的值修改組件Caption屬性;找不到,就認為是不需要動態配置,不做處理。
配置文件實例:
配置項解釋:Section:[Login Dialog]:界面窗口;等號左邊:窗口中需要設置其Caption屬性的組件ID;等號左邊:窗口中需要設置其Caption屬性的組件Caption值;
[Login Dialog]
1001 = 用戶帳號
1002 = 用戶密碼
1017 = 登 錄
1018 = 退 出
語言包配置信息加載代碼:
BOOL CLanguageManager::loadFromFile()
{
BOOL bRead=FALSE;
int i;
ItemContext temp;
CStringArray itemBuf,valueBuf;
bRead = fileManager.GetSectionValues("Main Window",itemBuf,valueBuf);
if(bRead)
{
for(i=0;i<itemBuf.GetSize();i++)
{
temp.uCtrlID = atoi(itemBuf.GetAt(i));
temp.strContext = valueBuf.GetAt(i);
m_vtContexts.push_back(temp);
}
}
itemBuf.RemoveAll();
valueBuf.RemoveAll();
bRead = fileManager.GetSectionValues("Login Dialog",itemBuf,valueBuf);
if(bRead)
{
for(i=0;i<itemBuf.GetSize();i++)
{
temp.uCtrlID = atoi(itemBuf.GetAt(i));
temp.strContext = valueBuf.GetAt(i);
m_vtContexts.push_back(temp);
}
}
return bRead;
}
讀取語言包配置信息:
BOOL CIniFile::GetSectionValues(CString Section, CStringArray &strItemBuf, CStringArray &strValueBuf)
{
BOOL bRead = FALSE;
ReadIniFile();//打開文件
if(bFileExist == FALSE || FileContainer.GetSize() < 0)
return bRead;//文件打開出錯或文件為空,返回默認值
int i = 0;
int iFileLines = FileContainer.GetSize();
CString strline,str;
while(i<iFileLines)
{
strline = FileContainer.GetAt(i++);
strline.TrimLeft();
if(strline.GetLength()<=0)
continue; //跳過空行
if(strline.Left(2)=="//")
continue; //跳過注釋行
if(strline.GetAt(0)=='[')//查找Section,第一個必須為[
{
str=strline.Left(strline.Find("]"));//去掉]右邊
str=str.Right(str.GetLength()-str.Find("[")-1);//去掉[左邊
str.TrimLeft();
str.TrimRight();
if(Section == str)//找到Section
{
while(i<iFileLines)
{
strline = FileContainer.GetAt(i++);
strline.TrimLeft();
if(strline.GetLength()<=0)
continue; //跳過空行
if(strline.GetAt(0)=='[')
return bRead;//如果到達下一個[],即找不到,返回默認值
if(strline.Left(2)=="//")
continue; //跳過注釋行
str = strline.Left(strline.Find("="));//去掉=右邊
str.TrimLeft();
str.TrimRight();
//保存等號左邊項
strItemBuf.Add(str);
str=strline.Right(strline.GetLength()-strline.Find("=")-1);//去掉=左邊
str.TrimLeft();
str.TrimRight();
//保存等號右邊項
strValueBuf.Add(str);
bRead = TRUE;
}
//當前Section遍歷結束
}
//沒有找到Section
}
//當前行遍歷結束
}
return bRead;
}
修改指定組件Caption屬性代碼:
BOOL CLanguageManager::setControlCaption(CWnd * pCtrl, UINT ctrlID)
{
BOOL isOK=FALSE;
for(int i=0;i<m_vtContexts.size();i++)
{
isOK = (m_vtContexts[i].uCtrlID==ctrlID);
if(isOK)
{
pCtrl->SetWindowText(m_vtContexts[i].strContext);
break;
}
}
return isOK;
}
遍歷設置指定窗口所有組件Caption屬性代碼:
void CLanguageManager::setCaptionForWindow(CWnd * pWnd)
{
char *buf = new char[512];
//枚舉對話框中所有組件
pWnd->SetWindowText("JKLJKL");
UINT ctrlID = pWnd->GetDlgCtrlID();
setControlCaption(pWnd,ctrlID);
CWnd *pCtrl = pWnd->GetWindow(GW_CHILD);
while(pCtrl!=NULL)
{
ctrlID = pCtrl->GetDlgCtrlID();
setControlCaption(pCtrl,ctrlID);
pCtrl = pCtrl->GetNextWindow();
}
}