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 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
13 #pragma implementation "listbox.h"
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
26 #include "wx/listbox.h"
27 #include "wx/settings.h"
34 #include "wx/window.h"
35 #include "wx/msw/private.h"
39 #include "wx/dynarray.h"
43 #include "wx/ownerdrw.h"
46 #ifdef __GNUWIN32_OLD__
47 #include "wx/msw/gnuwin32/extra.h"
50 #if wxUSE_EXTENDED_RTTI
51 IMPLEMENT_DYNAMIC_CLASS_XTI(wxListBox
, wxControl
,"wx/listbox.h")
53 WX_BEGIN_PROPERTIES_TABLE(wxListBox
)
55 WX_PROPERTY( Font
, wxFont
, SetFont
, GetFont
, , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
56 WX_PROPERTY_COLLECTION( Choices
, wxArrayString
, wxString
, AppendString
, GetStrings
, 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
57 WX_PROPERTY( Selection
,int, SetSelection
, GetSelection
,, 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
58 WX_END_PROPERTIES_TABLE()
60 WX_BEGIN_HANDLERS_TABLE(wxListBox
)
61 WX_END_HANDLERS_TABLE()
63 WX_CONSTRUCTOR_4( wxListBox
, wxWindow
* , Parent
, wxWindowID
, Id
, wxPoint
, Position
, wxSize
, Size
)
65 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
75 // ============================================================================
76 // list box item declaration and implementation
77 // ============================================================================
81 class wxListBoxItem
: public wxOwnerDrawn
84 wxListBoxItem(const wxString
& str
= wxEmptyString
);
87 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
89 // no bitmaps/checkmarks
93 wxOwnerDrawn
*wxListBox::CreateLboxItem(size_t WXUNUSED(n
))
95 return new wxListBoxItem();
98 #endif //USE_OWNER_DRAWN
100 // ============================================================================
101 // list box control implementation
102 // ============================================================================
104 // ----------------------------------------------------------------------------
106 // ----------------------------------------------------------------------------
109 wxListBox::wxListBox()
115 bool wxListBox::Create(wxWindow
*parent
,
119 int n
, const wxString choices
[],
121 const wxValidator
& validator
,
122 const wxString
& name
)
130 SetValidator(validator
);
131 #endif // wxUSE_VALIDATORS
134 parent
->AddChild(this);
136 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
137 SetForegroundColour(parent
->GetForegroundColour());
139 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
145 m_windowStyle
= style
;
147 DWORD wstyle
= WS_VISIBLE
| WS_CHILD
| WS_VSCROLL
| WS_TABSTOP
|
148 LBS_NOTIFY
| LBS_HASSTRINGS
;
150 wxASSERT_MSG( !(style
& wxLB_MULTIPLE
) || !(style
& wxLB_EXTENDED
),
151 _T("only one of listbox selection modes can be specified") );
153 if ( (m_windowStyle
& wxBORDER_MASK
) == wxBORDER_DEFAULT
)
154 m_windowStyle
|= wxBORDER_SUNKEN
;
156 if ( m_windowStyle
& wxCLIP_SIBLINGS
)
157 wstyle
|= WS_CLIPSIBLINGS
;
159 if (m_windowStyle
& wxLB_MULTIPLE
)
160 wstyle
|= LBS_MULTIPLESEL
;
161 else if (m_windowStyle
& wxLB_EXTENDED
)
162 wstyle
|= LBS_EXTENDEDSEL
;
164 if (m_windowStyle
& wxLB_ALWAYS_SB
)
165 wstyle
|= LBS_DISABLENOSCROLL
;
166 if (m_windowStyle
& wxLB_HSCROLL
)
167 wstyle
|= WS_HSCROLL
;
168 if (m_windowStyle
& wxLB_SORT
)
171 #if wxUSE_OWNER_DRAWN && !defined(__WXWINCE__)
172 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
173 // we don't support LBS_OWNERDRAWVARIABLE yet
174 wstyle
|= LBS_OWNERDRAWFIXED
;
178 // Without this style, you get unexpected heights, so e.g. constraint layout
179 // doesn't work properly
180 wstyle
|= LBS_NOINTEGRALHEIGHT
;
183 (void) MSWGetStyle(m_windowStyle
, & exStyle
) ;
185 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, wxT("LISTBOX"), NULL
,
188 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
189 wxGetInstance(), NULL
);
191 wxCHECK_MSG( m_hWnd
, FALSE
, wxT("Failed to create listbox") );
193 // Subclass again to catch messages
197 for (ui
= 0; ui
< (size_t)n
; ui
++) {
201 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
202 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
204 SetFont(parent
->GetFont());
206 SetSize(x
, y
, width
, height
);
211 wxListBox::~wxListBox()
216 void wxListBox::SetupColours()
218 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
219 SetForegroundColour(GetParent()->GetForegroundColour());
222 // ----------------------------------------------------------------------------
223 // implementation of wxListBoxBase methods
224 // ----------------------------------------------------------------------------
226 void wxListBox::DoSetFirstItem(int N
)
228 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
229 wxT("invalid index in wxListBox::SetFirstItem") );
231 SendMessage(GetHwnd(), LB_SETTOPINDEX
, (WPARAM
)N
, (LPARAM
)0);
234 void wxListBox::Delete(int N
)
236 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
237 wxT("invalid index in wxListBox::Delete") );
239 // for owner drawn objects, the data is used for storing wxOwnerDrawn
240 // pointers and we shouldn't touch it
241 #if !wxUSE_OWNER_DRAWN
242 if ( !(m_windowStyle
& wxLB_OWNERDRAW
) )
243 #endif // !wxUSE_OWNER_DRAWN
244 if ( HasClientObjectData() )
246 delete GetClientObject(N
);
249 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
252 SetHorizontalExtent(wxEmptyString
);
255 int wxListBox::DoAppend(const wxString
& item
)
257 int index
= ListBox_AddString(GetHwnd(), item
);
260 #if wxUSE_OWNER_DRAWN
261 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
262 wxOwnerDrawn
*pNewItem
= CreateLboxItem(index
); // dummy argument
263 pNewItem
->SetName(item
);
264 m_aItems
.Insert(pNewItem
, index
);
265 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
266 pNewItem
->SetFont(GetFont());
268 #endif // wxUSE_OWNER_DRAWN
270 SetHorizontalExtent(item
);
275 void wxListBox::DoSetItems(const wxArrayString
& choices
, void** clientData
)
277 // avoid flicker - but don't need to do this for a hidden listbox
278 bool hideAndShow
= IsShown();
281 ShowWindow(GetHwnd(), SW_HIDE
);
284 ListBox_ResetContent(GetHwnd());
286 m_noItems
= choices
.GetCount();
288 for (i
= 0; i
< m_noItems
; i
++)
290 ListBox_AddString(GetHwnd(), choices
[i
]);
293 SetClientData(i
, clientData
[i
]);
297 #if wxUSE_OWNER_DRAWN
298 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
299 // first delete old items
300 WX_CLEAR_ARRAY(m_aItems
);
302 // then create new ones
303 for ( size_t ui
= 0; ui
< (size_t)m_noItems
; ui
++ ) {
304 wxOwnerDrawn
*pNewItem
= CreateLboxItem(ui
);
305 pNewItem
->SetName(choices
[ui
]);
306 m_aItems
.Add(pNewItem
);
307 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
310 #endif // wxUSE_OWNER_DRAWN
312 SetHorizontalExtent();
316 // show the listbox back if we hid it
317 ShowWindow(GetHwnd(), SW_SHOW
);
321 int wxListBox::FindString(const wxString
& s
) const
323 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
330 void wxListBox::Clear()
334 ListBox_ResetContent(GetHwnd());
337 SetHorizontalExtent();
340 void wxListBox::Free()
342 #if wxUSE_OWNER_DRAWN
343 if ( m_windowStyle
& wxLB_OWNERDRAW
)
345 WX_CLEAR_ARRAY(m_aItems
);
348 #endif // wxUSE_OWNER_DRAWN
349 if ( HasClientObjectData() )
351 for ( size_t n
= 0; n
< (size_t)m_noItems
; n
++ )
353 delete GetClientObject(n
);
358 void wxListBox::SetSelection(int N
, bool select
)
360 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
361 wxT("invalid index in wxListBox::SetSelection") );
363 if ( HasMultipleSelection() )
365 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
369 SendMessage(GetHwnd(), LB_SETCURSEL
, select
? N
: -1, 0);
373 bool wxListBox::IsSelected(int N
) const
375 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
376 wxT("invalid index in wxListBox::Selected") );
378 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
381 wxClientData
* wxListBox::DoGetItemClientObject(int n
) const
383 return (wxClientData
*)DoGetItemClientData(n
);
386 void *wxListBox::DoGetItemClientData(int n
) const
388 wxCHECK_MSG( n
>= 0 && n
< m_noItems
, NULL
,
389 wxT("invalid index in wxListBox::GetClientData") );
391 return (void *)SendMessage(GetHwnd(), LB_GETITEMDATA
, n
, 0);
394 void wxListBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
396 DoSetItemClientData(n
, clientData
);
399 void wxListBox::DoSetItemClientData(int n
, void *clientData
)
401 wxCHECK_RET( n
>= 0 && n
< m_noItems
,
402 wxT("invalid index in wxListBox::SetClientData") );
404 #if wxUSE_OWNER_DRAWN
405 if ( m_windowStyle
& wxLB_OWNERDRAW
)
407 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
408 // in OnMeasure/OnDraw.
409 wxFAIL_MSG(wxT("Can't use client data with owner-drawn listboxes"));
411 #endif // wxUSE_OWNER_DRAWN
413 if ( ListBox_SetItemData(GetHwnd(), n
, clientData
) == LB_ERR
)
414 wxLogDebug(wxT("LB_SETITEMDATA failed"));
417 // Return number of selections and an array of selected integers
418 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
422 if ( HasMultipleSelection() )
424 int countSel
= ListBox_GetSelCount(GetHwnd());
425 if ( countSel
== LB_ERR
)
427 wxLogDebug(_T("ListBox_GetSelCount failed"));
429 else if ( countSel
!= 0 )
431 int *selections
= new int[countSel
];
433 if ( ListBox_GetSelItems(GetHwnd(),
434 countSel
, selections
) == LB_ERR
)
436 wxLogDebug(wxT("ListBox_GetSelItems failed"));
441 aSelections
.Alloc(countSel
);
442 for ( int n
= 0; n
< countSel
; n
++ )
443 aSelections
.Add(selections
[n
]);
446 delete [] selections
;
451 else // single-selection listbox
453 if (ListBox_GetCurSel(GetHwnd()) > -1)
454 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
456 return aSelections
.Count();
460 // Get single selection, for single choice list items
461 int wxListBox::GetSelection() const
463 wxCHECK_MSG( !HasMultipleSelection(),
465 wxT("GetSelection() can't be used with multiple-selection listboxes, use GetSelections() instead.") );
467 return ListBox_GetCurSel(GetHwnd());
470 // Find string for position
471 wxString
wxListBox::GetString(int N
) const
473 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, wxEmptyString
,
474 wxT("invalid index in wxListBox::GetClientData") );
476 int len
= ListBox_GetTextLen(GetHwnd(), N
);
478 // +1 for terminating NUL
480 ListBox_GetText(GetHwnd(), N
, wxStringBuffer(result
, len
+ 1));
486 wxListBox::DoInsertItems(const wxArrayString
& items
, int pos
)
488 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
489 wxT("invalid index in wxListBox::InsertItems") );
491 int nItems
= items
.GetCount();
492 for ( int i
= 0; i
< nItems
; i
++ )
494 int idx
= ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
496 #if wxUSE_OWNER_DRAWN
497 if ( m_windowStyle
& wxLB_OWNERDRAW
)
499 wxOwnerDrawn
*pNewItem
= CreateLboxItem(idx
);
500 pNewItem
->SetName(items
[i
]);
501 pNewItem
->SetFont(GetFont());
502 m_aItems
.Insert(pNewItem
, idx
);
504 ListBox_SetItemData(GetHwnd(), idx
, pNewItem
);
506 #endif // wxUSE_OWNER_DRAWN
511 SetHorizontalExtent();
514 void wxListBox::SetString(int N
, const wxString
& s
)
516 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
517 wxT("invalid index in wxListBox::SetString") );
519 // remember the state of the item
520 bool wasSelected
= IsSelected(N
);
522 void *oldData
= NULL
;
523 wxClientData
*oldObjData
= NULL
;
524 if ( m_clientDataItemsType
== wxClientData_Void
)
525 oldData
= GetClientData(N
);
526 else if ( m_clientDataItemsType
== wxClientData_Object
)
527 oldObjData
= GetClientObject(N
);
529 // delete and recreate it
530 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
533 if ( N
== m_noItems
- 1 )
536 ListBox_InsertString(GetHwnd(), newN
, s
);
538 // restore the client data
540 SetClientData(N
, oldData
);
541 else if ( oldObjData
)
542 SetClientObject(N
, oldObjData
);
544 // we may have lost the selection
548 #if wxUSE_OWNER_DRAWN
549 if ( m_windowStyle
& wxLB_OWNERDRAW
)
551 // update item's text
552 m_aItems
[N
]->SetName(s
);
554 // reassign the item's data
555 ListBox_SetItemData(GetHwnd(), N
, m_aItems
[N
]);
557 #endif //USE_OWNER_DRAWN
560 int wxListBox::GetCount() const
565 // ----------------------------------------------------------------------------
567 // ----------------------------------------------------------------------------
569 // Windows-specific code to set the horizontal extent of the listbox, if
570 // necessary. If s is non-NULL, it's used to calculate the horizontal extent.
571 // Otherwise, all strings are used.
572 void wxListBox::SetHorizontalExtent(const wxString
& s
)
574 // Only necessary if we want a horizontal scrollbar
575 if (!(m_windowStyle
& wxHSCROLL
))
577 TEXTMETRIC lpTextMetric
;
581 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
582 HDC dc
= GetWindowDC(GetHwnd());
584 if (GetFont().Ok() && GetFont().GetResourceHandle())
585 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
587 GetTextMetrics(dc
, &lpTextMetric
);
589 ::GetTextExtentPoint(dc
, (LPTSTR
) (const wxChar
*)s
, s
.Length(), &extentXY
);
590 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
593 ::SelectObject(dc
, oldFont
);
595 ReleaseDC(GetHwnd(), dc
);
596 if (extentX
> existingExtent
)
597 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
601 int largestExtent
= 0;
602 HDC dc
= GetWindowDC(GetHwnd());
604 if (GetFont().Ok() && GetFont().GetResourceHandle())
605 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
607 GetTextMetrics(dc
, &lpTextMetric
);
609 // FIXME: buffer overflow!!
611 for (int i
= 0; i
< m_noItems
; i
++)
613 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LPARAM
)buf
);
616 ::GetTextExtentPoint(dc
, buf
, len
, &extentXY
);
617 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
618 if (extentX
> largestExtent
)
619 largestExtent
= extentX
;
622 ::SelectObject(dc
, oldFont
);
624 ReleaseDC(GetHwnd(), dc
);
625 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
629 wxSize
wxListBox::DoGetBestSize() const
631 // find the widest string
634 for ( int i
= 0; i
< m_noItems
; i
++ )
636 wxString
str(GetString(i
));
637 GetTextExtent(str
, &wLine
, NULL
);
638 if ( wLine
> wListbox
)
642 // give it some reasonable default value if there are no strings in the
647 // the listbox should be slightly larger than the widest string
649 wxGetCharSize(GetHWND(), &cx
, &cy
, &GetFont());
653 // don't make the listbox too tall (limit height to 10 items) but don't
654 // make it too small neither
655 int hListbox
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
)*
656 wxMin(wxMax(m_noItems
, 3), 10);
658 return wxSize(wListbox
, hListbox
);
661 // ----------------------------------------------------------------------------
663 // ----------------------------------------------------------------------------
665 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
668 if ( param
== LBN_SELCHANGE
)
670 evtType
= wxEVT_COMMAND_LISTBOX_SELECTED
;
672 else if ( param
== LBN_DBLCLK
)
674 evtType
= wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
;
678 // some event we're not interested in
682 wxCommandEvent
event(evtType
, m_windowId
);
683 event
.SetEventObject( this );
685 // retrieve the affected item
686 int n
= SendMessage(GetHwnd(), LB_GETCARETINDEX
, 0, 0);
689 if ( HasClientObjectData() )
690 event
.SetClientObject( GetClientObject(n
) );
691 else if ( HasClientUntypedData() )
692 event
.SetClientData( GetClientData(n
) );
694 event
.SetString( GetString(n
) );
695 event
.SetExtraLong( HasMultipleSelection() ? IsSelected(n
) : TRUE
);
698 event
.m_commandInt
= n
;
700 return GetEventHandler()->ProcessEvent(event
);
703 // ----------------------------------------------------------------------------
704 // wxCheckListBox support
705 // ----------------------------------------------------------------------------
707 #if wxUSE_OWNER_DRAWN
712 // space beneath/above each row in pixels
713 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
714 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
716 // the height is the same for all items
717 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
719 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
720 // message is not yet sent when we get here!
721 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
723 // only owner-drawn control should receive this message
724 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
726 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
729 HDC hdc
= GetDC(NULL
);
731 HDC hdc
= CreateIC(wxT("DISPLAY"), NULL
, NULL
, 0);
735 dc
.SetHDC((WXHDC
)hdc
);
736 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_ANSI_VAR_FONT
));
738 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
739 pStruct
->itemWidth
= dc
.GetCharWidth();
748 // forward the message to the appropriate item
749 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
751 // only owner-drawn control should receive this message
752 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
754 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
755 UINT itemID
= pStruct
->itemID
;
757 // the item may be -1 for an empty listbox
758 if ( itemID
== (UINT
)-1 )
761 long data
= ListBox_GetItemData(GetHwnd(), pStruct
->itemID
);
763 wxCHECK( data
&& (data
!= LB_ERR
), FALSE
);
765 wxListBoxItem
*pItem
= (wxListBoxItem
*)data
;
767 wxDCTemp
dc((WXHDC
)pStruct
->hDC
);
768 wxRect
rect(wxPoint(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
),
769 wxPoint(pStruct
->rcItem
.right
, pStruct
->rcItem
.bottom
));
771 return pItem
->OnDrawItem(dc
, rect
,
772 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
773 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);
776 #endif // wxUSE_OWNER_DRAWN
778 #endif // wxUSE_LISTBOX