1 /////////////////////////////////////////////////////////////////////////////// 
   2 // Name:        src/msw/listbox.cpp 
   4 // Author:      Julian Smart 
   5 // Modified by: Vadim Zeitlin (owner drawn stuff) 
   8 // Copyright:   (c) Julian Smart 
   9 // Licence:     wxWindows licence 
  10 /////////////////////////////////////////////////////////////////////////////// 
  12 // For compilers that support precompilation, includes "wx.h". 
  13 #include "wx/wxprec.h" 
  21 #include "wx/listbox.h" 
  24     #include "wx/dynarray.h" 
  25     #include "wx/settings.h" 
  31     #include "wx/window.h" 
  34 #include "wx/msw/private.h" 
  35 #include "wx/msw/dc.h" 
  40     #include  "wx/ownerdrw.h" 
  43 // ============================================================================ 
  44 // list box item declaration and implementation 
  45 // ============================================================================ 
  49 class wxListBoxItem 
: public wxOwnerDrawn
 
  52     wxListBoxItem(wxListBox 
*parent
) 
  53         { m_parent 
= parent
; } 
  55     wxListBox 
*GetParent() const 
  59         { return m_parent
->GetItemIndex(const_cast<wxListBoxItem
*>(this)); } 
  61     wxString 
GetName() const 
  62         { return m_parent
->GetString(GetIndex()); } 
  68 wxOwnerDrawn 
*wxListBox::CreateLboxItem(size_t WXUNUSED(n
)) 
  70     return new wxListBoxItem(this); 
  73 #endif  //USE_OWNER_DRAWN 
  75 // ============================================================================ 
  76 // list box control implementation 
  77 // ============================================================================ 
  79 // ---------------------------------------------------------------------------- 
  81 // ---------------------------------------------------------------------------- 
  83 void wxListBox::Init() 
  86     m_updateHorizontalExtent 
= false; 
  87     m_selectedByKeyboard 
= false; 
  90 bool wxListBox::Create(wxWindow 
*parent
, 
  94                        int n
, const wxString choices
[], 
  96                        const wxValidator
& validator
, 
  99     // initialize base class fields 
 100     if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) ) 
 103     // create the native control 
 104     if ( !MSWCreateControl(wxT("LISTBOX"), wxEmptyString
, pos
, size
) ) 
 106         // control creation failed 
 110     // initialize the contents 
 111     for ( int i 
= 0; i 
< n
; i
++ ) 
 116     // now we can compute our best size correctly, so do it again 
 117     SetInitialSize(size
); 
 122 bool wxListBox::Create(wxWindow 
*parent
, 
 126                        const wxArrayString
& choices
, 
 128                        const wxValidator
& validator
, 
 129                        const wxString
& name
) 
 131     wxCArrayString 
chs(choices
); 
 132     return Create(parent
, id
, pos
, size
, chs
.GetCount(), chs
.GetStrings(), 
 133                   style
, validator
, name
); 
 136 wxListBox::~wxListBox() 
 141 WXDWORD 
wxListBox::MSWGetStyle(long style
, WXDWORD 
*exstyle
) const 
 143     WXDWORD msStyle 
= wxControl::MSWGetStyle(style
, exstyle
); 
 145     // we always want to get the notifications 
 146     msStyle 
|= LBS_NOTIFY
; 
 148     // without this style, you get unexpected heights, so e.g. constraint 
 149     // layout doesn't work properly 
 150     msStyle 
|= LBS_NOINTEGRALHEIGHT
; 
 152     wxASSERT_MSG( !(style 
& wxLB_MULTIPLE
) || !(style 
& wxLB_EXTENDED
), 
 153                   wxT("only one of listbox selection modes can be specified") ); 
 155     if ( style 
& wxLB_MULTIPLE 
) 
 156         msStyle 
|= LBS_MULTIPLESEL
; 
 157     else if ( style 
& wxLB_EXTENDED 
) 
 158         msStyle 
