第74章 メモ帳を作る その1


今回から、WINDOWS95に付属しているメモ帳もどきを 作ってみます。DOSのプログラムで同様のものを作るとすると 大変な工程がいります。しかし、ウィンドウズプログラムでは、 意外に簡単に作ることができます。

では、どうすればよいのでしょうか。答えは、 親ウィンドウのクライアント領域いっぱいにエジットコントロールを 貼り付ければよいのです。これだと、親ウィンドウの サイズが変更になるとまずいので自ずと、WM_SIZEメッセージを 処理すればよいということがわかります。

いっぺんに作るのは大変なので今回は、指定のテキストファイルを オープンして表示するまでを作ります。 今までの知識で十分作ることができます。

ファイルのオープンはCreateFile関数で行います。

HANDLE CreateFile( LPCTSTR lpFileName, // ファイル名 DWORD dwDesiredAccess, // アクセスモード DWORD dwShareMode, // シェアモード LPSECURITY_ATTRIBUTES lpSecurityAttributes, // Windows95では無視 DWORD dwCreationDistribution, // 作り方 DWORD dwFlagsAndAttributes, // file attributes HANDLE hTemplateFile // handle to file with attributes to copy );

lpFileNameには、オープンするファイル名のポインタを指定します。

dwDesirdAccessは、ファイルへのアクセスの指定をします。
0ならば、実際にアクセスせずにデバイス属性を問い合わせる
GENERIC_READは、読みとり。ファイルポインタ移動可能。
GENERIC_WRITEは、書き込み。ファイルポインタ移動可能。

dwShareModeは、共有の有無を設定します。マルチタスク環境では 同じファイルにたいして複数のアプリケーションが読み書きを行おうとする 場面が出てきます。
0ならば、ファイルを共有しません。
FILE_SHARE_READならば、読み込みを共有します。
FILE_SHARE_WRITEならば、書き込みを共有します。

dwCreationDistributesは、作り方を指定します。 CREATE_NEWならば、新しいファイルを作ります。すでにある場合は関数は失敗します。
CREATE_ALWAYSは、新しいファイルを作ります。すでにある場合は上書きします。
OPEN_EXISTINGは、ファイルをオープンしますが、存在しない場合は関数が失敗します。
OPEN_ALWAYSは、ファイルをオープンしますが、ない場合は新しくファイルを作ります。
TRUNCATE_EXISTINGは、ファイルをオープンしますが、オープン後切りつめて0バイトになります。 そのファイルがない場合は関数が失敗します。

dwFlagsAndAttributesは、ファイル属性を指定します。
FILE_ATTRIBUTE_ARCHIVEは、アーカイブファイルの指定。
FILE_ATTRIBUTE_NORMALは、属性を持ちません。他のフラグと用いると無効
FILE_ATTRIBUTE_READONLYは、読みとり専用
FILE_ATTRIBUTE_SYSTEMは、システムファイル
FILE_ATTRIBUTE_TEMPORARYは、テンポラリー用
後たくさんありますが必要時、ヘルプで調べてみてください。

hTemplateFileは、特殊な使い方ができますが通常はNULLで大丈夫です。

さて、ファイルをオープンしたらファイルポインタをファイルの先頭に持っていきます。

DWORD SetFilePointer( HANDLE hFile, // ファイルハンドル LONG lDistanceToMove, // ファイルポインタを移動する距離 PLONG lpDistanceToMoveHigh, // 距離64ビット分の上位32ビット DWORD dwMoveMethod // 移動の仕方 );

lDistanceToMoveは、ファイルポインタを移動する距離です。(32ビット)
lpDistanceToMoveHighは、移動距離が32ビットで表しきれないとき64ビットの 上位32ビットのポインタです。
dwMoveMethodは、移動開始点です。
FILE_BEGINは、開始点は0またはファイルの先頭です。
FILE_CURRENTは、ファイルポインタの現在位置が開始点です。
FILE_ENDは、ファイルの最後(EOF)が開始点となります。

ごちゃごちゃ書きましたが、DOSでファイル入出力をするよりは楽です。

さて、ファイルをオープンしてファイルポインタを先頭に移したら読み込みです。

BOOL ReadFile( HANDLE hFile, // ファイルハンドル LPVOID lpBuffer, // データを受け取るバッファのアドレス DWORD nNumberOfBytesToRead, // 読み込むバイト数 LPDWORD lpNumberOfBytesRead, // 読みとったバイト数を格納する変数のアドレス LPOVERLAPPED lpOverlapped // CreateFileでFILE_OVERLAPPEDを使わなければ関係ない );

ま、これは関数の引数のコメントを見れば何となくわかると思います。

ファイルの必要がなくなったらこれをクローズします。

BOOL CloseHandle( HANDLE hObject );

これも特に説明の必要はないですね。

では、実際にプログラムを作ってみましょう。

// edit01.rcの一部 // 自前で作る人はwindows.hと自作のヘッダファイル(IDM_OPEN, IDM_ENDの定義) //をインクルードしてください ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "開く(&O)", IDM_OPEN MENUITEM SEPARATOR MENUITEM "終了(&X)", IDM_END END END

リソーススクリプトは単なる簡単なメニューだけです。

// edit01.cpp #define STRICT #include <windows.h> #include <windowsx.h> #include "resource.h" #define ID_EDIT 100 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); int OpenMyFile(HWND); char szClassName[] = "edit01"; //ウィンドウクラス char szFileName[256]; // オープンするファイル名(パス付き) char szFile[64]; // ファイル名 HINSTANCE hInst; HWND hEdit; int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!hPrevInst) { if (!InitApp(hCurInst)) return FALSE; } if (!InitInstance(hCurInst, nCmdShow)) { return FALSE; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }

後からプログラムが楽なようにhInstをグローバル変数にしておきました。

//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; return (RegisterClass(&wc)); }

いつもと同じですが、lpszMenuNameの指定を忘れないでください。

//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; hWnd = CreateWindow(szClassName, "猫でもわかるエジタ",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW,//ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInstance, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

いつもと同じです。引数の名前をいつもとちょっと変えてみました。 (hInstをグローバル変数にしたため)

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; RECT rc; switch (msg) { case WM_CREATE: GetClientRect(hWnd, &rc); hEdit = CreateWindow( "EDIT", NULL, WS_CHILD | WS_VISIBLE | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | ES_AUTOHSCROLL | WS_HSCROLL, 0, 0, rc.right, rc.bottom, hWnd, (HMENU)ID_EDIT, hInst, NULL); SendMessage(hEdit, EM_SETLIMITTEXT, (WPARAM)1024*64, 0); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_OPEN: OpenMyFile(hWnd); break; } break; case WM_SIZE: GetClientRect(hWnd, &rc); MoveWindow(hEdit, rc.left, rc.top, rc.right, rc.bottom, TRUE); break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

親ウィンドウが作られたらすぐに、エジットコントロールを 子供ウィンドウとしてつくり、親のクライアント領域いっぱいに 貼り付けます。

ウィンドウスタイルは最低限WM_CHILDとWM_VISIBLEは指定してください。 複数行の入力が必要ならES_MULTILINEを入れてください。 さらに、リターンキーを押したときこれを改行と見なすには ES_WANTRETURNが必要です。ES_AUTOVSCROLL | WS_VSCROLLを入れておくと 垂直方向のスクロールバーが現れてスクロール関係の処理を 自動的にやってくれます。水平方向のスクロールは ES_AUTOHSCROLL | WS_HSCROLLを入れておけば自分でプログラムを 書く必要がないのでらくちんです。

また、ユーザーが無制限に入力ができないように ここでは、EM_SETLIMITTEXTメッセージを送っています。

EM_SETLIMITTEXT wParam = (WPARAM) cbMax; lParam = 0;

となっています。wParamに制限したいバイト数を指定します。 ここでは64kにしておきました。制限を越したバイト数を入力したら どうなるのでしょうか。この数字を極端に小さくして実験してみてください。

親ウィンドウのサイズが変更になるとまずいことが起こりますので、 WM_SIZEメッセージが来たら親のクライアント領域の大きさを調べて エジットウィンドウの大きさを合わせます。 この場合わざわざGetClientRect関数を使っていますが、 副メッセージを調べてもよいですね。 (第59章参照)

最後にファイルをオープンして読み出す自作関数です。

int OpenMyFile(HWND hWnd) { OPENFILENAME ofn; HANDLE hFile; char szStr[1024 * 64]; DWORD dwAccBytes; char szTitle[64], *szTitle_org = "猫でもわかるエジタ[%s]"; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = "text(*.txt)\0*.txt\0All files(*.*)\0*.*\0\0"; ofn.lpstrFile = szFileName; ofn.lpstrFileTitle = szFile; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrDefExt = "txt"; ofn.lpstrTitle = "ファイルオープン!"; GetOpenFileName(&ofn); hFile = CreateFile(szFileName, GENERIC_READ, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); SetFilePointer(hFile, 0, 0, FILE_BEGIN); ReadFile(hFile, szStr, 1024*64, &dwAccBytes, NULL); szStr[dwAccBytes] = 0; Edit_SetText(hEdit, szStr); wsprintf(szTitle, szTitle_org, szFile); SetWindowText(hWnd, szTitle); CloseHandle(hFile); return 0; }

OPENFILENAME構造体とか、GetOpenFileName関数については 第73章を参照してください。 OPENFILENAME構造体のlpstrFileを調べるとユーザーの選択した ファイルをフルパス付きで知ることができます。 これを使ってファイルをオープンします。次にファイルポインタを ファイルの先頭に移動します。そして、ファイルの中身をszStrに読み込みます。 読み込んだ中身をEdit_SetTextマクロでエジットウィンドウに表示します。 また同時にファイル名をSetWindowText関数で親ウィンドウのタイトルバーに表示します。 そして、忘れずにクローズします。


今回作った読み出し専用エジタ(読み出しだけではエジタとは言えませんが) で、このプログラムのソースを読みだしたところです。
ここでは、すぐにファイルをクローズしているのでファイルの共有の実験ができません。 実験をするにはCloseHandleをプログラム終了時に持っていきます。 hFileはグローバル変数にします。 こうしておいて、このプログラムを2つ起動します。1つ目であるファイルを 読み込みます。次にもう1つのプログラムで同じファイルを読み込んでみてください。


[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]

Update Sep/06/1997 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。