Forcing a combobox to “dropdown” above instead of

2019-05-30 07:04发布

When you click on the "dropdown" button of a combobox, the dropped down listbox appears below the combobox, unless there is not enough space below, in which case the listbox appears above.

Now I wonder if there is a possibility to force the lisbox to appear above the combobox, even if there is enough space below.

Illustration

When I click on the combo box, I'd like the "drop down" list box appear always above as on the left screen copy.

enter image description here

2条回答
三岁会撩人
2楼-- · 2019-05-30 07:47

Everything is possible, and you don't need to implement the control "from scratch".

First, you can subclass the ListBox part of your ComboBox to get complete control over it, as explained in MSDN. You can create a class, derived from CListBox, using the Class Wizard. You only need to implement WM_WINPOSITIONCHANGING handler in it:

void CTopListBox::OnWindowPosChanging(WINDOWPOS* lpwndpos)
{
    CListBox::OnWindowPosChanging(lpwndpos);
    if ((lpwndpos->flags & SWP_NOMOVE) == 0)
    {
        lpwndpos->y -= lpwndpos->cy + 30;
    }
}

Here, for simplicity, I am moving the box up by the (heights+30). You can get the height of your ComboBox instead of my 30.

Then you declare a member variable in your dialog class:

CTopListBox m_listbox;

and subclass it like that:

HBRUSH CMFCDlgDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    if (nCtlColor == CTLCOLOR_LISTBOX)
    {
        if (m_listbox.GetSafeHwnd() == NULL)
        {
            m_listbox.SubclassWindow(pWnd->GetSafeHwnd());
            CRect r;
            m_listbox.GetWindowRect(r);
            m_listbox.MoveWindow(r);
        }
    }

    HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
    return hbr;
}

Note that I am calling m_listbox.MoveWindow(r) there; it is needed because first WM_CONTROLCOLOR message for that list box comes after it is positioned, so the very first time it would drop down instead of up.

Disclaimer: this is not a very clean solution, as, if you have windows animation enabled, you'd see that the list unrolls from top to bottom.

Alternatively, you should be able to "fool" the combobox that it is too close to the bottom of the screen; then it will drop up by itself. I leave it as an exercise for the readers :)

查看更多
女痞
3楼-- · 2019-05-30 07:51

This would be relatively easy except when combo box has "slide open" effect. If you move the dropdown listbox to the top, and the combo slides open from top-to-bottom, it would look odd. So you have to disable the animation or reverse it.

In this function I call AnimateWindow in OnWindowPosChanging, it doesn't seem to cause any problems but I am not a 100% sure about it!

class CComboBox_ListBox : public CListBox
{
public:
    CWnd *comboBox;
    void OnWindowPosChanging(WINDOWPOS *wndpos)
    {
        CListBox::OnWindowPosChanging(wndpos);
        if (comboBox && wndpos->cx && wndpos->cy && !(wndpos->flags & SWP_NOMOVE))
        {
            CRect rc;
            comboBox->GetWindowRect(&rc);

            //if listbox is at the bottom...
            if (wndpos->y > rc.top) {
                //if there is enough room for listbox to go on top...
                if (rc.top > wndpos->cy) {
                    wndpos->y = rc.top - wndpos->cy;
                    BOOL animation;
                    SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &animation, 0);
                    //if combobox slides open...
                    if (animation) {
                        //we have to set the x coordinate otherwise listbox 
                        //is in the wrong place when parent window moves
                        SetWindowPos(0, wndpos->x, wndpos->y, 0, 0, 
                            SWP_NOSENDCHANGING | SWP_HIDEWINDOW | SWP_NOSIZE);

                        AnimateWindow(100, AW_VER_NEGATIVE);
                    }
                }
            }
        }
    }
    DECLARE_MESSAGE_MAP()
};

Usage:

COMBOBOXINFO ci = { sizeof(COMBOBOXINFO) };
comboBox.GetComboBoxInfo(&ci);
CComboBox_ListBox *listBox = new CComboBox_ListBox;
listBox->comboBox = &comboBox;
listBox->SubclassWindow(ci.hwndList);

Also you can use SetMinVisibleItems to reduce the listbox height and make sure the dropdown list fits on top.

查看更多
登录 后发表回答