第73章 イメージビューアを作る


今回はイメージビューアを作ります。最初に断っておますが 扱えるのはBMPとかアイコンだけで、JPEGとかGIF は扱えません。BMPファイルの画像を表示しようというものです。 ついでに、ファイルを選択するときに使う「ファイルを開く」コモン ダイアログについても解説します。今回も大して難しくないです。 サンプルのプログラムは30分くらいで作りました。

毎度おなじみの「ファイルを開く」ダイアログボックスです。 メニューから「開く」を選択すると左のようなダイアログボックスが 出てきます。自分でこれと同じものを作るとするとかなり大変な 作業になります。でも、コモンダイアログを使えばすぐに使えます。 また、ちゃっかりとダイアログボックスのタイトル名を 変えていることに注意してください。

コモンダイアログについては、すでに 第38章で「フォントの指定」 ダイアログボックスを使いました。このダイアログボックスから BMPファイルを選択して「開く」ボタンを押すと、・・・

親ウィンドウのクライアント領域にビットマップイメージが 表示されます。親ウィンドウの大きさはちょうどクライアント領域に ビットマップがぴったり収まる大きさに調整されます。

また、親ウィンドウのタイトルバーにはフルパス付きのファイル名と イメージサイズが表示されていることに注意してください。

また、メニューのオプションでフルパス付きか、ファイル名のみかを 選択できるようにしてあります。
今までは、ビットマップはリソースになっていました。 外部のファイルからビットマップを読み込むにはどうしたらよいのでしょうか。 実はWindows95ではLoadImage関数という便利なものが用意されています。

