ホーム  ざれごと  ワシントン州  ツール  NT豆知識  Win32プログラミングノート  私的用語  ジョーク  いろいろ  ゲーム雑記  Favorites  掲示板   Mail

Win32プログラミングノート --- Misc. ---

Last modified: Tue Dec 09 00:04:48 2003 PDT

一つ上へ

ここでの記述は、Visual C++(tm) Ver.6.0 USを元にしています

バージョンリソースからバージョン情報を取得

次の関数は、自分のバージョン番号を取得するためのものです。

1: #include <malloc.h>
2: DWORD GetVersionInfo()
3: {
4:     TCHAR path[MAX_PATH];
5:     path[::GetModuleFileName(AfxGetInstanceHandle(), path, sizeof path / sizeof path[0])] = 0;
6:  
7:     DWORD dummy;
8:     DWORD size = GetFileVersionInfoSize(path, &dummy);
9:     if (size == 0) {
10:         // Error...
11:     }
12:     LPVOID lpData = NULL;
13:     __try {
14:         lpData = alloca(size);
15:     }
16:     __except (EXCEPTION_EXECUTE_HANDLER) {
17:         // Error...
18:     }
19:     if (!GetFileVersionInfo(path, 0, size, lpData)) {
20:         // Error...
21:     }
22:     LPVOID lpBuffer;
23:     UINT vSize;
24:     if (!VerQueryValue(lpData, _T("\\"), &lpBuffer, &vSize)) {
25:         // Error...
26:     }
27:     VS_FIXEDFILEINFO* info = (VS_FIXEDFILEINFO*)lpBuffer;
28:  
29:     return info->dwFileVersionMS
30: }

解説

GetModuleFileName()にAfxGetInstanceHandle()の戻り値を渡すと、実行中のexeファイルの フルパスを取得できます。

GetFileVersionInfoSize()は、引数のファイル名に渡したファイルに含まれるリソースの バージョン情報の大きさを取得します。

alloca()でスタック上にバッファを用意し、 GetFileVersionInfo()で、バージョンリソースのチャンクを取得します。 取得したいのは単にバージョン情報だけなので、 ルートの情報をVerQueryValue()を使って取得します。

余談ですが、alloca は NULL を返しません。 NT の場合、メモリがアロケートできなかった場合には例外が発生します。 一般的には、alloca は使わないほうが良いライブラリ関数とされています。 (でも楽チンだから使っちゃうんだこれが…)

これで得られるポインタの型はvoid*ですが、VS_FIXEDFILEINFO*へキャストします。

その中の、 dwFileVersionMS というフィールドにバージョン情報が格納されています。 HIWORD()がメジャー番号、LOWORD()がマイナー番号となっています。

[補遺] 以前は dwProductVersionMS だと記述していたのですが、プロパティ他の表示にあわせ、 dwFileVersionMS の方を使うように変更しました。

Import Library version.lib
インポートライブラリとして、version.libをリンクする必要があります。 プロジェクトのリンクするライブラリに追加してください。

二重起動防止

アプリケーションの二重起動防止には、Mutexを使います。

1: HANDLE gMutex = NULL;
2: const TCHAR gMutexName[] = _T("Xxx Exclusive Mutex");
3:  
4: BOOL CXxxApp::InitInstance()
5: {
6:     ...
7:     gMutex = CreateMutex(NULL, FALSE, gMutexName);
8:     if (gMutex == NULL) {
9:         AfxMessageBox(_T("Cannot create Mutex"));
10:         return FALSE;
11:     }
12:     if (GetLastError() == ERROR_ALREADY_EXISTS) {
13: #ifdef ACTIVATE_ON_DUPLICATE
14:         // activate the first instance
15:         CString title;
16:         title.LoadString(IDR_MAINFRAME);
17:         HWND hwnd = ::FindWindow(NULL, title);
18:         if (hwnd) {
19:             ::SetForegroundWindow(hwnd);
20:         }
21: #endif
22:         return FALSE;
23:     }
24:     ...
25: }

ACTIVATE_ON_DUPLICATEで囲まれた部分は、既にオープンしているアプリケーション(がウィンドウを作っていれば、ですが)をフォアグラウンドにします。 トップレベルウィンドウのキャプションが固定なダイアログアプリケーションで有効です。 ドキュメント・ビューモデルの場合には、通常MFCが勝手にウィンドウクラス名を割り当てる上、 編集中のドキュメントがキャプションに付加されるため、FindWindow()が使いにくくなっています。 このような場合、CMainFrameクラスのPreCreateWindow()をオーバーライドしてウィンドウクラス名を固定します。 その上で、FindWindow()でウィンドウクラス名を元に検索をかけます。

