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 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
59 // ============================================================================
60 // list box item declaration and implementation
61 // ============================================================================
65 class wxListBoxItem
: public wxOwnerDrawn
68 wxListBoxItem(const wxString
& str
= wxEmptyString
);
71 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
73 // no bitmaps/checkmarks
77 wxOwnerDrawn
*wxListBox::CreateLboxItem(size_t WXUNUSED(n
))
79 return new wxListBoxItem();
82 #endif //USE_OWNER_DRAWN
84 // ============================================================================
85 // list box control implementation
86 // ============================================================================
88 // ----------------------------------------------------------------------------
90 // ----------------------------------------------------------------------------
93 wxListBox::wxListBox()
99 bool wxListBox::Create(wxWindow
*parent
,
103 int n
, const wxString choices
[],
105 const wxValidator
& validator
,
106 const wxString
& name
)
114 SetValidator(validator
);
115 #endif // wxUSE_VALIDATORS
118 parent
->AddChild(this);
120 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
121 SetForegroundColour(parent
->GetForegroundColour());
123 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
129 m_windowStyle
= style
;
131 DWORD wstyle
= WS_VISIBLE
| WS_CHILD
| WS_VSCROLL
| WS_TABSTOP
|
132 LBS_NOTIFY
| LBS_HASSTRINGS
;
134 wxASSERT_MSG( !(style
& wxLB_MULTIPLE
) || !(style
& wxLB_EXTENDED
),
135 _T("only one of listbox selection modes can be specified") );
137 if ( (m_windowStyle
& wxBORDER_MASK
) == wxBORDER_DEFAULT
)
138 m_windowStyle
|= wxBORDER_SUNKEN
;
140 if ( m_windowStyle
& wxCLIP_SIBLINGS
)
141 wstyle
|= WS_CLIPSIBLINGS
;
143 if (m_windowStyle
& wxLB_MULTIPLE
)
144 wstyle
|= LBS_MULTIPLESEL
;
145 else if (m_windowStyle
& wxLB_EXTENDED
)
146 wstyle
|= LBS_EXTENDEDSEL
;
148 if (m_windowStyle
& wxLB_ALWAYS_SB
)
149 wstyle
|= LBS_DISABLENOSCROLL
;
150 if (m_windowStyle
& wxLB_HSCROLL
)
151 wstyle
|= WS_HSCROLL
;
152 if (m_windowStyle
& wxLB_SORT
)
155 #if wxUSE_OWNER_DRAWN && !defined(__WXWINCE__)
156 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
157 // we don't support LBS_OWNERDRAWVARIABLE yet
158 wstyle
|= LBS_OWNERDRAWFIXED
;
162 // Without this style, you get unexpected heights, so e.g. constraint layout
163 // doesn't work properly
164 wstyle
|= LBS_NOINTEGRALHEIGHT
;
167 (void) MSWGetStyle(m_windowStyle
, & exStyle
) ;
169 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, wxT("LISTBOX"), NULL
,
172 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
173 wxGetInstance(), NULL
);
175 wxCHECK_MSG( m_hWnd
, FALSE
, wxT("Failed to create listbox") );
177 // Subclass again to catch messages
181 for (ui
= 0; ui
< (size_t)n
; ui
++) {
185 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
186 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
188 SetFont(parent
->GetFont());
190 SetSize(x
, y
, width
, height
);
195 wxListBox::~wxListBox()
200 void wxListBox::SetupColours()
202 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
203 SetForegroundColour(GetParent()->GetForegroundColour());
206 // ----------------------------------------------------------------------------
207 // implementation of wxListBoxBase methods
208 // ----------------------------------------------------------------------------
210 void wxListBox::DoSetFirstItem(int N
)
212 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
213 wxT("invalid index in wxListBox::SetFirstItem") );
215 SendMessage(GetHwnd(), LB_SETTOPINDEX
, (WPARAM
)N
, (LPARAM
)0);
218 void wxListBox::Delete(int N
)
220 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
221 wxT("invalid index in wxListBox::Delete") );
223 // for owner drawn objects, the data is used for storing wxOwnerDrawn
224 // pointers and we shouldn't touch it
225 #if !wxUSE_OWNER_DRAWN
226 if ( !(m_windowStyle
& wxLB_OWNERDRAW
) )
227 #endif // !wxUSE_OWNER_DRAWN
228 if ( HasClientObjectData() )
230 delete GetClientObject(N
);
233 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
236 SetHorizontalExtent(wxEmptyString
);
239 int wxListBox::DoAppend(const wxString
& item
)
241 int index
= ListBox_AddString(GetHwnd(), item
);
244 #if wxUSE_OWNER_DRAWN
245 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
246 wxOwnerDrawn
*pNewItem
= CreateLboxItem(index
); // dummy argument
247 pNewItem
->SetName(item
);
248 m_aItems
.Insert(pNewItem
, index
);
249 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
250 pNewItem
->SetFont(GetFont());
252 #endif // wxUSE_OWNER_DRAWN
254 SetHorizontalExtent(item
);
259 void wxListBox::DoSetItems(const wxArrayString
& choices
, void** clientData
)
261 // avoid flicker - but don't need to do this for a hidden listbox
262 bool hideAndShow
= IsShown();
265 ShowWindow(GetHwnd(), SW_HIDE
);
268 ListBox_ResetContent(GetHwnd());
270 m_noItems
= choices
.GetCount();
272 for (i
= 0; i
< m_noItems
; i
++)
274 ListBox_AddString(GetHwnd(), choices
[i
]);
277 SetClientData(i
, clientData
[i
]);
281 #if wxUSE_OWNER_DRAWN
282 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
283 // first delete old items
284 WX_CLEAR_ARRAY(m_aItems
);
286 // then create new ones
287 for ( size_t ui
= 0; ui
< (size_t)m_noItems
; ui
++ ) {
288 wxOwnerDrawn
*pNewItem
= CreateLboxItem(ui
);
289 pNewItem
->SetName(choices
[ui
]);
290 m_aItems
.Add(pNewItem
);
291 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
294 #endif // wxUSE_OWNER_DRAWN
296 SetHorizontalExtent();
300 // show the listbox back if we hid it
301 ShowWindow(GetHwnd(), SW_SHOW
);
305 int wxListBox::FindString(const wxString
& s
) const
307 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
314 void wxListBox::Clear()
318 ListBox_ResetContent(GetHwnd());
321 SetHorizontalExtent();
324 void wxListBox::Free()
326 #if wxUSE_OWNER_DRAWN
327 if ( m_windowStyle
& wxLB_OWNERDRAW
)
329 WX_CLEAR_ARRAY(m_aItems
);
332 #endif // wxUSE_OWNER_DRAWN
333 if ( HasClientObjectData() )
335 for ( size_t n
= 0; n
< (size_t)m_noItems
; n
++ )
337 delete GetClientObject(n
);
342 void wxListBox::SetSelection(int N
, bool select
)
344 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
345 wxT("invalid index in wxListBox::SetSelection") );
347 if ( HasMultipleSelection() )
349 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
353 SendMessage(GetHwnd(), LB_SETCURSEL
, select
? N
: -1, 0);
357 bool wxListBox::IsSelected(int N
) const
359 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
360 wxT("invalid index in wxListBox::Selected") );
362 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
365 wxClientData
* wxListBox::DoGetItemClientObject(int n
) const
367 return (wxClientData
*)DoGetItemClientData(n
);
370 void *wxListBox::DoGetItemClientData(int n
) const
372 wxCHECK_MSG( n
>= 0 && n
< m_noItems
, NULL
,
373 wxT("invalid index in wxListBox::GetClientData") );
375 return (void *)SendMessage(GetHwnd(), LB_GETITEMDATA
, n
, 0);
378 void wxListBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
380 DoSetItemClientData(n
, clientData
);
383 void wxListBox::DoSetItemClientData(int n
, void *clientData
)
385 wxCHECK_RET( n
>= 0 && n
< m_noItems
,
386 wxT("invalid index in wxListBox::SetClientData") );
388 #if wxUSE_OWNER_DRAWN
389 if ( m_windowStyle
& wxLB_OWNERDRAW
)
391 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
392 // in OnMeasure/OnDraw.
393 wxFAIL_MSG(wxT("Can't use client data with owner-drawn listboxes"));
395 #endif // wxUSE_OWNER_DRAWN
397 if ( ListBox_SetItemData(GetHwnd(), n
, clientData
) == LB_ERR
)
398 wxLogDebug(wxT("LB_SETITEMDATA failed"));
401 // Return number of selections and an array of selected integers
402 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
406 if ( HasMultipleSelection() )
408 int countSel
= ListBox_GetSelCount(GetHwnd());
409 if ( countSel
== LB_ERR
)
411 wxLogDebug(_T("ListBox_GetSelCount failed"));
413 else if ( countSel
!= 0 )
415 int *selections
= new int[countSel
];
417 if ( ListBox_GetSelItems(GetHwnd(),
418 countSel
, selections
) == LB_ERR
)
420 wxLogDebug(wxT("ListBox_GetSelItems failed"));
425 aSelections
.Alloc(countSel
);
426 for ( int n
= 0; n
< countSel
; n
++ )
427 aSelections
.Add(selections
[n
]);
430 delete [] selections
;
435 else // single-selection listbox
437 if (ListBox_GetCurSel(GetHwnd()) > -1)
438 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
440 return aSelections
.Count();
444 // Get single selection, for single choice list items
445 int wxListBox::GetSelection() const
447 wxCHECK_MSG( !HasMultipleSelection(),
449 wxT("GetSelection() can't be used with multiple-selection listboxes, use GetSelections() instead.") );
451 return ListBox_GetCurSel(GetHwnd());
454 // Find string for position
455 wxString
wxListBox::GetString(int N
) const
457 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, wxEmptyString
,
458 wxT("invalid index in wxListBox::GetClientData") );
460 int len
= ListBox_GetTextLen(GetHwnd(), N
);
462 // +1 for terminating NUL
464 ListBox_GetText(GetHwnd(), N
, wxStringBuffer(result
, len
+ 1));
470 wxListBox::DoInsertItems(const wxArrayString
& items
, int pos
)
472 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
473 wxT("invalid index in wxListBox::InsertItems") );
475 int nItems
= items
.GetCount();
476 for ( int i
= 0; i
< nItems
; i
++ )
478 int idx
= ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
480 #if wxUSE_OWNER_DRAWN
481 if ( m_windowStyle
& wxLB_OWNERDRAW
)
483 wxOwnerDrawn
*pNewItem
= CreateLboxItem(idx
);
484 pNewItem
->SetName(items
[i
]);
485 pNewItem
->SetFont(GetFont());
486 m_aItems
.Insert(pNewItem
, idx
);
488 ListBox_SetItemData(GetHwnd(), idx
, pNewItem
);
490 #endif // wxUSE_OWNER_DRAWN
495 SetHorizontalExtent();
498 void wxListBox::SetString(int N
, const wxString
& s
)
500 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
501 wxT("invalid index in wxListBox::SetString") );
503 // remember the state of the item
504 bool wasSelected
= IsSelected(N
);
506 void *oldData
= NULL
;
507 wxClientData
*oldObjData
= NULL
;
508 if ( m_clientDataItemsType
== wxClientData_Void
)
509 oldData
= GetClientData(N
);
510 else if ( m_clientDataItemsType
== wxClientData_Object
)
511 oldObjData
= GetClientObject(N
);
513 // delete and recreate it
514 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
517 if ( N
== m_noItems
- 1 )
520 ListBox_InsertString(GetHwnd(), newN
, s
);
522 // restore the client data
524 SetClientData(N
, oldData
);
525 else if ( oldObjData
)
526 SetClientObject(N
, oldObjData
);
528 // we may have lost the selection
532 #if wxUSE_OWNER_DRAWN
533 if ( m_windowStyle
& wxLB_OWNERDRAW
)
535 // update item's text
536 m_aItems
[N
]->SetName(s
);
538 // reassign the item's data
539 ListBox_SetItemData(GetHwnd(), N
, m_aItems
[N
]);
541 #endif //USE_OWNER_DRAWN
544 int wxListBox::GetCount() const
549 // ----------------------------------------------------------------------------
551 // ----------------------------------------------------------------------------
553 // Windows-specific code to set the horizontal extent of the listbox, if
554 // necessary. If s is non-NULL, it's used to calculate the horizontal extent.
555 // Otherwise, all strings are used.
556 void wxListBox::SetHorizontalExtent(const wxString
& s
)
558 // Only necessary if we want a horizontal scrollbar
559 if (!(m_windowStyle
& wxHSCROLL
))
561 TEXTMETRIC lpTextMetric
;
565 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
566 HDC dc
= GetWindowDC(GetHwnd());
568 if (GetFont().Ok() && GetFont().GetResourceHandle())
569 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
571 GetTextMetrics(dc
, &lpTextMetric
);
573 ::GetTextExtentPoint(dc
, (LPTSTR
) (const wxChar
*)s
, s
.Length(), &extentXY
);
574 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
577 ::SelectObject(dc
, oldFont
);
579 ReleaseDC(GetHwnd(), dc
);
580 if (extentX
> existingExtent
)
581 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
585 int largestExtent
= 0;
586 HDC dc
= GetWindowDC(GetHwnd());
588 if (GetFont().Ok() && GetFont().GetResourceHandle())
589 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
591 GetTextMetrics(dc
, &lpTextMetric
);
593 // FIXME: buffer overflow!!
595 for (int i
= 0; i
< m_noItems
; i
++)
597 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LPARAM
)buf
);
600 ::GetTextExtentPoint(dc
, buf
, len
, &extentXY
);
601 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
602 if (extentX
> largestExtent
)
603 largestExtent
= extentX
;
606 ::SelectObject(dc
, oldFont
);
608 ReleaseDC(GetHwnd(), dc
);
609 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
613 wxSize
wxListBox::DoGetBestSize() const
615 // find the widest string
618 for ( int i
= 0; i
< m_noItems
; i
++ )
620 wxString
str(GetString(i
));
621 GetTextExtent(str
, &wLine
, NULL
);
622 if ( wLine
> wListbox
)
626 // give it some reasonable default value if there are no strings in the
631 // the listbox should be slightly larger than the widest string
633 wxGetCharSize(GetHWND(), &cx
, &cy
, &GetFont());
637 // don't make the listbox too tall (limit height to 10 items) but don't
638 // make it too small neither
639 int hListbox
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
)*
640 wxMin(wxMax(m_noItems
, 3), 10);
642 return wxSize(wListbox
, hListbox
);
645 // ----------------------------------------------------------------------------
647 // ----------------------------------------------------------------------------
649 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
652 if ( param
== LBN_SELCHANGE
)
654 evtType
= wxEVT_COMMAND_LISTBOX_SELECTED
;
656 else if ( param
== LBN_DBLCLK
)
658 evtType
= wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
;
662 // some event we're not interested in
666 wxCommandEvent
event(evtType
, m_windowId
);
667 event
.SetEventObject( this );
669 // retrieve the affected item
670 int n
= SendMessage(GetHwnd(), LB_GETCARETINDEX
, 0, 0);
673 if ( HasClientObjectData() )
674 event
.SetClientObject( GetClientObject(n
) );
675 else if ( HasClientUntypedData() )
676 event
.SetClientData( GetClientData(n
) );
678 event
.SetString( GetString(n
) );
679 event
.SetExtraLong( HasMultipleSelection() ? IsSelected(n
) : TRUE
);
682 event
.m_commandInt
= n
;
684 return GetEventHandler()->ProcessEvent(event
);
687 // ----------------------------------------------------------------------------
688 // wxCheckListBox support
689 // ----------------------------------------------------------------------------
691 #if wxUSE_OWNER_DRAWN
696 // space beneath/above each row in pixels
697 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
698 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
700 // the height is the same for all items
701 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
703 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
704 // message is not yet sent when we get here!
705 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
707 // only owner-drawn control should receive this message
708 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
710 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
713 HDC hdc
= GetDC(NULL
);
715 HDC hdc
= CreateIC(wxT("DISPLAY"), NULL
, NULL
, 0);
719 dc
.SetHDC((WXHDC
)hdc
);
720 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_ANSI_VAR_FONT
));
722 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
723 pStruct
->itemWidth
= dc
.GetCharWidth();
732 // forward the message to the appropriate item
733 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
735 // only owner-drawn control should receive this message
736 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
738 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
739 UINT itemID
= pStruct
->itemID
;
741 // the item may be -1 for an empty listbox
742 if ( itemID
== (UINT
)-1 )
745 long data
= ListBox_GetItemData(GetHwnd(), pStruct
->itemID
);
747 wxCHECK( data
&& (data
!= LB_ERR
), FALSE
);
749 wxListBoxItem
*pItem
= (wxListBoxItem
*)data
;
751 wxDCTemp
dc((WXHDC
)pStruct
->hDC
);
752 wxRect
rect(wxPoint(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
),
753 wxPoint(pStruct
->rcItem
.right
, pStruct
->rcItem
.bottom
));
755 return pItem
->OnDrawItem(dc
, rect
,
756 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
757 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);
760 #endif // wxUSE_OWNER_DRAWN
762 #endif // wxUSE_LISTBOX