启用
已禁用
当我禁用按钮(使用 BS_BITMAP 样式标志创建)时,它会改变其外观(请参见上图),编辑控件也会发生同样的事情。
禁用时如何使控件不更改?
我可以通过子类化控件来做到这一点,但有没有更简单的方法?
如果可能的话,我不想仅仅为此对控件进行子类化。
你不需要为了做到这一点而对控件进行子类化,尽管我会说它会更干净。设置BS_OWNERDRAW
样式和处理WM_DRAWITEM
消息的替代方法。这意味着您将接管所有绘图,但这没关系,因为您不希望它看起来像一个普通按钮。
我完全同意Jonathan Potter 的观察,即未能向用户指示哪些按钮已启用,哪些按钮未启用是非常糟糕的 UI 设计。有多种方法可以做到这一点,但不这样做不是一个可行的选择。幸运的是,使用 很容易WM_DRAWITEM
,因为它告诉您按钮的当前状态。
所以让WM_DRAWITEM
消息处理程序看起来像这样(在父窗口的窗口过程中):
case WM_DRAWITEM:
{
const DRAWITEMSTRUCT* pDIS = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
// See if this is the button we want to paint.
// You can either check the control ID, like I've done here,
// or check against the window handle (pDIS->hwndItem).
if (pDIS->CtlID == 1)
{
// Load the bitmap.
const HBITMAP hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
// Draw the bitmap to the button.
bool isEnabled = (pDIS->itemState & ODS_DISABLED) == 0;
DrawState(pDIS->hDC,
nullptr,
nullptr,
reinterpret_cast<LPARAM>(hBmp),
0, 0, 0, 0, 0,
DST_BITMAP | (isEnabled ? DSS_NORMAL : DSS_DISABLED));
// Delete the bitmap.
DeleteObject(hBmp);
// Draw the focus rectangle, if applicable.
if ((pDIS->itemState & ODS_FOCUS) && ((pDIS->itemState & ODS_NOFOCUSRECT) == 0))
{
DrawFocusRect(pDIS->hDC, &pDIS->rcItem);
}
// Indicate that we handled this message.
return TRUE;
}
break;
}
当然,您可以通过一次加载位图并将其缓存在全局对象中来进一步优化此代码,而不是在每次按钮需要绘制时加载和销毁它。
请注意,我使用了DrawState
函数,它可以在“正常”(DSS_NORMAL
)或“禁用”(DSS_DISABLED
)状态下绘制位图。这大大简化了代码,并允许我们轻松处理禁用状态,但不幸的是,结果看起来有点难看。这是因为该DrawState
函数在应用任何非正常效果之前将位图转换为单色。
你可能不喜欢这种效果,所以你需要做点别的。例如,使用两个单独的图像,一个用于启用状态,另一个用于禁用状态,并绘制适当的图像。或者将您的正常彩色图像转换为 grayscale,然后将其绘制为禁用状态。
如果自定义绘图代码运行速度太慢,您可以通过检查值pDIS->itemAction
并仅重新绘制必要部分来进一步优化它。
然后,一旦你认为你已经把所有的东西都打磨和高效了,不可避免的错误报告就会开始出现。例如,不支持键盘加速器。然后,一旦您添加了对这些的支持,您需要在 UI 中指出这一点。对于已经包含文本的位图,这将是困难的;绘制带下划线的字母的唯一方法是自己绘制文本。这一切都证明了所有者绘制是太多的工作。只需让 Windows 以正常方式绘制控件,不要因为某些设计师认为它“看起来很酷”而破坏用户的一切。