# 14.2 流操作相關類
所謂流模型,指的是一種用于提供相對于文件讀寫更高層的數據讀寫的模型.使用流模型,你的代碼不需要關心當前操作的是文件,內存還是socket(參考第 18章,"使用wxSocket編程",其中演示了用流方式使用socket的方法).某些wxWidgets標準類同時支持文件讀寫操作和流讀寫操作, 比如wxImage類.
wxStreamBase類是所有流類的基類,它聲明的函數包括類似OnSysRead和OnSysWrite等需要繼承類實現的函數.其子類wxInputStream和wxOutputStream則提供了更具體的流類(比如wxFileInputStream和 wxFileOutputStream子類)共同需要的用于讀寫操作的基本函數.讓我們來具體來看一下wxWidgets提供的各種流操作相關類.
文件流
wxFileInputStream和wxFileOutputStream是基于wxFile類實現的,可以通過文件名,wxFile 對象或者整數文件描述符的方式來進行初始化.下面的例子演示了使用wxFileInputStream來進行文件讀取,文件指針移位并讀取當前位置數據等.
```
#include "wx/wfstream.h"
// 構造函數初始化流緩沖區并且打開文件名參數對應的文件.
// wxFileInputStream將在析構函數中自動關閉對應的文件描述符.
wxFileInputStream inStream(filename);
// 讀100個字節
int byteCount = 100;
char data[100];
if (inStream.Read((void*) data, byteCount).
LastError() != wxSTREAM_NOERROR)
{
// 發生了異常
// 異常列表請參考wxStreamBase的相關文檔.
}
// 你可以通過下面的方法判斷到底成功讀取了多少個字節.
size_t reallyRead = inStream.LastRead();
// 將文件游標移動到文件開始處.
// SeekI函數的返回值為移動游標以前的游標相對于文件起始處的位置
off_t oldPosition = inStream.SeekI(0, wxFromBeginning);
// 獲得當前的文件游標位置
off_t position = inStream.TellI();
```
使用wxFileOutputStream的方法也很直觀.下面的代碼演示了使用wxFileInputStream和wxFileOutputStream實現文件拷貝的方法.每次拷貝1024個字節.為了使代碼更簡介,這里沒有顯示錯誤處理的代碼.
```
// 下面的代碼實現固定單位大小的流拷貝.
// 緩沖區的使用是為了加快拷貝的速度.
void BufferedCopy(wxInputStream& inStream, wxOutputStream& outStream,
size_t size)
{
static unsigned char buf[1024];
size_t bytesLeft = size;
while (bytesLeft > 0)
{
size_t bytesToRead = wxMin((size_t) sizeof(buf), bytesLeft);
inStream.Read((void*) buf, bytesToRead);
outStream.Write((void*) buf, bytesToRead);
bytesLeft -= bytesToRead;
}
}
void CopyFile(const wxString& from, const wxString& to)
{
wxFileInputStream inStream(from);
wxFileOutputStream outStream(to);
BufferedCopy(inStream, outStream, inStream.GetSize());
}
```
wxFFileInputStream和wxFFileOutputStream跟wxFileInputStream和 wxFileOutputStream的用法幾乎完全一樣, 不同之處在于前者是基于wxFFile類而不是wxFile類的. 因此它們的初始化方法也不同,相應的,前者可以使用FILE指針或wxFFile對象來初始化.文件結束處理相應的有所不同: wxFileInputStream在最后一個字節被讀取的時候報告wxSTREAM_EOF, 而wxFFileInputStream則在最后一個字節以后進行讀操作的時候返回wxSTREAM_EOF.
內存和字符串流
wxMemoryInputStream和wxMemoryOutputStream使用內部緩沖區來處理流數據.默認的構造函數都采用 char*類型的緩沖區指針和緩沖區大小作為參數.如果沒有這些參數,則表明要求該類的事例自己進行動態緩沖區管理.我們很快會看到一個相關的例子.
wxStringInputStream則采用一個wxString引用作為構造參數來進行數據讀取. wxStringOutputStream采用一個開選的wxString指針作為參數來進行數據寫操作;如果構造參數沒有指示wxString指針,則將構造一個內部的wxString對象,這個對象可以通過GetString函數來訪問.
讀寫數據類型
到目前為止,我們描述的流類型都處理的是原始的字節流數據.在實際的應用程序中,這些字節流必須被賦予特定的函數.為了幫助你實現這一點,你可以使用下面四個類來以一個更高的層級處理數據.這四個類分別是:wxTextInputStream, wxTextOutputStream, wxDataInputStream和wxDataOutputStream.這些類通過別的流類型類構造,它們提供了操作更高級的C++數據類型的方法.
wxTextInputStream從一段人類可讀的文本中獲取數據.如果你使用的構造類為文件相關類,你需要自己進行文件是否讀完的判斷.即使這樣,讀到空數據項(長度為0的字符串或者數字0)還是無法避免,因為很多文本文件都用空白符(比如換行符)來結束.下面的例子演示了怎樣使用由wxFileInputStream構造的wxTextInputStream:
```
wxFileInputStream input( wxT("mytext.txt") );
wxTextInputStream text( input );
wxUint8 i1;
float f2;
wxString line;
text >> i1; // 讀一個8bit整數.
text >> i1 >> f2; // 先讀一個8bit整數再讀一個浮點數.
text >> line; // 讀一行文本
```
wxTextOutputStream則將文本數據寫至輸出流,換行符自動使用當前平臺的換行符.下面的例子演示了將文本數據輸出到標準錯誤輸出流:
```
#include "wx/wfstream.h"
#include "wx/txtstrm.h"
wxFFileOutputStream output( stderr );
wxTextOutputStream cout( output );
cout << wxT("This is a text line") << endl;
cout << 1234;
cout << 1.23456;
```
wxDataInputStream和wxDataOutputStream使用發放類似,但是它使用二進制的方法處理數據.數據使用可移植的方式存儲因此能夠作到平臺無關.下面的例子分別演示了以這種方式從數據文件讀取以及寫入數據文件.
```
#include "wx/wfstream.h"
#include "wx/datstrm.h"
wxFileInputStream input( wxT("mytext.dat") );
wxDataInputStream store( input );
wxUint8 i1;
float f2;
wxString line;
store >> i1; // 讀取一個8bit整數
store >> i1 >> f2; // 讀取一個8bit整數,然后讀取一個浮點數.
store >> line; // 讀取一行文本
#include "wx/wfstream.h"
#include "wx/datstrm.h"
wxFileOutputStream output(wxT("mytext.dat") );
wxDataOutputStream store( output );
store << 2 << 8 << 1.2;
store << wxT("This is a text line") ;
```
Socket流
wxSocketOutputStream和wxSocketInputStream是通過wxSocket對象構造的,詳情參見第18章.
過濾器流對象
wxFilterInputStream和wxFilterOutputStream是過濾器流對象的基類,過濾器流對象是一種特殊的流對象,它用來將過濾后的數據輸入到其它的流對象.wxZlibInputStream是一個典型的過濾器流對象.如果你在其構造函數中指定一個文件源為一個zlib格式的壓縮文件的文件流對象,你可以直接從wxZlibInputStream中讀取數據而不需要關心解壓縮的機制.類似的,你可以使用一個 wxFileOutputStream來構造一個wxZlibOutputStream對象,如果你將數據寫入wxZlibOutputStream對象,壓縮后的數據將被寫入對應的文件中.
下面的例子演示了怎樣將一段文本壓縮以后存放入另外一個緩沖區中:
```
#include "wx/mstream.h"
#include "wx/zstream.h"
const char* buf =
"01234567890123456789012345678901234567890123456789";
// 創建一個寫入wxMemoryOutputStream對象的wxZlibOutputStream類
wxMemoryOutputStream memStreamOut;
wxZlibOutputStream zStreamOut(memStreamOut);
// 壓縮buf以后寫入wxMemoryOutputStream
zStreamOut.Write(buf, strlen(buf));
// 獲取寫入的大小
int sz = memStreamOut.GetSize();
// 分配合適大小的緩沖區
// 拷貝數據
unsigned char* data = new unsigned char[sz];
memStreamOut.CopyTo(data, sz);
```
Zip流對象
wxZipInputStream是一個更復雜一點的流對象,因為它是以文檔的方式而不是線性的二進制數據的方式工作的.事實上,文檔是通過另外的類wxArchiveClassFactory和wxArchiveEntry來處理的,但是你可以不用關心這些細節.要使用 wxZipInputStream類,你可以有兩種構造方法,一種是直接使用一個指向zip文件的文件流對象,另外一種方法則是通過一個zip文件路徑和一個zip文件中文檔的路徑來指定一個zip數據流.下面的例子演示了這兩種方法:
```
#include "wx/wfstream.h"
#include "wx/zipstrm.h"
#include "wx/txtstrm.h"
// 方法一: 以兩步方式創建zip輸入流.
wxZipEntry* entry;
wxFFileInputStream in(wxT("test.zip"));
wxZipInputStream zip(in);
wxTextInputStream txt(zip);
wxString data;
while (entry = zip.GetNextEntry())
{
wxString name = entry->GetName(); // 訪問元數據
txt >> data; // 訪問數據
delete entry;
}
// 方法二: 直接指定源文檔路徑和內部文件路徑.
wxZipInputStream in(wxT("test.zip"), wxT("text.txt"));
wxTextInputStream txt(zip);
wxString data;
txt >> data; // 訪問數據
```
wxZipOutputStream用來寫zip壓縮文件. PutNextEntry或PutNextDirEntry函數用來在壓縮文件中創建一個新的文件(目錄),然后就可以寫相應的數據了.例如:
```
#include "wx/wfstream.h"
#include "wx/zipstrm.h"
#include "wx/txtstrm.h"
wxFFileOutputStream out(wxT("test.zip"));
wxZipOutputStream zip(out);
wxTextOutputStream txt(zip);
zip.PutNextEntry(wxT("entry1.txt"));
txt << wxT("Some text for entry1\n");
zip.PutNextEntry(wxT("entry2.txt"));
txt << wxT("Some text for entry2\n");
```
虛擬文件系統
wxWidgets提供了一套虛擬文件系統機制,讓你的應用程序可以象使用普通文件那樣使用包括zip文件中的文件,內存文件以及HTTP或FTP協議這樣的特殊數據.不過,這種虛擬文件機制通常是只讀的,意味著你不可以修改其中的內容.wxWidgets提供的wxHtmlWindow類(用于提供 wxWidgets內部的HTML幫助文件的顯示)和wxWidgets的XRC資源文件機制都可以識別虛擬文件系統路徑格式.虛擬文件系統的使用比起前面介紹的zip文件流要簡單,但是后者可以更改zip文檔的內容.除了內部都使用了流機制以外,這兩者其實沒有任何其它的聯系.
各種不同的虛擬文件系統類都繼承自wxFileSystemHandler類,要在應用程序中使用某個特定實現,需要在程序的某個地方 (通常是OnInit函數中)調用wxFileSystem::AddHandler函數.使用虛擬文件系統通常只需要使用那些定義在 wxFileSystem對象中的函數,但是有些虛擬文件系統的實現也提供了直接給用于使用的函數,比如wxMemoryFSHandler's的 AddFile和RemoveFile函數.
在我們介紹怎樣通過C++函數訪問虛擬文件系統之前,我們先看看怎樣在wxWidgets提供的其它子系統中使用虛擬文件系統.下面的例子演示了怎樣在用于在wxHtmlWindow中顯示的HTML文件中使用指定虛擬文件系統中的路徑:
```
<img src="file:myapp.bin#zip:images/logo.png">
```
"#"號前面的部分是文件名,后面的部分則是虛擬文件系統類型以及文件在虛擬文件系統中的路徑.
類似的,我們也可以在XRC資源文件中使用虛擬文件系統:
```
<object class="wxBitmapButton">
<bitmap>file:myapp.bin#zip:images/fuzzy.gif</bitmap>
</object>
```
在上面的這些用法中,操作虛擬文件系統的代碼被隱藏在wxHtmlWindow和XRC系統的實現中.如果你希望直接使用虛擬文件系統, 通常你需要通過wxFileSystem和wxFSFile類.下面的代碼演示了怎樣從虛擬文件系統中加載一幅圖片,當應用程序初始化的時候,增加一個 wxZipFSHandler類型的虛擬文件系統處理器.然后創建一個wxFileSystem的實例,這個實例可以是臨時使用也可以存在于整個應用程序的生命周期,這個實例用來從zip文件myapp.bin中獲取logo.png圖片.wxFSFile對象用于返回這個文件對應的數據流,然后通過流的方式創建wxImage對象.在這個對象被轉換成wxBitmap格式以后,wxFSFile和wxFileSystem對象就可以被釋放了.
```
#include "wx/fs_zip.h"
#include "wx/filesys.h"
#include "wx/wfstream.h"
// 這一行代碼只應該被執行依次,最好是在應用程序初始化的時候
wxFileSystem::AddHandler(new wxZipFSHandler);
wxFileSystem* fileSystem = new wxFileSystem;
wxString archive = wxT("file:///c:/myapp/myapp.bin");
wxString filename = wxT("images/logo.png");
wxFSFile* file = fileSystem->OpenFile(
archive + wxString(wxT("#zip:")) + filename);
if (file)
{
wxInputStream* stream = file->GetStream();
wxImage image(* stream, bitmapType);
wxBitmap bitmap = wxBitmap(image);
delete file;
}
delete fileSystem;
```
注意要使用wxFileSystem:: OpenFile函數,其參數必須是一個URL而不能是一個絕對路徑,其格式應該為"file:/<主機名>//<文件名>", 如果主機名為空,則使用三個"/"符號.你可以使用wxFileSystem::FileNameToURL函數獲取某個文件對應的URL,也可以用 wxFileSystem::URLToFileName函數將某個URL轉換成對應的文件名.
下面的例子演示了怎樣獲取虛擬文件系統中獲取一個文本文件的內容,并將其存放在某個wxString對象中,所需參數為zip文件路徑以及zip文件中的虛擬文件的路徑:
```
// 從zip文件中加載一個文本文件
bool LoadTextResource(wxString& text, const wxString& archive,
const wxString& filename)
{
wxString archiveURL(wxFileSystem::FileNameToURL(archive));
wxFileSystem* fileSystem = new wxFileSystem;
wxFSFile* file = fileSystem->OpenFile(
archiveURL + wxString(wxT("#zip:")) + filename);
if (file)
{
wxInputStream* stream = file->GetStream();
size_t sz = stream->GetSize();
char* buf = new char[sz + 1];
stream->Read((void*) buf, sz);
buf[sz] = 0;
text = wxString::FromAscii(buf);
delete[] buf;
delete file;
delete fileSystem;
return true;
}
else
return false;
}
```
wxMemoryFSHandler允許你將數據保存在內存中并且通過內存協議在虛擬文件系統中使用它.顯然在內存中存放大量數據并不是一個值得推薦的作法,不過有時候卻可以給應用程序提供一定程序的靈活性,比如你正在使用只讀的文件系統或者說使用磁盤文件存在性能上的問題的時候.在 DialogBlocks軟件中,如果用戶提供的自定義圖標文件還不具備,DialogBlocks仍然可以通過下面的XRC文件顯示一個內存中的圖片, 這個圖片并不存在于任何的物理磁盤中.
```
<object class="wxBitmapButton">
<bitmap>memory:default.png</bitmap>
</object>
```
wxMemoryFSHandler的AddFile函數可以使用的參數包括一個虛擬文件名和一個wxImage, wxBitmap, wxString或void*數據. 如果你不再使用某個內存虛擬文件了,可以通過RemoveFile函數將其刪除.如下所示:
```
#include "wx/fs_mem.h"
#include "wx/filesys.h"
#include "wx/wfstream.h"
#include "csquery.xpm"
wxFileSystem::AddHandler(new wxMemoryFSHandler);
wxBitmap bitmap(csquery_xpm);
wxMemoryFSHandler::AddFile(wxT("csquery.xpm"), bitmap,
wxBITMAP_TYPE_XPM);
...
wxMemoryFSHandler::RemoveFile(wxT("csquery.xpm"));
```
wxWidgets支持的第三種虛擬文件系統是wxInternetFSHandler,它支持FTP和HTTP協議.
- 第一章 介紹
- 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 全書小結