MFCが生成するウィンドウクラス名を変更する方法 (MSKKサポート情報: cpj4721)

MSのサイトはよく配置が変わってしまうので、コードだけ抜粋。
後日注:よく見るとMSサイトに出ているのはあまりよろしくないコードだったので、ちょいと書き換えました。

1: BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
2: {
3:     BOOL r = CFrameWnd::PreCreateWindow(cs);   // MDI では CMDIFrameWnd
4:  
5:     if (r) {
6:         static const TCHAR* newName = _T("newClassName");
7:         WNDCLASS wndcls;
8:  
9:         HINSTANCE hInst = AfxGetInstanceHandle();
10:  
11:         // see if the class already exists
12:         if (!::GetClassInfo(hInst, newName, &wndcls)) {
13:             // get default stuff
14:             r = ::GetClassInfo(hInst, cs.lpszClass, &wndcls);
15:             if (r) {
16:                 // register a new class
17:                 wndcls.lpszClassName = newName;
18:                 wndcls.hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(IDR_MAINFRAME));
19:                 if (::RegisterClass(&wndcls)) {
20:                     cs.lpszClass = newName;
21:                 }
22:                 else {
23:                     r = FALSE;
24:                 }
25:             }
26:         }
27:     }
28:  
29:     return r;
30: }

ウィンドウを隠してWin32コンソールプログラムを起動

Win32アプリケーションからWin32コンソールプログラムを起動すると、新しいコンソールウィンドウが表示されてしまいます。 WinExec()やShellExecute(?)ではなく、CreateProcess APIを使えば、コンソールウィンドウの表示を抑止することができます。

1: bool RunCmd()
2: {
3:     STARTUPINFO startupInfo = {
4:         sizeof startupInfo,
5:         NULL, /*lpDesktop=*/NULL, /*lpTitle=*/_T("Update cmd"),
6:         0, 0, 0, 0,
7:         /*dwXCountChars=*/10, /*dwYCountChars=*/10,
8:         /*dwFillAttribute=*/0,
9:         /*dwFlags=*/STARTF_USEFILLATTRIBUTE | STARTF_USECOUNTCHARS | STARTF_USESHOWWINDOW,
10:         /*wShowWindow=*/SW_HIDE,
11:         /*cbReserved2=*/0,
12:     };
13:     PROCESS_INFORMATION processInfo;
14:  
15:     TCHAR path[_MAX_PATH];
16:     GetSystemDirectory(path, _MAX_PATH);
17:     _tcscat(path, _T("\\cmd.exe"));
18:  
19:     if (!CreateProcess(
20:             path, _T("cmd /c echo hello"),
21:             NULL, NULL,
22:             FALSE,
23:             CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS,
24:             NULL,
25:             NULL,
26:             &startupInfo,
27:             &processInfo)) {
28:         DWORD err = GetLastError();
29:         TRACE1("error code = %d\n", err);
30:         AfxMessageBox(_T("Could not run cmd.exe"));
31:         return false;
32:     }
33:  
34:     CloseHandle(processInfo.hProcess);
35:     CloseHandle(processInfo.hThread);
36:     return true;
37: }

STARTF_USESHOWWINDOW と、SW_HIDE がポイントっすかね。


タスクトレイアイコンの完璧な(?)登録方法

タスクトレイへアイコンを登録するには、もう決り文句のようなイディオムがありますが、 せっかくタスクトレイにアイコンを登録しても、シェルが死んで復活すると消えてしまいます(シェルが自動的に復活することそのものは良いアイディアだと思いますよ)。プロセスは残っているのに…。

で、朗報です。IE4からだかIE5からだか忘れましたが、シェルが復活したときにプライベートウィンドウメッセージが送られるようになりました。こいつを使えば、 不安定な(?)シェルが死んでもばっちり(死語?)です。 で も、自分のDLLをシェルにインジェクトして不安定にさせちゃぁ元も子もありませんけど〜。

具体的には、 TaskbarCreated という名前でメッセージを登録して、そのハンドラを定義し、再度タスクトレイに登録してやります。