|= LBS_EXTENDEDSEL
; 
 160     wxASSERT_MSG( !(style 
& wxLB_ALWAYS_SB
) || !(style 
& wxLB_NO_SB
), 
 161                   wxT( "Conflicting styles wxLB_ALWAYS_SB and wxLB_NO_SB." ) ); 
 163     if ( !(style 
& wxLB_NO_SB
) ) 
 165         msStyle 
|= WS_VSCROLL
; 
 166         if ( style 
& wxLB_ALWAYS_SB 
) 
 167             msStyle 
|= LBS_DISABLENOSCROLL
; 
 170     if ( m_windowStyle 
& wxLB_HSCROLL 
) 
 171         msStyle 
|= WS_HSCROLL
; 
 172     if ( m_windowStyle 
& wxLB_SORT 
) 
 175 #if wxUSE_OWNER_DRAWN && !defined(__WXWINCE__) 
 176     if ( m_windowStyle 
& wxLB_OWNERDRAW 
) 
 178         // we don't support LBS_OWNERDRAWVARIABLE yet and we also always put 
 179         // the strings in the listbox for simplicity even though we could have 
 180         // avoided it in this case 
 181         msStyle 
|= LBS_OWNERDRAWFIXED 
| LBS_HASSTRINGS
; 
 183 #endif // wxUSE_OWNER_DRAWN 
 188 void wxListBox::OnInternalIdle() 
 190     wxWindow::OnInternalIdle(); 
 192     if (m_updateHorizontalExtent
) 
 194         SetHorizontalExtent(wxEmptyString
); 
 195         m_updateHorizontalExtent 
= false; 
 199 void wxListBox::MSWOnItemsChanged() 
 201     // we need to do two things when items change: update their max horizontal 
 202     // extent so that horizontal scrollbar could be shown or hidden as 
 203     // appropriate and also invlaidate the best size 
 205     // updating the max extent is slow (it's an O(N) operation) and so we defer 
 206     // it until the idle time but the best size should be invalidated 
 207     // immediately doing it in idle time is too late -- layout using incorrect 
 208     // old best size will have been already done by then 
 210     m_updateHorizontalExtent 
= true; 
 212     InvalidateBestSize(); 
 215 // ---------------------------------------------------------------------------- 
 216 // implementation of wxListBoxBase methods 
 217 // ---------------------------------------------------------------------------- 
 219 void wxListBox::DoSetFirstItem(int N
) 
 221     wxCHECK_RET( IsValid(N
), 
 222                  wxT("invalid index in wxListBox::SetFirstItem") ); 
 224     SendMessage(GetHwnd(), LB_SETTOPINDEX
, (WPARAM
)N
, (LPARAM
)0); 
 227 void wxListBox::DoDeleteOneItem(unsigned int n
) 
 229     wxCHECK_RET( IsValid(n
), 
 230                  wxT("invalid index in wxListBox::Delete") ); 
 232 #if wxUSE_OWNER_DRAWN 
 233     if ( HasFlag(wxLB_OWNERDRAW
) ) 
 236         m_aItems
.RemoveAt(n
); 
 238 #endif // wxUSE_OWNER_DRAWN 
 240     SendMessage(GetHwnd(), LB_DELETESTRING
, n
, 0); 
 245     UpdateOldSelections(); 
 248 int wxListBox::FindString(const wxString
& s
, bool bCase
) const 
 250     // back to base class search for not native search type 
 252        return wxItemContainerImmutable::FindString( s
, bCase 
); 
 254     int pos 
= ListBox_FindStringExact(GetHwnd(), -1, s
.wx_str()); 
 261 void wxListBox::DoClear() 
 263 #if wxUSE_OWNER_DRAWN 
 264     if ( HasFlag(wxLB_OWNERDRAW
) ) 
 266         WX_CLEAR_ARRAY(m_aItems
); 
 268 #endif // wxUSE_OWNER_DRAWN 
 270     ListBox_ResetContent(GetHwnd()); 
 275     UpdateOldSelections(); 
 278 void wxListBox::DoSetSelection(int N
, bool select
) 
 280     wxCHECK_RET( N 
== wxNOT_FOUND 
|| IsValid(N
), 
 281                  wxT("invalid index in wxListBox::SetSelection") ); 
 283     if ( HasMultipleSelection() ) 
 285         // Setting selection to -1 should deselect everything. 
 286         const bool deselectAll 
