更新:
在过去的 15 年里,我一直生活在时间胶囊中,最初发布的答案并未解决如何解决围绕 Visual Styles 的问题(见下文)。
启用视觉样式后,WM_DRAWITEM message 的行为发生了变化:DRAWITEMSTRUCTs itemAction 字段不再有 ODA_FOCUS 位设置焦点丢失。结果是无法再应用将焦点矩形移到此答案底部的解决方案。
在启用视觉样式的情况下移除焦点矩形需要再次渲染控件。消息处理程序的以下代码 sn-p 显示了如何执行此操作:
switch ( message ) {
// ...
case WM_DRAWITEM: {
const DRAWITEMSTRUCT& dis = *(DRAWITEMSTRUCT*)lParam;
if ( dis.itemAction & ODA_DRAWENTIRE ) {
// Render the control
// ...
// If the control has the input focus...
if ( dis.itemState & ODS_FOCUS ) {
// Render the focus rectangle
DrawFocusRect( dis.hDC, &dis.rcItem );
}
}
}
// ...
}
不需要在焦点丢失时重新绘制整个控件。 DrawFocusRect 以 XOR 模式呈现,可以通过再次应用相同的操作来移除。
渲染焦点矩形的逻辑由两部分组成:
- 如果
itemAction 包含ODA_FOCUS,则无论其他状态如何,都会渲染焦点矩形。这会切换可见性。
- 否则,如果
itemState 包含ODS_FOCUS,则仅渲染焦点矩形。这是必要的,以便正确考虑初始状态。
以下代码演示了这种策略。
resource.h:
#define IDD_MAINDLG 101
DlgBasedWin32.rc(声明一个只有确定和取消按钮的简单对话框):
#include "resource.h"
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_MAINDLG DIALOGEX 0, 0, 309, 176
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
CONTROL "OK",IDOK,"Button",BS_OWNERDRAW | WS_TABSTOP,198,155,50,14
CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW | WS_TABSTOP,252,155,50,14
END
DlgBasedWin32.cpp(创建主对话框和消息循环):
#include <windows.h>
#include "resource.h"
// Forward declarations of functions included in this code module:
INT_PTR CALLBACK DlgProc( HWND, UINT, WPARAM, LPARAM );
int APIENTRY _tWinMain( HINSTANCE hInstance,
HINSTANCE /*hPrevInstance*/,
LPTSTR /*lpCmdLine*/,
int /*nCmdShow*/)
{
HWND hDlg = CreateDialogW( hInstance, MAKEINTRESOURCEW( IDD_MAINDLG ),
NULL, DlgProc );
ShowWindow( hDlg, SW_SHOW );
UpdateWindow( hDlg );
MSG msg = { 0 };
// Main message loop:
while ( GetMessageW( &msg, NULL, 0, 0 ) )
{
if ( !IsDialogMessageW( hDlg, &msg ) ) {
TranslateMessage( &msg );
DispatchMessageW( &msg );
}
}
return (int) msg.wParam;
}
DlgBasedWin32.cpp(对话消息处理程序):
// Message handler for IDD_MAINDLG
INT_PTR CALLBACK DlgProc( HWND hDlg,
UINT message,
WPARAM wParam,
LPARAM lParam )
{
switch ( message )
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if ( LOWORD( wParam ) == IDOK || LOWORD( wParam ) == IDCANCEL ) {
DestroyWindow( hDlg );
return (INT_PTR)TRUE;
}
break;
case WM_DESTROY:
PostQuitMessage( 0 );
return (INT_PTR)TRUE;
case WM_DRAWITEM: {
WORD wID = (WORD)wParam;
const DRAWITEMSTRUCT& dis = *(DRAWITEMSTRUCT*)lParam;
// Focus change?
if ( dis.itemAction & ODA_FOCUS ) {
// Toggle focus rectangle
DrawFocusRect( dis.hDC, &dis.rcItem );
}
else if ( dis.itemAction & ODA_DRAWENTIRE ) {
// Not a focus change -> render rectangle if requested
if ( dis.itemState & ODS_FOCUS ) {
DrawFocusRect( dis.hDC, &dis.rcItem );
}
}
return (INT_PTR)TRUE;
}
}
return (INT_PTR)FALSE;
}
上面的代码显示了一个简单的对话框,只有一个确定和取消按钮。按钮具有BS_OWNERDRAW 样式集,WM_DRAWITEM 处理程序仅呈现焦点矩形;否则按钮将保持不可见。完整的键盘和鼠标支持分别通过IsDialogMessage 和默认消息处理程序实现。