エディットコントロールにテキストファイルを開く
それではいよいよテキストファイルを開きましょう!
大まかな流れは、次のようになります。
- ファイルを開くダイアログボックスを表示して、ファイルを指定します。
- 指定したファイルの内容をメモリに読み込みます。
- 読み込まれたデータが Unicode データかどうか判定します。
- Unicode のファイルではない場合、読み込んだデータを Unicode のデータに変換します。
- エディットコントロールに文字をセットします。
ではさっそく、ファイルを開くダイアログボックスを使って、ファイルを開きましょう。
[ファイルを開く] ダイアログボックス
ファイルを開くダイアログボックスは、以下のような概観です。 Windows を使っている人は良く使いますよね。
[ファイルを開く] ダイアログボックス
このダイアログボックスは、GetOpenFileName 関数を呼ぶことによって表示できます。
BOOL GetOpenFileName( LPOPENFILENAME lpofn );
ここで引数の lpofn は、OPENFILENAME 構造体へのポインタです。
typedef struct tagOFN { DWORD lStructSize; HWND hwndOwner; HINSTANCE hInstance; LPCTSTR lpstrFilter; LPTSTR lpstrCustomFilter; DWORD nMaxCustFilter; DWORD nFilterIndex; LPTSTR lpstrFile; DWORD nMaxFile; LPTSTR lpstrFileTitle; DWORD nMaxFileTitle; LPCTSTR lpstrInitialDir; LPCTSTR lpstrTitle; DWORD Flags; WORD nFileOffset; WORD nFileExtension; LPCTSTR lpstrDefExt; LPARAM lCustData; LPOFNHOOKPROC lpfnHook; LPCTSTR lpTemplateName; #if (_WIN32_WINNT >= 0x0500) void * pvReserved; DWORD dwReserved; DWORD FlagsEx; #endif // (_WIN32_WINNT >= 0x0500) } OPENFILENAME, *LPOPENFILENAME;
何やら大きな構造体ですが、要はこの中にどのように 「ファイルを開く」 ダイアログボックスを開けばよいか、 この構造体の中に詰め込んで、GetOpenFileName 関数に教えてあげるのです。
今回利用したフィールドは以下です。
g_OFN.lStructSize = sizeof( OPENFILENAME ); g_OFN.hwndOwner = hwnd; g_OFN.hInstance = NULL; g_OFN.lpstrFilter = OPENFILE_FILTER; g_OFN.nMaxFile = MAX_PATH; g_OFN.lpstrFile = g_szFileName; g_OFN.Flags = OFN_FILEMUSTEXIST; g_OFN.lpstrDefExt = TEXT( "txt" );
ちょっと変っているのは、lpstrFilter でしょうか。これは 「ファイルを開く」 ダイアログボックスについている、 ファイルの種類を選ぶドロップダウンを初期化します。
これはこんな形式で指定します。
種類の説明1 \0 拡張子1 \0 種類の説明2 \0 拡張子2 \0 \0
説明、拡張子、説明、拡張子、... と繰り返し、最後に \0 を続けることで繰り返しの終了とします。上記、黄色でハイライト下部分です。
ですから、実際の値は次のようになります。
"Text Documents (*.txt)\0*.txt\0All Files (*.*)\0*.*\0\0"
ファイルを開く
GetOpenFileName 関数が返ってくると、OPENFILENAME 構造体の lpstrFile に指定したバッファに、 指定されたファイル名が格納されています。その名前を使ってファイルを開きます。
ファイルを開くには、CreateFile 関数を使います。
HANDLE WINAPI CreateFile( __in LPCTSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile );
lpFileName にはファイル名を指定します。
dwDesiredAccess はそのファイルへのアクセス方法を指定します。 きめ細かく指定できるのですが、読みより用に開くには GENERIC_READ、書き込み用には GENERIC_WRITE、 実行するには GENERIC_EXECUTE を指定すればよいでしょう。
dwShareMode に 0 を指定すると、ファイルを開いている間、このファイルへのアクセスできません。 FILE_SHARE_READ を指定すると、ファイルを開いている間でも、読み取りアクセスは許可されます。
lpSecurityAttributes は通常 NULL を指定します。子プロセスでこのハンドルを継承したい場合はここで指定します。
dwCreationDisposition は、ファイルをどのように扱うか決めます。例えば、OPEN_EXISTING を指定すると、 ファイルが存在するときのみ開くことができます。ファイルが存在しない場合は失敗します。
ファイルを読み込む
GetFileSize 関数にファイルハンドルを渡すと、そのファイルのサイズを取得できます。
そこで取得したサイズ+少し大きめのサイズのメモリを確保し ReadFile 関数で、 バッファにファイルの内容を読み込みます。
この部分はとても素直な流れなので、コードを見てください。
Unicode の判定
このプログラムは Unicode ビルドですから、エディットコントロールに渡せるのは、Unicode 文字列のみです。 このため、読み込んだテキストファイルのデータ形式が Unicode でない場合、正常に読み取ることができません。
バッファに格納されたデータが Unicode テキストかどうか判定するには、IsTextUnicode 関数が使えます。
このプログラムでは Unicode では無い場合、そのファイルのエンコーディング方式を システム既定のロケール (=CP_ACP)として読み込むようにしています。
つまり、システム既定の言語として日本語が指定されている場合 (日本語 OS ならば大抵そうです。 たとえ英語版 Windows を使っていても、既定のロケールを日本語にすることは可能です)、 そのテキストファイルを日本語ファイルとして読み込みます。
日本語ファイルとして読み込むということは、Windows ではそのファイルを、 Shift JIS でエンコードされているとみなす、ということです。コードページは 932 です。
マルチバイトの文字列を、ワイドキャラクタ (つまり Unicode) に変換するには、MultiByteToWideChar 関数を使用します。 MultiByteToWideChar の第一引数に、コードページを指定します。
このプログラムでは CP_ACP を常に利用していますが、日本語 OS のロケールの場合、前述のように Shift JIS として開きます。このようにすると、UTF-8 のファイルを読み込むと文字化けします。 ご自分で UTF-8 かどうかの判定ロジックを組み入れるなどして、さまざまな形式のファイルに対応できるように書き換えてみてください。
コード
それでは、実際にプログラムを構築してみましょう。
simple.cpp は以下です。
#include "simple.h" #include <windowsx.h> #include "resource.h" HWND g_hEdit = NULL; TCHAR g_szFileName [MAX_PATH]; OPENFILENAME g_OFN; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { HWND hWnd; WNDCLASSEX wcl; TCHAR szTitle[256]; wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hInstance; wcl.lpszClassName = WND_CLASS_NAME; wcl.lpfnWndProc = WindowProc; wcl.style = NULL; wcl.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APP)); wcl.hIconSm = NULL; wcl.hCursor = LoadCursor(NULL, IDC_ARROW); wcl.lpszMenuName = MAKEINTRESOURCE (IDR_MENU_MAIN); wcl.cbClsExtra = 0; wcl.cbWndExtra = 0; wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); if(!RegisterClassEx(&wcl)) { return FALSE; } LoadString ( hInstance, IDS_APP_TITLE, szTitle, sizeof(szTitle)/sizeof(szTitle[0]) ); hWnd = CreateWindowEx( NULL, WND_CLASS_NAME, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); if(!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); MSG msg; BOOL bRet; while( ( bRet = GetMessage( &msg, NULL, 0, 0 ) ) != 0) { if (bRet == -1) { break; } else { TranslateMessage ( &msg ); DispatchMessage( &msg ); } } return msg.wParam; } LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch( uMsg ) { HANDLE_MSG( hwnd, WM_CLOSE, Main_OnClose ); HANDLE_MSG( hwnd, WM_COMMAND, Main_OnCommand ); HANDLE_MSG( hwnd, WM_CREATE, Main_OnCreate ); HANDLE_MSG( hwnd, WM_DESTROY, Main_OnDestroy ); HANDLE_MSG( hwnd, WM_SIZE, Main_OnSize ); default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } void Main_OnClose( HWND hwnd ) { TCHAR szMsg [256]; TCHAR szCaption [256]; LoadString ( GetModuleHandle(NULL), IDS_MSG_QUIT, szMsg, sizeof(szMsg)/sizeof(szMsg[0]) ); LoadString ( GetModuleHandle(NULL), IDS_CAPTION_CONF, szCaption, sizeof(szCaption)/sizeof(szCaption[0]) ); if ( IDYES == MessageBox ( hwnd, szMsg, szCaption, MB_YESNO ) ) { DestroyWindow(hwnd); } } void Main_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify ) { switch( id ) { case IDM_FILE_EXIT: SendMessage ( hwnd, WM_CLOSE, 0, 0 ); break;case IDM_FILE_OPEN: File_Open (); break;case IDM_HELP_ABOUT: ::MessageBox ( NULL, TEXT("Hello, About!"), TEXT("About This Program"), MB_OK ); break; } } BOOL Main_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { // // Edit Control // g_hEdit = CreateWindow ( TEXT ("edit"), NULL, WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL, 0, 0, 0, 0, hwnd, (HMENU) IDM_EDIT, lpCreateStruct->hInstance, NULL) ; UpdateWindow(g_hEdit); if(!g_hEdit) { MessageBox(hwnd, TEXT("CreateWindow Failed."), TEXT("Title"), MB_OK); return FALSE; }// // File // File_Initialize ( hwnd ); return TRUE;} void Main_OnDestroy( HWND hwnd ) { ::PostQuitMessage( 0 ); } void Main_OnSize( HWND hwnd, UINT state, int cx, int cy ) { MoveWindow (g_hEdit, 0, 0, cx, cy, TRUE ); }BOOL File_Initialize ( HWND hwnd ) { ::ZeroMemory( g_szFileName, sizeof( g_szFileName ) ); ::ZeroMemory( &g_OFN, sizeof( OPENFILENAME ) ); g_OFN.lStructSize = sizeof( OPENFILENAME ); g_OFN.hwndOwner = hwnd; g_OFN.hInstance = NULL; g_OFN.lpstrFilter = OPENFILE_FILTER; g_OFN.nMaxFile = MAX_PATH; g_OFN.lpstrFile = g_szFileName; g_OFN.Flags = OFN_FILEMUSTEXIST; g_OFN.lpstrDefExt = TEXT( "txt" ); return TRUE; } BOOL File_Open () { if ( GetOpenFileName ( &g_OFN ) ) { if ( File_Read ( g_szFileName ) ) { // OK } } return TRUE; } BOOL File_Read ( LPTSTR pszFileName ) { HANDLE hFile; HANDLE hHeap; DWORD cFileLen; DWORD dwBytesRead; PBYTE pBuff = NULL; BOOL bUnicode; // // ファイルを開く // hFile = CreateFile ( pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (INVALID_HANDLE_VALUE == hFile ) { return FALSE ; } // // ファイルサイズを調べ、ファイルのデータを読み込むバッファを用意する // cFileLen = GetFileSize ( hFile, NULL ) ; hHeap = GetProcessHeap (); pBuff = (PBYTE) HeapAlloc ( hHeap, HEAP_ZERO_MEMORY, cFileLen + sizeof( WCHAR ) ); if ( !pBuff ) { CloseHandle( hFile ); return FALSE; } // // ファイルのデータを読み込みます。 // if ( !ReadFile ( hFile, pBuff, cFileLen, &dwBytesRead, NULL) ) { CloseHandle( hFile ); HeapFree ( hHeap, 0, pBuff ); return FALSE; } CloseHandle (hFile) ; // // エディットコントロールに、データをセットします。 // このときに、Unicode のデータかどうかによって処理が変ります。 // bUnicode = IsTextUnicode ( pBuff, cFileLen, NULL); if ( bUnicode ) { // // Unicode の場合はそのまま SetWindowText 関数でデータをセット // SetWindowText ( g_hEdit, (PTSTR) pBuff ) ; HeapFree ( hHeap, 0, pBuff ); } else { // // Unicode のデータではない場合、Unicode に変換しなければならない。 // MultiByteToWideChar 関数を利用する。 // ここでは Unicode のデータではない場合、システム既定のコードページと // みなして、データ変換する。(実際にはシステム既定のコードページのデータではない場合、 // 誤ったデータ変換をしてしまうために、いわゆる文字化けを起こす。 // // 必要なバッファサイズの取得 INT cchRecWChar = MultiByteToWideChar ( CP_ACP, 0, (LPCSTR) pBuff, cFileLen, NULL, 0 ); // メモリ割り当て DWORD dwAllocByte = ( cchRecWChar + 1 ) * sizeof(WCHAR); PWSTR pUnicodeText = (PWSTR) HeapAlloc ( hHeap, HEAP_ZERO_MEMORY, dwAllocByte ); if ( !pUnicodeText ) { HeapFree ( hHeap, 0, pBuff ); return FALSE; } // データ変換 MultiByteToWideChar ( CP_ACP, 0, (LPCSTR) pBuff, cFileLen, pUnicodeText, cchRecWChar ); // エディットコントロールにデータをセットする SetWindowText ( g_hEdit, (PTSTR) pUnicodeText ); // 使ったメモリのクリーンアップ HeapFree ( hHeap, 0, pBuff ); HeapFree ( hHeap, 0, pUnicodeText ); } return TRUE ; }
黄色の部分が、今回新しいところです。今回は多いですね。
simple.h は以下の通りです。
#pragma once #include <windows.h> #define WND_CLASS_NAME TEXT("My_Window")#define OPENFILE_FILTER TEXT("Text Documents (*.txt)\0*.txt\0All Files (*.*)\0*.*\0\0")#define IDM_EDIT 1 LRESULT CALLBACK WindowProc( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam); void Main_OnClose( HWND hwnd ); void Main_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify ); BOOL Main_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); void Main_OnDestroy( HWND hwnd ); void Main_OnSize( HWND hwnd, UINT state, int cx, int cy );BOOL File_Initialize ( HWND hwnd ); BOOL File_Open (); BOOL File_Read ( LPTSTR pszFileName );
makefile は次の通りです。
LINK32=link.exe
TARGETNAME=simple
OUTDIR=.\chk
ALL : "$(OUTDIR)\$(TARGETNAME).exe"
"$(OUTDIR)" :
if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
# compile
CPPFLAGS=\
/nologo\
/MT\
/W3\
/Fo"$(OUTDIR)\\"\
/Fd"$(OUTDIR)\\"\
/c\
/Zi\
/DWIN32\
/DUNICODE\
/D_UNICODE\
#link
LINK32_FLAGS=\
user32.lib\
gdi32.lib\
comdlg32.lib\
advapi32.lib\
/nologo\
/subsystem:windows\
/pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
/out:"$(OUTDIR)\$(TARGETNAME).exe"\
/DEBUG\
/RELEASE\
LINK32_OBJS= \
"$(OUTDIR)\$(TARGETNAME).obj"\
"$(OUTDIR)\resource.res"
#resource compiler
RFLAGS=/l 0x411 /fo"$(OUTDIR)\resource.res" /d DEBUG
"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
$(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)
.cpp{$(OUTDIR)}.obj:
$(CPP) $(CPPFLAGS) $<
.rc{$(OUTDIR)}.res:
$(RC) $(RFLAGS) $<
以上、プログラムをビルドすると simple.exe ができるはずです。これでテキストファイルを開くことができました。