= N 
== wxNOT_FOUND
; 
 287         SendMessage(GetHwnd(), LB_SETSEL
, 
 288                     deselectAll 
? FALSE 
: select
, 
 289                     deselectAll 
? -1 : N
); 
 293         SendMessage(GetHwnd(), LB_SETCURSEL
, select 
? N 
: -1, 0); 
 296     UpdateOldSelections(); 
 299 bool wxListBox::IsSelected(int N
) const 
 301     wxCHECK_MSG( IsValid(N
), false, 
 302                  wxT("invalid index in wxListBox::Selected") ); 
 304     return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? false : true; 
 307 void *wxListBox::DoGetItemClientData(unsigned int n
) const 
 309     LPARAM rc 
= SendMessage(GetHwnd(), LB_GETITEMDATA
, n
, 0); 
 310     if ( rc 
== LB_ERR 
&& GetLastError() != ERROR_SUCCESS 
) 
 312         wxLogLastError(wxT("LB_GETITEMDATA")); 
 320 void wxListBox::DoSetItemClientData(unsigned int n
, void *clientData
) 
 322     if ( ListBox_SetItemData(GetHwnd(), n
, clientData
) == LB_ERR 
) 
 324         wxLogDebug(wxT("LB_SETITEMDATA failed")); 
 328 // Return number of selections and an array of selected integers 
 329 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const 
 333     if ( HasMultipleSelection() ) 
 335         int countSel 
= ListBox_GetSelCount(GetHwnd()); 
 336         if ( countSel 
== LB_ERR 
) 
 338             wxLogDebug(wxT("ListBox_GetSelCount failed")); 
 340         else if ( countSel 
!= 0 ) 
 342             int *selections 
= new int[countSel
]; 
 344             if ( ListBox_GetSelItems(GetHwnd(), 
 345                                      countSel
, selections
) == LB_ERR 
) 
 347                 wxLogDebug(wxT("ListBox_GetSelItems failed")); 
 352                 aSelections
.Alloc(countSel
); 
 353                 for ( int n 
= 0; n 
< countSel
; n
++ ) 
 354                     aSelections
.Add(selections
[n
]); 
 357             delete [] selections
; 
 362     else  // single-selection listbox 
 364         if (ListBox_GetCurSel(GetHwnd()) > -1) 
 365             aSelections
.Add(ListBox_GetCurSel(GetHwnd())); 
 367         return aSelections
.Count(); 
 371 // Get single selection, for single choice list items 
 372 int wxListBox::GetSelection() const 
 374     wxCHECK_MSG( !HasMultipleSelection(), 
 376                  wxT("GetSelection() can't be used with multiple-selection listboxes, use GetSelections() instead.") ); 
 378     return ListBox_GetCurSel(GetHwnd()); 
 381 // Find string for position 
 382 wxString 
wxListBox::GetString(unsigned int n
) const 
 384     wxCHECK_MSG( IsValid(n
), wxEmptyString
, 
 385                  wxT("invalid index in wxListBox::GetString") ); 
 387     int len 
= ListBox_GetTextLen(GetHwnd(), n
); 
 389     // +1 for terminating NUL 
 391     ListBox_GetText(GetHwnd(), n
, (wxChar
*)wxStringBuffer(result
, len 
+ 1)); 
 396 int wxListBox::DoInsertItems(const wxArrayStringsAdapter 
& items
, 
 399                              wxClientDataType type
) 
 401     MSWAllocStorage(items
, LB_INITSTORAGE
); 
 403     const bool append 
= pos 
== GetCount(); 
 405     // we must use CB_ADDSTRING when appending as only it works correctly for 
 406     // the sorted controls 
 407     const unsigned msg 
