C++ 组合框中包含多个相同项的选择无效

C++ 组合框中包含多个相同项的选择无效,c++,winapi,combobox,C++,Winapi,Combobox,我使用样式CBS\u下拉列表创建了组合框。此组合框包含多个具有名称的项目,例如: 项目a B项 B项 项目C 如您所见,第二项和第三项具有相同的名称。它是任务所要求的。当用户打开组合框的列表框并选择第三项时,其名称将被复制到组合框的编辑部分,并且我的类将收到CBN\u SELCHANGE通知。我发送消息CB_GETCURSEL,并接收到所选项目的索引等于“2”(基于零的计数)。在这个阶段,一切都很好 但是,当用户第二次打开组合框的列表框时,组合框显示为选中的第二项(索引为“1”)!我的代码没

我使用样式
CBS\u下拉列表创建了组合框。此组合框包含多个具有名称的项目,例如:

  • 项目a
  • B项
  • B项
  • 项目C
如您所见,第二项和第三项具有相同的名称。它是任务所要求的。当用户打开组合框的列表框并选择第三项时,其名称将被复制到组合框的编辑部分,并且我的类将收到
CBN\u SELCHANGE
通知。我发送消息
CB_GETCURSEL
,并接收到所选项目的索引等于“2”(基于零的计数)。在这个阶段,一切都很好

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

如果我将组合框样式从
CBS\u DROPDOWN
更改为
CBS\u DROPDOWNLIST
,它将正常工作。但我需要使用CBS_下拉列表


如何修复它?

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

简单解决方案-为重复项添加后缀,如“项B(1)”、“项B(2)”等,使其唯一

如果这是不可能的,您可以对组合的列表框进行子类化,并阻止它处理组合框的选择更改请求

为此,请将以下代码放入组合框的
CBN\u下拉列表
通知处理程序中:

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\u EDITCHANGE
通知,并设置要签入
ComboLBoxProc
的标志。在这种情况下,您将允许默认处理
LB_SETCURSEL
。在列表框(
CBN\u SELCHANGE
)中进行选择后,重置此标志


这是一个小技巧,所以我可能会采用“简单的解决方案”,在副本中添加后缀。

非常感谢Zett42。 他关于combo列表框子类化的想法非常好。它是有效的。 这是我在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;
};
模板
类CComboDDownBox
:公共数据库
,受保护的ATL::CMessageMap
{
公众:
CComboDDownBox()
:m_lb(_T(“”),this,m_lbMapId)
,m_parent(_T(“”),this,m_parentMapId)
,m_块选择(假)
{};
虚拟~CComboDDownBox(){};
公众:
bool InitLB()
{
COMBOBOXINFO={sizeof(COMBOBOXINFO),0};
bool res=::GetComboBoxInfo(TBase::operator HWND(),&info)!=FALSE;
如果(res)
{
res=(::GetWindowLong(TBase::operator HWND(),GWL_样式)和CBS_下拉菜单)=CBS_下拉菜单;
如果(res)
{
res=m_lb.subclass窗口(info.hwnlist)!=FALSE;
如果(res)
{
res=m_parent.subclass窗口(::GetParent(TBase::operator HWND())!=FALSE;
}
}
}
返回res;
}
受保护的:
开始消息映射(CComboDDownBox)
ALT\U MSG\U映射(m\U lbMapId)
消息处理程序(LB_SETCURSEL、OnLbSetCurSel)
ALT_MSG_映射(m_parentMapId)
命令\代码\处理程序(CBN\ U下拉列表、OnCbDropDown)
命令\u代码\u处理程序(CBN\u特写、OnCBCloseUp或SelectionChanged)
命令\u代码\u处理程序(CBN\u SELCHANGE、ONCBCLOSEUP或SELECTIONCHANGED)
END_MSG_MAP()
LRESULT OnLbSetCurSel(UINT/*uMsg*/,WPARAM/*WPARAM*/,LPARAM/*LPARAM*/,BOOL&bHandled)
{
如果(!m_块选择)
{
bHandled=FALSE;
}
返回LB_ERR;
}
LRESULT OnCbDropDown(WORD/*wNotifyCode*/、WORD/*wID*/、HWND hWndCtl、BOOL和bHandled)
{
if(hWndCtl==TBase::operator HWND())
{
m_blockSelection=true;
}
bHandled=FALSE;
返回0;
}
LRESULT oncbcloseUporSelection已更改(WORD/*wNotifyCode*/、WORD/*wID*/、HWND hWndCtl、BOOL&bHandled)
{
if(hWndCtl==TBase::operator HWND())
{
m_blockSelection=false;
}
bHandled=FALSE;
返回0;
}
私人:
静态常数DWORD m_lbMapId=1;
静态常量DWORD m_parentMapId=2;
ATL::CContainedWindow m_lb;
ATL::CContainedWindow m_父母;
布尔m_区块选择;
};
您可以在对话中使用此模板,例如在MFC中:

class CMyDialog 
    : public CDialog
{
public:

    // .... Other methods ...

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

        m_combo.InitLB();

        return TRUE;
    }

private:
    CComboDDownBox<CComboBox> m_combo;
};
类CMyDialog
:公共对话
{
公众:
//……其他方法。。。
虚拟BOOL-OnInitDialog()
{
CDialog::OnInitDialog();
m_combo.InitLB();
返回TRUE;
}
私人:
CComboDDownBox m_组合;
};

该组合是否有可能被发送到编辑框中当前值的CB_SELECTSTRING消息?这将导致列表选择更改为第一个可用的匹配项。设计思路:根据上面提供的信息,如果两个“条目B”条目确实相同,则用户选择哪一个并不重要。另一方面,如果它们在某些方面确实不同,则用户无法知道哪个是哪个,因此无法确保正确选择。不,我的代码不能做到这一点。当您打开列表框时,控件将选择以编辑控件中显示的文本开头的第一个列表框项。毕竟,文本可能是由用户输入的。控件如何知道两个列表框项中的哪一个是m