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"
39 #include "wx/dynarray.h"
43 #include "wx/ownerdrw.h"
47 #ifdef __GNUWIN32_OLD__
48 #include "wx/msw/gnuwin32/extra.h"
52 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
54 // ============================================================================
55 // list box item declaration and implementation
56 // ============================================================================
60 class wxListBoxItem
: public wxOwnerDrawn
63 wxListBoxItem(const wxString
& str
= wxEmptyString
);
66 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
68 // no bitmaps/checkmarks
72 wxOwnerDrawn
*wxListBox::CreateLboxItem(size_t WXUNUSED(n
))
74 return new wxListBoxItem();
77 #endif //USE_OWNER_DRAWN
79 // ============================================================================
80 // list box control implementation
81 // ============================================================================
83 // ----------------------------------------------------------------------------
85 // ----------------------------------------------------------------------------
88 wxListBox::wxListBox()
94 bool wxListBox::Create(wxWindow
*parent
,
98 int n
, const wxString choices
[],
100 const wxValidator
& validator
,
101 const wxString
& name
)
109 SetValidator(validator
);
110 #endif // wxUSE_VALIDATORS
113 parent
->AddChild(this);
115 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
116 SetForegroundColour(parent
->GetForegroundColour());
118 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
124 m_windowStyle
= style
;
126 DWORD wstyle
= WS_VISIBLE
| WS_VSCROLL
| WS_TABSTOP
|
127 LBS_NOTIFY
| LBS_HASSTRINGS
/* | WS_CLIPSIBLINGS */;
129 wxASSERT_MSG( !(style
& wxLB_MULTIPLE
) || !(style
& wxLB_EXTENDED
),
130 _T("only one of listbox selection modes can be specified") );
132 if ( (m_windowStyle
& wxBORDER_MASK
) == wxBORDER_DEFAULT
)
133 m_windowStyle
|= wxBORDER_SUNKEN
;
135 if ( m_windowStyle
& wxCLIP_SIBLINGS
)
136 wstyle
|= WS_CLIPSIBLINGS
;
138 if (m_windowStyle
& wxLB_MULTIPLE
)
139 wstyle
|= LBS_MULTIPLESEL
;
140 else if (m_windowStyle
& wxLB_EXTENDED
)
141 wstyle
|= LBS_EXTENDEDSEL
;
143 if (m_windowStyle
& wxLB_ALWAYS_SB
)
144 wstyle
|= LBS_DISABLENOSCROLL
;
145 if (m_windowStyle
& wxLB_HSCROLL
)
146 wstyle
|= WS_HSCROLL
;
147 if (m_windowStyle
& wxLB_SORT
)
150 #if wxUSE_OWNER_DRAWN
151 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
152 // we don't support LBS_OWNERDRAWVARIABLE yet
153 wstyle
|= LBS_OWNERDRAWFIXED
;
157 // Without this style, you get unexpected heights, so e.g. constraint layout
158 // doesn't work properly
159 wstyle
|= LBS_NOINTEGRALHEIGHT
;
162 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
);
164 // Even with extended styles, need to combine with WS_BORDER for them to
166 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
171 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, wxT("LISTBOX"), NULL
,
174 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
175 wxGetInstance(), NULL
);
177 wxCHECK_MSG( m_hWnd
, FALSE
, wxT("Failed to create listbox") );
182 Ctl3dSubclassCtl(GetHwnd());
187 // Subclass again to catch messages
191 for (ui
= 0; ui
< (size_t)n
; ui
++) {
195 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
196 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
198 SetFont(parent
->GetFont());
200 SetSize(x
, y
, width
, height
);
205 wxListBox::~wxListBox()
210 void wxListBox::SetupColours()
212 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW
));
213 SetForegroundColour(GetParent()->GetForegroundColour());
216 // ----------------------------------------------------------------------------
217 // implementation of wxListBoxBase methods
218 // ----------------------------------------------------------------------------
220 void wxListBox::DoSetFirstItem(int N
)
222 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
223 wxT("invalid index in wxListBox::SetFirstItem") );
225 SendMessage(GetHwnd(), LB_SETTOPINDEX
, (WPARAM
)N
, (LPARAM
)0);
228 void wxListBox::Delete(int N
)
230 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
231 wxT("invalid index in wxListBox::Delete") );
233 // for owner drawn objects, the data is used for storing wxOwnerDrawn
234 // pointers and we shouldn't touch it
235 #if !wxUSE_OWNER_DRAWN
236 if ( !(m_windowStyle
& wxLB_OWNERDRAW
) )
237 #endif // !wxUSE_OWNER_DRAWN
238 if ( HasClientObjectData() )
240 delete GetClientObject(N
);
243 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
246 SetHorizontalExtent(wxEmptyString
);
249 int wxListBox::DoAppend(const wxString
& item
)
251 int index
= ListBox_AddString(GetHwnd(), item
);
254 #if wxUSE_OWNER_DRAWN
255 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
256 wxOwnerDrawn
*pNewItem
= CreateLboxItem(index
); // dummy argument
257 pNewItem
->SetName(item
);
258 m_aItems
.Insert(pNewItem
, index
);
259 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
260 pNewItem
->SetFont(GetFont());
262 #endif // wxUSE_OWNER_DRAWN
264 SetHorizontalExtent(item
);
269 void wxListBox::DoSetItems(const wxArrayString
& choices
, void** clientData
)
271 // avoid flicker - but don't need to do this for a hidden listbox
272 bool hideAndShow
= IsShown();
275 ShowWindow(GetHwnd(), SW_HIDE
);
278 ListBox_ResetContent(GetHwnd());
280 m_noItems
= choices
.GetCount();
282 for (i
= 0; i
< m_noItems
; i
++)
284 ListBox_AddString(GetHwnd(), choices
[i
]);
287 SetClientData(i
, clientData
[i
]);
291 #if wxUSE_OWNER_DRAWN
292 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
293 // first delete old items
294 WX_CLEAR_ARRAY(m_aItems
);
296 // then create new ones
297 for ( size_t ui
= 0; ui
< (size_t)m_noItems
; ui
++ ) {
298 wxOwnerDrawn
*pNewItem
= CreateLboxItem(ui
);
299 pNewItem
->SetName(choices
[ui
]);
300 m_aItems
.Add(pNewItem
);
301 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
304 #endif // wxUSE_OWNER_DRAWN
306 SetHorizontalExtent();
310 // show the listbox back if we hid it
311 ShowWindow(GetHwnd(), SW_SHOW
);
315 int wxListBox::FindString(const wxString
& s
) const
317 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
324 void wxListBox::Clear()
328 ListBox_ResetContent(GetHwnd());
331 SetHorizontalExtent();
334 void wxListBox::Free()
336 #if wxUSE_OWNER_DRAWN
337 if ( m_windowStyle
& wxLB_OWNERDRAW
)
339 WX_CLEAR_ARRAY(m_aItems
);
342 #endif // wxUSE_OWNER_DRAWN
343 if ( HasClientObjectData() )
345 for ( size_t n
= 0; n
< (size_t)m_noItems
; n
++ )
347 delete GetClientObject(n
);
352 void wxListBox::SetSelection(int N
, bool select
)
354 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
355 wxT("invalid index in wxListBox::SetSelection") );
357 if ( HasMultipleSelection() )
359 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
363 SendMessage(GetHwnd(), LB_SETCURSEL
, select
? N
: -1, 0);
367 bool wxListBox::IsSelected(int N
) const
369 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
370 wxT("invalid index in wxListBox::Selected") );
372 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
375 wxClientData
* wxListBox::DoGetItemClientObject(int n
) const
377 return (wxClientData
*)DoGetItemClientData(n
);
380 void *wxListBox::DoGetItemClientData(int n
) const
382 wxCHECK_MSG( n
>= 0 && n
< m_noItems
, NULL
,
383 wxT("invalid index in wxListBox::GetClientData") );
385 return (void *)SendMessage(GetHwnd(), LB_GETITEMDATA
, n
, 0);
388 void wxListBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
390 DoSetItemClientData(n
, clientData
);
393 void wxListBox::DoSetItemClientData(int n
, void *clientData
)
395 wxCHECK_RET( n
>= 0 && n
< m_noItems
,
396 wxT("invalid index in wxListBox::SetClientData") );
398 #if wxUSE_OWNER_DRAWN
399 if ( m_windowStyle
& wxLB_OWNERDRAW
)
401 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
402 // in OnMeasure/OnDraw.
403 wxFAIL_MSG(wxT("Can't use client data with owner-drawn listboxes"));
405 #endif // wxUSE_OWNER_DRAWN
407 if ( ListBox_SetItemData(GetHwnd(), n
, clientData
) == LB_ERR
)
408 wxLogDebug(wxT("LB_SETITEMDATA failed"));
411 // Return number of selections and an array of selected integers
412 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
416 if ( HasMultipleSelection() )
418 int countSel
= ListBox_GetSelCount(GetHwnd());
419 if ( countSel
== LB_ERR
)
421 wxLogDebug(_T("ListBox_GetSelCount failed"));
423 else if ( countSel
!= 0 )
425 int *selections
= new int[countSel
];
427 if ( ListBox_GetSelItems(GetHwnd(),
428 countSel
, selections
) == LB_ERR
)
430 wxLogDebug(wxT("ListBox_GetSelItems failed"));
435 aSelections
.Alloc(countSel
);
436 for ( int n
= 0; n
< countSel
; n
++ )
437 aSelections
.Add(selections
[n
]);
440 delete [] selections
;
445 else // single-selection listbox
447 if (ListBox_GetCurSel(GetHwnd()) > -1)
448 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
450 return aSelections
.Count();
454 // Get single selection, for single choice list items
455 int wxListBox::GetSelection() const
457 wxCHECK_MSG( !HasMultipleSelection(),
459 wxT("GetSelection() can't be used with multiple-selection listboxes, use GetSelections() instead.") );
461 return ListBox_GetCurSel(GetHwnd());
464 // Find string for position
465 wxString
wxListBox::GetString(int N
) const
467 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, wxEmptyString
,
468 wxT("invalid index in wxListBox::GetClientData") );
470 int len
= ListBox_GetTextLen(GetHwnd(), N
);
472 // +1 for terminating NUL
474 ListBox_GetText(GetHwnd(), N
, result
.GetWriteBuf(len
+ 1));
475 result
.UngetWriteBuf();
481 wxListBox::DoInsertItems(const wxArrayString
& items
, int pos
)
483 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
484 wxT("invalid index in wxListBox::InsertItems") );
486 int nItems
= items
.GetCount();
487 for ( int i
= 0; i
< nItems
; i
++ )
489 int idx
= ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
491 #if wxUSE_OWNER_DRAWN
492 if ( m_windowStyle
& wxLB_OWNERDRAW
)
494 wxOwnerDrawn
*pNewItem
= CreateLboxItem(idx
);
495 pNewItem
->SetName(items
[i
]);
496 pNewItem
->SetFont(GetFont());
497 m_aItems
.Insert(pNewItem
, idx
);
499 ListBox_SetItemData(GetHwnd(), idx
, pNewItem
);
501 #endif // wxUSE_OWNER_DRAWN
506 SetHorizontalExtent();
509 void wxListBox::SetString(int N
, const wxString
& s
)
511 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
512 wxT("invalid index in wxListBox::SetString") );
514 // remember the state of the item
515 bool wasSelected
= IsSelected(N
);
517 void *oldData
= NULL
;
518 wxClientData
*oldObjData
= NULL
;
519 if ( m_clientDataItemsType
== wxClientData_Void
)
520 oldData
= GetClientData(N
);
521 else if ( m_clientDataItemsType
== wxClientData_Object
)
522 oldObjData
= GetClientObject(N
);
524 // delete and recreate it
525 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
528 if ( N
== m_noItems
- 1 )
531 ListBox_InsertString(GetHwnd(), newN
, s
);
533 // restore the client data
535 SetClientData(N
, oldData
);
536 else if ( oldObjData
)
537 SetClientObject(N
, oldObjData
);
539 // we may have lost the selection
543 #if wxUSE_OWNER_DRAWN
544 if ( m_windowStyle
& wxLB_OWNERDRAW
)
546 // update item's text
547 m_aItems
[N
]->SetName(s
);
549 // reassign the item's data
550 ListBox_SetItemData(GetHwnd(), N
, m_aItems
[N
]);
552 #endif //USE_OWNER_DRAWN
555 int wxListBox::GetCount() const
560 // ----------------------------------------------------------------------------
562 // ----------------------------------------------------------------------------
564 // Windows-specific code to set the horizontal extent of the listbox, if
565 // necessary. If s is non-NULL, it's used to calculate the horizontal extent.
566 // Otherwise, all strings are used.
567 void wxListBox::SetHorizontalExtent(const wxString
& s
)
569 // Only necessary if we want a horizontal scrollbar
570 if (!(m_windowStyle
& wxHSCROLL
))
572 TEXTMETRIC lpTextMetric
;
576 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
577 HDC dc
= GetWindowDC(GetHwnd());
579 if (GetFont().Ok() && GetFont().GetResourceHandle())
580 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
582 GetTextMetrics(dc
, &lpTextMetric
);
584 ::GetTextExtentPoint(dc
, (LPTSTR
) (const wxChar
*)s
, s
.Length(), &extentXY
);
585 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
588 ::SelectObject(dc
, oldFont
);
590 ReleaseDC(GetHwnd(), dc
);
591 if (extentX
> existingExtent
)
592 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
596 int largestExtent
= 0;
597 HDC dc
= GetWindowDC(GetHwnd());
599 if (GetFont().Ok() && GetFont().GetResourceHandle())
600 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
602 GetTextMetrics(dc
, &lpTextMetric
);
604 for (i
= 0; i
< m_noItems
; i
++)
606 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LONG
)wxBuffer
);
609 ::GetTextExtentPoint(dc
, (LPTSTR
)wxBuffer
, len
, &extentXY
);
610 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
611 if (extentX
> largestExtent
)
612 largestExtent
= extentX
;
615 ::SelectObject(dc
, oldFont
);
617 ReleaseDC(GetHwnd(), dc
);
618 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
622 wxSize
wxListBox::DoGetBestSize() const
624 // find the widest string
627 for ( int i
= 0; i
< m_noItems
; i
++ )
629 wxString
str(GetString(i
));
630 GetTextExtent(str
, &wLine
, NULL
);
631 if ( wLine
> wListbox
)
635 // give it some reasonable default value if there are no strings in the
640 // the listbox should be slightly larger than the widest string
642 wxGetCharSize(GetHWND(), &cx
, &cy
, &GetFont());
646 // don't make the listbox too tall (limit height to 10 items) but don't
647 // make it too small neither
648 int hListbox
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
)*
649 wxMin(wxMax(m_noItems
, 3), 10);
651 return wxSize(wListbox
, hListbox
);
654 // ----------------------------------------------------------------------------
656 // ----------------------------------------------------------------------------
658 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
661 if ( param
== LBN_SELCHANGE
)
663 evtType
= wxEVT_COMMAND_LISTBOX_SELECTED
;
665 else if ( param
== LBN_DBLCLK
)
667 evtType
= wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
;
671 // some event we're not interested in
675 wxCommandEvent
event(evtType
, m_windowId
);
676 event
.SetEventObject( this );
678 // retrieve the affected item
679 int n
= SendMessage(GetHwnd(), LB_GETCARETINDEX
, 0, 0);
682 if ( HasClientObjectData() )
683 event
.SetClientObject( GetClientObject(n
) );
684 else if ( HasClientUntypedData() )
685 event
.SetClientData( GetClientData(n
) );
687 event
.SetString( GetString(n
) );
688 event
.SetExtraLong( HasMultipleSelection() ? IsSelected(n
) : TRUE
);
691 event
.m_commandInt
= n
;
693 return GetEventHandler()->ProcessEvent(event
);
696 // ----------------------------------------------------------------------------
697 // wxCheckListBox support
698 // ----------------------------------------------------------------------------
700 #if wxUSE_OWNER_DRAWN
705 // space beneath/above each row in pixels
706 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
707 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
709 // the height is the same for all items
710 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
712 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
713 // message is not yet sent when we get here!
714 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
716 // only owner-drawn control should receive this message
717 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
719 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
721 HDC hdc
= CreateIC(wxT("DISPLAY"), NULL
, NULL
, 0);
724 dc
.SetHDC((WXHDC
)hdc
);
725 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_ANSI_VAR_FONT
));
727 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
728 pStruct
->itemWidth
= dc
.GetCharWidth();
737 // forward the message to the appropriate item
738 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
740 // only owner-drawn control should receive this message
741 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
743 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
744 UINT itemID
= pStruct
->itemID
;
746 // the item may be -1 for an empty listbox
747 if ( itemID
== (UINT
)-1 )
750 long data
= ListBox_GetItemData(GetHwnd(), pStruct
->itemID
);
752 wxCHECK( data
&& (data
!= LB_ERR
), FALSE
);
754 wxListBoxItem
*pItem
= (wxListBoxItem
*)data
;
756 wxDCTemp
dc((WXHDC
)pStruct
->hDC
);
757 wxRect
rect(wxPoint(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
),
758 wxPoint(pStruct
->rcItem
.right
, pStruct
->rcItem
.bottom
));
760 return pItem
->OnDrawItem(dc
, rect
,
761 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
762 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);
765 #endif // wxUSE_OWNER_DRAWN
767 #endif // wxUSE_LISTBOX