プライベートウィンドウメッセージの登録は、RegisterWindowMessage APIを使います。戻り値はUINTです。これを、クラスのスタティックメンバに収めておきます。
余談ですが、 一個のアプリケーションで閉じた世界では、 プライベートウィンドウメッセージを使うまでもなくWM_APP+xxxで済ませることも出来ます。 しかし、 複数のアプリケーションが関わる世界では、 RegisterWindowMessage()を使って、メッセージの一意性と一貫性を持たせるべきです。RegisterWindowMessage()は、引数の文字列をアトムとして登録し、同じアトムに対しては、同じ値を返します。
WM_APPは、0x8000に定義されています。RegisterWindowMessage() は、0xC000〜0xFFFFを返します。ですから、常識的にWM_APP+xxxと、RegisterWindowMessage() の返す値がぶつかることはまずないでしょう。もちろん絶対 とは言い切れませんが。世の中には、予想もつかない妙な処理をするアプリケーションがあるものですから…。ま、そういう変なアプリは、この際無視しておきます。

さらに余談。 NOTIFYICONDATA の uCallbackMessage には通知に使われるメッセージを指定します。 時折、WM_USER + xxx を通知メッセージとして使っているサンプルコードを見かけることがあります。 たいていの場合問題はないと思われますが、厳密に言うと WM_USER を基準にするのはあまり誉められたことではありません。 WM_USER がユーザ(あるいはアプリケーション)に完全に開放されていたのは Win 3.1x の頃の話で、Win32 では WM_APP 以降がアプリに開放されています。 WM_USER + xxx の領域は predefined なウィンドウクラス(たとえばツリービューやリストビューなど)で用いられている可能性があります。

というわけで、自分で定義したウィンドウクラスのウィンドウインスタンスに対して用いるのは問題がないのですが、ダイアログへポストさせるメッセージとしては、WM_USER + xxx はあまり適切ではありません。 代わりに WM_APP + xxx を使ってください。 32ビット Windows API が登場してからもう10年近く経とうとしていますが、いまだにこういう混乱が見られるのですね……。

MFCでは、登録したメッセージハンドラは、ON_REGISTERED_MESSAGE() マクロを使ってテーブルに記述します。さすがMFC、こんなマクロまで用意しているとはやるじゃん、という感じ。
なお、メッセージハンドラのテーブルはクラスのスタティックメンバなので、プライベートメッセージを入れておくUINT変数もスタティックメンバにしておくわけです。蛇足ですが、ON_REGISTERED_MESSAGE() はクラスウィ ザードでは設定できないので、手で書き込みます。まぁ大した手間ではありませんよね。

プライベートメッセージのハンドラは、ON_MESSAGE()のハンドラと同様、次の形になります。

LRESULT OnTaskbarCreated(WPARAM, LPARAM);
MFCのメッセージクラッカは何もしてくれないわけですが、この場合問題ないでしょ。

ハンドラでは、wParamもlParamも参照せず、単にタスクトレイにアイコンを設定しなおします。簡単でしょ?

そういうわけで、MFCでのコードは次のようになります。

ヘッダ

1: // ヘッダの方
2:  
3: class CMainFrame : public CFrameWnd
4: {
5:     
6: public:
7:     CMainFrame();
8: protected:
9:     DECLARE_DYNAMIC(CMainFrame)
10:  
11:     ...
12:  
13: protected:
14:     enum ICON_OPERATION { ICON_ADD = NIM_ADD, ICON_MODIFY = NIM_MODIFY};
15:     BOOL SetTrayIcon(UINT id, ICON_OPERATION ope);
16:     BOOL SetTaskTray();
17:     BOOL UnsetTaskTray();
18:     static BOOL CALLBACK EnumChildWndProc(HWND hwnd, LPARAM lParam);
19:  
20: protected:
21:     HWND m_hwndNotificationArea;
22:     DWORD m_tidTaskTray;
23:     DWORD m_pidTaskTray;
24:  
25:  
26: protected:  // for message map...
27:     static UINT s_wmTaskbarCreated;
28:  
29: // Generated message map functions
30: protected:
31:     //{{AFX_MSG(CMainFrame)
32:     ...
33:     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
34:     ...
35:     //}}AFX_MSG
36:     ...
37:     afx_msg LRESULT OnNotifyIcon(WPARAM, LPARAM);
38:     afx_msg LRESULT OnTaskbarCreated(WPARAM, LPARAM);
39:     ...
40:     DECLARE_MESSAGE_MAP()
41: };
42:  
43: #define MWM_NOTIFYICON  (WM_APP + 2)
44: #define ID_TASKTRAY     12

CPPファイル