= append 
? LB_ADDSTRING 
: LB_INSERTSTRING
; 
 414     const unsigned int numItems 
= items
.GetCount(); 
 415     for ( unsigned int i 
= 0; i 
< numItems
; i
++ ) 
 417         n 
= MSWInsertOrAppendItem(pos
, items
[i
], msg
); 
 418         if ( n 
== wxNOT_FOUND 
) 
 426 #if wxUSE_OWNER_DRAWN 
 427         if ( HasFlag(wxLB_OWNERDRAW
) ) 
 429             wxOwnerDrawn 
*pNewItem 
= CreateLboxItem(n
); 
 430             pNewItem
->SetFont(GetFont()); 
 431             m_aItems
.Insert(pNewItem
, n
); 
 433 #endif // wxUSE_OWNER_DRAWN 
 434         AssignNewItemClientData(n
, clientData
, i
, type
); 
 439     UpdateOldSelections(); 
 444 int wxListBox::DoHitTestList(const wxPoint
& point
) const 
 446     LRESULT lRes 
= ::SendMessage(GetHwnd(), LB_ITEMFROMPOINT
, 
 447                                  0, MAKELPARAM(point
.x
, point
.y
)); 
 449     // non zero high-order word means that this item is outside of the client 
 450     // area, IOW the point is outside of the listbox 
 451     return HIWORD(lRes
) ? wxNOT_FOUND 
: LOWORD(lRes
); 
 454 void wxListBox::SetString(unsigned int n
, const wxString
& s
) 
 456     wxCHECK_RET( IsValid(n
), 
 457                  wxT("invalid index in wxListBox::SetString") ); 
 459     // remember the state of the item 
 460     bool wasSelected 
= IsSelected(n
); 
 462     void *oldData 
= NULL
; 
 463     wxClientData 
*oldObjData 
= NULL
; 
 464     if ( HasClientUntypedData() ) 
 465         oldData 
= GetClientData(n
); 
 466     else if ( HasClientObjectData() ) 
 467         oldObjData 
= GetClientObject(n
); 
 469     // delete and recreate it 
 470     SendMessage(GetHwnd(), LB_DELETESTRING
, n
, 0); 
 473     if ( n 
== (m_noItems 
- 1) ) 
 476     ListBox_InsertString(GetHwnd(), newN
, s
.wx_str()); 
 478     // restore the client data 
 480         SetClientData(n
, oldData
); 
 481     else if ( oldObjData 
) 
 482         SetClientObject(n
, oldObjData
); 
 484     // we may have lost the selection 
 491 unsigned int wxListBox::GetCount() const 
 496 // ---------------------------------------------------------------------------- 
 497 // size-related stuff 
 498 // ---------------------------------------------------------------------------- 
 500 void wxListBox::SetHorizontalExtent(const wxString
& s
) 
 502     // the rest is only necessary if we want a horizontal scrollbar 
 503     if ( !HasFlag(wxHSCROLL
) ) 
 507     WindowHDC 
dc(GetHwnd()); 
 508     SelectInHDC 
selFont(dc
, GetHfontOf(GetFont())); 
 510     TEXTMETRIC lpTextMetric
; 
 511     ::GetTextMetrics(dc
, &lpTextMetric
); 
 513     int largestExtent 
= 0; 
 518         // set extent to the max length of all strings 
 519         for ( unsigned int i 
= 0; i 
< m_noItems
; i
++ ) 
 521             const wxString str 
= GetString(i
); 
 522             ::GetTextExtentPoint32(dc
, str
.c_str(), str
.length(), &extentXY
); 
 524             int extentX 
= (int)(extentXY
.cx 
+ lpTextMetric
.tmAveCharWidth
); 
 525             if ( extentX 
> largestExtent 
) 
 526                 largestExtent 
= extentX
; 
 529     else // just increase the extent to the length of this string 
 531         int existingExtent 
