# 16.3 字符編碼和Unicode
這個世界上有太多太多的字符,遠超過了一個字節(8bit)可能容納的256個數目.為了顯式超過256個字符以外的其它字符,一個新的手段被增加進來,那就是字符編碼和字符集(更新和更好的"Unicode"解決方案,我們也將很快談到.).
因此,到底字節161代表什么字符,是由當前使用的字符集決定的.在ISO 8859-1(Latin-1)字符集中,它代表的是一個倒寫的感嘆號,而在ISO 8859-2字符集中,則代表的是字母a(Aogonek).
當你在一個窗口上繪制字符的時候,系統必須知道你使用的編碼,這成為字體編碼,也就是所謂的字符集.創建一個沒有指定字符集的字體意味著使用默認編碼,這在大多數系統上都是沒有問題的,因為大多數人都在使用支持本國語言的系統.
但是,如果你確定某些字符使用的是不同的編碼(比如ISO 8859-2),在創建字體的時候,你應該指定這種編碼,如下所示:
```
wxFont myFont(10, wxFONTFAMILY_DEFAULT, wxNORMAL, wxNORMAL,
false, wxT("Arial"), wxFONTENCODING_ISO8859_2);
```
否則,在一個西文系統ISO 8859-1中,字符將不能被正確顯式.
有時候可能我們無法找到一個合適的滿足某種編碼的字體,這種情況下,我們可以嘗試使用一種代替字體,不過你需要將要顯式的字體轉換成那種代替字體對應的編碼方式.下面的代碼演示了應該怎樣作.一個字符串text的編碼為enc,準備用字體facename顯示.同時下面的代碼也演示了wxCSConv的用法:
```
// 我們有一段'enc'編碼的文本,我們希望用字體
// 'facename'顯示.
//
// 首先,我們必須確定這個字體可以顯示這種編碼
wxString text; // 編碼方式為 'enc'
if (!wxFontMapper::Get()->IsEncodingAvailable(enc, facename))
{
// 不能支持這種編碼,需要查找替代編碼.
// 能支持某種替代編碼嗎?
wxFontEncoding alternative;
if (wxFontMapper::Get()->GetAltForEncoding(enc, &alternative,
facename, false))
{
// 我們找到了替代編碼方案'alternative',
// 因此我們進行編碼的轉換,轉換成alternative.
wxCSConv convFrom(wxFontMapper::GetEncodingName(enc));
wxCSConv convTo(wxFontMapper::GetEncodingName(alternative));
text = wxString(text.wc_str(convFrom), convTo) ;
// 然后創建alternative編碼的字體
wxFont myFont(10, wxFONTFAMILY_DEFAULT, wxNORMAL, wxNORMAL,
false, facename , alternative);
dc.SetFont(myFont);
}
else
{
// 不能找到完美替代編碼;嘗試有損耗的編碼方案
// ISO 8859-1 (7-bit ASCII)
wxFont myFont(10, wxFONTFAMILY_DEFAULT, wxNORMAL, wxNORMAL,
false, facename, wxFONTENCODING_ISO8859_1);
dc.SetFont(myFont);
}
}
else
{
// OK,這個字體可以支持這個編碼.
wxFont myFont(10, wxFONTFAMILY_DEFAULT, wxNORMAL, wxNORMAL,
false, facename, enc);
dc.SetFont(myFont);
}
// 最后,我們使用選擇的字體繪制可能已經經過編碼轉換的字符串.
dc.DrawText(text, 100, 100);
```
轉換數據
前面的代碼演示了將一組字節流從一種編碼轉換為另外一種編碼的方法.這種轉換可以有兩種方法,第一種是使用 wxEncodingConverter類,這種方法是不被推薦的(可能在后續版本種被淘汰的方法),你不應該在新的代碼種使用這種方法,除非你的編譯器不支持wchar_t結構. 推薦使用第二種方法,字符集轉換(使用基于wxMBConv的wxCSConv).
wxEncodingConverter
這種方法只能支持部分的字符集,但是如果你的編譯器不支持wchar_t結構,這是你唯一的選擇,轉換方法如下:
```
wxEncodingConverter converter(enc, alternative, wxCONVERT_SUBSTITUTE);
text = converter.Convert(text);
```
wxCONVERT_SUBSTITUTE標記表明允許轉換過程中如果找不到嚴格對應的字符,允許存在信息損失, 這將導致帶重音符號的字母變成普通的字母或者短破折號和長破折號統一用"-"來代替等.
wxCSConv (wxMBConv)
Unicode的解決方案的核心是,它使用16bit或者甚至是32bit的wchar_t結構來代表一個字符,因此它可以把全世界所有的字符用一種編碼表示.這意味著你不需要處理任何編碼轉換之類的問題除非你需要處理老的8-bit格式數據,前面我們已經說過,8bit的數據必須和字符集一起使用才有意義.
即使你沒有把wxWidgets編譯成Unicode模式(這種模式下,所有的字符串都是Unicode編碼格式),只要你的系統支持,你還是可以使用它進行編碼轉換.轉換的方法是,先把你的字符串從它的編碼轉換成Unicode編碼,然后再從Unicode編碼轉換成目標編碼. wxString類也使用這種方法來提供編碼轉換支持.要記住的是:非Unicode版本的wxWidgets中的wxString對象采用的是8bit 的方法保存字符串,因此它自己并不知道其內部的數據使用的是什么編碼方式.
如果想把wxString轉換成Unicode,你需要使用wxString::wc_str函數,這個函數采用一個多字節轉換類作為它的參數,這個參數告訴非Unicode版本的wxString它內部的字符串是采用什么編碼方式的,但是在Unicode版本的wxWidgets中, 這個參數被忽略,因為wxString內部的編碼已經是Unicode了.
在Unicode版本中,我們可以直接使用wx_str返回的字符串了,但是在非Unicode版本中,我們還需要將其轉換為我們可以支持的編碼方式convTo,因此在下面的代碼中,在Unicode版本中,convTo也將被忽略:
```
text = wxString(text.wc_str(convFrom), convTo);
```
可以看到字符集編碼比字體字體編碼更常使用,因此有時候你需要通過下面的代碼將字體編碼名字裝換成字符集編碼名字:
```
wxFontMapper::GetEncodingName(fontencoding);
```
這就是上面例子中下面這一部分代碼的含義:
```
wxCSConv convFrom(wxFontMapper::GetEncodingName(enc));
wxCSConv convTo(wxFontMapper::GetEncodingName(alternative));
text = wxString(text.wc_str(convFrom) , convTo) ;
```
有時候你需要直接使用8bit的字節流而不是使用wxString,這可以通過使用wxCharBuffer類獲得,下面我們看看這一行代碼:
```
wxCharBuffer output = convTo.cWC2MB(text.wc_str(convFrom));
```
如果你的輸入數據不是一個字符串而也是一個8bit的數據流(比如也是一個wxCharBuffer),你可以使用下面的轉換方式:
```
wxCharBuffer output = convTo.cWC2MB(convFrom.cMB2WC(input));
```
wxWidgets定義了一些全局的類用于實現字符轉換,比如wxConvISO8859_1是一個對象,而wxConvCurrent 是一個指針,指向當前標準C的locale指定的編碼類.另外還有一些wxMBConv的子類用來優化特定的編碼轉換任務,比如 wxMBConvUTF7,wxMBConvUTF8, wxMBConvUTF16LE/BE和wxMBConvUTF32LE/BE.其中后兩個被重定義為wxMBConvUFT16/32,它使用機器本身的字節序.更多信息請參考wxWidgets手冊中的"wxMBConv Classes Overview"小節.
轉化來自外部的臨時緩存數據
正如我們剛剛討論的那樣,轉換類允許你很方便的把一種字符集轉換為另外一種字符集.然而,大多數的轉換結果為一個新創建的字符串或者一個臨時緩存.有時候我們需要將轉換的結果保存起來已備以后使用,這種情況下我們可以把轉換的結果復制到一個獨立的存儲區.
假設我們想在兩個電腦之間通過socket傳遞字符串.我們首先應該在字符串采用的編碼上取得一致.否則,平臺默認的編碼可能把傳遞的字符串搞的一團糟.在我們的這個例子中,我們把發送出去的字符串先轉換成UTF-8編碼,在接收的部分,在將UTF-8編碼的字符串轉換成系統默認的字符串.
下面的代碼演示了怎樣將符合本地編碼的字符串轉換成UTF-8,將轉換結果存儲在一個char*指針中,然后通過socket發送出去,接收的電腦再將收到的字符串從UTF-8轉換成它自己的電腦上的本地編碼.
```
// 將本地編碼字符串轉換成UTF-8編碼
const wxCharBuffer ConvertToUTF8(wxString anyString)
{
return wxConvUTF8.cWC2MB( anyString.wc_str(*wxConvCurrent) ) ;
}
// 將UTF-8編碼的字符串轉換成本地編碼字符串
wxString ConvertFromUTF8(const char* rawUTF8)
{
return wxString(wxConvUTF8.cMB2WC(rawUTF8), *wxConvCurrent);
}
// 測試以下這兩個轉換函數
void StringConversionTest(wxString anyString)
{
// 轉化成UTF-8編碼并保存在wxCharBuffer中.
const wxCharBuffer bUTF8 = ConvertToUTF8(anyString);
// wxCharBuffer可以隱式的轉換成char*.
const char *cUTF8 = bUTF8 ;
// 重建字符串
wxString stringCopy = ConvertFromUTF8(cUTF8);
// 因為是同一個電腦,這兩個字符串應該是完全相同的.
wxASSERT(anyString == stringCopy);
}
```
幫助文件
你需要為每個支持的語言制作一份幫助文件.你的幫助文件控制器在初始化的時候將指定幫助文件的名稱.你可以使用wxLocale::GetName來獲取語言相關的名稱,也可以直接使用前面介紹的_()宏以便獲得語言相關的名稱.比如:
```
m_helpController->Initialize(_("help_english"));
```
如果你使用的是wxHtmlHelpController,記住你需要給每一個幫助頁面指定META標記,如下所示:
```
<meta http-equiv="Content-Type" content="text/html; charset=iso8859 //2">
```
你還需要注意幫助工程文件(擴展名.hhp)也許要包含一個指定編碼的選項行:
```
Charset=iso8859-2
```
這個額外的條目告訴HTML幫助控制器幫助內容和幫助索引使用什么編碼格式編碼的.
- 第一章 介紹
- 1.1 為什么要使用wxWidgets?
- 1.2 wxWidgets的歷史
- 1.3 wxWidgets社區
- 1.4 wxWidgets和面向對象編程
- 1.5 wxWidgets的體系結構
- 1.6 許可協議
- 第一章小結
- 第二章 開始使用
- 2.1 一個小例子
- 2.2 應用程序類
- 2.3 Frame窗口類
- 2.4 事件處理函數
- 2.5 Frame窗口的構造函數
- 2.6 完整的例子
- 2.7 wxWidgets程序一般執行過程
- 2.8 編譯和運行程序
- 第二章小結
- 第三章 事件處理
- 3.1 事件驅動編程
- 3.2 事件表和事件處理過程
- 3.3 過濾某個事件
- 3.4 掛載事件表
- 3.5 動態事件處理方法
- 3.6 窗口標識符
- 3.7 自定義事件
- 第三章小結
- 第四章 窗口的基礎知識
- 4.1 窗口解析
- 4.2 窗口類概覽
- 4.3 基礎窗口類
- 4.4 頂層窗口
- 4.5 容器窗口
- 4.6 非靜態控件
- 4.7 靜態控件
- 4.8 菜單
- 4.9 控制條
- 第四章小結
- 第五章繪畫和打印
- 5.1 理解設備上下文
- 5.2 繪畫工具
- 5.3 設備上下文中的繪畫函數
- 5.4 使用打印框架
- 5.5 使用wxGLCanvas繪制三維圖形
- 第五章小節
- 第六章處理用戶輸入
- 6.1 鼠標輸入
- 6.2 處理鍵盤事件
- 6.3 處理游戲手柄事件
- 第六章小結
- 第七章使用布局控件進行窗口布局
- 7.1 窗口布局基礎
- 7.2 窗口布局控件
- 7.3 使用布局控件進行編程
- 7.4 更多關于布局的話題
- 第七章小結
- 第八章使用標準對話框
- 8.1信息對話框
- 8.2 文件和目錄對話框
- 8.3 選擇和選項對話框
- 8.4 輸入對話框
- 8.5 打印對話框
- 第八章小結
- 第九章創建定制的對話框
- 9.1 創建定制對話框的步驟
- 9.2 一個例子:PersonalRecordDialog
- 9.3 在小型設備上調整你的對話框
- 9.4 一些更深入的話題
- 9.5 使用wxWidgets資源文件
- 第九章小結
- 第十章使用圖像編程
- 10.1 wxWidgets中圖片相關的類
- 10.2 使用wxBitmap編程
- 10.3 使用wxIcon編程
- 10.4 使用wxCursor編程
- 10.5 使用wxImage編程
- 10.6 圖片列表和圖標集
- 10.7 自定義wxWidgets提供的小圖片
- 第十章小結
- 第十一章剪貼板和拖放操作
- 11.1 數據對象
- 11.2 使用剪貼板
- 11.3 實現拖放操作
- 第十一章小結
- 第十二章高級窗口控件
- 12.1 wxTreeCtrl
- 12.2 wxListCtrl
- 12.3 wxWizard
- 12.4 wxHtmlWindow
- 12.5 wxGrid
- 12.6 wxTaskBarIcon
- 12.7 編寫自定義的控件
- 第十二章小結
- 第十三章數據結構類
- 13.1 為什么沒有使用STL?
- 13.2 字符串類型
- 13.3 wxArray
- 13.4 wxList和wxNode
- 13.5 wxHashMap
- 13.6 存儲和使用日期和時間
- 13.7 其它常用的數據類型
- 第十三章小結
- 第十四章文件和流操作
- 14.1 文件類和函數
- 14.2 流操作相關類
- 第十四章小結
- 第十五章內存管理,調試和錯誤處理
- 15.1 內存管理基礎
- 15.2 檢測內存泄漏和其它錯誤
- 15.3 構建自防御的程序
- 15.4 錯誤報告
- 15.5 提供運行期類型信息
- 15.6 使用wxModule
- 15.7 加載動態鏈接庫
- 15.8 異常處理
- 15.9 調試提示
- 第十五章小結
- 第十六章編寫國際化程序
- 16.1 國際化介紹
- 16.2 從翻譯說起
- 16.3 字符編碼和Unicode
- 16.4 數字和日期
- 16.5 其它媒介
- 16.6 一個小例子
- 第十六章小結
- 第十七章編寫多線程程序
- 17.1 什么時候使用多線程,什么時候不要使用
- 17.2 使用wxThread
- 17.3 用于線程同步的對象
- 17.4 多線程的替代方案
- 第十七章小結
- 第十八章使用wxSocket編程
- 18.1 Socket類和功能概覽
- 18.2 Socket及其基本處理介紹
- 18.3 Socket標記
- 18.4 使用Socket流
- 18.5 替代wxSocket
- 第十八章小結
- 第十九章使用文檔/視圖框架
- 19.1 文檔/視圖基礎
- 19.2 文檔/視圖框架的其它能力
- 19.3 實現Undo/Redo的策略
- 第十九章小結
- 第二十章完善你的應用程序
- 20.1 單個實例和多個實例
- 20.2 更改事件處理機制
- 20.3 降低閃爍
- 20.4 實現聯機幫助
- 20.5 解析命令行參數
- 20.6 存儲應用程序資源
- 20.7 調用別的應用程序
- 20.8 管理應用程序設置
- 20.9 應用程序安裝
- 20.10 遵循用戶界面設計規范
- 20.11 全書小結