组合框中的选项无效,包含多个相同的项目

时间:2017-08-25 18:16:47

标签: c++ winapi combobox

我创建了带有样式CBS_DROPDOWN的组合框。此组合框包含多个具有名称的项目,例如:

  • 意达,
  • ItemB,
  • ItemB,
  • ItemC

如您所见,第二项和第三项具有相同的名称。它是任务所需要的。当用户打开组合的列表框并选择第三个项目时,会复制其名称以编辑组合框的一部分,并且我的班级会收到CBN_SELCHANGE通知。我发送消息CB_GETCURSEL并接收所选项目的索引等于“2”(基于零的计算)。在这个阶段,一切都很好。

但是,当用户第二次打开组合的列表框时,组合框显示为选定的第二项(索引为“1”)!我的代码没有收到有关项目选择更改的任何通知,那么为什么组合显示错误的选择?

如果我将组合框样式从CBS_DROPDOWN更改为CBS_DROPDOWNLIST,则可以正常使用。但我需要使用CBS_DROPDOWN

如何解决?

2 个答案:

答案 0 :(得分:2)

当您打开列表框时,控件将选择以编辑控件中显示的文本开头的第一个列表框项。毕竟,文本可能已由用户输入。控件如何知道用户所指的两个列表框项目中的哪一个?

简单解决方案 - 为“项目B(1)”,“项目B(2)”等重复项添加后缀,使其独一无二。

如果无法做到这一点,您可以继承组合的列表框,并阻止它处理组合框的选择更改请求。

为此,请将以下代码放入组合框的CBN_DROPDOWN通知处理程序中:

COMBOBOXINFO info{ sizeof(info) };
GetComboBoxInfo( hwndOfComboBox, &info );
SetWindowSubclass( info.hwndList, ComboLBoxProc, 0, 0 );

ComboLBoxProc是一个回调函数,可能如下所示:

LRESULT CALLBACK ComboLBoxProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
    switch( uMsg )
    {
        case LB_SETCURSEL:
            return LB_ERR;  // to prevent selection change
    }

    return DefSubclassProc( hWnd, uMsg, wParam, lParam );
}

使用上面的代码,用户仍然可以更改列表框中的选择,但不会再在列表框中选择在编辑控件中输入的文本。如果您想保留此功能,可以处理组合框的CBN_EDITCHANGE通知,并设置一个您将在ComboLBoxProc中检查的标记。在这种情况下,您将允许LB_SETCURSEL的默认处理。在列表框(CBN_SELCHANGE)中进行了选择后,重置此标志。

这有点像黑客,所以我可以选择为重复项添加后缀的“简单解决方案”。

答案 1 :(得分:1)

非常感谢 Zett42 。 他关于combo的listbox子类化的想法很棒。它有效。 这是我在ATL中实现他的方法。

template<typename TBase>
class CComboDDownBox
    : public TBase
    , protected ATL::CMessageMap
{
public:

    CComboDDownBox() 
        : m_lb(_T(""), this, m_lbMapId)
        , m_parent(_T(""), this, m_parentMapId)
        , m_blockSelection(false)
    {};

    virtual ~CComboDDownBox() {};

public:

    bool InitLB()
    {
        COMBOBOXINFO info = { sizeof(COMBOBOXINFO), 0 };
        bool res = ::GetComboBoxInfo(TBase::operator HWND(), &info) != FALSE;
        if (res)
        {
            res = (::GetWindowLong(TBase::operator HWND(), GWL_STYLE) & CBS_DROPDOWN) == CBS_DROPDOWN;
            if (res)
            {
                res = m_lb.SubclassWindow(info.hwndList) != FALSE;
                if (res)
                {
                    res = m_parent.SubclassWindow(::GetParent(TBase::operator HWND())) != FALSE;
                }
            }
        }

        return res;
    }

protected:

    BEGIN_MSG_MAP(CComboDDownBox<TBase>)
    ALT_MSG_MAP(m_lbMapId)
        MESSAGE_HANDLER(LB_SETCURSEL, OnLbSetCurSel)
    ALT_MSG_MAP(m_parentMapId)
        COMMAND_CODE_HANDLER(CBN_DROPDOWN, OnCbDropDown)
        COMMAND_CODE_HANDLER(CBN_CLOSEUP, OnCbCloseUpOrSelectionChanged)
        COMMAND_CODE_HANDLER(CBN_SELCHANGE, OnCbCloseUpOrSelectionChanged)
    END_MSG_MAP()

    LRESULT OnLbSetCurSel(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
    {
        if (!m_blockSelection)
        {
            bHandled = FALSE;
        }

        return LB_ERR;
    }

    LRESULT OnCbDropDown(WORD /*wNotifyCode*/, WORD /*wID*/, HWND hWndCtl, BOOL& bHandled)
    {
        if (hWndCtl == TBase::operator HWND())
        {
            m_blockSelection = true;
        }

        bHandled = FALSE;
        return 0;
    }

    LRESULT OnCbCloseUpOrSelectionChanged(WORD /*wNotifyCode*/, WORD /*wID*/, HWND hWndCtl, BOOL& bHandled)
    {
        if (hWndCtl == TBase::operator HWND())
        {
            m_blockSelection = false;
        }

        bHandled = FALSE;
        return 0;
    }

private:

    static const DWORD      m_lbMapId = 1;
    static const DWORD      m_parentMapId = 2;

    ATL::CContainedWindow   m_lb;
    ATL::CContainedWindow   m_parent;

    bool                    m_blockSelection;
};

您可以在对话中使用此模板,例如在MFC:

class CMyDialog 
    : public CDialog
{
public:

    // .... Other methods ...

    virtual BOOL OnInitDialog() 
    {
        CDialog::OnInitDialog();

        m_combo.InitLB();

        return TRUE;
    }

private:
    CComboDDownBox<CComboBox> m_combo;
};