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 license
10 ///////////////////////////////////////////////////////////////////////////////
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"
40 #if defined(GetWindowStyle)
45 #include "wx/dynarray.h"
49 #include "wx/ownerdrw.h"
53 #ifdef __GNUWIN32_OLD__
54 #include "wx/msw/gnuwin32/extra.h"
59 #ifndef ListBox_SetItemData
60 #define ListBox_SetItemData(hwndCtl, index, data) \
61 ((int)(DWORD)SendMessage((hwndCtl), LB_SETITEMDATA, (WPARAM)(int)(index), (LPARAM)(data)))
63 #ifndef ListBox_GetHorizontalExtent
64 #define ListBox_GetHorizontalExtent(hwndCtl) \
65 ((int)(DWORD)SendMessage((hwndCtl), LB_GETHORIZONTALEXTENT, 0L, 0L))
67 #ifndef ListBox_GetSelCount
68 #define ListBox_GetSelCount(hwndCtl) \
69 ((int)(DWORD)SendMessage((hwndCtl), LB_GETSELCOUNT, 0L, 0L))
71 #ifndef ListBox_GetSelItems
72 #define ListBox_GetSelItems(hwndCtl, cItems, lpItems) \
73 ((int)(DWORD)SendMessage((hwndCtl), LB_GETSELITEMS, (WPARAM)(int)(cItems), (LPARAM)(int *)(lpItems)))
75 #ifndef ListBox_GetTextLen
76 #define ListBox_GetTextLen(hwndCtl, index) \
77 ((int)(DWORD)SendMessage((hwndCtl), LB_GETTEXTLEN, (WPARAM)(int)(index), 0L))
79 #ifndef ListBox_GetText
80 #define ListBox_GetText(hwndCtl, index, lpszBuffer) \
81 ((int)(DWORD)SendMessage((hwndCtl), LB_GETTEXT, (WPARAM)(int)(index), (LPARAM)(LPCTSTR)(lpszBuffer)))
85 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
87 // ============================================================================
88 // list box item declaration and implementation
89 // ============================================================================
93 class wxListBoxItem
: public wxOwnerDrawn
96 wxListBoxItem(const wxString
& str
= wxEmptyString
);
99 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
101 // no bitmaps/checkmarks
105 wxOwnerDrawn
*wxListBox::CreateLboxItem(size_t WXUNUSED(n
))
107 return new wxListBoxItem();
110 #endif //USE_OWNER_DRAWN
112 // ============================================================================
113 // list box control implementation
114 // ============================================================================
116 // ----------------------------------------------------------------------------
118 // ----------------------------------------------------------------------------
121 wxListBox::wxListBox()
127 bool wxListBox::Create(wxWindow
*parent
,
131 int n
, const wxString choices
[],
133 const wxValidator
& validator
,
134 const wxString
& name
)
142 SetValidator(validator
);
143 #endif // wxUSE_VALIDATORS
146 parent
->AddChild(this);
148 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
149 SetForegroundColour(parent
->GetForegroundColour());
151 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
157 m_windowStyle
= style
;
159 DWORD wstyle
= WS_VISIBLE
| WS_VSCROLL
| WS_TABSTOP
|
160 LBS_NOTIFY
| LBS_HASSTRINGS
/* | WS_CLIPSIBLINGS */;
162 wxASSERT_MSG( !(style
& wxLB_MULTIPLE
) || !(style
& wxLB_EXTENDED
),
163 _T("only one of listbox selection modes can be specified") );
165 if ( (m_windowStyle
& wxBORDER_MASK
) == wxBORDER_DEFAULT
)
166 m_windowStyle
|= wxBORDER_SUNKEN
;
168 if ( m_windowStyle
& wxCLIP_SIBLINGS
)
169 wstyle
|= WS_CLIPSIBLINGS
;
171 if (m_windowStyle
& wxLB_MULTIPLE
)
172 wstyle
|= LBS_MULTIPLESEL
;
173 else if (m_windowStyle
& wxLB_EXTENDED
)
174 wstyle
|= LBS_EXTENDEDSEL
;
176 if (m_windowStyle
& wxLB_ALWAYS_SB
)
177 wstyle
|= LBS_DISABLENOSCROLL
;
178 if (m_windowStyle
& wxLB_HSCROLL
)
179 wstyle
|= WS_HSCROLL
;
180 if (m_windowStyle
& wxLB_SORT
)
183 #if wxUSE_OWNER_DRAWN
184 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
185 // we don't support LBS_OWNERDRAWVARIABLE yet
186 wstyle
|= LBS_OWNERDRAWFIXED
;
190 // Without this style, you get unexpected heights, so e.g. constraint layout
191 // doesn't work properly
192 wstyle
|= LBS_NOINTEGRALHEIGHT
;
195 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
);
197 // Even with extended styles, need to combine with WS_BORDER for them to
199 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
204 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, wxT("LISTBOX"), NULL
,
207 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
208 wxGetInstance(), NULL
);
210 wxCHECK_MSG( m_hWnd
, FALSE
, wxT("Failed to create listbox") );
215 Ctl3dSubclassCtl(GetHwnd());
220 // Subclass again to catch messages
224 for (ui
= 0; ui
< (size_t)n
; ui
++) {
228 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
229 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
231 SetFont(parent
->GetFont());
233 SetSize(x
, y
, width
, height
);
238 wxListBox::~wxListBox()
243 void wxListBox::SetupColours()
245 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
246 SetForegroundColour(GetParent()->GetForegroundColour());
249 // ----------------------------------------------------------------------------
250 // implementation of wxListBoxBase methods
251 // ----------------------------------------------------------------------------
253 void wxListBox::DoSetFirstItem(int N
)
255 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
256 wxT("invalid index in wxListBox::SetFirstItem") );
258 SendMessage(GetHwnd(), LB_SETTOPINDEX
, (WPARAM
)N
, (LPARAM
)0);
261 void wxListBox::Delete(int N
)
263 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
264 wxT("invalid index in wxListBox::Delete") );
266 // for owner drawn objects, the data is used for storing wxOwnerDrawn
267 // pointers and we shouldn't touch it
268 #if !wxUSE_OWNER_DRAWN
269 if ( !(m_windowStyle
& wxLB_OWNERDRAW
) )
270 #endif // !wxUSE_OWNER_DRAWN
271 if ( HasClientObjectData() )
273 delete GetClientObject(N
);
276 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
279 SetHorizontalExtent(wxEmptyString
);
282 int wxListBox::DoAppend(const wxString
& item
)
284 int index
= ListBox_AddString(GetHwnd(), item
);
287 #if wxUSE_OWNER_DRAWN
288 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
289 wxOwnerDrawn
*pNewItem
= CreateLboxItem(index
); // dummy argument
290 pNewItem
->SetName(item
);
291 m_aItems
.Insert(pNewItem
, index
);
292 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
293 pNewItem
->SetFont(GetFont());
295 #endif // wxUSE_OWNER_DRAWN
297 SetHorizontalExtent(item
);
302 void wxListBox::DoSetItems(const wxArrayString
& choices
, void** clientData
)
304 // avoid flicker - but don't need to do this for a hidden listbox
305 bool hideAndShow
= IsShown();
308 ShowWindow(GetHwnd(), SW_HIDE
);
311 ListBox_ResetContent(GetHwnd());
313 m_noItems
= choices
.GetCount();
315 for (i
= 0; i
< m_noItems
; i
++)
317 ListBox_AddString(GetHwnd(), choices
[i
]);
320 SetClientData(i
, clientData
[i
]);
324 #if wxUSE_OWNER_DRAWN
325 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
326 // first delete old items
327 WX_CLEAR_ARRAY(m_aItems
);
329 // then create new ones
330 for ( size_t ui
= 0; ui
< (size_t)m_noItems
; ui
++ ) {
331 wxOwnerDrawn
*pNewItem
= CreateLboxItem(ui
);
332 pNewItem
->SetName(choices
[ui
]);
333 m_aItems
.Add(pNewItem
);
334 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
337 #endif // wxUSE_OWNER_DRAWN
339 SetHorizontalExtent();
343 // show the listbox back if we hid it
344 ShowWindow(GetHwnd(), SW_SHOW
);
348 int wxListBox::FindString(const wxString
& s
) const
350 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
357 void wxListBox::Clear()
361 ListBox_ResetContent(GetHwnd());
364 SetHorizontalExtent();
367 void wxListBox::Free()
369 #if wxUSE_OWNER_DRAWN
370 if ( m_windowStyle
& wxLB_OWNERDRAW
)
372 WX_CLEAR_ARRAY(m_aItems
);
375 #endif // wxUSE_OWNER_DRAWN
376 if ( HasClientObjectData() )
378 for ( size_t n
= 0; n
< (size_t)m_noItems
; n
++ )
380 delete GetClientObject(n
);
385 void wxListBox::SetSelection(int N
, bool select
)
387 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
388 wxT("invalid index in wxListBox::SetSelection") );
390 if ( HasMultipleSelection() )
392 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
396 SendMessage(GetHwnd(), LB_SETCURSEL
, select
? N
: -1, 0);
400 bool wxListBox::IsSelected(int N
) const
402 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
403 wxT("invalid index in wxListBox::Selected") );
405 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
408 wxClientData
* wxListBox::DoGetItemClientObject(int n
) const
410 return (wxClientData
*)DoGetItemClientData(n
);
413 void *wxListBox::DoGetItemClientData(int n
) const
415 wxCHECK_MSG( n
>= 0 && n
< m_noItems
, NULL
,
416 wxT("invalid index in wxListBox::GetClientData") );
418 return (void *)SendMessage(GetHwnd(), LB_GETITEMDATA
, n
, 0);
421 void wxListBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
423 DoSetItemClientData(n
, clientData
);
426 void wxListBox::DoSetItemClientData(int n
, void *clientData
)
428 wxCHECK_RET( n
>= 0 && n
< m_noItems
,
429 wxT("invalid index in wxListBox::SetClientData") );
431 #if wxUSE_OWNER_DRAWN
432 if ( m_windowStyle
& wxLB_OWNERDRAW
)
434 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
435 // in OnMeasure/OnDraw.
436 wxFAIL_MSG(wxT("Can't use client data with owner-drawn listboxes"));
438 #endif // wxUSE_OWNER_DRAWN
440 if ( ListBox_SetItemData(GetHwnd(), n
, clientData
) == LB_ERR
)
441 wxLogDebug(wxT("LB_SETITEMDATA failed"));
444 // Return number of selections and an array of selected integers
445 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
449 if ( HasMultipleSelection() )
451 int countSel
= ListBox_GetSelCount(GetHwnd());
452 if ( countSel
== LB_ERR
)
454 wxLogDebug(_T("ListBox_GetSelCount failed"));
456 else if ( countSel
!= 0 )
458 int *selections
= new int[countSel
];
460 if ( ListBox_GetSelItems(GetHwnd(),
461 countSel
, selections
) == LB_ERR
)
463 wxLogDebug(wxT("ListBox_GetSelItems failed"));
468 aSelections
.Alloc(countSel
);
469 for ( int n
= 0; n
< countSel
; n
++ )
470 aSelections
.Add(selections
[n
]);
473 delete [] selections
;
478 else // single-selection listbox
480 if (ListBox_GetCurSel(GetHwnd()) > -1)
481 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
483 return aSelections
.Count();
487 // Get single selection, for single choice list items
488 int wxListBox::GetSelection() const
490 wxCHECK_MSG( !HasMultipleSelection(),
492 wxT("GetSelection() can't be used with multiple-selection listboxes, use GetSelections() instead.") );
494 return ListBox_GetCurSel(GetHwnd());
497 // Find string for position
498 wxString
wxListBox::GetString(int N
) const
500 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, wxEmptyString
,
501 wxT("invalid index in wxListBox::GetClientData") );
503 int len
= ListBox_GetTextLen(GetHwnd(), N
);
505 // +1 for terminating NUL
507 ListBox_GetText(GetHwnd(), N
, result
.GetWriteBuf(len
+ 1));
508 result
.UngetWriteBuf();
514 wxListBox::DoInsertItems(const wxArrayString
& items
, int pos
)
516 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
517 wxT("invalid index in wxListBox::InsertItems") );
519 int nItems
= items
.GetCount();
520 for ( int i
= 0; i
< nItems
; i
++ )
522 int idx
= ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
524 #if wxUSE_OWNER_DRAWN
525 if ( m_windowStyle
& wxLB_OWNERDRAW
)
527 wxOwnerDrawn
*pNewItem
= CreateLboxItem(idx
);
528 pNewItem
->SetName(items
[i
]);
529 pNewItem
->SetFont(GetFont());
530 m_aItems
.Insert(pNewItem
, idx
);
532 ListBox_SetItemData(GetHwnd(), idx
, pNewItem
);
534 #endif // wxUSE_OWNER_DRAWN
539 SetHorizontalExtent();
542 void wxListBox::SetString(int N
, const wxString
& s
)
544 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
545 wxT("invalid index in wxListBox::SetString") );
547 // remember the state of the item
548 bool wasSelected
= IsSelected(N
);
550 void *oldData
= NULL
;
551 wxClientData
*oldObjData
= NULL
;
552 if ( m_clientDataItemsType
== wxClientData_Void
)
553 oldData
= GetClientData(N
);
554 else if ( m_clientDataItemsType
== wxClientData_Object
)
555 oldObjData
= GetClientObject(N
);
557 // delete and recreate it
558 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
561 if ( N
== m_noItems
- 1 )
564 ListBox_InsertString(GetHwnd(), newN
, s
);
566 // restore the client data
568 SetClientData(N
, oldData
);
569 else if ( oldObjData
)
570 SetClientObject(N
, oldObjData
);
572 // we may have lost the selection
576 #if wxUSE_OWNER_DRAWN
577 if ( m_windowStyle
& wxLB_OWNERDRAW
)
579 // update item's text
580 m_aItems
[N
]->SetName(s
);
582 // reassign the item's data
583 ListBox_SetItemData(GetHwnd(), N
, m_aItems
[N
]);
585 #endif //USE_OWNER_DRAWN
588 int wxListBox::GetCount() const
593 // ----------------------------------------------------------------------------
595 // ----------------------------------------------------------------------------
597 // Windows-specific code to set the horizontal extent of the listbox, if
598 // necessary. If s is non-NULL, it's used to calculate the horizontal extent.
599 // Otherwise, all strings are used.
600 void wxListBox::SetHorizontalExtent(const wxString
& s
)
602 // Only necessary if we want a horizontal scrollbar
603 if (!(m_windowStyle
& wxHSCROLL
))
605 TEXTMETRIC lpTextMetric
;
609 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
610 HDC dc
= GetWindowDC(GetHwnd());
612 if (GetFont().Ok() && GetFont().GetResourceHandle())
613 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
615 GetTextMetrics(dc
, &lpTextMetric
);
617 ::GetTextExtentPoint(dc
, (LPTSTR
) (const wxChar
*)s
, s
.Length(), &extentXY
);
618 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
621 ::SelectObject(dc
, oldFont
);
623 ReleaseDC(GetHwnd(), dc
);
624 if (extentX
> existingExtent
)
625 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
629 int largestExtent
= 0;
630 HDC dc
= GetWindowDC(GetHwnd());
632 if (GetFont().Ok() && GetFont().GetResourceHandle())
633 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
635 GetTextMetrics(dc
, &lpTextMetric
);
637 for (i
= 0; i
< m_noItems
; i
++)
639 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LONG
)wxBuffer
);
642 ::GetTextExtentPoint(dc
, (LPTSTR
)wxBuffer
, len
, &extentXY
);
643 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
644 if (extentX
> largestExtent
)
645 largestExtent
= extentX
;
648 ::SelectObject(dc
, oldFont
);
650 ReleaseDC(GetHwnd(), dc
);
651 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
655 wxSize
wxListBox::DoGetBestSize() const
657 // find the widest string
660 for ( int i
= 0; i
< m_noItems
; i
++ )
662 wxString
str(GetString(i
));
663 GetTextExtent(str
, &wLine
, NULL
);
664 if ( wLine
> wListbox
)
668 // give it some reasonable default value if there are no strings in the
673 // the listbox should be slightly larger than the widest string
675 wxGetCharSize(GetHWND(), &cx
, &cy
, &GetFont());
679 // don't make the listbox too tall (limit height to 10 items) but don't
680 // make it too small neither
681 int hListbox
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
)*
682 wxMin(wxMax(m_noItems
, 3), 10);
684 return wxSize(wListbox
, hListbox
);
687 // ----------------------------------------------------------------------------
689 // ----------------------------------------------------------------------------
691 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
694 if ( param
== LBN_SELCHANGE
)
696 evtType
= wxEVT_COMMAND_LISTBOX_SELECTED
;
698 else if ( param
== LBN_DBLCLK
)
700 evtType
= wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
;
704 // some event we're not interested in
708 wxCommandEvent
event(evtType
, m_windowId
);
709 event
.SetEventObject( this );
711 wxArrayInt aSelections
;
712 int n
, count
= GetSelections(aSelections
);
716 if ( HasClientObjectData() )
717 event
.SetClientObject( GetClientObject(n
) );
718 else if ( HasClientUntypedData() )
719 event
.SetClientData( GetClientData(n
) );
720 event
.SetString( GetString(n
) );
727 event
.m_commandInt
= n
;
729 return GetEventHandler()->ProcessEvent(event
);
732 // ----------------------------------------------------------------------------
733 // wxCheckListBox support
734 // ----------------------------------------------------------------------------
736 #if wxUSE_OWNER_DRAWN
741 // space beneath/above each row in pixels
742 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
743 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
745 // the height is the same for all items
746 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
748 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
749 // message is not yet sent when we get here!
750 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
752 // only owner-drawn control should receive this message
753 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
755 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
757 HDC hdc
= CreateIC(wxT("DISPLAY"), NULL
, NULL
, 0);
760 dc
.SetHDC((WXHDC
)hdc
);
761 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_ANSI_VAR_FONT
));
763 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
764 pStruct
->itemWidth
= dc
.GetCharWidth();
773 // forward the message to the appropriate item
774 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
776 // only owner-drawn control should receive this message
777 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
779 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
780 UINT itemID
= pStruct
->itemID
;
782 // the item may be -1 for an empty listbox
783 if ( itemID
== (UINT
)-1 )
786 long data
= ListBox_GetItemData(GetHwnd(), pStruct
->itemID
);
788 wxCHECK( data
&& (data
!= LB_ERR
), FALSE
);
790 wxListBoxItem
*pItem
= (wxListBoxItem
*)data
;
792 wxDCTemp
dc((WXHDC
)pStruct
->hDC
);
793 wxRect
rect(wxPoint(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
),
794 wxPoint(pStruct
->rcItem
.right
, pStruct
->rcItem
.bottom
));
796 return pItem
->OnDrawItem(dc
, rect
,
797 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
798 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);
801 #endif // wxUSE_OWNER_DRAWN
803 #endif // wxUSE_LISTBOX