1: // CPPファイルの方
2:  
3: UINT CMainFrame::s_wmTaskbarCreated;
4:  
5: IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)
6:  
7: BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
8:     //{{AFX_MSG_MAP(CMainFrame)
9:     ON_WM_CREATE()
10:     ON_WM_DESTROY()
11:     ...
12:     ON_WM_QUERYENDSESSION()
13:     //}}AFX_MSG_MAP
14:  
15:     ON_MESSAGE(MWM_NOTIFYICON, OnNotifyIcon)
16:     ON_REGISTERED_MESSAGE(CMainFrame::s_wmTaskbarCreated, OnTaskbarCreated)
17: END_MESSAGE_MAP()
18:  
19:  
20: bool CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
21: {
22:     ...
23:     
24:     s_wmTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
25:  
26:     ...
27: }
28:  
29: //
30: // Register this app's icon to the notification area.
31: // 通知エリアにアイコンを登録
32: //
33: BOOL CMainFrame::SetTrayIcon(UINT id, ICON_OPERATION ope)
34: {
35:     // Register my icon
36:     NOTIFYICONDATA tnid;
37:  
38:     tnid.cbSize = sizeof tnid;
39:     ASSERT(m_hWnd);
40:     tnid.hWnd = m_hWnd;
41:     tnid.uID = ID_TASKTRAY;
42:     tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
43:     tnid.uCallbackMessage = MWM_NOTIFYICON;
44:     tnid.hIcon = theApp.LoadIcon(id);
45:     CString tip;
46:     VERIFY( tip.LoadString(IDR_MAINFRAME) );
47:     _tcscpy(tnid.szTip, tip);
48:  
49:     DWORD message = (DWORD)ope; // NIM_xxx
50:     return Shell_NotifyIcon(message, &tnid);
51: }
52:  
53: //
54: // Get HWND, PID/TID of the notification area, and
55: // register this app's icon to the notification area.
56: // 通知エリアのHWND、PID/TIDも取得して、
57: // "タスクトレイ"または"通知エリア"にアイコンを登録.
58: //
59: BOOL CMainFrame::SetTaskTray()
60: {
61:     // Get hwnd of TaskTray
62:     HWND hwndTray = ::FindWindow(_T("Shell_TrayWnd"), NULL);
63:     if (hwndTray == NULL) {
64:         AfxMessageBox(_T("Cannot find Shell_TrayWnd. "));
65:         return FALSE;
66:     }
67:     // Just FYI:
68:     // NotificationAreaを探し、m_hwndNotificationAreaへ格納する
69:     m_hwndNotificationArea = ::FindWindowEx(hwndTray, NULL, _T("TrayNotifyWnd"), NULL);
70:     if (m_hwndNotificationArea == NULL) {
71:         AfxMessageBox(_T("Cannot find NotificationArea Window."));
72:         return FALSE;
73:     }
74:     // 通知エリアウィンドウのプロセスID・スレッドIDを取得
75:     m_tidTaskTray = ::GetWindowThreadProcessId(m_hwndNotificationArea, &m_pidTaskTray);
76:     ASSERT(m_tidTaskTray && m_pidTaskTray);
77:  
78:     if (!SetTrayIcon(IDI_TASKTRAY, ICON_ADD)) {
79:         AfxMessageBox(_T("Failed to register task tray Icon. OK to proceed, CANCEL to quit"), MB_ICONASTERISK | MB_OKCANCEL);
80:         return FALSE;
81:     }
82:     return TRUE;
83: }
84:  
85: BOOL CMainFrame::UnsetTaskTray()
86: {
87:     NOTIFYICONDATA tnid;
88:  
89:     tnid.cbSize = sizeof tnid;
90:     ASSERT(m_hWnd);
91:     tnid.hWnd = m_hWnd;
92:     tnid.uID = ID_TASKTRAY;
93:  
94:     m_tidTaskTray = m_pidTaskTray = 0;
95:  
96:     return Shell_NotifyIcon(NIM_DELETE, &tnid);
97: }
98:  
99: LRESULT CMainFrame::OnNotifyIcon(WPARAM wParam, LPARAM lParam)
100: {
101: #if !defined(_DEBUG)
102:     UNREFERENCED_PARAMTER(wParam);
103: #endif
104:     ASSERT(wParam == ID_TASKTRAY);
105:     switch (lParam) {   // lParam == mouse message ?
106:     case WM_LBUTTONDOWN:
107:         // 何か処理をする
108:         break;
109:         ...
110:     }
111:  
112:     return 0L;
113: }
114:  
115: //
116: // New to IE4 or 5: called when Explorer restarts.
117: // Explorerが再生したときに呼ばれる
118: //
119: LRESULT CMainFrame::OnTaskbarCreated(WPARAM, LPARAM)
120: {
121:     SetTaskTray();
122:  
123:     return 0;
124: }

