生の Windows API (および関連 API) を C/C++ で使用するために調べたことのメモおよび Tips 集です.随時追加します.
Windows | WINVER | _WIN32_WINNT | _WIN32_WINDOWS |
---|---|---|---|
Windows 10 | ≧0x0A00 | ≧_WIN32_WINNT_WIN10 | |
Windows 8.1 | ≧0x0603 | ≧_WIN32_WINNT_WINBLUE | |
Windows 8 | ≧0x0602 | ≧_WIN32_WINNT_WIN8 | |
Windows 7 | ≧0x0601 | ≧_WIN32_WINNT_WIN7 | |
Vista / Server 2008 |
≧0x0600 | ≧_WIN32_WINNT_{VISTA,WS08} | |
XP SP2 / Server 2003 SP1 |
≧0x0502 | ≧_WIN32_WINNT_WS03 | |
XP / Server 2003 |
≧0x0501 | ≧_WIN32_WINNT_WINXP | |
2000 | ≧0x0500 | ≧_WIN32_WINNT_WIN2K | |
NT4.0 | ≧0x0400 | ≧_WIN32_WINNT_NT4 | |
Me | ≧0x0490 | ≧0x0490 | |
98 | ≧0x0410 | ≧0x0410 | |
95 | ≧0x0400 | ≧0x0400 |
参考
Visual C/C++ Version |
Compiler Version |
Tools Version |
_MSC_VER | _MSC_FULL_VER (自分の環境で確認した値) |
出典 |
---|---|---|---|---|---|
2003 | 13.10 | ? | 1310 | 未定義 | Predefined Macros (Visual Studio .NET 2003) |
2005 | 14.00 | ? | 1400 | 未定義 | Predefined Macros (Visual Studio 2005) |
2008 | 15.00.nnnnn.nn | 3.5? | 1500 | 1500nnnnn (150030729) |
Predefined Macros (Visual Studio 2008) |
2010 | 16.00.nnnnn.nn | 4.0 | 1600 | 1600nnnnn (未確認) |
Predefined Macros (Visual Studio 2010) |
2012 | 17.00.nnnnn.nn | 4.0 | 1700 | 1700nnnnn (170061030) |
Predefined Macros (Visual Studio 2012) |
2013 | 18.00.nnnnn.nn | 12.0 | 1800 | 1800nnnnn (未確認) |
Predefined Macros (Visual Studio 2013) |
VS 2013 Community |
18.00.nnnnn.nn | 12.0 | 1800 | 1800nnnnn (180031101) |
|
2015 Community |
19.00.nnnnn.nn | 14.0 | 1900 | 1900nnnnn (190023026) |
Predefined Macros (Visual Studio 2015) |
2017 Community |
19.1x.nnnnn | 14.1 15.0 |
1910 1913 |
191nnnnnn (191025019 191326129) |
Predefined Macros |
2019 Community |
19.22.27905 | 14.2 | 1922 | 1922nnnnn (192227905) |
Predefined Macros |
参考
アプリケーションのメインウインドウとして使用するためのトップレベル・ ウインドウ.タイトルバー,境界,クライアントエリアを持つ. ウインドウメニュー,最大化および最小化ボタン, スクロールバーを持つこともできる.
ウインドウスタイル:
// WinUser.h #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | \ WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)
Overlapped window の一種で, アプリケーションのメインウインドウの外に表示されるダイアログボックス, メッセージボックス,ツールチップ, その他の一時的なウインドウとして用いられる. タイトルバーがオプションである点を除いて WS_OVERLAPPED と同じ.
CreateWindowEx() で WS_POPUP を指定する場合, hwndParent には親ではなくオーナー (または NULL) を指定する. ポップアップウインドウの親はデスクトップウインドウになる.(実験で確認)
ウインドウスタイル:
// WinUser.h #define WS_POPUPWINDOW (WS_POPUP | WS_BORDER | WS_SYSMENU)
通常,子ウインドウはクライアントエリアのみを持つが, 明示的に指定すれば次の要素を持つこともできる.
レイヤード・ウインドウ. 次のような視覚効果を持つトップレベル・ウインドウ (子ウインドウは不可) を,ちらつかずに高速表示する. Windows 8 では子ウインドウも可 (出典:SetLayeredWindowAttributes function).
参考
メッセージを受信するためだけのウインドウ.不可視であり,Z-order はなく,列挙できない.また,ブロードキャストメッセージは受信しない.
参考
番号範囲 | 説明 | ヘッダファイル |
---|---|---|
0x0000 〜 WM_USER−1 (0x03FF) | システム定義済または予約. | WinUser.h (Windows.h が include する.) |
WM_USER (0x0400) 〜 WM_APP−1 (0x7FFF) | WM_USER+n:
ウインドウクラス内のプライベート・メッセージ. (コモンコントロール固有メッセージなど) |
CommCtrl.h,CommDlg.h など |
WM_APP (0x8000) 〜 0xBFFF | WM_APP+n:アプリケーション定義のプライベート・メッセージ. | - |
0xC000 〜 0xFFFF | RegisterWindowMessage()
が動的に割り当てる.アプリケーション間の通信用.
2013/11/17(日) 追記 |
- |
0x10000 〜 | システム予約. | - |
■Memo
下記のマニュアルの項目から推測した DestroyWindow(hwnd) の処理手順.
つまり WM_DESTROY は子孫ウインドウを破棄する前,WM_NCDESTROY は子孫ウインドウを破棄した後に send される.
参考
Msg No. |
Message Symbol | wParam | lParam (カーソル位置(*1)) |
Windows Version |
備考 | ||
---|---|---|---|---|---|---|---|
HIWORD | LOWORD | HIWORD | LOWORD | ||||
0x00A0 | WM_NCMOUSEMOVE | HitTest(*2) | Screen-Y | Screen-X | 全 | ||
0x00A1 | WM_NCLBUTTONDOWN | ||||||
0x00A2 | WM_NCLBUTTONUP | ||||||
0x00A3 | WM_NCLBUTTONDBLCLK | ||||||
0x00A4 | WM_NCRBUTTONDOWN | ||||||
0x00A5 | WM_NCRBUTTONUP | ||||||
0x00A6 | WM_NCRBUTTONDBLCLK | ||||||
0x00A7 | WM_NCMBUTTONDOWN | ||||||
0x00A8 | WM_NCMBUTTONUP | ||||||
0x00A9 | WM_NCMBUTTONDBLCLK | ||||||
0x00AA | (未定義) | ||||||
0x00AB | WM_NCXBUTTONDOWN | XBUTTON[12] | HitTest(*2) | Screen-Y | Screen-X | 2000 以降 |
|
0x00AC | WM_NCXBUTTONUP | ||||||
0x00AD | WM_NCXBUTTONDBLCLK | ||||||
0x0200 | WM_MOUSEMOVE | - | KeyState | Client-Y | Client-X | 全 | |
0x0201 | WM_LBUTTONDOWN | ||||||
0x0202 | WM_LBUTTONUP | ||||||
0x0203 | WM_LBUTTONDBLCLK | ||||||
0x0204 | WM_RBUTTONDOWN | ||||||
0x0205 | WM_RBUTTONUP | ||||||
0x0206 | WM_RBUTTONDBLCLK | ||||||
0x0207 | WM_MBUTTONDOWN | ||||||
0x0208 | WM_MBUTTONUP | ||||||
0x0209 | WM_MBUTTONDBLCLK | ||||||
0x020A | WM_MOUSEWHEEL | ホイール 回転量(*3) |
KeyState | Screen-Y | Screen-X | 98/NT4.0 以降 |
|
0x020B | WM_XBUTTONDOWN | XBUTTON[12] | KeyState | Client-Y | Client-X | 2000 以降 |
|
0x020C | WM_XBUTTONUP | ||||||
0x020D | WM_XBUTTONDBLCLK | ||||||
0x020E | WM_MOUSEHWHEEL | ホイール 回転量(*3) |
KeyState | Screen-Y | Screen-X | Vista 以降 |
(*1) | マルチモニタ環境ではカーソル座標が負になる場合があるので,lParam からカーソル座標を取得するには LOWORD(),HIWORD() ではなく GET_X_LPARAM(), GET_Y_LPARAM() を使用すること. |
(*2) | HitTest:DefWindowProc() が WM_NCHITTEST メッセージの処理結果として返すヒットテスト値. |
(*3) | ホイール回転量:
|
参考
Msg No. |
Message Symbol | wParam | lParam (カーソル位置(*1)) |
Windows Version |
備考 | ||
---|---|---|---|---|---|---|---|
HIWORD | LOWORD | HIWORD | LOWORD | ||||
0x0020 | WM_SETCURSOR | カーソルを含む ウインドウ |
マウス メッセージID |
HitTest(*2) | 95/NT3.1 以降 |
ウインドウがメニューモードのときは HIWORD(lParam)=0. | |
0x0021 | WM_MOUSEACTIVATE | アクティブにされるトップ レベルの親ウインドウ |
ボタン押下時に発生したマウス メッセージ |
HitTest(*2) | 全 | ||
0x0215 | WM_CAPTURECHANGED | - | マウス入力を獲得する ウインドウのハンドル |
9x/Me/NT4.0 /2000/XP/ Server2003 以降 |
|||
0x02A0 | WM_NCMOUSEHOVER | HitTest(*2) | Screen-Y | Screen-X | 98/2000 以降 |
事前に TrackMouseEvent() の呼び出しが必要. | |
0x02A1 | WM_MOUSEHOVER | - | KeyState | Client-Y | Client-X | 98/NT4.0 以降 |
|
0x02A2 | WM_NCMOUSELEAVE | 0 | 0 | 98/2000 以降 |
|||
0x02A3 | WM_MOUSELEAVE | 98/NT4.0 以降 |
TRACKMOUSEEVENT track; track.cbSize = sizeof(track); track.hwndTrack = hwnd; track.dwHoverTime = HOVER_DEFAULT; // WM_MOUSEHOVER,WM_MOUSELEAVE をリクエストする. track.dwFlags = TME_HOVER | TME_LEAVE; if(!TrackMouseEvent(&track)) goto Error; // WM_NCMOUSEHOVER,WM_NCMOUSELEAVE をリクエストする. track.dwFlags = TME_HOVER | TME_LEAVE | TME_NONCLIENT; if(!TrackMouseEvent(&track)) goto Error;
ComboBox のメッセージの一部に,
マニュアルの内容から予想されるのと異なる動作があったので実験で確認してみた.
(Windows XP SP3 + comctl32.dll 5.82.2900.5512 (2004/08/05))
case CBN_DROPDOWN: comboBox->isDroppedDown = TRUE ; break; case CBN_CLOSEUP : comboBox->isDroppedDown = FALSE; break;一方,これらのタイミングで CB_GETDROPPEDSTATE を使用する方法は期待通りに動作しない. (CBN_DROPDOWN は表示される直前に発生するため.)
// ×うまくいかない. // (仮にうまくいくとしても,上記の方法に比べて効率が悪い.) case CBN_DROPDOWN: case CBN_CLOSEUP: comboBox->isDroppedDown = SendMessage(comboBox->hwnd, CB_GETDROPPEDSTATE, 0, 0); break;
ダイアログでは,座標の単位として画素数ではなく 「ダイアログテンプレート単位 (dialog template units,ダイアログ単位 (DLU) ともいう)」を用いる. リソースファイル (*.rc) の中のダイアログ定義データに含まれる座標はこれである. ダイアログテンプレート単位は「ダイアログベース単位 (dialog base units)」 を用いて次のように定義される.
TemplateUnitX = BaseUnitX / 4 TemplateUnitY = BaseUnitY / 8
ダイアログベース単位は, そのダイアログで使用するフォントのサイズにより次のように定義される.
BaseUnitX = <ダイアログのフォントの平均幅> BaseUnitY = <ダイアログのフォントの高さ>
したがってダイアログ (テンプレート) 単位は次のようになる.
TemplateUnitX = <ダイアログのフォントの平均幅> / 4 TemplateUnitY = <ダイアログのフォントの高さ> / 8
2013/10/20(日) 追記
BaseUnitX は「フォントの平均幅」と定義されているが,TEXTMETRIC::tmAveCharWidth
とは必ずしも一致しない.
(実験で確認.他方,BaseUnitY は TEXTMETRIC::tmHeight と一致する.)
後述する MapDialogRect()
では,どうやらこのページのサンプルにあるように,
英字 [A-Za-z] の平均幅 (端数四捨五入)
を使用しているらしい.(数種類のフォントとサイズについて実験で確認.)
要するにダイアログベース単位は, GetTextExtentPoint32("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") の横幅を 1/52 にしたサイズに等しいはず.(端数四捨五入,高さはそのまま.)
ダイアログベース単位を取得する API 関数として, GetDialogBaseUnits() がある.この関数は引数なしである. つまり特定のダイアログのベース単位を返すのではなく,(Windows 95 時代の?) システムフォント (8ポイントの "System") を用いたダイアログのベース単位を返す. そのためそれ以外のフォントを使用するダイアログでは使えない. 困ったことに,Visual Studio.NET 2003 のダイアログエディタでは, ダイアログのデフォルトフォントは "MS Shell Dlg" であって "System" ではない. だから使えない. (ダイアログエディタでは "MS Shell Dlg" の方を「システムフォント」と呼んでいる.)
ダイアログテンプレート単位の座標を画素座標に変換するには MapDialogRect() を用いる.この関数は,RECT のX座標 (left および right) およびY座標 (top および bottom) をテンプレート単位から画素単位に変換する. テンプレート単位の座標を (Tx, Ty),画素座標を (Px, Py) とすると変換式は次のとおり.
Px = MulDiv(Tx, BaseUnitX, 4) Py = MulDiv(Ty, BaseUnitY, 8)
この式において,Tx=4,Ty=8 とすれば, Px=BaseUnitX,Py=BaseUnitY となる. つまり使えない GetDialogBaseUnits() の代わりに,MapDialogRect() を用いて実際のダイアログベース単位を求めることができる. 次にその例を示す.
/*───────────────────────────────────── 機能 :ダイアログ hDialog の実際のダイアログベース単位を取得する. GetDialogBaseUnits() はシステムフォント (8ポイントの "System") を使 用するダイアログでしか使えないが,この関数は hDialog が使用している フォントに基づいて実際のダイアログベース単位を返す. 入力 :hDialog:ダイアログのウインドウハンドル. 出力 :*baseUnit:hDialog の実際のダイアログベース単位. 戻り値:取得成功のときそのときに限り真. 2006/12/24(日) 作成 ─────────────────────────────────────*/ BOOL GetActualDialogBaseUnits(HWND hDialog, SIZE *baseUnit) { RECT rect; BOOL result; rect.left = 4; rect.top = 8; if(result = MapDialogRect(hDialog, &rect)) { baseUnit->cx = rect.left; baseUnit->cy = rect.top; } return result; }
■実行結果
GetDialogBaseUnits() で求めたベース単位:(8, 18) ・ダイアログのフォントが "System" (サイズ:8ポイント) の場合 GetActualDialogBaseUnits() で求めた実際のベース単位:(8, 18) ・ダイアログのフォントが "MS Shell Dlg" (サイズ:8ポイント) の場合 GetActualDialogBaseUnits() で求めた実際のベース単位:(6, 13)
2006/12/24(日) 初版 2007/01/08(月) 誤記訂正 2013/06/15(土) 参考リンク追加 2013/10/20(日) ダイアログベース単位について追記 |
この項目は,そもそも昨年12月に複数のモードレスダイアログを使おうとして TAB が効かなかったので追加したんだけど, リンクだけ紹介して中身を書くのをサボっていた. しかしついさっき「モードレスダイアログ 複数」で検索してきた人がいるのでこの機会に書くことにした. (2007/03/20(火) 13:40)
ちなみに「モーダレス」だと勘違いしている人が多いみたいだけど「モードレス (modeless)」だよ!!
(「モーダル (modal)」とごっちゃになってる.(笑))
モードレスダイアログで TAB が使えるようにする方法は下のリンク先にあるとおりだが, 複数のモードレスダイアログの場合については書かれていなかった. そこで自分で試した結果, メッセージループを次のようにするとうまくいった.
HWND Dialog[NDIALOGS]; // モードレスダイアログのハンドルの配列 HACCEL hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDR_xxxx); MSG msg; UINT i; while(GetMessage(&msg, NULL, 0, 0)) { for(i = 0; ; i++) { if(i >= NDIALOGS) { // Dialog[*] 宛ではないメッセージの場合 if(!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } break; } // Dialog[i] またはそのコントロール宛のメッセージか? // そうならば処理する. if(IsDialogMessage(Dialog[i], &msg)) break; } }
なお,モードレスダイアログの親 (または祖先) がモーダルダイアログだとダメなので, 親 (祖先) もモードレスにする必要がある. これはモーダルダイアログが自分専用のメッセージループを持っており, 同じスレッドに属する他のウインドウへの入力 (マウス,キーボード) メッセージを握りつぶしてしまうため.
つまり,モーダルダイアログが開いている間は, 同じスレッドに属する他のウインドウ (モーダルおよびモードレスダイアログを含む) をマウスやキーボードで操作することはできない.
また,上記のコードはすべてのウインドウがモードレスダイアログ (最大4個) であるアプリケーションでうまく動いたが, 数が多くなると非効率なので,下記の Microsoft の技術サポート記事を参考にする方がよい.
2006/12/11(月) リンクのみ掲載 2007/03/20(火) 本文作成 2012/07/30(月) 本文ちょっと改訂 |
2013/10/20(日) 追記
ダイアログプロシージャは,WM_INITDIALOG より先にいくつかのメッセージを受信する.実験で確認したところ,次の順番になった.
コントロールのクラス名は,CommCtrl.h の中で次のように定義されている.
(どうしてこんなに名前の付け方がバラバラなんだ.)
コントロール名 | クラス名 | クラス名定義マクロ | 備考 |
---|---|---|---|
Animate | SysAnimate32 | ANIMATE_CLASS[AW] | |
Button | Button | WC_BUTTON[AW] | |
ComboBox | ComboBox | WC_COMBOBOX[AW] | |
ComboBoxEx | ComboBoxEx32 | WC_COMBOBOXEX[AW] | |
DateTimePicker | SysDateTimePick32 | DATETIMEPICK_CLASS[AW] | |
Edit | Edit | WC_EDIT[AW] | |
Header | SysHeader32 | WC_HEADER[AW] | |
HotKey | msctls_hotkey32 | HOTKEY_CLASS[AW] | |
IPAddress | SysIPAddress32 | WC_IPADDRESS[AW] | |
ListBox | ListBox | WC_LISTBOX[AW] | |
ListView | SysListView32 | WC_LISTVIEW[AW] | |
MonthCalendar | SysMonthCal32 | MONTHCAL_CLASS[AW] | |
NativeFont | NativeFontCtl | WC_NATIVEFONTCTL[AW] | |
PageScroller | SysPager | WC_PAGESCROLLER[AW] | |
RichEdit 4.1 | RICHEDIT50W | MSFTEDIT_CLASS | Msftedit.dll |
RichEdit 2.0/3.0 | RichEdit20W | RICHEDIT_CLASS[AW] | Riched20.dll |
Progress | msctls_progress32 | PROGRESS_CLASS[AW] | |
Rebar | ReBarWindow32 | REBARCLASSNAME[AW] | |
ScrollBar | ScrollBar | WC_SCROLLBAR[AW] | |
Static | Static | WC_STATIC[AW] | |
StatusBar | msctls_statusbar32 | STATUSCLASSNAME[AW] | |
SysLink | SysLink | WC_LINK (Unicode のみ) | XP 以降 |
Tab | SysTabControl32 | WC_TABCONTROL[AW] | |
ToolBar | ToolbarWindow32 | TOOLBARCLASSNAME[AW] | |
ToolTips | tooltips_class32 | TOOLTIPS_CLASS[AW] | |
TrackBar | msctls_trackbar32 | TRACKBAR_CLASS[AW] | |
TreeView | SysTreeView32 | WC_TREEVIEW[AW] | |
UpDown | msctls_updown32 | UPDOWN_CLASS[AW] | |
Dialog | MAKEINTATOM(0x8002) | WC_DIALOG | WinUser.h |
TaskDialog | Vista 以降 |
■マニュアル
2006/09/21(木) 作成 2009/01/20(火) SysLink を追加 2010/02/13(土) マニュアルへのリンクを追加 |
以下はマニュアルに書いてある内容そのままではなく,自分で実験した結果を含んでいる.
正確・網羅的に調べたわけではないので間違いがあるかもしれない.
(Vista Home Premium SP2,Windows 7 Home Premium
SP1,ComCtl32.dll version:6.16.7601.2)
ボタン画像の右にテキストを表示する.(OFF の場合は下に表示.)
Windows classic style の場合,ツールバーの背景を透明にする.
ボタンが1行に収まらない場合,複数行で表示する. 改行は行の右端のセパレータの位置で行われる. セパレータがなければボタン間で改行される.
Windows classic style では,セパレータ位置で改行された場合に限り, 行間にスペースが入る.(TBSTYLE_FLAT が ON の場合は8画素,OFF ならば5画素.(実測))
CCS_NODIVIDER が OFF ならば,ツールバー上部に高さ2画素 (マニュアルに記載あり,実測でも確認) の横線を描く. (実験で確認したところ,この境界線は, ツールバーウインドウの非クライアント領域となっている.)
Windows classic style では,境界線を表示すると, (TBSTYLE_EX_DOUBLEBUFFER を指定していても) ウインドウのリサイズ時にツールバーのちらつきが大きい.(Aero Basic/Glass style では,CCS_NODIVIDER にかかわらずちらつきが大きい.)
ちらつき防止のため,ダブルバッファリングを行う. 実験では,次の条件を両方満たした場合にのみ, ウインドウをリサイズしてもツールバーのちらつきが発生しなかった.
Aero Basic/Glass style では CCS_NODIVIDER の設定によらずちらつく.
ツールチップでボタンの説明テキストを表示する.
このフラグが ON の場合,BTNS_SHOWTEXT が ON のボタンにだけテキストが表示される.(TBSTYLE_LIST も ON にすること.)
部分的に隠れるボタンを表示しない.(Rebar で使用する場合に ON にする.)
次の部分のハイライトと影の色を設定する.
Mapping Mode | 1論理単位 | X軸 方向 |
Y軸 方向 |
デバイス 依存性 |
等方性 (*1) |
備考 |
---|---|---|---|---|---|---|
MM_TEXT | 1画素 | → | ↓ | 有 | デバイス 依存 |
デフォルトのマッピングモード |
MM_LOMETRIC | 0.1mm | ↑ | 無 | ○ | メートル法 (低解像度) | |
MM_HIMETRIC | 0.01mm | 無 | ○ | メートル法 (高解像度) | ||
MM_LOENGLISH | 0.01inch (約 0.254mm) |
無 | ○ | 英国単位 (低解像度) | ||
MM_HIENGLISH | 0.001inch (約 0.0254mm) |
無 | ○ | 英国単位 (高解像度) | ||
MM_TWIPS | 1/1440 inch (約 0.0176mm) |
無 | ○ | 1/20 ポイント (Twentieth of an Inch Point) |
||
MM_ISOTROPIC | ユーザ定義 (等方座標系) | ○ | SetWindowExtEx(),SetViewportExtEx()
で 座標軸の向きとスケールを設定する. 等方座標系になるように自動調整される. |
|||
MM_ANISOTROPIC | ユーザ定義 (非等方座標系) | 座標系 依存 |
SetWindowExtEx(),SetViewportExtEx()
で 座標軸の向きとスケールを設定する. |
(*1) 等方性 (isotropy):どちらの方向を向いても数学的・物理的性質が等しいこと. 特に2DグラフィックスではX軸方向とY軸方向の1論理単位の (デバイス上での物理的な) 長さが等しいこと. 論理座標空間で正方形や円を描くと,デバイス上でも正方形や円になる. 非等方 (異方) 座標系ではデバイス上で長方形や楕円になる.
Windows GDI では多角形や閉じたパスの塗りつぶしモードとして,ALTERNATE および WINDING の2種類がある.これらは次の API (や MFC のメソッド) で使用できる.
Direct2D1 (D2D1) にも同様の塗りつぶしモード (D2D1_FILL_MODE) がある.
.NET Framework や Silverlight の FillRule 列挙体では,EvenOdd と Nonzero という名前になっている.
ALTERNATE (EvenOdd) と WINDING (Nonzero) とは何かというと, どちらも閉曲線の内部と外部を区別 (定義) するための規則である. 閉曲線を塗りつぶすには,当然ながら内部と外部を区別する必要がある. 閉曲線が単純 (数学用語で「曲線がそれ自身と交差・接触しない」) ならば,その内部と外部の区別は明らかだが, 単純閉曲線でない場合はあいまいになる場合がある.
┏━━━━━━━━━━━━━━━━━┓ ┃ Y ┃ ┃ ┏━━━┓ ┏━━┓ ┃ L ┃ ┃ P ┃ ┃ ┃ ┃ ──・→─╂─╂─・ ┃ ┃ ┃ ┃C Q ┃ ┃ ┃ ┃ ┃ ┃ W┗━╋━━━┛X ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗━━━━━━━┛ ┃ ┃ Z ┃ ┃ ┗━━━━┛
上図で点Pが閉曲線Cの内部にあるかどうかを判定したい場合, Pから無限遠に半直線Lを引き,それがCを何回横切るかを数える. 奇数回ならばPはCの内部,偶数回ならば外部にあると定義する. 上図の場合は2回なので,PはCの外部ということになる.
別の言い方をすると,L上の点Qを無限遠からPに近づけるとき, QがCを横切るたびに外部と内部が交互に (alternate) 現れる.
閉曲線C上の点QがCを1周するとき, 点Pのまわりを何回まわるか (巻数,winding number) により内外を定義する方法.
(1) Pが単純閉曲線Cの内部にある場合 ┏━━━━━┓C ┃ P ┃ ┃ ・ ┃ ┃ ・Q ┗━━━━━┛ QがCを1周すると,Pのまわりを1回まわる.つまり巻数は1. (2) Pが閉曲線Cの「明らかに」外部にある場合 ┏━━━━━━━┓C ┃ ┏━━━┓ ┃ ┃ ┃ ┃ ・Q ┗━┛ ┃ ┃ ・P ┏━━╋━┛ ┃ ┃ ┗━━┛ QがCを何周しようが,Pからみれば8時の方向と10時の方向の間をウロウロ するだけで,Pのまわりを1周もすることはない.つまり巻数は0.
そこでPの巻数が0でないとき内部と定義すれば, 最初の ALTERNATE の図のPは,
なお .NET Framework / Silverlight の
Nonzero
の説明には「ある点から任意の方向に無限に伸びる射線を…」と書かれているので一見 EvenOdd
と紛らわしいが,曲線がその射線を右回りに横切る回数から左回りに横切る回数を引いているので,
これは巻数 (右回りが+) を求めていることになる.
(図解:方法 : 複合図形の塗りつぶしを制御する)
2007/06/16(土) | 作成 |
2013/02/09(土) | Direct2D1 追記 |
2016/11/05(土) | .NET Framework / Silverlight 追記 |
ごみ箱は Windows95 以降のシェルアプリケーションの機能です. したがってごみ箱用の API は Windows API ではなく,シェル API であり, 次の3つがあります.
API 名 | 機能 |
---|---|
SHFileOperation() | ファイルまたはディレクトリをごみ箱に移動する. (ごみ箱以外の機能もあり.) |
SHEmptyRecycleBin() | ごみ箱を空にする. |
SHQueryRecycleBin() | ごみ箱内にあるオブジェクトの個数と合計サイズを取得する. |
ここでは,SHFileOperation() を用いてファイルやディレクトリをごみ箱に送る方法を説明します. といっても,私は SHFileOperation() の動作についてまだ十分理解しているわけではないので, 色々試行錯誤しながら理解したことや注意点を書きます. 間違いや補足があればご指摘ください.
SHFileOperation() は,次の構造体を使って必要なパラメータを受け取ります.
typedef struct { HWND hwnd; UINT wFunc; LPCTSTR pFrom; LPCTSTR pTo; FILEOP_FLAGS fFlags; BOOL fAnyOperationsAborted; LPVOID hNameMappings; LPCTSTR lpszProgressTitle; // only used if FOF_SIMPLEPROGRESS } SHFILEOPSTRUCT;
この構造体のメンバのうち,注意が必要なのは (というより,私がつまずいたのは (^^;)) 次の2つです.
<パス名1>\0<パス名2>\0 … \0<パス名N>\0\0
私がつまづいたのはこの点.マニュアルには書かれていないし,
SHFileOperation() を説明している他のサイトにも書かれていません.
(そもそも,Windows でパス名の区切りに '/'
を使おうとする人自体,非常に少ないんだろうけど.(^^;)
私は (少なくとも日本語環境では) 見やすいから Unix 流の '/'
の方が好き.)
多くの (すべての?) Windows API は,パス名の区切りとして '\\' の代わりに '/' を使っても,あるいは両者を混在させてもかまいません. しかし SHFileOperation() は Windows API ではなくシェル API であるせいか,そうはなっていないようです. この点にハマってしまいました.
パス名の区切りとして '/' を用いたファイル名を指定したところ, ごみ箱には移動されるのですが, ごみ箱の中身を表示した際の「名前」が空文字列に, 「元の場所」が "D:" になってしまいました. また,「ファイル削除の確認」ダイアログに表示されるファイル名は, 単一の '"' だけになっていました. どうやらパス名の最初の '/' 以後の部分が無視されてしまったようです. しかし,ごみ箱のディレクトリ内にあるごみ箱管理ファイル "INFO2" をダンプしてみたところ, そのようなファイルでも元のパス名がちゃんと記録されており, ごみ箱から「元に戻す」こともできました. どうやら表示だけの問題のようです.
2007/08/18(土) 追記パス名を \\?\ で始めると,Windows API でも '/' をディレクトリ区切り文字として使用できなくなるらしい. (参考:下記 外部へのリンク → Windowsパス名の落とし穴 → サービス機能の不活性化)
NULL で大丈夫です.GUI アプリケーションだけでなく,hwnd に NULL
を渡さざるを得ないコンソール・アプリケーションでも動きます.
ただしコンソール・アプリケーションの場合はなぜか ERROR_INVALID_HANDLE
がセットされますが,ごみ箱への移動はちゃんとできているようなので,
このエラーコードは無視してよさそうです.
(ついでに言うと,fFlags に FOF_NOCONFIRMATION や FOF_NOERRORUI
を指定しなければ,コンソール・アプリケーションであっても
削除確認ダイアログやエラーダイアログが表示されます.)
いやむしろ,NULL 以外の値として何を渡せばよいのか, マニュアルを見てもさっぱりわかりません. マニュアルの hwnd の説明には次のように書かれています.
「ファイル操作の状態に関する情報を表示するダイアログボックスのウインドウハンドル」(拙訳)
しかし,どんなコントロールをどのように配置すればいいのか? 全く謎です. テキトーにダミーのダイアログを作ってそのハンドルを渡してみたり, アプリケーションで別の目的に使用しているダイアログのハンドルを渡してみたりしましたが, ERROR_INVALID_HANDLE になり,ファイルはごみ箱に送られませんでした. 他のサイトのサンプルコードでは,当たり前のようにハンドルを渡しており, 疑問を持っている人はいないようです. ひょっとして,私はなにか大きな勘違いをしているのでしょうか?
現時点ではどんなダイアログのハンドル渡したらいいのかわからないし,NULL で動くので NULL を渡しています.
あと,ごみ箱に移動できないファイルがあると, なぜかそのようなファイルごとに数秒程度時間がかかります. SHFILEOPSTRUCT::fFlags に FOF_NOCONFIRMATION と FOF_NOERRORUI を指定していない場合,削除確認ダイアログはすぐに表示されるものの, それに「はい」と答えると約5秒後にエラーダイアログが出ました. なぜこんなに時間がかかるんだろう?
ファイルまたはディレクトリをごみ箱に送る関数の例を次に示します.
#include <ShellAPI.h> #include <tchar.h> /* その他のヘッダは省略 */ /* fullPathName はフツーの '\0' 終端文字列. * ただしフルパス名で,ディレクトリの区切りに '/' は不可. */ int MoveFileToRecycleBin(const TCHAR *fullPathName, HWND hwnd, FILEOP_FLAGS flags) { SHFILEOPSTRUCT fileOp; const TCHAR *src = fullPathName; TCHAR *dest; /* ファイルのフルパス名を2つの _T('\0') で終端する必要があるので, * fullPathName[] を fileOp.pFrom[] にコピーし,2個目の _T('\0') を追加する. */ fileOp.pFrom = dest = alloca(sizeof(*dest) * (_tcslen(fullPathName) + 2)); while((*dest++ = *src++) != _T('\0')) {} *dest = _T('\0'); /* もう1個必要 */ fileOp.hwnd = hwnd; fileOp.wFunc = FO_DELETE; fileOp.pTo = NULL; fileOp.fFlags = FOF_ALLOWUNDO | flags; fileOp.fAnyOperationsAborted = FALSE; fileOp.hNameMappings = NULL; fileOp.lpszProgressTitle = NULL; return SHFileOperation(&fileOp); }
まだ調査中です.
ごみ箱フォルダのパス名は,各ドライブの <drive>:\RECYCLER\<OwnerSID> (隠しフォルダ).
<drive> はドライブ文字 (A〜Z), <OwnerSID> はユーザの SecurityID で,例えば次のような文字列.
"S-1-5-21-1234567890-123456789-123456789-1234"
したがって複数ユーザが使用している PC では, ドライブごと,ユーザごとにごみ箱フォルダが作られる (はず).
●参考ごみ箱フォルダの中には,削除されたファイルが名前を変えて入れられる. そのファイル名は次のとおり.
D<drive><number>.<元の拡張子>
ここで <drive> はドライブ文字 (小文字), <number> はファイル番号 (削除された順に1から).
例えば,E: ドライブで5番目に削除された *.txt ファイルは, De5.txt という名前になる.
ファイルを「元に戻す」と,そのファイル番号は欠番になる.
ごみ箱フォルダ内の隠しファイル INFO2 には,
ごみ箱内の削除ファイルの情報が含まれている.
INFO2 の先頭20バイトはヘッダ部.その後に,
ごみ箱内のファイルの情報が必要個数繰り返される.
(ファイルはごみ箱フォルダと同じドライブのものだけで,
「元に戻した」ものを含む.)
// INFO2 ファイル全体の構造 typedef struct { Info2Header_t header; // ヘッダ (20バイト) Info2FileData_t fileInfo[N]; // ファイル情報 (各800バイト) } INFO2_t;調べた範囲では,どのドライブのごみ箱も,ヘッダは全く同じ内容だった (下記16進ダンプ).このため色々条件を変えて内容の変化を調べ, 各部の意味を推定するという手が使えない.orz
// ヘッダ部の16進ダンプ 00000000 05 00 00 00 00 00 00 00-00 00 00 00 20 03 00 00 00000010 00 00 00 00
よくあるパターンだが,ファイル先頭の数バイトはおそらくバージョン番号だろう. 途中の 20 03 00 00 は,sizeof(Info2FileData_t) と思われる.
// ヘッダ部の構造 (20バイト) typedef struct { BYTE unknown1[12]; // 不明 (最初の数バイトは INFO2 ファイルのバージョンか?) DWORD fileInfoSize; // sizeof(Info2FileData_t) か? BYTE unknown2[4]; // 0固定? } Info2Header_t;
// ごみ箱内の各ファイルの情報 typedef struct { // 削除前のフルパス名 (マルチバイト文字列). // ・余白は '\0' で埋める. // ・ファイルを元に戻した場合,最初の1バイトが '\0' になる. char ansiPathName[MAX_PATH]; DWORD fileNo; // ファイル番号? (1から始まる連番) DWORD driveNo; // ドライブ番号? (A:0,B:1,…,Z:25) BYTE unknown[12]; // 未確認.削除時刻と属性フラグか? // 削除前のフルパス名 (UTF-16LE 文字列). // ・余白は L'\0' で埋める. // ・ファイルを元に戻しても変更されない. wchar_t unicodePathName[MAX_PATH]; } Info2FileData_t;
全面改訂中
ウインドウクラスを登録する際,スタイルに CS_HREDRAW | CS_VREDRAW を指定しておくと,ウインドウがリサイズされるたびに再描画が行われるが, それが頻繁すぎてちらつきが気になる場合がある. これは,次のようにすると低減することができる場合が多い (ちらつきを減らすのであって,完全になくせるわけではない).
CS_HREDRAW | CS_VREDRAW を指定するのをやめ, ウインドウプロシージャの WM_SIZE の処理で InvalidateRect(hwnd, NULL, FALSE) を行うようにする.
この方法が効果がある理由は次のとおり.(前半は推測)
CS_HREDRAW | CS_VREDRAW を指定した場合,ウインドウをリサイズするたびに WM_PAINT メッセージが (たぶん) send され,非常に頻繁に再描画が行われる. (いまいち確信なし)
これに対し,WM_SIZE の処理で InvalidateRect を行う方法では次のようになる.
このため再描画が行われる回数は激減する. リサイズが行われている間は WM_SIZE などが繰り返しディスパッチされ続け, WM_PAINT はほとんどディスパッチされないのに対し,リサイズが (一瞬でも) 停止して 他のメッセージがなくなると WM_PAINT が (1回だけ) ディスパッチされて再描画が行われる.
■参考リンク (とりあえずメモ,未整理)
Windows のエラーメッセージは FormatMessage() で取得できるが, 引数が多いので自由度が高い反面,使うのが面倒. そこでエラー番号だけを指定すればエラーメッセージ文字列を取得できる, 次のような関数を用意しておくと簡単に使えて便利.
#include <windows.h> #include <tchar.h> TCHAR *WinErrorMessage(DWORD errorCode) { TCHAR *message; DWORD n = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode, LANG_USER_DEFAULT, (TCHAR*)&message, 0, NULL); return (n > 0) ? message : NULL; }
この関数は,必要な文字列バッファを自分で確保するので,
不要になったら LocalFree() で解放する必要がある.
(注意:このため,メモリ不足に陥った場合,
そのメモリ不足を知らせるメッセージを取得できない可能性がある.)
この関数は Windows API を直接呼び出す場合だけでなく, C標準ライブラリ関数を呼び出した後にも (仕様として保証されているわけではないが) 事実上使用できる. (ライブラリ関数の中で Windows API を呼んでいるため.)
C標準ライブラリでエラーメッセージを取得するには strerror(errno) を使うが, これは英語だけなので,日本語表示が必要な場合には WinErrorMessage() や FormatMessage() の方が便利.また FormatMessage() の dwLanguageId 引数に言語識別子を指定すれば, 他言語のメッセージを取得できる.
Windows の基本的なエラーメッセージは kernel32.dll のリソース (メッセージテーブル) で定義されており,Windows 日本語版では日本語と英語のメッセージテーブルが定義されている.
メッセージテーブルの内容は,Resource Hacker などのリソース編集ツールで見ることができる.
TCHAR *fileName, *message, buf[1024]; FILE *in; if((in = _tfopen(fileName, _T("r"))) == NULL) { message = WinErrorMessage(GetLastError()); _stprintf(buf, _T("\"%s\" をオープンできません。\n%s"), fileName, message); LocalFree(message); MessageBox(NULL, buf, AppName, MB_ICONERROR); return -1; }
注意:FormatMessage() が返すメッセージの一部には, 末尾に余分な空白や改行がついているものがあり, これらが邪魔になる場合がある.
Modifier Keys (SHIFT,CTRL,ALT など) を指定するための定数. いずれも WinUser.h で定義されている.
MOD_ALT | 0x0001 | |
MOD_CONTROL | 0x0002 | |
MOD_SHIFT | 0x0004 | |
MOD_WIN | 0x0008 | Windows キーを含むショートカットは OS が使用するために予約されている. |
MOD_NOREPEAT | 0x4000 | Win7 以降.ホットキーのオートリピートを無効にする. |
MK_SHIFT | 0x0004 | |
MK_CONTROL | 0x0008 | |
MK_LBUTTON | 0x0001 | |
MK_RBUTTON | 0x0002 | |
MK_MBUTTON | 0x0010 | |
MK_XBUTTON1 | 0x0020 | Win2000 以降. |
MK_XBUTTON2 | 0x0040 | Win2000 以降. |
FSHIFT | 0x04 |
FCONTROL | 0x08 |
FALT | 0x10 |
■ATOM とは何か,なぜそういう名前なのか?
(たぶん間違っていない個人的推測 ― 以前回答したページに今更リンク.)
■各種コントロールの ATOM の値 (ATOM values for various control classes)
各種コントロールについて,
テストプログラムを作って実際の値を調べてみると次のようになった.
(I wrote a test code to confirm the ATOM values, and the result is as follows.)
DLL | Class | ATOM | Remarks | |
---|---|---|---|---|
System-defined Controls (User32.dll?) |
Dialog | 0x8002 (WC_DIALOG) |
WinUser.h で定数として定義されている. (Defined in WinUser.h) |
|
Button | 0xC017 | DLGITEMTEMPLATEEX Structure には,
これらのクラスはシステム定義と書かれている. すると ATOM は (事実上) 定数か? ATOM の値は上記のページに書かれている windowClass の値に 0xBF97 を加算したものに なっている. ("DLGITEMTEMPLATEEX Structure" says these controls are predefined system classes, and the ATOM values seem to be (DLGITEMTEMPLATEEX::windowClass + 0xBF97).) |
||
Edit | 0xC018 | |||
Static | 0xC019 | |||
ListBox | 0xC01A | |||
ScrollBar | 未確認だが 0xC01B のはず (Not Tested, but should be 0xC01B) |
|||
ComboBox | 0xC01C | |||
Common Controls (ComCtl32.dll) |
DLL Version | 5.82.2900.2 | 6.16.7601.2 | |
ComboBoxEx | 0xC04A | 0xC0B6 0xC0E2 |
ATOM の値は定数ではないので,
動的に割り当てられていると思われる. (These ATOM values do NOT seem to be constant, so they should be assigned dynamically.) |
|
(ComboLBox) | Not Tested | 0xC01E | ||
ListView | Not Tested | 0xC098 | ||
StatusBar | 0xC03E | 0xC075 0xC076 |
||
TabControl | Not Tested | 0xC0D6 | ||
ToolBar | Not Tested | 0xC072 0xC075 |
||
TrackBar | Not Tested | 0xC077 | ||
TreeView | Not Tested | 0xC0D4 | ||
UpDown | Not Tested | 0xC0AE | ||
その他 (Others) |
Not Tested | Not Tested |
INITCOMMONCONTROLSEX::dwICC に ICC_STANDARD_CLASSES または ICC_LINK_CLASS
が含まれているとなぜかエラーになる.
(Windows XP SP3 + comctl32.dll 5.82.2900.5512 (2004/08/05))
■参考
GetLastError() で ERROR_ALREADY_EXISTS が返るが, 上記のフラグの代わりに未定義のビット (0x00010000 以上) をセットしても同じ結果になるので, このエラーコードには意味がなさそうである.
GetModuleFileName() の第1引数 (hModule) に NULL を渡すと, 現在のプロセスに対応する実行ファイルのフルパス名を取得できる.
#include <windows.h> #include <tchar.h> TCHAR ExeFileName[MAX_PATH]; GetModuleFileName(NULL, ExeFileName, MAX_PATH);
|
インサイドWindows 第6版 上 (マイクロソフト公式解説書) posted with amazlet at 13.07.27 Mark E. Russinovich David A. Solomon Alex Ionescu 日経BP社 売り上げランキング: 82,632 |
|
|
インサイドWindows 第6版 下 (マイクロソフト公式解説書) posted with amazlet at 13.07.27 Mark E. Russinovich David A. Solomon Alex Ionescu 日経BP社 売り上げランキング: 82,761 |
|
Windowsプログラミングの極意 歴史から学ぶ実践的Windowsプログラミング! posted with amazlet at 12.02.05 Raymond Chen アスキー 売り上げランキング: 190469 |
日本語のタイトルは誤解を招きやすいが,Windows のプログラミング方法ではなく, 「Windows の○○はなぜそうなっているのか?」を解説した本.
■参考
本サイトはC言語とWindows APIを用いたプログラムが永遠に栄えることを願うべく
多数のプログラミング技術を提供しています
Windows のパス名 (ファイル名,ディレクトリ名,デバイス名) を扱う上で非常に参考になる (MSDN を調べても,パス名の細かい規則に関するまとまった情報がなかなか得られない).
「末尾文字の落とし穴」は私も昨年偶然発見した.ノード名の先頭, 途中,末尾,そして単一文字のノード名で使える文字を調べるプログラムを書いてテストしてみたところ, ノード名の末尾に半角のピリオドまたはスペースが連続している (正規表現で書けば "[ .]+$") と, それらはすべて無視 (削除) されることがわかった.
なお,「Windows のパス名は大文字小文字を区別しない」 ということは誰でも知っていると思うが,これは ASCII 英字だけの話ではなく,Unicode レベルで大文字小文字が同一視される点に注意 (えっ,それも知ってる?). したがって日本語環境で使える文字としては,全角英字もそうだし, ギリシャ文字やロシア文字もそうである. 例えばギリシャ文字を含むファイル名 "ΑΒΓ.TXT" は, それらを小文字にした "αβγ.txt" と同じと見なされる (実験で確認済).
パス名の先頭に \\.\ を付けて,259文字 (MAX_PATH=260 から終端 NUL を除いた有効文字数) 以上のパス名を持つファイルを作ってみたらどうなるか, という実験.私も \\.\ で長いパス名が使えることは知っていたので, 以前から実験してみたいとは思っていた.
しかしほとんどのアプリケーションは固定長のバッファ (ひどいのになると MAX_PATH より小さい値を勝手に定義している (-_-#)) でパス名を扱い,\\.\ なんて使っていないので, そういうパス名は扱えないだろう.
長過ぎるパス名が存在する可能性がある以上, 受け入れられるようにはしておきたいが, それを扱えないアプリケーションがほとんどなので, 自分からそういうパス名のファイルを作るのは避けた方がよい.
Copyright © 2006-2020 noocyte E-mail: relipmoced (a) yahoo.co.jp (" (a) " を半角のアットマークに書き替えてください.) リンクはご自由に. 「noocyte のプログラミング研究室」トップページに戻る. |