【问题标题】:Changing push button border style更改按钮边框样式
【发布时间】:2023-04-03 17:47:01
【问题描述】:

我有一个自定义对话框,其中包含更改对话框默认按钮的方法。

WM_INITDIALOG期间,以下函数用于在默认按钮(系统选择的按钮)上绘制焦点矩形

case WM_INITDIALOG:
// ...

// ensure focus rectangle is properly drawn around control with focus
PostMessageW(mhWnd, WM_KEYDOWN, VK_TAB, 0);
return TRUE;

Dialog 类有 SetDefaultControl 方法,让我可以在创建对话框后指定不同的按钮作为默认按钮:

bool Dialog::SetDefaultControl(DWORD ctrlID) const noexcept
{
    BOOL result = TRUE;

    // Get current default button
    const LRESULT status = SendMessageW(mhWnd, DM_GETDEFID, 0, 0);

    if (HIWORD(status) == DC_HASDEFID)
    {
        // remove focus rectangle from default
        result = PostMessageW(GetDlgItem(mhWnd, LOWORD(status)), BM_SETSTYLE, BS_DEFPUSHBUTTON, TRUE);
    }

    // change default button to specified ctrlID
    result = result && PostMessageW(mhWnd, DM_SETDEFID, ctrlID, 0);

    return result != FALSE;
}

默认对话框有确定和取消按钮,在WM_INITDIALOG期间确定按钮被设置为默认,后来我使用上面的函数将它设置为默认,结果是这样的:

发生的情况是取消按钮是新的默认按钮,但是焦点矩形并未从以前的默认按钮(即确定按钮)中删除。

SetDefaultControl 通过向 OK 按钮发送 BM_SETSTYLE 消息来处理这种情况,但是我不知道应该发送什么样式来从 OK 按钮中删除焦点边框。

根据 MSDN 更改按钮样式:

发送 DM_SETDEFID 消息来更改默认按钮不会 始终从第一个按钮中删除默认状态边框。在 在这些情况下,应用程序应该发送一个 BM_SETSTYLE 消息到 更改第一个按钮边框样式。

https://docs.microsoft.com/en-us/windows/win32/dlgbox/dm-getdefid

我不知道如何继续,我应该在以前的默认按钮上设置什么样式来移除焦点边框状态。

按钮是用WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON创建的

【问题讨论】:

  • DM_SETDEFID 告诉对话框在用户按 Enter 时激活哪个按钮,但不会更新任何样式以在 UI 中反映这一点。您可以使用GetWindowLong(hwndButton, GWL_STYLE) 获取当前样式,然后将BS_PUSHBUTTON 更改为BS_DEFPUSHBUTTON(使其具有默认外观)或将其改回(使其看起来非默认)。此外,发布虚假键盘消息不是触发焦点矩形的方式。使用WM_CHANGEUISTATE
  • WM_CHANGEUISTATE docs 对我来说不清楚,如果我想将焦点矩形从一个按钮移动到另一个按钮,这是行不通的:SendMessageW(hDlg, WM_CHANGEUISTATE, MAKEWPARAM(UIS_SET, UISF_HIDEFOCUS), 0) 以下都不起作用MAKEWPARAM(UIS_CLEAR, UISF_HIDEFOCUS)
  • WM_CHANGEUISTATE 不适用于移动焦点。它用于强制焦点矩形可见(通常在第一次按 TAB 之前隐藏。要移动焦点,请使用 WM_NEXTDLGCTL
  • 现在我有了解决这个问题所需的所有信息! + 需要了解 UI 状态。与往常一样,Raymond,感谢您的有用回复!
  • WM_NEXTDLGCTL 解决了我的问题,无需讨论任何其他内容,您可能想写一个答案。

标签: c++ winapi


【解决方案1】:

感谢 Raymond 的 cmets,我提出了这样的解决方案:

bool Dialog::SetDefaultControl(DWORD ctrlID) const noexcept
{
    // MSDN: WM_NEXTDLGCTL updates the default pushbutton border, sets the default control identifier, and
    // automatically selects the text of an edit control (if the target window is an edit control)
    return PostMessageW(mhWnd, WM_NEXTDLGCTL, reinterpret_cast<WPARAM>(GetDlgItem(mhWnd, ctrlID)), MAKELPARAM(TRUE, 0)) != FALSE;
}

这样当TAB 键第一次被点击时,它将从具有焦点的控件移动到具有BS_TABSTOP 样式的下一个控件。

但是 API 设计可能存在一个小问题,当对话框第一次显示时,具有焦点的按钮没有绘制焦点矩形,需要按 TAB 才能显示。

这是第一次显示的示例对话框(确定按钮具有焦点并且已正确绘制但未设置焦点矩形):

如果您知道在显示对话框时为什么以及如何使焦点矩形出现,请告诉我。

【讨论】:

    【解决方案2】:

    The WM_INITDIALOG Message可用于设置默认输入焦点。

    在从WM_INITDIALOG 消息返回之前,该过程应该 确定是否应该将输入焦点设置为指定的 控制。如果对话框程序返回TRUE,则系统 自动将输入焦点设置到其窗口句柄的控件 在wParam 参数中。如果控件接收到默认焦点 不合适,可以通过设置焦点到合适的控件 使用SetFocus 函数。如果程序设置了输入焦点,它 必须返回FALSE,防止系统设置默认 重点。接收默认输入焦点的控件始终是 模板中指定的第一个控件可见未禁用、 并具有WS_TABSTOP 风格。如果不存在这样的控制,系统 将默认输入焦点设置为模板中的第一个控件。

    【讨论】:

    • 系统可以在对话框创建期间或之后设置输入焦点,但它不会绘制与输入焦点不同的焦点矩形。一个控件可能是默认的并且有焦点,但这与我不知道如何设置为默认控件的焦点矩形不同。