おまけでタスクトレイのウィンドウハンドルを取得する部分も載っけてますが、単にタスクトレイにアイコンを登録するだけならそこらへんは不要です。


イメージリストを使ったドラッグイメージの描画

CImageListには、ドラッグ中にビットマップを描画するためのメンバが用意されていますが、どの順番で何を呼べばいいのか、いまいちよく分かりません。というわけで、ちょっと整理しておきます。

準備

イメージリストの作り方は省略します。透過色を設定しておいてね。ここでは、クラスのメンバ m_dragImage とします。 んで、 LBUTTONDOWN辺りでドラッグの準備をするわけです。ここでは、イメージリストの0番目のビットマップを使います。まず呼ばなきゃいけないのが、BeginDrag()。イメージのインデクスと、イメージ中のホットスポッ トを指定します。ここでは32x32のイメージで、その真中にホットスポットがあることにします。

次に、現在のマウスカーソルの位置を取得して、ドラッグモードに入ることをCImageListへ伝えます。また、ドラッグの親になるウィンドウを指定します。このウィンドウからは、 ドラッグがはみ出ないというわけ。 私の コードは、シェルにフックする変なアプリから取ってきたので、デスクトップウィンドウを指定していますが、普通はメインフレームかビューを指定することになるでしょう。あと、カーソルの位置は、指定したウィンドウ の相対座標になると思う(確かめていませんが)ので、ちょっとだけ注意しましょう。 なお、ドキュメントによると、相対座標の原点は、クライアントエリアではなく、ウィンドウそのものだそうです。キャプションなんかが含まれることになりますね。 CWnd::GetWindowRect()、CRect::TopLeft()、CRect::operator-=(POINT)あたりを使えばよいでしょう。

蛇足ながら、ドラッグ&ドロップを使う場合には、マウスをキャプチャするのが普通(?)。SetCapture()を呼んでください。 それから、マウスカーソルを消したくなるでしょうから、SetCursor()も呼びましょう。マウスをキャプチャしている間は、WM_CURSORによるカーソルシェイプの設定は行われませんから、一度SetCursor()を呼ぶだけでOK。

1: void CMainFrame::StartTracking()
2: {
3:     ASSERT(!m_inTracking);
4:  
5:     SetCapture();
6:     // Double check if we got the mouse capture.
7:     if (GetCapture() == this) {
8:         m_inTracking = true;
9:     }
10:     else {
11:         TRACE0("Failed to get the capture!\n");
12:         return;
13:     }
14:  
15:     // Create drag image & enter drag mode
16:     m_prevCursor = SetCursor(NULL);
17:     m_dragImage.BeginDrag(0, CPoint(15, 15));
18:     CPoint curPos;
19:     GetCursorPos(&curPos);
20:     m_dragImage.DragEnter(GetDesktopWindow(), curPos);
21: }

m_inTrackingはクラスのメンバ変数で、ドラッグを行っているかどうかのフラグとして使っています。そのほか、m_で始まるのはメンバ変数であるというのはお約束。

マウスの移動

マウスをキャプチャしていると、マウスカーソルの移動に伴って、キャプチャしたウィンドウへWM_MOUSEMOVEが延々と送られてきます。ので、ドラッグイメージもそれに合わせて描画します。これは簡単で、DragMove()に座 標を渡すだけ。気をつけることといえば、さっきも出てきた相対座標の原点だけですね。

ドラッグに伴ってカーソル下のアイテムを強調表示するなど、DCを使って描画する場合には、描画を開始する前に一旦ドラッグイメージを消します。DragShowNolock()を使います。
というわけで、こんな風なコード。

1: void CMainFrame::DoTracking(CPoint point)
2: {
3:     GetCursorPos(&point);
4:  
5:     if (描画が必要か?) {
6:  
7:         // ドラッグアイコンを隠す
8:         m_dragImage.DragShowNolock(FALSE);
9:  
10:  
11:         // 描画(省略!)
12:  
13:         ....
14:         
15:         // ドラッグアイコンを再表示
16:         m_dragImage.DragShowNolock(TRUE);
17:     }
18:  
19:     // ドラッグアイコンの表示
20:     m_dragImage.DragMove(point);
21: }