= (int)SendMessage(GetHwnd(), 
 532                                               LB_GETHORIZONTALEXTENT
, 0, 0L); 
 534         ::GetTextExtentPoint32(dc
, s
.c_str(), s
.length(), &extentXY
); 
 536         int extentX 
= (int)(extentXY
.cx 
+ lpTextMetric
.tmAveCharWidth
); 
 537         if ( extentX 
> existingExtent 
) 
 538             largestExtent 
= extentX
; 
 542         SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L); 
 543     //else: it shouldn't change 
 546 wxSize 
wxListBox::DoGetBestClientSize() const 
 548     // find the widest string 
 551     for (unsigned int i 
= 0; i 
< m_noItems
; i
++) 
 553         wxString 
str(GetString(i
)); 
 554         GetTextExtent(str
, &wLine
, NULL
); 
 555         if ( wLine 
> wListbox 
) 
 559     // give it some reasonable default value if there are no strings in the 
 564     // the listbox should be slightly larger than the widest string 
 565     wListbox 
+= 3*GetCharWidth(); 
 567     // add room for the scrollbar 
 568     wListbox 
+= wxSystemSettings::GetMetric(wxSYS_VSCROLL_X
); 
 570     // don't make the listbox too tall (limit height to 10 items) but don't 
 571     // make it too small neither 
 572     int hListbox 
= SendMessage(GetHwnd(), LB_GETITEMHEIGHT
, 0, 0)* 
 573                     wxMin(wxMax(m_noItems
, 3), 10); 
 575     return wxSize(wListbox
, hListbox
); 
 578 // ---------------------------------------------------------------------------- 
 580 // ---------------------------------------------------------------------------- 
 582 bool wxListBox::MSWCommand(WXUINT param
, WXWORD 
WXUNUSED(id
)) 
 586     if ( param 
== LBN_SELCHANGE 
) 
 588         if ( HasMultipleSelection() ) 
 589             return CalcAndSendEvent(); 
 591         evtType 
= wxEVT_COMMAND_LISTBOX_SELECTED
; 
 593         if ( m_selectedByKeyboard 
) 
 595             // We shouldn't use the mouse position to find the item as mouse 
 596             // can be anywhere, ask the listbox itself. Notice that this can't 
 597             // be used when the item is selected using the mouse however as 
 598             // LB_GETCARETINDEX will always return a valid item, even if the 
 599             // mouse is clicked below all the items, which is why we find the 
 600             // item ourselves below in this case. 
 601             n 
= SendMessage(GetHwnd(), LB_GETCARETINDEX
, 0, 0); 
 603         //else: n will be determined below from the mouse position 
 605     else if ( param 
== LBN_DBLCLK 
) 
 607         evtType 
= wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
; 
 611         // some event we're not interested in 
 615     // Find the item position if it was a mouse-generated selection event or a 
 616     // double click event (which is always generated using the mouse) 
 617     if ( n 
== wxNOT_FOUND 
) 
 619         const DWORD pos 
= ::GetMessagePos(); 
 620         const wxPoint 
pt(GET_X_LPARAM(pos
), GET_Y_LPARAM(pos
)); 
 621         n 
= HitTest(ScreenToClient(wxPoint(pt
))); 
 624     // We get events even when mouse is clicked outside of any valid item from 
 625     // Windows, just ignore them. 
 626     if ( n 
== wxNOT_FOUND 
) 
 629     if ( param 
== LBN_SELCHANGE 
) 
 631         if ( !DoChangeSingleSelection(n
) ) 
 635     // Do generate an event otherwise. 
 636     return SendEvent(evtType
, n
, true /* selection */); 
 640 wxListBox::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
) 
 642     // Remember whether there was a keyboard or mouse event before 
 643     // LBN_SELCHANGE: this allows us to correctly determine the item affected 
 644     // by it in MSWCommand() above in any case. 
 645     if ( WM_KEYFIRST 
<= nMsg 
&& nMsg 
<= WM_KEYLAST 
) 
 646         m_selectedByKeyboard 
