【问题标题】:Invalid selection in Combo Box with several identical items具有多个相同项目的组合框中的选择无效
【发布时间】:2017-08-25 18:16:46
【问题描述】:

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

  • 项目A,
  • 项目B,
  • 项目B,
  • 项目C

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

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

如果我将组合框样式从 CBS_DROPDOWN 更改为 CBS_DROPDOWNLIST,它将正常工作。但我需要与CBS_DROPDOWN 合作。

如何解决?

【问题讨论】:

  • 组合是否有机会针对编辑框中的当前值发送 CB_SELECTSTRING 消息?这将导致列表选择更改为第一个可用的匹配项。
  • 一个设计思路:根据上面提供的信息,如果两个“Item B”条目确实相同,那么用户选择哪个并不重要。另一方面,如果它们在某些方面实际上不同,您的用户无法知道哪个是哪个,因此无法保证正确选择。
  • 不,我的代码没有这样做。
  • 当您打开列表框时,控件会选择第一个以编辑控件中显示的 text 开头的列表框项。毕竟,文本可能是由用户输入的。控件如何知道用户所指的两个列表框项中的哪一个?您能否为“Item B (1)”、“Item B (2)”等重复项添加后缀以使其唯一?

标签: c++ winapi combobox


【解决方案1】:

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

简单的解决方案 - 为重复的项目添加一个后缀,如“Item B (1)”、“Item 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) 中进行选择后,重置此标志。

这有点小题大做,所以我可能会采用“简单的解决方案”,即为重复项添加后缀。

【讨论】:

    【解决方案2】:

    非常感谢 Zett42。 他关于组合的列表框子类化的想法非常好。它有效。 这是我在 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;
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多