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 #if defined(__GNUWIN32__)
52 #ifndef wxUSE_NORLANDER_HEADERS
53 #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 #if !USE_SHARED_LIBRARY
86 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
89 // ============================================================================
90 // list box item declaration and implementation
91 // ============================================================================
95 class wxListBoxItem
: public wxOwnerDrawn
98 wxListBoxItem(const wxString
& str
= "");
101 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
103 // no bitmaps/checkmarks
107 wxOwnerDrawn
*wxListBox::CreateItem(size_t n
)
109 return new wxListBoxItem();
112 #endif //USE_OWNER_DRAWN
114 // ============================================================================
115 // list box control implementation
116 // ============================================================================
118 // ----------------------------------------------------------------------------
120 // ----------------------------------------------------------------------------
123 wxListBox::wxListBox()
129 bool wxListBox::Create(wxWindow
*parent
,
133 int n
, const wxString choices
[],
135 const wxValidator
& validator
,
136 const wxString
& name
)
143 SetValidator(validator
);
146 parent
->AddChild(this);
148 wxSystemSettings settings
;
149 SetBackgroundColour(settings
.GetSystemColour(wxSYS_COLOUR_WINDOW
));
150 SetForegroundColour(parent
->GetForegroundColour());
152 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
158 m_windowStyle
= style
;
160 DWORD wstyle
= WS_VISIBLE
| WS_VSCROLL
| WS_TABSTOP
|
161 LBS_NOTIFY
| LBS_HASSTRINGS
;
162 if (m_windowStyle
& wxLB_MULTIPLE
)
163 wstyle
|= LBS_MULTIPLESEL
;
164 else if (m_windowStyle
& wxLB_EXTENDED
)
165 wstyle
|= LBS_EXTENDEDSEL
;
167 if (m_windowStyle
& wxLB_ALWAYS_SB
)
168 wstyle
|= LBS_DISABLENOSCROLL
;
169 if (m_windowStyle
& wxLB_HSCROLL
)
170 wstyle
|= WS_HSCROLL
;
171 if (m_windowStyle
& wxLB_SORT
)
174 #if wxUSE_OWNER_DRAWN
175 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
176 // we don't support LBS_OWNERDRAWVARIABLE yet
177 wstyle
|= LBS_OWNERDRAWFIXED
;
181 // Without this style, you get unexpected heights, so e.g. constraint layout
182 // doesn't work properly
183 wstyle
|= LBS_NOINTEGRALHEIGHT
;
186 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
);
188 // Even with extended styles, need to combine with WS_BORDER for them to
190 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
195 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, wxT("LISTBOX"), NULL
,
198 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
199 wxGetInstance(), NULL
);
201 wxCHECK_MSG( m_hWnd
, FALSE
, wxT("Failed to create listbox") );
206 Ctl3dSubclassCtl(GetHwnd());
211 // Subclass again to catch messages
215 for (ui
= 0; ui
< (size_t)n
; ui
++) {
219 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
220 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
222 SetFont(parent
->GetFont());
224 SetSize(x
, y
, width
, height
);
231 wxListBox::~wxListBox()
233 #if wxUSE_OWNER_DRAWN
234 size_t uiCount
= m_aItems
.Count();
235 while ( uiCount
-- != 0 ) {
236 delete m_aItems
[uiCount
];
238 #endif // wxUSE_OWNER_DRAWN
241 void wxListBox::SetupColours()
243 SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW
));
244 SetForegroundColour(GetParent()->GetForegroundColour());
247 // ----------------------------------------------------------------------------
248 // implementation of wxListBoxBase methods
249 // ----------------------------------------------------------------------------
251 void wxListBox::DoSetFirstItem(int N
)
253 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
254 wxT("invalid index in wxListBox::SetFirstItem") );
256 SendMessage(GetHwnd(), LB_SETTOPINDEX
, (WPARAM
)N
, (LPARAM
)0);
259 void wxListBox::Delete(int N
)
261 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
262 wxT("invalid index in wxListBox::Delete") );
264 #if wxUSE_OWNER_DRAWN
267 #else // !wxUSE_OWNER_DRAWN
268 if ( HasClientObjectData() )
270 delete GetClientObject(N
);
272 #endif // wxUSE_OWNER_DRAWN/!wxUSE_OWNER_DRAWN
274 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
277 SetHorizontalExtent("");
280 int wxListBox::DoAppend(const wxString
& item
)
282 int index
= ListBox_AddString(GetHwnd(), item
);
285 #if wxUSE_OWNER_DRAWN
286 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
287 wxOwnerDrawn
*pNewItem
= CreateItem(index
); // dummy argument
288 pNewItem
->SetName(item
);
289 m_aItems
.Add(pNewItem
);
290 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
294 SetHorizontalExtent(item
);
299 void wxListBox::DoSetItems(const wxArrayString
& choices
, void** clientData
)
301 ShowWindow(GetHwnd(), SW_HIDE
);
303 ListBox_ResetContent(GetHwnd());
305 m_noItems
= choices
.GetCount();
307 for (i
= 0; i
< m_noItems
; i
++)
309 ListBox_AddString(GetHwnd(), choices
[i
]);
312 #if wxUSE_OWNER_DRAWN
313 wxASSERT_MSG(clientData
[i
] == NULL
,
314 wxT("Can't use client data with owner-drawn listboxes"));
315 #else // !wxUSE_OWNER_DRAWN
316 ListBox_SetItemData(GetHwnd(), i
, clientData
[i
]);
317 #endif // wxUSE_OWNER_DRAWN/!wxUSE_OWNER_DRAWN
321 #if wxUSE_OWNER_DRAWN
322 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
323 // first delete old items
324 size_t ui
= m_aItems
.Count();
325 while ( ui
-- != 0 ) {
330 // then create new ones
331 for ( ui
= 0; ui
< (size_t)m_noItems
; ui
++ ) {
332 wxOwnerDrawn
*pNewItem
= CreateItem(ui
);
333 pNewItem
->SetName(choices
[ui
]);
334 m_aItems
.Add(pNewItem
);
335 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
338 #endif // wxUSE_OWNER_DRAWN
340 SetHorizontalExtent();
342 ShowWindow(GetHwnd(), SW_SHOW
);
345 int wxListBox::FindString(const wxString
& s
) const
347 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
354 void wxListBox::Clear()
356 #if wxUSE_OWNER_DRAWN
357 size_t uiCount
= m_aItems
.Count();
358 while ( uiCount
-- != 0 ) {
359 delete m_aItems
[uiCount
];
363 #else // !wxUSE_OWNER_DRAWN
364 if ( HasClientObjectData() )
366 for ( size_t n
= 0; n
< (size_t)m_noItems
; n
++ )
368 delete GetClientObject(n
);
371 #endif // wxUSE_OWNER_DRAWN/!wxUSE_OWNER_DRAWN
373 ListBox_ResetContent(GetHwnd());
376 SetHorizontalExtent();
379 void wxListBox::SetSelection(int N
, bool select
)
381 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
382 wxT("invalid index in wxListBox::SetSelection") );
384 if ( HasMultipleSelection() )
386 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
390 SendMessage(GetHwnd(), LB_SETCURSEL
, select
? N
: -1, 0);
394 bool wxListBox::IsSelected(int N
) const
396 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
397 wxT("invalid index in wxListBox::Selected") );
399 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
402 wxClientData
* wxListBox::DoGetItemClientObject(int n
) const
404 return (wxClientData
*)DoGetItemClientData(n
);
407 void *wxListBox::DoGetItemClientData(int n
) const
409 wxCHECK_MSG( n
>= 0 && n
< m_noItems
, NULL
,
410 wxT("invalid index in wxListBox::GetClientData") );
412 return (void *)SendMessage(GetHwnd(), LB_GETITEMDATA
, n
, 0);
415 void wxListBox::DoSetItemClientObject(int n
, wxClientData
* clientData
)
417 DoSetItemClientData(n
, clientData
);
420 void wxListBox::DoSetItemClientData(int n
, void *clientData
)
422 wxCHECK_RET( n
>= 0 && n
< m_noItems
,
423 wxT("invalid index in wxListBox::SetClientData") );
425 #if wxUSE_OWNER_DRAWN
426 if ( m_windowStyle
& wxLB_OWNERDRAW
)
428 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
429 // in OnMeasure/OnDraw.
430 wxFAIL_MSG(wxT("Can't use client data with owner-drawn listboxes"));
432 #endif // wxUSE_OWNER_DRAWN
434 if ( ListBox_SetItemData(GetHwnd(), n
, clientData
) == LB_ERR
)
435 wxLogDebug(wxT("LB_SETITEMDATA failed"));
438 bool wxListBox::HasMultipleSelection() const
440 return (m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
);
443 // Return number of selections and an array of selected integers
444 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
448 if ( HasMultipleSelection() )
450 int no_sel
= ListBox_GetSelCount(GetHwnd());
452 int *selections
= new int[no_sel
];
453 int rc
= ListBox_GetSelItems(GetHwnd(), no_sel
, selections
);
455 wxCHECK_MSG(rc
!= LB_ERR
, -1, wxT("ListBox_GetSelItems failed"));
457 aSelections
.Alloc(no_sel
);
458 for ( int n
= 0; n
< no_sel
; n
++ )
459 aSelections
.Add(selections
[n
]);
461 delete [] selections
;
466 else // single-selection listbox
468 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
474 // Get single selection, for single choice list items
475 int wxListBox::GetSelection() const
477 wxCHECK_MSG( !HasMultipleSelection(),
479 wxT("GetSelection() can't be used with multiple-selection "
480 "listboxes, use GetSelections() instead.") );
482 return ListBox_GetCurSel(GetHwnd());
485 // Find string for position
486 wxString
wxListBox::GetString(int N
) const
488 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, "",
489 wxT("invalid index in wxListBox::GetClientData") );
491 int len
= ListBox_GetTextLen(GetHwnd(), N
);
493 // +1 for terminating NUL
495 ListBox_GetText(GetHwnd(), N
, result
.GetWriteBuf(len
+ 1));
496 result
.UngetWriteBuf();
502 wxListBox::DoInsertItems(const wxArrayString
& items
, int pos
)
504 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
505 wxT("invalid index in wxListBox::InsertItems") );
507 int nItems
= items
.GetCount();
508 for ( int i
= 0; i
< nItems
; i
++ )
509 ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
512 SetHorizontalExtent();
515 void wxListBox::SetString(int N
, const wxString
& s
)
517 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
518 wxT("invalid index in wxListBox::SetString") );
520 // remember the state of the item
521 bool wasSelected
= IsSelected(N
);
523 void *oldData
= NULL
;
524 wxClientData
*oldObjData
= NULL
;
525 if ( m_clientDataItemsType
== ClientData_Void
)
526 oldData
= GetClientData(N
);
527 else if ( m_clientDataItemsType
== ClientData_Object
)
528 oldObjData
= GetClientObject(N
);
530 // delete and recreate it
531 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
534 if ( N
== m_noItems
- 1 )
537 ListBox_InsertString(GetHwnd(), newN
, s
);
539 // restore the client data
541 SetClientData(N
, oldData
);
542 else if ( oldObjData
)
543 SetClientObject(N
, oldObjData
);
545 // we may have lost the selection
549 #if wxUSE_OWNER_DRAWN
550 if ( m_windowStyle
& wxLB_OWNERDRAW
)
551 // update item's text
552 m_aItems
[N
]->SetName(s
);
553 #endif //USE_OWNER_DRAWN
556 int wxListBox::GetCount() const
561 // ----------------------------------------------------------------------------
563 // ----------------------------------------------------------------------------
565 // Windows-specific code to set the horizontal extent of the listbox, if
566 // necessary. If s is non-NULL, it's used to calculate the horizontal extent.
567 // Otherwise, all strings are used.
568 void wxListBox::SetHorizontalExtent(const wxString
& s
)
570 // Only necessary if we want a horizontal scrollbar
571 if (!(m_windowStyle
& wxHSCROLL
))
573 TEXTMETRIC lpTextMetric
;
577 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
578 HDC dc
= GetWindowDC(GetHwnd());
580 if (GetFont().Ok() && GetFont().GetResourceHandle())
581 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
583 GetTextMetrics(dc
, &lpTextMetric
);
585 ::GetTextExtentPoint(dc
, (LPTSTR
) (const wxChar
*)s
, s
.Length(), &extentXY
);
586 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
589 ::SelectObject(dc
, oldFont
);
591 ReleaseDC(GetHwnd(), dc
);
592 if (extentX
> existingExtent
)
593 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
597 int largestExtent
= 0;
598 HDC dc
= GetWindowDC(GetHwnd());
600 if (GetFont().Ok() && GetFont().GetResourceHandle())
601 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
603 GetTextMetrics(dc
, &lpTextMetric
);
605 for (i
= 0; i
< m_noItems
; i
++)
607 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LONG
)wxBuffer
);
610 ::GetTextExtentPoint(dc
, (LPTSTR
)wxBuffer
, len
, &extentXY
);
611 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
612 if (extentX
> largestExtent
)
613 largestExtent
= extentX
;
616 ::SelectObject(dc
, oldFont
);
618 ReleaseDC(GetHwnd(), dc
);
619 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
623 wxSize
wxListBox::DoGetBestSize()
625 // find the widest string
628 for ( int i
= 0; i
< m_noItems
; i
++ )
630 wxString
str(GetString(i
));
631 GetTextExtent(str
, &wLine
, NULL
);
632 if ( wLine
> wListbox
)
636 // give it some reasonable default value if there are no strings in the
641 // the listbox should be slightly larger than the widest string
643 wxGetCharSize(GetHWND(), &cx
, &cy
, &GetFont());
647 int hListbox
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
)*(wxMax(m_noItems
, 7));
649 return wxSize(wListbox
, hListbox
);
652 // ----------------------------------------------------------------------------
654 // ----------------------------------------------------------------------------
656 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
659 if (param == LBN_SELCANCEL)
661 event.extraLong = FALSE;
664 if (param
== LBN_SELCHANGE
)
666 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, m_windowId
);
667 wxArrayInt aSelections
;
668 int count
= GetSelections(aSelections
);
671 event
.m_commandInt
= aSelections
[0];
672 event
.m_clientData
= GetClientData(event
.m_commandInt
);
673 wxString
str(GetString(event
.m_commandInt
));
676 event
.m_commandString
= str
;
681 event
.m_commandInt
= -1;
682 event
.m_commandString
.Empty();
685 event
.SetEventObject( this );
686 ProcessCommand(event
);
689 else if (param
== LBN_DBLCLK
)
691 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, m_windowId
);
692 event
.SetEventObject( this );
693 GetEventHandler()->ProcessEvent(event
);
700 WXHBRUSH
wxListBox::OnCtlColor(WXHDC pDC
, WXHWND pWnd
, WXUINT nCtlColor
,
701 WXUINT message
, WXWPARAM wParam
, WXLPARAM lParam
)
706 HBRUSH hbrush
= Ctl3dCtlColorEx(message
, wParam
, lParam
);
707 return (WXHBRUSH
) hbrush
;
711 if (GetParent()->GetTransparentBackground())
712 SetBkMode((HDC
) pDC
, TRANSPARENT
);
714 SetBkMode((HDC
) pDC
, OPAQUE
);
716 ::SetBkColor((HDC
) pDC
, RGB(GetBackgroundColour().Red(), GetBackgroundColour().Green(), GetBackgroundColour().Blue()));
717 ::SetTextColor((HDC
) pDC
, RGB(GetForegroundColour().Red(), GetForegroundColour().Green(), GetForegroundColour().Blue()));
719 wxBrush
*backgroundBrush
= wxTheBrushList
->FindOrCreateBrush(GetBackgroundColour(), wxSOLID
);
721 // Note that this will be cleaned up in wxApp::OnIdle, if backgroundBrush
722 // has a zero usage count.
723 backgroundBrush
->RealizeResource();
724 return (WXHBRUSH
) backgroundBrush
->GetResourceHandle();
727 // ----------------------------------------------------------------------------
728 // wxCheckListBox support
729 // ----------------------------------------------------------------------------
731 #if wxUSE_OWNER_DRAWN
736 // space beneath/above each row in pixels
737 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
738 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
740 // the height is the same for all items
741 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
743 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
744 // message is not yet sent when we get here!
745 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
747 // only owner-drawn control should receive this message
748 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
750 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
753 dc
.SetHDC((WXHDC
)CreateIC(wxT("DISPLAY"), NULL
, NULL
, 0));
754 dc
.SetFont(wxSystemSettings::GetSystemFont(wxSYS_ANSI_VAR_FONT
));
756 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
757 pStruct
->itemWidth
= dc
.GetCharWidth();
762 // forward the message to the appropriate item
763 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
765 // only owner-drawn control should receive this message
766 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
768 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
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
);