1 ///////////////////////////////////////////////////////////////////////////////
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"
23 #include "wx/window.h"
24 #include "wx/msw/private.h"
27 #include "wx/listbox.h"
28 #include "wx/settings.h"
38 #if defined(GetWindowStyle)
43 #include "wx/dynarray.h"
47 #include "wx/ownerdrw.h"
51 #ifdef __GNUWIN32_OLD__
52 #include "wx/msw/gnuwin32/extra.h"
57 #ifndef ListBox_SetItemData
58 #define ListBox_SetItemData(hwndCtl, index, data) \
59 ((int)(DWORD)SendMessage((hwndCtl), LB_SETITEMDATA, (WPARAM)(int)(index), (LPARAM)(data)))
61 #ifndef ListBox_GetHorizontalExtent
62 #define ListBox_GetHorizontalExtent(hwndCtl) \
63 ((int)(DWORD)SendMessage((hwndCtl), LB_GETHORIZONTALEXTENT, 0L, 0L))
65 #ifndef ListBox_GetSelCount
66 #define ListBox_GetSelCount(hwndCtl) \
67 ((int)(DWORD)SendMessage((hwndCtl), LB_GETSELCOUNT, 0L, 0L))
69 #ifndef ListBox_GetSelItems
70 #define ListBox_GetSelItems(hwndCtl, cItems, lpItems) \
71 ((int)(DWORD)SendMessage((hwndCtl), LB_GETSELITEMS, (WPARAM)(int)(cItems), (LPARAM)(int *)(lpItems)))
73 #ifndef ListBox_GetTextLen
74 #define ListBox_GetTextLen(hwndCtl, index) \
75 ((int)(DWORD)SendMessage((hwndCtl), LB_GETTEXTLEN, (WPARAM)(int)(index), 0L))
77 #ifndef ListBox_GetText
78 #define ListBox_GetText(hwndCtl, index, lpszBuffer) \
79 ((int)(DWORD)SendMessage((hwndCtl), LB_GETTEXT, (WPARAM)(int)(index), (LPARAM)(LPCTSTR)(lpszBuffer)))
83 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
85 // ============================================================================
86 // list box item declaration and implementation
87 // ============================================================================
91 class wxListBoxItem
: public wxOwnerDrawn
94 wxListBoxItem(const wxString
& str
= "");
97 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
99 // no bitmaps/checkmarks
103 wxOwnerDrawn
*wxListBox::CreateItem(size_t WXUNUSED(n
))
105 return new wxListBoxItem();
108 #endif //USE_OWNER_DRAWN
110 // ============================================================================
111 // list box control implementation
112 // ============================================================================
114 // ----------------------------------------------------------------------------
116 // ----------------------------------------------------------------------------
119 wxListBox::wxListBox()
125 bool wxListBox::Create(wxWindow
*parent
,
129 int n
, const wxString choices
[],
131 const wxValidator
& validator
,
132 const wxString
& name
)
140 SetValidator(validator
);
141 #endif // wxUSE_VALIDATORS
144 parent
->AddChild(this);
146 wxSystemSettings settings
;
147 SetBackgroundColour(settings
.GetSystemColour(wxSYS_COLOUR_WINDOW
));
148 SetForegroundColour(parent
->GetForegroundColour());
150 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
156 m_windowStyle
= style
;
158 DWORD wstyle
= WS_VISIBLE
| WS_VSCROLL
| WS_TABSTOP
|
159 LBS_NOTIFY
| LBS_HASSTRINGS
/* | WS_CLIPSIBLINGS */;
161 wxASSERT_MSG( !(style
& wxLB_MULTIPLE
) || !(style
& wxLB_EXTENDED
),
162 _T("only one of listbox selection modes can be specified") );
163 if ( m_windowStyle
& wxCLIP_SIBLINGS
)
164 wstyle
|= WS_CLIPSIBLINGS
;
166 if (m_windowStyle
& wxLB_MULTIPLE
)
167 wstyle
|= LBS_MULTIPLESEL
;
168 else if (m_windowStyle
& wxLB_EXTENDED
)
169 wstyle
|= LBS_EXTENDEDSEL
;
171 if (m_windowStyle
& wxLB_ALWAYS_SB
)
172 wstyle
|= LBS_DISABLENOSCROLL
;
173 if (m_windowStyle
& wxLB_HSCROLL
)
174 wstyle
|= WS_HSCROLL
;
175 if (m_windowStyle
& wxLB_SORT
)
178 #if wxUSE_OWNER_DRAWN
179 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
180 // we don't support LBS_OWNERDRAWVARIABLE yet
181 wstyle
|= LBS_OWNERDRAWFIXED
;
185 // Without this style, you get unexpected heights, so e.g. constraint layout
186 // doesn't work properly
187 wstyle
|= LBS_NOINTEGRALHEIGHT
;
190 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
);
192 // Even with extended styles, need to combine with WS_BORDER for them to
194 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
199 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, wxT("LISTBOX"), NULL
,
202 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
203 wxGetInstance(), NULL
);
205 wxCHECK_MSG( m_hWnd
, FALSE
, wxT("Failed to create listbox") );
210 Ctl3dSubclassCtl(GetHwnd());
215 // Subclass again to catch messages
219 for (ui
= 0; ui
< (size_t)n
; ui
++) {
223 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
224 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
226 SetFont(parent
->GetFont());
228 SetSize(x
, y
, width
, height
);
233 wxListBox::~wxListBox()
238 void wxListBox::SetupColours()
240 SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW
));
241 SetForegroundColour(GetParent()->GetForegroundColour());
244 // ----------------------------------------------------------------------------
245 // implementation of wxListBoxBase methods
246 // ----------------------------------------------------------------------------
248 void wxListBox::DoSetFirstItem(int N
)
250 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
251 wxT("invalid index in wxListBox::SetFirstItem") );
253 SendMessage(GetHwnd(), LB_SETTOPINDEX
, (WPARAM
)N
, (LPARAM
)0);
256 void wxListBox::Delete(int N
)
258 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
259 wxT("invalid index in wxListBox::Delete") );
261 // for owner drawn objects, the data is used for storing wxOwnerDrawn
262 // pointers and we shouldn't touch it
263 #if !wxUSE_OWNER_DRAWN
264 if ( !(m_windowStyle
& wxLB_OWNERDRAW
) )
265 #endif // !wxUSE_OWNER_DRAWN
266 if ( HasClientObjectData() )
268 delete GetClientObject(N
);
271 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
274 SetHorizontalExtent("");
277 int wxListBox::DoAppend(const wxString
& item
)
279 int index
= ListBox_AddString(GetHwnd(), item
);
282 #if wxUSE_OWNER_DRAWN
283 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
284 wxOwnerDrawn
*pNewItem
= CreateItem(index
); // dummy argument
285 pNewItem
->SetName(item
);
286 m_aItems
.Add(pNewItem
);
287 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
288 pNewItem
->SetFont(GetFont());
292 SetHorizontalExtent(item
);
297 void wxListBox::DoSetItems(const wxArrayString
& choices
, void** clientData
)
299 // avoid flicker - but don't need to do this for a hidden listbox
300 bool hideAndShow
= IsShown();
303 ShowWindow(GetHwnd(), SW_HIDE
);
306 ListBox_ResetContent(GetHwnd());
308 m_noItems
= choices
.GetCount();
310 for (i
= 0; i
< m_noItems
; i
++)
312 ListBox_AddString(GetHwnd(), choices
[i
]);
315 #if wxUSE_OWNER_DRAWN
316 if ( m_windowStyle
& wxLB_OWNERDRAW
)
318 wxASSERT_MSG(clientData
[i
] == NULL
,
319 wxT("Can't use client data with owner-drawn listboxes"));
321 ListBox_SetItemData(GetHwnd(), i
, clientData
[i
]);
322 #else // !wxUSE_OWNER_DRAWN
323 ListBox_SetItemData(GetHwnd(), i
, clientData
[i
]);
324 #endif // wxUSE_OWNER_DRAWN/!wxUSE_OWNER_DRAWN
328 #if wxUSE_OWNER_DRAWN
329 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
330 // first delete old items
331 size_t ui
= m_aItems
.Count();
332 while ( ui
-- != 0 ) {
337 // then create new ones
338 for ( ui
= 0; ui
< (size_t)m_noItems
; ui
++ ) {
339 wxOwnerDrawn
*pNewItem
= CreateItem(ui
);
340 pNewItem
->SetName(choices
[ui
]);
341 m_aItems
.Add(pNewItem
);
342 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
345 #endif // wxUSE_OWNER_DRAWN
347 SetHorizontalExtent();
351 // show the listbox back if we hid it
352 ShowWindow(GetHwnd(), SW_SHOW
);
356 int wxListBox::FindString(const wxString
& s
) const
358 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
365 void wxListBox::Clear()
369 ListBox_ResetContent(GetHwnd());
372 SetHorizontalExtent();
375 void wxListBox::Free()
377 #if wxUSE_OWNER_DRAWN
378 if ( m_windowStyle
& wxLB_OWNERDRAW
)
380 size_t uiCount
= m_aItems
.Count();
381 while ( uiCount
-- != 0 ) {
382 delete m_aItems
[uiCount
];
388 #endif // wxUSE_OWNER_DRAWN
389 if ( HasClientObjectData() )
391 for ( size_t n
= 0; n
< (size_t)m_noItems
; n
++ )
393 delete GetClientObject(n
);
398 void wxListBox::SetSelection(int N
, bool select
)
400 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
401 wxT("invalid index in wxListBox::SetSelection") );
403 if ( HasMultipleSelection() )
405 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
409 SendMessage(GetHwnd(), LB_SETCURSEL
, select
? N
: -1, 0);
413 bool wxListBox::IsSelected(int N
) const
415 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
416 wxT("invalid index in wxListBox::Selected") );
418 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
421 wxClientData
* wxListBox::DoGetItemClientObject(int n
) const
423 return (wxClientData
*)DoGetItemClientData(n
);
426 void *wxListBox::DoGetItemClientData(int n
) const
428 wxCHECK_MSG( n
>= 0 && n
< m_noItems
, NULL
,
429 wxT("invalid index in wxListBox::GetClientData") );
431 return (void *)SendMessage(GetHwnd(), LB_GETITEMDATA
, n
, 0);
434 void wxListBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
436 DoSetItemClientData(n
, clientData
);
439 void wxListBox::DoSetItemClientData(int n
, void *clientData
)
441 wxCHECK_RET( n
>= 0 && n
< m_noItems
,
442 wxT("invalid index in wxListBox::SetClientData") );
444 #if wxUSE_OWNER_DRAWN
445 if ( m_windowStyle
& wxLB_OWNERDRAW
)
447 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
448 // in OnMeasure/OnDraw.
449 wxFAIL_MSG(wxT("Can't use client data with owner-drawn listboxes"));
451 #endif // wxUSE_OWNER_DRAWN
453 if ( ListBox_SetItemData(GetHwnd(), n
, clientData
) == LB_ERR
)
454 wxLogDebug(wxT("LB_SETITEMDATA failed"));
457 bool wxListBox::HasMultipleSelection() const
459 return (m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
);
462 // Return number of selections and an array of selected integers
463 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
467 if ( HasMultipleSelection() )
469 int no_sel
= ListBox_GetSelCount(GetHwnd());
471 int *selections
= new int[no_sel
];
472 int rc
= ListBox_GetSelItems(GetHwnd(), no_sel
, selections
);
474 wxCHECK_MSG(rc
!= LB_ERR
, -1, wxT("ListBox_GetSelItems failed"));
476 aSelections
.Alloc(no_sel
);
477 for ( int n
= 0; n
< no_sel
; n
++ )
478 aSelections
.Add(selections
[n
]);
480 delete [] selections
;
485 else // single-selection listbox
487 if (ListBox_GetCurSel(GetHwnd()) > -1)
488 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
490 return aSelections
.Count();
494 // Get single selection, for single choice list items
495 int wxListBox::GetSelection() const
497 wxCHECK_MSG( !HasMultipleSelection(),
499 wxT("GetSelection() can't be used with multiple-selection listboxes, use GetSelections() instead.") );
501 return ListBox_GetCurSel(GetHwnd());
504 // Find string for position
505 wxString
wxListBox::GetString(int N
) const
507 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, "",
508 wxT("invalid index in wxListBox::GetClientData") );
510 int len
= ListBox_GetTextLen(GetHwnd(), N
);
512 // +1 for terminating NUL
514 ListBox_GetText(GetHwnd(), N
, result
.GetWriteBuf(len
+ 1));
515 result
.UngetWriteBuf();
521 wxListBox::DoInsertItems(const wxArrayString
& items
, int pos
)
523 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
524 wxT("invalid index in wxListBox::InsertItems") );
526 int nItems
= items
.GetCount();
527 for ( int i
= 0; i
< nItems
; i
++ )
528 ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
531 SetHorizontalExtent();
534 void wxListBox::SetString(int N
, const wxString
& s
)
536 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
537 wxT("invalid index in wxListBox::SetString") );
539 // remember the state of the item
540 bool wasSelected
= IsSelected(N
);
542 void *oldData
= NULL
;
543 wxClientData
*oldObjData
= NULL
;
544 if ( m_clientDataItemsType
== ClientData_Void
)
545 oldData
= GetClientData(N
);
546 else if ( m_clientDataItemsType
== ClientData_Object
)
547 oldObjData
= GetClientObject(N
);
549 // delete and recreate it
550 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
553 if ( N
== m_noItems
- 1 )
556 ListBox_InsertString(GetHwnd(), newN
, s
);
558 // restore the client data
560 SetClientData(N
, oldData
);
561 else if ( oldObjData
)
562 SetClientObject(N
, oldObjData
);
564 // we may have lost the selection
568 #if wxUSE_OWNER_DRAWN
569 if ( m_windowStyle
& wxLB_OWNERDRAW
)
570 // update item's text
571 m_aItems
[N
]->SetName(s
);
572 #endif //USE_OWNER_DRAWN
575 int wxListBox::GetCount() const
580 // ----------------------------------------------------------------------------
582 // ----------------------------------------------------------------------------
584 // Windows-specific code to set the horizontal extent of the listbox, if
585 // necessary. If s is non-NULL, it's used to calculate the horizontal extent.
586 // Otherwise, all strings are used.
587 void wxListBox::SetHorizontalExtent(const wxString
& s
)
589 // Only necessary if we want a horizontal scrollbar
590 if (!(m_windowStyle
& wxHSCROLL
))
592 TEXTMETRIC lpTextMetric
;
596 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
597 HDC dc
= GetWindowDC(GetHwnd());
599 if (GetFont().Ok() && GetFont().GetResourceHandle())
600 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
602 GetTextMetrics(dc
, &lpTextMetric
);
604 ::GetTextExtentPoint(dc
, (LPTSTR
) (const wxChar
*)s
, s
.Length(), &extentXY
);
605 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
608 ::SelectObject(dc
, oldFont
);
610 ReleaseDC(GetHwnd(), dc
);
611 if (extentX
> existingExtent
)
612 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
616 int largestExtent
= 0;
617 HDC dc
= GetWindowDC(GetHwnd());
619 if (GetFont().Ok() && GetFont().GetResourceHandle())
620 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
622 GetTextMetrics(dc
, &lpTextMetric
);
624 for (i
= 0; i
< m_noItems
; i
++)
626 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LONG
)wxBuffer
);
629 ::GetTextExtentPoint(dc
, (LPTSTR
)wxBuffer
, len
, &extentXY
);
630 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
631 if (extentX
> largestExtent
)
632 largestExtent
= extentX
;
635 ::SelectObject(dc
, oldFont
);
637 ReleaseDC(GetHwnd(), dc
);
638 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
642 wxSize
wxListBox::DoGetBestSize() const
644 // find the widest string
647 for ( int i
= 0; i
< m_noItems
; i
++ )
649 wxString
str(GetString(i
));
650 GetTextExtent(str
, &wLine
, NULL
);
651 if ( wLine
> wListbox
)
655 // give it some reasonable default value if there are no strings in the
660 // the listbox should be slightly larger than the widest string
662 wxGetCharSize(GetHWND(), &cx
, &cy
, &GetFont());
666 int hListbox
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
)*(wxMax(m_noItems
, 7));
668 return wxSize(wListbox
, hListbox
);
671 // ----------------------------------------------------------------------------
673 // ----------------------------------------------------------------------------
675 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
678 if ( param
== LBN_SELCHANGE
)
680 evtType
= wxEVT_COMMAND_LISTBOX_SELECTED
;
682 else if ( param
== LBN_DBLCLK
)
684 evtType
= wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
;
688 // some event we're not interested in
692 wxCommandEvent
event(evtType
, m_windowId
);
693 event
.SetEventObject( this );
695 wxArrayInt aSelections
;
696 int n
, count
= GetSelections(aSelections
);
700 if ( HasClientObjectData() )
701 event
.SetClientObject( GetClientObject(n
) );
702 else if ( HasClientUntypedData() )
703 event
.SetClientData( GetClientData(n
) );
704 event
.SetString( GetString(n
) );
711 event
.m_commandInt
= n
;
713 return GetEventHandler()->ProcessEvent(event
);
716 // ----------------------------------------------------------------------------
717 // wxCheckListBox support
718 // ----------------------------------------------------------------------------
720 #if wxUSE_OWNER_DRAWN
725 // space beneath/above each row in pixels
726 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
727 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
729 // the height is the same for all items
730 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
732 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
733 // message is not yet sent when we get here!
734 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
736 // only owner-drawn control should receive this message
737 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
739 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
741 HDC hdc
= CreateIC(wxT("DISPLAY"), NULL
, NULL
, 0);
744 dc
.SetHDC((WXHDC
)hdc
);
745 dc
.SetFont(wxSystemSettings::GetSystemFont(wxSYS_ANSI_VAR_FONT
));
747 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
748 pStruct
->itemWidth
= dc
.GetCharWidth();
757 // forward the message to the appropriate item
758 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
760 // only owner-drawn control should receive this message
761 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
763 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
764 UINT itemID
= pStruct
->itemID
;
766 // the item may be -1 for an empty listbox
767 if ( itemID
== (UINT
)-1 )
770 long data
= ListBox_GetItemData(GetHwnd(), pStruct
->itemID
);
772 wxCHECK( data
&& (data
!= LB_ERR
), FALSE
);
774 wxListBoxItem
*pItem
= (wxListBoxItem
*)data
;
777 dc
.SetHDC((WXHDC
)pStruct
->hDC
, FALSE
);
778 wxRect
rect(wxPoint(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
),
779 wxPoint(pStruct
->rcItem
.right
, pStruct
->rcItem
.bottom
));
781 return pItem
->OnDrawItem(dc
, rect
,
782 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
783 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);