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 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
121 if (param == LBN_SELCANCEL)
123 event.extraLong = FALSE;
126 if (param
== LBN_SELCHANGE
)
128 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, m_windowId
);
129 wxArrayInt aSelections
;
130 int count
= GetSelections(aSelections
);
133 event
.m_commandInt
= aSelections
[0] ;
134 event
.m_clientData
= GetClientData(event
.m_commandInt
);
135 wxString
str(GetString(event
.m_commandInt
));
138 event
.m_commandString
= str
;
143 event
.m_commandInt
= -1 ;
144 event
.m_commandString
.Empty();
147 event
.SetEventObject( this );
148 ProcessCommand(event
);
151 else if (param
== LBN_DBLCLK
)
153 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, m_windowId
);
154 event
.SetEventObject( this );
155 GetEventHandler()->ProcessEvent(event
) ;
163 wxListBox::wxListBox()
169 bool wxListBox::Create(wxWindow
*parent
,
173 int n
, const wxString choices
[],
175 const wxValidator
& validator
,
176 const wxString
& name
)
183 SetValidator(validator
);
186 parent
->AddChild(this);
188 wxSystemSettings settings
;
189 SetBackgroundColour(settings
.GetSystemColour(wxSYS_COLOUR_WINDOW
));
190 SetForegroundColour(parent
->GetForegroundColour());
192 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
198 m_windowStyle
= style
;
200 DWORD wstyle
= WS_VISIBLE
| WS_VSCROLL
| WS_TABSTOP
|
201 LBS_NOTIFY
| LBS_HASSTRINGS
;
202 if (m_windowStyle
& wxLB_MULTIPLE
)
203 wstyle
|= LBS_MULTIPLESEL
;
204 else if (m_windowStyle
& wxLB_EXTENDED
)
205 wstyle
|= LBS_EXTENDEDSEL
;
207 if (m_windowStyle
& wxLB_ALWAYS_SB
)
208 wstyle
|= LBS_DISABLENOSCROLL
;
209 if (m_windowStyle
& wxLB_HSCROLL
)
210 wstyle
|= WS_HSCROLL
;
211 if (m_windowStyle
& wxLB_SORT
)
214 #if wxUSE_OWNER_DRAWN
215 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
216 // we don't support LBS_OWNERDRAWVARIABLE yet
217 wstyle
|= LBS_OWNERDRAWFIXED
;
221 // Without this style, you get unexpected heights, so e.g. constraint layout
222 // doesn't work properly
223 wstyle
|= LBS_NOINTEGRALHEIGHT
;
226 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
) ;
228 // Even with extended styles, need to combine with WS_BORDER
229 // for them to look right.
230 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
235 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, _T("LISTBOX"), NULL
,
238 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
239 wxGetInstance(), NULL
);
241 wxCHECK_MSG( m_hWnd
, FALSE
, _T("Failed to create listbox") );
246 Ctl3dSubclassCtl(GetHwnd());
251 // Subclass again to catch messages
255 for (ui
= 0; ui
< (size_t)n
; ui
++) {
259 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
260 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
262 SetFont(parent
->GetFont());
264 SetSize(x
, y
, width
, height
);
271 wxListBox::~wxListBox()
273 #if wxUSE_OWNER_DRAWN
274 size_t uiCount
= m_aItems
.Count();
275 while ( uiCount
-- != 0 ) {
276 delete m_aItems
[uiCount
];
278 #endif // wxUSE_OWNER_DRAWN
281 void wxListBox::SetupColours()
283 SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW
));
284 SetForegroundColour(GetParent()->GetForegroundColour());
287 void wxListBox::SetFirstItem(int N
)
289 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
290 _T("invalid index in wxListBox::SetFirstItem") );
292 SendMessage(GetHwnd(),LB_SETTOPINDEX
,(WPARAM
)N
,(LPARAM
)0) ;
295 void wxListBox::SetFirstItem(const wxString
& s
)
297 int N
= FindString(s
) ;
303 void wxListBox::Delete(int N
)
305 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
306 _T("invalid index in wxListBox::Delete") );
308 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
311 SetHorizontalExtent("");
314 void wxListBox::Append(const wxString
& item
)
316 int index
= ListBox_AddString(GetHwnd(), item
);
319 #if wxUSE_OWNER_DRAWN
320 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
321 wxOwnerDrawn
*pNewItem
= CreateItem(index
); // dummy argument
322 pNewItem
->SetName(item
);
323 m_aItems
.Add(pNewItem
);
324 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
328 SetHorizontalExtent(item
);
331 void wxListBox::Append(const wxString
& item
, void *Client_data
)
333 int index
= ListBox_AddString(GetHwnd(), item
);
336 #if wxUSE_OWNER_DRAWN
337 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
338 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
339 // in OnMeasure/OnDraw.
340 wxFAIL_MSG(_T("Can't use client data with owner-drawn listboxes"));
345 ListBox_SetItemData(GetHwnd(), index
, Client_data
);
347 SetHorizontalExtent(item
);
350 void wxListBox::Set(int n
, const wxString
*choices
, void** clientData
)
352 ShowWindow(GetHwnd(), SW_HIDE
);
353 ListBox_ResetContent(GetHwnd());
355 for (i
= 0; i
< n
; i
++)
357 ListBox_AddString(GetHwnd(), choices
[i
]);
359 ListBox_SetItemData(GetHwnd(), i
, clientData
[i
]);
363 #if wxUSE_OWNER_DRAWN
364 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
365 // first delete old items
366 size_t ui
= m_aItems
.Count();
367 while ( ui
-- != 0 ) {
372 // then create new ones
373 for (ui
= 0; ui
< (size_t)n
; ui
++) {
374 wxOwnerDrawn
*pNewItem
= CreateItem(ui
);
375 pNewItem
->SetName(choices
[ui
]);
376 m_aItems
.Add(pNewItem
);
377 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
379 wxASSERT_MSG(clientData
[ui
] == NULL
,
380 _T("Can't use client data with owner-drawn listboxes"));
385 SetHorizontalExtent("");
386 ShowWindow(GetHwnd(), SW_SHOW
);
389 int wxListBox::FindString(const wxString
& s
) const
391 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
398 void wxListBox::Clear()
400 ListBox_ResetContent(GetHwnd());
402 #if wxUSE_OWNER_DRAWN
403 size_t uiCount
= m_aItems
.Count();
404 while ( uiCount
-- != 0 ) {
405 delete m_aItems
[uiCount
];
409 #endif // wxUSE_OWNER_DRAWN
412 ListBox_GetHorizontalExtent(GetHwnd());
415 void wxListBox::SetSelection(int N
, bool select
)
417 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
418 _T("invalid index in wxListBox::SetSelection") );
420 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
421 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
427 SendMessage(GetHwnd(), LB_SETCURSEL
, N1
, 0);
431 bool wxListBox::Selected(int N
) const
433 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
434 _T("invalid index in wxListBox::Selected") );
436 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
439 void wxListBox::Deselect(int N
)
441 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
442 _T("invalid index in wxListBox::Deselect") );
444 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
445 SendMessage(GetHwnd(), LB_SETSEL
, FALSE
, N
);
448 void *wxListBox::GetClientData(int N
) const
450 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, NULL
,
451 _T("invalid index in wxListBox::GetClientData") );
453 return (void *)SendMessage(GetHwnd(), LB_GETITEMDATA
, N
, 0);
456 void wxListBox::SetClientData(int N
, void *Client_data
)
458 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
459 _T("invalid index in wxListBox::SetClientData") );
461 if ( ListBox_SetItemData(GetHwnd(), N
, Client_data
) == LB_ERR
)
462 wxLogDebug(_T("LB_SETITEMDATA failed"));
465 // Return number of selections and an array of selected integers
466 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
470 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
472 int no_sel
= ListBox_GetSelCount(GetHwnd());
474 int *selections
= new int[no_sel
];
475 if ( ListBox_GetSelItems(GetHwnd(), no_sel
, selections
) == LB_ERR
) {
476 wxFAIL_MSG(_T("This listbox can't have single-selection style!"));
479 aSelections
.Alloc(no_sel
);
480 for ( int n
= 0; n
< no_sel
; n
++ )
481 aSelections
.Add(selections
[n
]);
483 delete [] selections
;
488 else // single-selection listbox
490 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
496 // Get single selection, for single choice list items
497 int wxListBox::GetSelection() const
499 wxCHECK_MSG( !(m_windowStyle
& wxLB_MULTIPLE
) &&
500 !(m_windowStyle
& wxLB_EXTENDED
),
502 _T("GetSelection() can't be used with multiple-selection "
503 "listboxes, use GetSelections() instead.") );
505 return ListBox_GetCurSel(GetHwnd());
508 // Find string for position
509 wxString
wxListBox::GetString(int N
) const
511 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, "",
512 _T("invalid index in wxListBox::GetClientData") );
514 int len
= ListBox_GetTextLen(GetHwnd(), N
);
516 // +1 for terminating NUL
518 ListBox_GetText(GetHwnd(), N
, result
.GetWriteBuf(len
+ 1));
519 result
.UngetWriteBuf();
524 // Windows-specific code to set the horizontal extent of the listbox, if
525 // necessary. If s is non-NULL, it's used to calculate the horizontal extent.
526 // Otherwise, all strings are used.
527 void wxListBox::SetHorizontalExtent(const wxString
& s
)
529 // Only necessary if we want a horizontal scrollbar
530 if (!(m_windowStyle
& wxHSCROLL
))
532 TEXTMETRIC lpTextMetric
;
536 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
537 HDC dc
= GetWindowDC(GetHwnd());
539 if (GetFont().Ok() && GetFont().GetResourceHandle())
540 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
542 GetTextMetrics(dc
, &lpTextMetric
);
544 ::GetTextExtentPoint(dc
, (LPTSTR
) (const wxChar
*)s
, s
.Length(), &extentXY
);
545 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
548 ::SelectObject(dc
, oldFont
);
550 ReleaseDC(GetHwnd(), dc
);
551 if (extentX
> existingExtent
)
552 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
557 int largestExtent
= 0;
558 HDC dc
= GetWindowDC(GetHwnd());
560 if (GetFont().Ok() && GetFont().GetResourceHandle())
561 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
563 GetTextMetrics(dc
, &lpTextMetric
);
565 for (i
= 0; i
< m_noItems
; i
++)
567 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LONG
)wxBuffer
);
570 ::GetTextExtentPoint(dc
, (LPTSTR
)wxBuffer
, len
, &extentXY
);
571 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
572 if (extentX
> largestExtent
)
573 largestExtent
= extentX
;
576 ::SelectObject(dc
, oldFont
);
578 ReleaseDC(GetHwnd(), dc
);
579 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
584 wxListBox::InsertItems(int nItems
, const wxString items
[], int pos
)
586 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
587 _T("invalid index in wxListBox::InsertItems") );
590 for (i
= 0; i
< nItems
; i
++)
591 ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
594 SetHorizontalExtent(_T(""));
597 void wxListBox::SetString(int N
, const wxString
& s
)
599 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
600 _T("invalid index in wxListBox::SetString") );
603 if (!(m_windowStyle
& wxLB_MULTIPLE
) && !(m_windowStyle
& wxLB_EXTENDED
))
604 sel
= GetSelection();
606 void *oldData
= wxListBox::GetClientData(N
);
608 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
611 if (N
== (m_noItems
- 1))
614 SendMessage(GetHwnd(), LB_INSERTSTRING
, newN
, (LPARAM
) (const wxChar
*)s
);
616 wxListBox::SetClientData(N
, oldData
);
618 // Selection may have changed
622 #if wxUSE_OWNER_DRAWN
623 if ( m_windowStyle
& wxLB_OWNERDRAW
)
624 // update item's text
625 m_aItems
[N
]->SetName(s
);
626 #endif //USE_OWNER_DRAWN
629 int wxListBox::Number () const
634 // For single selection items only
635 wxString
wxListBox::GetStringSelection () const
637 int sel
= GetSelection ();
639 return this->GetString (sel
);
644 bool wxListBox::SetStringSelection (const wxString
& s
, bool flag
)
646 int sel
= FindString (s
);
649 SetSelection (sel
, flag
);
656 wxSize
wxListBox::DoGetBestSize()
658 // find the widest string
661 for ( int i
= 0; i
< m_noItems
; i
++ )
663 wxString
str(GetString(i
));
664 GetTextExtent(str
, &wLine
, NULL
);
665 if ( wLine
> wListbox
)
669 // give it some reasonable default value if there are no strings in the
674 // the listbox should be slightly larger than the widest string
676 wxGetCharSize(GetHWND(), &cx
, &cy
, &GetFont());
680 int hListbox
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
)*(wxMax(m_noItems
, 7));
682 return wxSize(wListbox
, hListbox
);
685 // Is this the right thing? Won't setselection generate a command
686 // event too? No! It'll just generate a setselection event.
687 // But we still can't have this being called whenever a real command
688 // is generated, because it sets the selection, which will already
689 // have been done! (Unless we have an optional argument for calling
690 // by the actual window system, or a separate function, ProcessCommand)
691 void wxListBox::Command (wxCommandEvent
& event
)
693 if (event
.m_extraLong
)
694 SetSelection (event
.m_commandInt
);
697 Deselect (event
.m_commandInt
);
700 ProcessCommand (event
);
703 WXHBRUSH
wxListBox::OnCtlColor(WXHDC pDC
, WXHWND pWnd
, WXUINT nCtlColor
,
704 WXUINT message
, WXWPARAM wParam
, WXLPARAM lParam
)
709 HBRUSH hbrush
= Ctl3dCtlColorEx(message
, wParam
, lParam
);
710 return (WXHBRUSH
) hbrush
;
714 if (GetParent()->GetTransparentBackground())
715 SetBkMode((HDC
) pDC
, TRANSPARENT
);
717 SetBkMode((HDC
) pDC
, OPAQUE
);
719 ::SetBkColor((HDC
) pDC
, RGB(GetBackgroundColour().Red(), GetBackgroundColour().Green(), GetBackgroundColour().Blue()));
720 ::SetTextColor((HDC
) pDC
, RGB(GetForegroundColour().Red(), GetForegroundColour().Green(), GetForegroundColour().Blue()));
722 wxBrush
*backgroundBrush
= wxTheBrushList
->FindOrCreateBrush(GetBackgroundColour(), wxSOLID
);
724 // Note that this will be cleaned up in wxApp::OnIdle, if backgroundBrush
725 // has a zero usage count.
726 backgroundBrush
->RealizeResource();
727 return (WXHBRUSH
) backgroundBrush
->GetResourceHandle();
730 long wxListBox::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
732 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
735 #if wxUSE_OWNER_DRAWN
740 // space beneath/above each row in pixels
741 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
742 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
744 // the height is the same for all items
745 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
747 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
748 // message is not yet sent when we get here!
749 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
751 // only owner-drawn control should receive this message
752 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
754 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
757 dc
.SetHDC((WXHDC
)CreateIC(_T("DISPLAY"), NULL
, NULL
, 0));
758 dc
.SetFont(wxSystemSettings::GetSystemFont(wxSYS_ANSI_VAR_FONT
));
760 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
761 pStruct
->itemWidth
= dc
.GetCharWidth();
766 // forward the message to the appropriate item
767 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
769 // only owner-drawn control should receive this message
770 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
772 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
774 long data
= ListBox_GetItemData(GetHwnd(), pStruct
->itemID
);
776 wxCHECK( data
&& (data
!= LB_ERR
), FALSE
);
778 wxListBoxItem
*pItem
= (wxListBoxItem
*)data
;
781 dc
.SetHDC((WXHDC
)pStruct
->hDC
, FALSE
);
782 wxRect
rect(wxPoint(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
),
783 wxPoint(pStruct
->rcItem
.right
, pStruct
->rcItem
.bottom
));
785 return pItem
->OnDrawItem(dc
, rect
,
786 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
787 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);