= true; 
 647     else if ( WM_MOUSEFIRST 
<= nMsg 
&& nMsg 
<= WM_MOUSELAST 
) 
 648         m_selectedByKeyboard 
= false; 
 650     return wxListBoxBase::MSWWindowProc(nMsg
, wParam
, lParam
); 
 653 // ---------------------------------------------------------------------------- 
 654 // owner-drawn list boxes support 
 655 // ---------------------------------------------------------------------------- 
 657 #if wxUSE_OWNER_DRAWN 
 659 // misc overloaded methods 
 660 // ----------------------- 
 662 bool wxListBox::SetFont(const wxFont 
&font
) 
 664     if ( HasFlag(wxLB_OWNERDRAW
) ) 
 666         const unsigned count 
= m_aItems
.GetCount(); 
 667         for ( unsigned i 
= 0; i 
< count
; i
++ ) 
 668             m_aItems
[i
]->SetFont(font
); 
 671     wxListBoxBase::SetFont(font
); 
 676 bool wxListBox::GetItemRect(size_t n
, wxRect
& rect
) const 
 678     wxCHECK_MSG( IsValid(n
), false, 
 679                  wxT("invalid index in wxListBox::GetItemRect") ); 
 683     if ( ListBox_GetItemRect(GetHwnd(), n
, &rc
) != LB_ERR 
) 
 685         rect 
= wxRectFromRECT(rc
); 
 690         // couldn't retrieve rect: for example, item isn't visible 
 695 bool wxListBox::RefreshItem(size_t n
) 
 698     if ( !GetItemRect(n
, rect
) ) 
 702     wxCopyRectToRECT(rect
, rc
); 
 704     return ::InvalidateRect((HWND
)GetHWND(), &rc
, FALSE
) == TRUE
; 
 713     // space beneath/above each row in pixels 
 714     static const int LISTBOX_EXTRA_SPACE 
= 1; 
 716 } // anonymous namespace 
 718 // the height is the same for all items 
 719 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes 
 721 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA 
 722 //     message is not yet sent when we get here! 
 723 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT 
*item
) 
 725     // only owner-drawn control should receive this message 
 726     wxCHECK( HasFlag(wxLB_OWNERDRAW
), false ); 
 728     MEASUREITEMSTRUCT 
*pStruct 
= (MEASUREITEMSTRUCT 
*)item
; 
 731     HDC hdc 
= GetDC(NULL
); 
 733     HDC hdc 
= CreateIC(wxT("DISPLAY"), NULL
, NULL
, 0); 
 737         wxDCTemp 
dc((WXHDC
)hdc
); 
 738         dc
.SetFont(GetFont()); 
 740         pStruct
->itemHeight 
= dc
.GetCharHeight() + 2 * LISTBOX_EXTRA_SPACE
; 
 741         pStruct
->itemWidth  
= dc
.GetCharWidth(); 
 745     ReleaseDC(NULL
, hdc
); 
 753 // forward the message to the appropriate item 
 754 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT 
*item
) 
 756     // only owner-drawn control should receive this message 
 757     wxCHECK( HasFlag(wxLB_OWNERDRAW
), false ); 
 759     DRAWITEMSTRUCT 
*pStruct 
= (DRAWITEMSTRUCT 
*)item
; 
 761     // the item may be -1 for an empty listbox 
 762     if ( pStruct
->itemID 
== (UINT
)-1 ) 
 765     wxListBoxItem 
*pItem 
= (wxListBoxItem 
*)m_aItems
[pStruct
->itemID
]; 
 767     wxDCTemp 
dc((WXHDC
)pStruct
->hDC
); 
 769     return pItem
->OnDrawItem(dc
, wxRectFromRECT(pStruct
->rcItem
), 
 770                              (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
, 
 771                              (wxOwnerDrawn::wxODStatus
)(pStruct
->itemState 
| wxOwnerDrawn::wxODHidePrefix
)); 
 774 #endif // wxUSE_OWNER_DRAWN 
 776 #endif // wxUSE_LISTBOX