ドラッグの終了

ドラッグの終了は、WM_LBUTTONUP から行います。m_inTracking が true になっていたらドラッグ中です。

まずDragLeave()を呼び出してから、EndDrag()を呼びます。DragLeave()に渡すウィンドウは、DragEnter()に渡したものと同じ物にします。あぁ、終わった終わった。
おっと、カーソルシェイプも忘れずに元に戻しておきましょう。注意一秒けが一生。意味不明。
まだあった、キャプチャのリリース。忘れずにね。

1: void CMainFrame::EndTracking(bool fRegister)
2: {
3:     TRACE0("EndTracking\n");
4:     ASSERT(m_inTracking);
5:     m_inTracking = false;
6:     ReleaseCapture();
7:  
8:     // 描画関係の後始末(省略!)
9:     ....
10:     
11:     m_dragImage.DragLeave(GetDesktopWindow());
12:     m_dragImage.EndDrag();
13:     SetCursor(m_prevCursor);
14:     m_prevCursor = NULL;
15: }

m_inTrackingは、 RelaseCapture()の前にクリアします。すぐ後でキャプチャの変更ハンドラの説明をしますが、なにしろ自分でReleaseCapture()を呼び出しても、WM_CAPTURECHANGED メッセージが送られます。フラグはそ の前に倒しておかなきゃ意味がないのです。

落穂拾い

細かいことを言い始めるとキリがありませんが、2つだけ。

1. 完全にプリエンプティブなOSである32ビットWindowsでは、他のプロセスは時々意表もつかないことをやってくれます。また、マウスキャプチャ中にユーザがAlt+Tabでウィンドウを切り替えたりすると、 キャプチャは外 れてしまいます。ということで、WM_CAPTURECHANGED を捕捉して、ドラッグモードを抜けるようにしておきましょう。

1: void CMainFrame::OnCaptureChanged(CWnd *pWnd)
2: {
3: #if !defined(_DEBUG)
4:     UNREFERENCED_PARAMETER(pWnd);
5: #endif
6:  
7:     TRACE1("Capture Changed! from %p\n", pWnd);
8:  
9:     if (m_inTracking) {
10:         // If mouse capture is changed while we're
11:         // supposed to keep it, anyway we have to
12:         // cleanup the things we're doing.
13:         EndTracking(false);
14:     }
15: }

EndTracking()というのは、上で説明したドラッグの終了を行う関数ということで。引数の false は、単なる実装に過ぎませんが、ドラッグ&ドロップがキャンセルされたということにしておきましょう。

2. ドラッグ&ドロップは、エスケープキーでキャンセルできるようにしておきましょう。

1: void CMainFrame::OnChar(UINT nChar, UINT /*nRepCnt*/, UINT /*nFlags*/)
2: {
3:     if (m_inTracking &amp;&amp; nChar == 0x1b) {    // ESC
4:         EndTracking(false);
5:     }
6: }

コントロールキーやシフトキーでドロップの動作を変えたい場合には、OnKeyDown() ですね。フラグでも立てて、ドラッグイメージに変更を加えるなりなんなりすればよいでしょう。

ちなみに、ここで引用した元のソースは、不要なウィンドウを自動的に閉じるGoAwayという自作アプリから抜き出したものです。 フルバージョンのソース込みで挙げてますので興味のある方はどぞ。


CCheckListBoxの罠

CCheckListBoxというのは、MFCでListBoxをサブクラス化してチェック状態を追加したコントロールです。 「罠」というほど大げさではありませんが…、CCheckListBoxを使うときに引っかかりやすい点について列挙しときます。

デバッグビルドでスタティックリンクする場合、このMFC固有のリソースが定義されていないようです。 最後のランタイムエラーを避けるには、手っ取り早いのは、どっかからビットマップを引っ張ってくることでしょう。 ということで、%windir%\system32\mfc42ud.dllから、30996番のビットマップをリソースにコピーしたりして。

デバッグ時のみスタティックリンクにしているのだったら、コンパイルのコンディションとして、_DEBUG を付けとけばちょっと安心でしょう。


画像ファイルの読み込み

画像ファイル(GIF, JPEG, BMP, ICOなど)を読み込むのに、いちいちフォーマットに対応したコードを書くのも勉強になりそうですが、そんなこたぁやってらんねぇ読めさえすりゃぁいいじゃん、という私のようなすぼらプ ログラマのために。