HANDLE LoadImage( HINSTANCE hinst, // インスタンスハンドル LPCTSTR lpszName, // イメージの名前 UINT uType, // イメージタイプ int cxDesired, // 幅 int cyDesired, // 高さ UINT fuLoad // ロードフラグ );

ファイルよりビットマップをロードするには、 lpszNameにファイル名、イメージタイプにIMAGE_BITMAP、 ロードフラグにLR_LOADFROMFILEを指定します。 このとき幅、高さは無視してかまいません。 後は、リソースからBMPを読み込んだときと 同じ処理です。第26章を 参照してください。

次に、「ファイルを開く」ダイアログの扱いですが 次のように行います。

1.OPENFILENAME構造体に必要な事項をセットする 2.GetOpenFileName関数の呼び出し

たったこれだけです。でも、例によってOPENFILENAME構造体の メンバが山のようにあります。サンプルのプログラムで使用した メンバのみについて解説します。他のメンバについては各自で ヘルプで調べてみてください。

typedef struct tagOFN { // ofn 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; DWORD lCustData; LPOFNHOOKPROC lpfnHook; LPCTSTR lpTemplateName; } OPENFILENAME;

lStructSizeはこの構造体の大きさです。

hwndOwnerはこのダイアログの親ウィンドウのハンドルです。

hInstanceは、lpTemplateNameを指定しない場合は無視します。

lpstrFilterは、その名の通りフィルタです。
"テキストファイル(*.txt)\0*.txt\0すべて(*.*)\0*.*\0\0"
などのように使います。文字列の区切りにはヌル文字(\0)を 入れます。最後はヌル文字2つ(\0\0)を使います。実際使って みるとすぐにわかります。

lpstrFileには、GetOpenFileName関数が呼ばれるとここに フルパス付きのファイル名が格納されます。

nMaxFileは、lpstrFileの大きさです。なおMAX_PATHはwindef.hで 260と定義されています。

lpstrFileTitleには、選択されたファイル名が格納されます。

nMaxFileTitleは、lpstrFileTitleの大きさです。

Flagsには、ダイアログを作成時の細かい指定です。 ここでは、OFN_FILEMUSTEXISTを使っています。 これは、実際に存在しないファイル名をユーザーが無理矢理 入力したとき、警告が表示されます。

必要なメンバだけセットしていくわけですが、最初に memset関数でメンバを全部0で埋めておくと安全です。

次に、GetOpenFileName関数です。

BOOL GetOpenFileName( LPOPENFILENAME lpofn

引数は、先ほど定義した構造体のアドレスだけです。

では、サンプルのプログラムを見てみましょう。

// imgview.rcの一部 // 自前で作る人はwindows.hと自作のヘッダをインクルード ///////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "開く(&O)", IDM_OPEN MENUITEM SEPARATOR MENUITEM "終了(&X)", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "フルバス付き(&P)", IDM_FULL END POPUP "ヘルプ(&H)" BEGIN MENUITEM "..について(&A)", IDM_ABOUT END END

リソーススクリプトです。いつも書いていますが自分で作る人は windows.hと自作ヘッダーファイル(IDM_OPENなどを定義)を インクルードしてください。

// imgview.cpp #define STRICT #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void OpenImgFiles(HWND); char szClassName[] = "imgview"; //ウィンドウクラス HINSTANCE hInst; HMENU hMenu; int show = 0; int dx, dy; //ウィンドウサイズとクライアントサイズの差 char FileName[MAX_PATH], FileTitle[64]; int filename_type; //タイトルバーに表示するファイル名の種別 //0:フルパス付きファイル名 1:ファイル名のみ enum {full, title}; 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; }

いつもとたいして変わりませんが、enumに注意してください。オプションで使います。

//ウィンドウ・クラスの登録 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)); }

これは、いつもと同じです。

//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; RECT rc0, rc1; 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); GetWindowRect(hWnd, &rc0); GetClientRect(hWnd, &rc1); dx = (rc0.right - rc0.left) - (rc1.right - rc1.left); dy = (rc0.bottom - rc0.top) - (rc1.bottom - rc1.top); return TRUE; }

インスタンスハンドルをグローバル変数にコピーしています。 こうすると後でインスタンスハンドルを使うときに便利です。

それと、後で使うのですがクライアント領域の大きさを 特定の大きさにするのは結構面倒です。

AdjustWindowRect関数はこれからウィンドウを作るときには つかえてもすでにできてしまったウィンドウの調整には使えません。

そこで、筆者は次のように考えました。

ウィンドウサイズとクライアント領域の大きさの違いは ウィンドウの枠の太さとか、メニューバーなどの太さなどを加えたものである。 これらは、ウィンドウの大きさが変わっても変化しない。

そこで、まずウィンドウの幅、高さを測ります。次にクライアント領域の 幅、高さを測ります。この差はある範囲内ではウィンドウの大きさに関係なく 一定であると考えられます。 したがって、この差をdx, dyとすると、もしクライアント領域の 幅、高さがwx, wyのウィンドウが必要ならwx + dx, wy + dyの大きさに ウィンドウを調整すればよいことになります。 ただし、ウィンドウサイズが極端に小さくなるとメニューが2列以上にに表示される ことがあります。こうなるとこれは、当てはまりません。また、ツールバーや、 ステータスバーがあると話はもっと複雑になります。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, wx, wy; HDC hdc, hdc_mem; HBITMAP hBitmap; BITMAP bmp_info; PAINTSTRUCT ps; RECT rc; char str[32], str_title[128]; char *str_org = " %d * %d"; switch (msg) { case WM_CREATE: hMenu = GetMenu(hWnd); filename_type = title; // 表示するファイル名をタイトルのみにする break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_OPEN: OpenImgFiles(hWnd); break; case IDM_FULL: switch (filename_type) { case full: CheckMenuItem(hMenu, IDM_FULL, MF_BYCOMMAND | MF_UNCHECKED); filename_type = title; break; case title: CheckMenuItem(hMenu, IDM_FULL, MF_BYCOMMAND | MF_CHECKED); filename_type = full; break; } break; case IDM_ABOUT: MessageBox(hWnd, "イメージビューア\n(c)1997 Y.Kumei", "..について", MB_OK | MB_ICONINFORMATION); break; } break; case WM_PAINT: if (show == 1) { hdc = BeginPaint(hWnd, &ps); hBitmap = (HBITMAP)LoadImage(hInst, FileName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); if (hBitmap == NULL) MessageBox(hWnd, "失敗です", "ok", MB_OK); GetObject(hBitmap, sizeof(BITMAP), &bmp_info); wx = bmp_info.bmWidth; wy = bmp_info.bmHeight; GetWindowRect(hWnd, &rc); MoveWindow(hWnd, rc.left, rc.top, wx+dx, wy+dy, TRUE); hdc_mem = CreateCompatibleDC(hdc); SelectObject(hdc_mem, hBitmap); BitBlt(hdc,0, 0, wx, wy, hdc_mem, 0, 0, SRCCOPY); DeleteDC(hdc_mem); DeleteObject(hBitmap); EndPaint(hWnd, &ps); memset(str_title, '\0', sizeof(str)); wsprintf(str, str_org, wx, wy); if (filename_type == full) strcpy(str_title, FileName); if (filename_type == title) strcpy(str_title, FileTitle); strcat(str_title, str); SetWindowText(hWnd, str_title); } else { return (DefWindowProc(hWnd, msg, wp, lp)); } 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_PAINTの部分を注意深く見てください。 GetObject関数でビットマップの大きさを取得したら MoveWindow関数でちょうどよい大きさにウィンドウサイズを 変更しています。また、タイトルバーにファイル名を 表示しています。このとき、オプションの種類によって フルパス付きか、ファイル名のみかを区別しています。

void OpenImgFiles(HWND hWnd) { OPENFILENAME ofn; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = "BMP files(*.bmp)\0*.bmp\0All Files(*.*)\0*.*\0\0"; ofn.lpstrFile = FileName; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_FILEMUSTEXIST; ofn.lpstrDefExt = "bmp"; ofn.nMaxFileTitle = 64; ofn.lpstrFileTitle = FileTitle; ofn.lpstrTitle = "粂井康孝制作・ファイルを開く!"; if (GetOpenFileName(&ofn)) { show = 1; InvalidateRect(hWnd, NULL, TRUE); } return; }

これは、あまり説明はいりませんね。ユーザーが ファイルを選択し終わったらクライアント領域を再描画 させています。変数のshowは、ウィンドウが表示されて まだ、BMPが表示されていないときのみ0で、一度この関数が 実行されてからはずっと1です。というのは、ウィンドウが できたばかりの時は、WM_PAINTメッセージが発行されても 表示するものがなくてエラーになるのを防ぐためです。

このプログラムを少し改良して、小さなビットマップを 表示するときにウィンドウがある一定の大きさより小さくならない ようにしてください。

また、アイコンを表示するように作り替えてみてください。


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

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