ホーム ざれごと ワシントン州 ツール NT豆知識 Win32プログラミングノート 私的用語 ジョーク いろいろ ゲーム雑記 Favorites 掲示板 Mail
次の関数は、自分のバージョン番号を取得するためのものです。
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 の方を使うように変更しました。
アプリケーションの二重起動防止には、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コンソールプログラムを起動すると、新しいコンソールウィンドウが表示されてしまいます。 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 && nChar == 0x1b) { // ESC |
4: | EndTracking(false); |
5: | } |
6: | } |
コントロールキーやシフトキーでドロップの動作を変えたい場合には、OnKeyDown() ですね。フラグでも立てて、ドラッグイメージに変更を加えるなりなんなりすればよいでしょう。
ちなみに、ここで引用した元のソースは、不要なウィンドウを自動的に閉じるGoAwayという自作アプリから抜き出したものです。 フルバージョンのソース込みで挙げてますので興味のある方はどぞ。
CCheckListBoxというのは、MFCでListBoxをサブクラス化してチェック状態を追加したコントロールです。 「罠」というほど大げさではありませんが…、CCheckListBoxを使うときに引っかかりやすい点について列挙しときます。
デバッグビルドでスタティックリンクする場合、このMFC固有のリソースが定義されていないようです。 最後のランタイムエラーを避けるには、手っ取り早いのは、どっかからビットマップを引っ張ってくることでしょう。 ということで、%windir%\system32\mfc42ud.dllから、30996番のビットマップをリソースにコピーしたりして。
デバッグ時のみスタティックリンクにしているのだったら、コンパイルのコンディションとして、_DEBUG を付けとけばちょっと安心でしょう。
画像ファイル(GIF, JPEG, BMP, ICOなど)を読み込むのに、いちいちフォーマットに対応したコードを書くのも勉強になりそうですが、そんなこたぁやってらんねぇ読めさえすりゃぁいいじゃん、という私のようなすぼらプ ログラマのために。
ちょっと COM を使うハメになりますが、OleLoadPicturePath という API を使うと、種々の画像ファイルのロードを任せてしまうことができます(ただし、あまり robust ではないらしい)。
とりあえず、ファイルを読み込んで 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() が使えます。
なにも難しいことはないので、View の OnInitialUpdate() から、ResizeParentToFit(FALSE); を呼べばおっけ。 FALSE をつけることで、必要ならメインフレームの大きさを拡大します。 デフォルトの TRUE だと縮小だけになるので、環境によってはダイアログテンプレートのサイズよりクライアント領域が小さくなってしまうことがあってうまくありません。
void CXxxView::OnInitialUpdate() { CFormView::OnInitialUpdate(); GetParentFrame()->RecalcLayout(); ResizeParentToFit(FALSE); }
常識といえば常識なのですが、久々にコードを書いていて忘れていることに気がついたので、自分用のメモ。 どうやって思い出したかって? 昔のコードを引っ張り出してそれっぽいところを眺めたわけです(笑)。
これまた知っていたらなんてことはないんですが。 CMainFrame クラスの OnCreate() に次の行を書き込むだけです。
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { ... // ステータスバーのペイン0を、縁ありにする m_wndStatusBar.SetPaneInfo(0, 0, SBPS_STRETCH, 0); return 0; }
ホーム ざれごと ワシントン州 ツール NT豆知識 Win32プログラミングノート 私的用語 ジョーク いろいろ ゲーム雑記 Favorites 掲示板 Mail