ちょっと COM を使うハメになりますが、OleLoadPicturePath という API を使うと、種々の画像ファイルのロードを任せてしまうことができます(ただし、あまり robust ではないらしい)。

読み込みとBMPの書き込み

とりあえず、ファイルを読み込んで BMP にしてファイルへ保存する関数。
OleLoadPicturePath() で、IPicture のインスタンスを作ります。 次に、SHCreateStreamOnFile() で、ファイルを書き込みモードでオープン、IStream のインスタンスを作ります。 あとは、IPicture::SaveAsFile() を使ってストリームへ書き込むだけ。 どうです、簡単でしょ? これだけ簡単じゃないと、私には画像なんて扱えませんて(笑)。

1: #include <olectl.h>
2: #include <objbase.h>
3: #include <shlwapi.h>
4:  
5: #include <interface.h>
6:  
7: #pragma comment(lib, "delayimp.lib")
8: #pragma comment(lib, "shlwapi.lib")
9: #pragma comment(linker, "/delayload:oleaut32.dll")
10: #pragma comment(linker, "/delayload:ole32.dll")
11: #pragma comment(linker, "/delayload:shlwapi.dll")
12:  
13: bool ConvertToBmp(LPCWSTR src, LPCTSTR out)
14: {
15:     try {
16:         Interface<IPicture> pic;
17:  
18:         //
19:         // Create IPicture object that is associated with the src file.
20:         //
21:         HRESULT hr = ::OleLoadPicturePath((LPWSTR)src, NULL, NULL, 0, IID_IPicture, pic);
22:         ASSERT(hr == S_OK);
23:         if (FAILED(hr)) {
24:             TRACE1("Failed to OleLoadPicturePath. hr=%x\n", hr);
25:             return false;
26:         }
27:  
28:         Interface<IStream> stream;
29:         hr = ::SHCreateStreamOnFile(out, STGM_WRITE | STGM_SHARE_DENY_WRITE | STGM_CREATE | STGM_DIRECT, &stream);
30:         ASSERT(hr == S_OK);
31:         if (FAILED(hr)) {
32:             TRACE1("SHCreateStreamOnFile failed hr=%x\n", hr);
33:             return false;
34:         }
35:  
36:         LONG cbSize;
37:         hr = pic->SaveAsFile(stream, TRUE, &cbSize);
38:         TRACE1("SaveAsFile wrote %d bytes.\n", cbSize);
39:         ASSERT(hr == S_OK);
40:         if (FAILED(hr)) {
41:             return false;
42:         }
43:         //
44:         // N.b. the COM interfaces are automatically
45:         // released by their destructors.
46:         //
47:     } catch (...) {
48:         TRACE0("ConvertToBmp: failed to delay load oleaut32. dll\n");
49:         return false;
50:     }
51:  
52:     return true;
53: }

class Interface てのは、ものすごくシンプルな COM の自作ラッパです。 ここに出すのも恥ずかしいぐらいシンプルでいいかげんなので、出しません(笑)。 簡単に作れると思います。 ブロックを出るときに、デストラクタで COM のインスタンスを破壊してくれることだけが長所です。

ディレイロードを使っている関係で全体を try ~ except で囲っていますが、本質ではありません。

画像のレンダリング

さて、一度 IPicture のインスタンスを得てしまえば、画面に描画するのも簡単です。 たとえば、以下のコードは、トランスルーセントカレンダのプレビューダイアログのコードです。

特に難しいところはありませんが、ウィンドウのサイズを画像の縦横比に合わせてリサイズしてるとこが、ミソといえばミソでしょうか? IPicture::get_Width() や get_Height() で大きさがわかります。
実際にレンダリングをするのは、IPicture::Render() です。 これも簡単ですね。 あえて言うと、height を負にしなければいけないところでしょうか? これは確か、下から上へラスタラインが並んでいるという BMP の変な癖のためです(確かそうだったと思う…。もう忘れてしまった)。

1: class CImagePreview : public CDialog
2: {
3: // Construction
4: public:
5:     CImagePreview(CWnd* pParent = NULL);   // standard constructor
6:  
7: // Dialog Data
8:     //{{AFX_DATA(CImagePreview)
9:     enum { IDD = IDD_IMAGE_PREVIEW };
10:         // NOTE: the ClassWizard will add data members here
11:     //}}AFX_DATA
12:  
13:  
14: // Overrides
15:     // ClassWizard generated virtual function overrides
16:     //{{AFX_VIRTUAL(CImagePreview)
17:     protected:
18:     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
19:     //}}AFX_VIRTUAL
20:  
21: // Implementation
22: protected:
23:  
24:     // Generated message map functions
25:     //{{AFX_MSG(CImagePreview)
26:     virtual BOOL OnInitDialog();
27:     afx_msg void OnPaint();
28:     afx_msg UINT OnNcHitTest(CPoint point);
29:     //}}AFX_MSG
30:     DECLARE_MESSAGE_MAP()
31:  
32: public:
33:     bool Create();
34:     bool SetImage(const CString& path);
35:     bool ClearImage();
36:  
37: protected:
38:     CString m_path;
39:  
40:     hirofumi::Interface<IPicture> m_pic;
41:  
42:     CRect m_rc;
43: };
44:  
45:  
46: void CImagePreview::OnPaint()
47: {
48:     CPaintDC dc(this); // device context for painting
49:     
50:     if (m_pic.Invalid()) {  
51:         return;
52:     }
53:  
54:     OLE_XSIZE_HIMETRIC width;
55:     OLE_YSIZE_HIMETRIC height;
56:     m_pic->get_Width(&width);
57:     m_pic->get_Height(&height);
58:  
59:     m_pic->Render(dc.m_hDC, 0, 0, m_rc.Width(), m_rc.Height(),
60:         0, height, width, -height, NULL);
61: }
62:  
63: bool CImagePreview::SetImage(const CString& path)
64: {
65:     if (m_path == path) {
66:         return true;
67:     }
68:     m_path = path;
69:  
70:     if (m_pic.Valid()) {
71:         m_pic.Release();
72:     }
73:  
74:     try {
75:         HRESULT hr = OleLoadPicturePath((LPWSTR)(LPCTSTR)m_path, NULL, NULL, 0, IID_IPicture, m_pic);
76:         if (FAILED(hr)) {
77:             Invalidate();
78:             return false;
79:         }
80:     } catch (...) {
81:         TRACE0("CImagePreview::SetImage: delay load of OleLoadPicturePath failed. \n");
82:         return false;
83:     }
84:  
85:     // Resize the window to fit the picture aspect.
86:     OLE_XSIZE_HIMETRIC width;
87:     OLE_YSIZE_HIMETRIC height;
88:     m_pic->get_Width(&width);
89:     m_pic->get_Height(&height);
90:     GetClientRect(&m_rc);
91:     if (width && height) {
92:         CRect rcW;
93:         GetWindowRect(&rcW);
94:         int offset = rcW.bottom - m_rc.bottom;
95:         m_rc.bottom = m_rc.Width() * height / width;
96:         rcW.bottom =  m_rc.bottom + offset;
97:         MoveWindow(rcW);
98:     }
99:  
100:     Invalidate();
101:  
102:     return true;
103: }
104:  
105: [以下略]

蛇足ですが、ストリームをメモリ上に作るには CreateStreamOnHGlobal() という API が、そのストリームから HGLOBAL を取ってくるには GetHGlobalFromStream() が使えます。


CFormViewを使った場合:メインフレームの大きさ

なにも難しいことはないので、View の OnInitialUpdate() から、ResizeParentToFit(FALSE); を呼べばおっけ。 FALSE をつけることで、必要ならメインフレームの大きさを拡大します。 デフォルトの TRUE だと縮小だけになるので、環境によってはダイアログテンプレートのサイズよりクライアント領域が小さくなってしまうことがあってうまくありません。

void CXxxView::OnInitialUpdate()
{
    CFormView::OnInitialUpdate();
    GetParentFrame()->RecalcLayout();
    ResizeParentToFit(FALSE);
}

常識といえば常識なのですが、久々にコードを書いていて忘れていることに気がついたので、自分用のメモ。 どうやって思い出したかって? 昔のコードを引っ張り出してそれっぽいところを眺めたわけです(笑)。


ステータスバーのペイン0に縁をつける

これまた知っていたらなんてことはないんですが。 CMainFrame クラスの OnCreate() に次の行を書き込むだけです。

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ...

    // ステータスバーのペイン0を、縁ありにする
    m_wndStatusBar.SetPaneInfo(0, 0, SBPS_STRETCH, 0);

    return 0;
}

Since 1996

一つ上へ

ホーム  ざれごと  ワシントン州  ツール  NT豆知識  Win32プログラミングノート  私的用語  ジョーク  いろいろ  ゲーム雑記  Favorites  掲示板   Mail