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 #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 #if !USE_SHARED_LIBRARY
84 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
87 // ============================================================================
88 // list box item declaration and implementation
89 // ============================================================================
93 class wxListBoxItem
: public wxOwnerDrawn
96 wxListBoxItem(const wxString
& str
= "");
99 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
101 // no bitmaps/checkmarks
105 wxOwnerDrawn
*wxListBox::CreateItem(size_t n
)
107 return new wxListBoxItem();
110 #endif //USE_OWNER_DRAWN
112 // ============================================================================
113 // list box control implementation
114 // ============================================================================
116 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
119 if (param == LBN_SELCANCEL)
121 event.extraLong = FALSE;
124 if (param
== LBN_SELCHANGE
)
126 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, m_windowId
);
127 wxArrayInt aSelections
;
128 int count
= GetSelections(aSelections
);
131 event
.m_commandInt
= aSelections
[0] ;
132 event
.m_clientData
= GetClientData(event
.m_commandInt
);
133 wxString
str(GetString(event
.m_commandInt
));
136 event
.m_commandString
= str
;
141 event
.m_commandInt
= -1 ;
142 event
.m_commandString
.Empty();
145 event
.SetEventObject( this );
146 ProcessCommand(event
);
149 else if (param
== LBN_DBLCLK
)
151 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, m_windowId
);
152 event
.SetEventObject( this );
153 GetEventHandler()->ProcessEvent(event
) ;
161 wxListBox::wxListBox()
167 bool wxListBox::Create(wxWindow
*parent
,
171 int n
, const wxString choices
[],
173 const wxValidator
& validator
,
174 const wxString
& name
)
181 SetValidator(validator
);
184 parent
->AddChild(this);
186 wxSystemSettings settings
;
187 SetBackgroundColour(settings
.GetSystemColour(wxSYS_COLOUR_WINDOW
));
188 SetForegroundColour(parent
->GetForegroundColour());
190 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
196 m_windowStyle
= style
;
198 DWORD wstyle
= WS_VISIBLE
| WS_VSCROLL
| WS_TABSTOP
|
199 LBS_NOTIFY
| LBS_HASSTRINGS
;
200 if (m_windowStyle
& wxLB_MULTIPLE
)
201 wstyle
|= LBS_MULTIPLESEL
;
202 else if (m_windowStyle
& wxLB_EXTENDED
)
203 wstyle
|= LBS_EXTENDEDSEL
;
205 if (m_windowStyle
& wxLB_ALWAYS_SB
)
206 wstyle
|= LBS_DISABLENOSCROLL
;
207 if (m_windowStyle
& wxLB_HSCROLL
)
208 wstyle
|= WS_HSCROLL
;
209 if (m_windowStyle
& wxLB_SORT
)
212 #if wxUSE_OWNER_DRAWN
213 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
214 // we don't support LBS_OWNERDRAWVARIABLE yet
215 wstyle
|= LBS_OWNERDRAWFIXED
;
219 // Without this style, you get unexpected heights, so e.g. constraint layout
220 // doesn't work properly
221 wstyle
|= LBS_NOINTEGRALHEIGHT
;
224 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
) ;
226 // Even with extended styles, need to combine with WS_BORDER
227 // for them to look right.
228 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
233 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, _T("LISTBOX"), NULL
,
236 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
237 wxGetInstance(), NULL
);
239 wxCHECK_MSG( m_hWnd
, FALSE
, _T("Failed to create listbox") );
244 Ctl3dSubclassCtl(GetHwnd());
249 // Subclass again to catch messages
253 for (ui
= 0; ui
< (size_t)n
; ui
++) {
257 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
258 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
260 SetFont(parent
->GetFont());
262 SetSize(x
, y
, width
, height
);
269 wxListBox::~wxListBox()
271 #if wxUSE_OWNER_DRAWN
272 size_t uiCount
= m_aItems
.Count();
273 while ( uiCount
-- != 0 ) {
274 delete m_aItems
[uiCount
];
276 #endif // wxUSE_OWNER_DRAWN
279 void wxListBox::SetupColours()
281 SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW
));
282 SetForegroundColour(GetParent()->GetForegroundColour());
285 void wxListBox::SetFirstItem(int N
)
287 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
288 _T("invalid index in wxListBox::SetFirstItem") );
290 SendMessage(GetHwnd(),LB_SETTOPINDEX
,(WPARAM
)N
,(LPARAM
)0) ;
293 void wxListBox::SetFirstItem(const wxString
& s
)
295 int N
= FindString(s
) ;
301 void wxListBox::Delete(int N
)
303 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
304 _T("invalid index in wxListBox::Delete") );
306 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
309 SetHorizontalExtent("");
312 void wxListBox::Append(const wxString
& item
)
314 int index
= ListBox_AddString(GetHwnd(), item
);
317 #if wxUSE_OWNER_DRAWN
318 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
319 wxOwnerDrawn
*pNewItem
= CreateItem(index
); // dummy argument
320 pNewItem
->SetName(item
);
321 m_aItems
.Add(pNewItem
);
322 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
326 SetHorizontalExtent(item
);
329 void wxListBox::Append(const wxString
& item
, void *Client_data
)
331 int index
= ListBox_AddString(GetHwnd(), item
);
334 #if wxUSE_OWNER_DRAWN
335 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
336 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
337 // in OnMeasure/OnDraw.
338 wxFAIL_MSG(_T("Can't use client data with owner-drawn listboxes"));
343 ListBox_SetItemData(GetHwnd(), index
, Client_data
);
345 SetHorizontalExtent(item
);
348 void wxListBox::Set(int n
, const wxString
*choices
, void** clientData
)
350 ShowWindow(GetHwnd(), SW_HIDE
);
351 ListBox_ResetContent(GetHwnd());
353 for (i
= 0; i
< n
; i
++)
355 ListBox_AddString(GetHwnd(), choices
[i
]);
357 ListBox_SetItemData(GetHwnd(), i
, clientData
[i
]);
361 #if wxUSE_OWNER_DRAWN
362 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
363 // first delete old items
364 size_t ui
= m_aItems
.Count();
365 while ( ui
-- != 0 ) {
370 // then create new ones
371 for (ui
= 0; ui
< (size_t)n
; ui
++) {
372 wxOwnerDrawn
*pNewItem
= CreateItem(ui
);
373 pNewItem
->SetName(choices
[ui
]);
374 m_aItems
.Add(pNewItem
);
375 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
377 wxASSERT_MSG(clientData
[ui
] == NULL
,
378 _T("Can't use client data with owner-drawn listboxes"));
383 SetHorizontalExtent("");
384 ShowWindow(GetHwnd(), SW_SHOW
);
387 int wxListBox::FindString(const wxString
& s
) const
389 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
396 void wxListBox::Clear()
398 ListBox_ResetContent(GetHwnd());
400 #if wxUSE_OWNER_DRAWN
401 size_t uiCount
= m_aItems
.Count();
402 while ( uiCount
-- != 0 ) {
403 delete m_aItems
[uiCount
];
407 #endif // wxUSE_OWNER_DRAWN
410 ListBox_GetHorizontalExtent(GetHwnd());
413 void wxListBox::SetSelection(int N
, bool select
)
415 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
416 _T("invalid index in wxListBox::SetSelection") );
418 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
419 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
425 SendMessage(GetHwnd(), LB_SETCURSEL
, N1
, 0);
429 bool wxListBox::Selected(int N
) const
431 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
432 _T("invalid index in wxListBox::Selected") );
434 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
437 void wxListBox::Deselect(int N
)
439 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
440 _T("invalid index in wxListBox::Deselect") );
442 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
443 SendMessage(GetHwnd(), LB_SETSEL
, FALSE
, N
);
446 void *wxListBox::GetClientData(int N
) const
448 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, NULL
,
449 _T("invalid index in wxListBox::GetClientData") );
451 return (void *)SendMessage(GetHwnd(), LB_GETITEMDATA
, N
, 0);
454 void wxListBox::SetClientData(int N
, void *Client_data
)
456 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
457 _T("invalid index in wxListBox::SetClientData") );
459 if ( ListBox_SetItemData(GetHwnd(), N
, Client_data
) == LB_ERR
)
460 wxLogDebug(_T("LB_SETITEMDATA failed"));
463 // Return number of selections and an array of selected integers
464 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
468 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
470 int no_sel
= ListBox_GetSelCount(GetHwnd());
472 int *selections
= new int[no_sel
];
473 if ( ListBox_GetSelItems(GetHwnd(), no_sel
, selections
) == LB_ERR
) {
474 wxFAIL_MSG(_T("This listbox can't have single-selection style!"));
477 aSelections
.Alloc(no_sel
);
478 for ( int n
= 0; n
< no_sel
; n
++ )
479 aSelections
.Add(selections
[n
]);
481 delete [] selections
;
486 else // single-selection listbox
488 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
494 // Get single selection, for single choice list items
495 int wxListBox::GetSelection() const
497 wxCHECK_MSG( !(m_windowStyle
& wxLB_MULTIPLE
) &&
498 !(m_windowStyle
& wxLB_EXTENDED
),
500 _T("GetSelection() can't be used with multiple-selection "
501 "listboxes, use GetSelections() instead.") );
503 return ListBox_GetCurSel(GetHwnd());
506 // Find string for position
507 wxString
wxListBox::GetString(int N
) const
509 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, "",
510 _T("invalid index in wxListBox::GetClientData") );
512 int len
= ListBox_GetTextLen(GetHwnd(), N
);
514 // +1 for terminating NUL
516 ListBox_GetText(GetHwnd(), N
, result
.GetWriteBuf(len
+ 1));
517 result
.UngetWriteBuf();
522 // Windows-specific code to set the horizontal extent of the listbox, if
523 // necessary. If s is non-NULL, it's used to calculate the horizontal extent.
524 // Otherwise, all strings are used.
525 void wxListBox::SetHorizontalExtent(const wxString
& s
)
527 // Only necessary if we want a horizontal scrollbar
528 if (!(m_windowStyle
& wxHSCROLL
))
530 TEXTMETRIC lpTextMetric
;
534 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
535 HDC dc
= GetWindowDC(GetHwnd());
537 if (GetFont().Ok() && GetFont().GetResourceHandle())
538 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
540 GetTextMetrics(dc
, &lpTextMetric
);
542 ::GetTextExtentPoint(dc
, (LPTSTR
) (const wxChar
*)s
, s
.Length(), &extentXY
);
543 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
546 ::SelectObject(dc
, oldFont
);
548 ReleaseDC(GetHwnd(), dc
);
549 if (extentX
> existingExtent
)
550 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
555 int largestExtent
= 0;
556 HDC dc
= GetWindowDC(GetHwnd());
558 if (GetFont().Ok() && GetFont().GetResourceHandle())
559 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
561 GetTextMetrics(dc
, &lpTextMetric
);
563 for (i
= 0; i
< m_noItems
; i
++)
565 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LONG
)wxBuffer
);
568 ::GetTextExtentPoint(dc
, (LPTSTR
)wxBuffer
, len
, &extentXY
);
569 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
570 if (extentX
> largestExtent
)
571 largestExtent
= extentX
;
574 ::SelectObject(dc
, oldFont
);
576 ReleaseDC(GetHwnd(), dc
);
577 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
582 wxListBox::InsertItems(int nItems
, const wxString items
[], int pos
)
584 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
585 _T("invalid index in wxListBox::InsertItems") );
588 for (i
= 0; i
< nItems
; i
++)
589 ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
592 SetHorizontalExtent(_T(""));
595 void wxListBox::SetString(int N
, const wxString
& s
)
597 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
598 _T("invalid index in wxListBox::SetString") );
601 if (!(m_windowStyle
& wxLB_MULTIPLE
) && !(m_windowStyle
& wxLB_EXTENDED
))
602 sel
= GetSelection();
604 void *oldData
= wxListBox::GetClientData(N
);
606 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
609 if (N
== (m_noItems
- 1))
612 SendMessage(GetHwnd(), LB_INSERTSTRING
, newN
, (LPARAM
) (const wxChar
*)s
);
614 wxListBox::SetClientData(N
, oldData
);
616 // Selection may have changed
620 #if wxUSE_OWNER_DRAWN
621 if ( m_windowStyle
& wxLB_OWNERDRAW
)
622 // update item's text
623 m_aItems
[N
]->SetName(s
);
624 #endif //USE_OWNER_DRAWN
627 int wxListBox::Number () const
632 // For single selection items only
633 wxString
wxListBox::GetStringSelection () const
635 int sel
= GetSelection ();
637 return this->GetString (sel
);
642 bool wxListBox::SetStringSelection (const wxString
& s
, bool flag
)
644 int sel
= FindString (s
);
647 SetSelection (sel
, flag
);
654 wxSize
wxListBox::DoGetBestSize()
656 // find the widest string
659 for ( int i
= 0; i
< m_noItems
; i
++ )
661 wxString
str(GetString(i
));
662 GetTextExtent(str
, &wLine
, NULL
);
663 if ( wLine
> wListbox
)
667 // give it some reasonable default value if there are no strings in the
672 // the listbox should be slightly larger than the widest string
674 wxGetCharSize(GetHWND(), &cx
, &cy
, &GetFont());
678 int hListbox
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
)*(wxMax(m_noItems
, 7));
680 return wxSize(wListbox
, hListbox
);
683 // Is this the right thing? Won't setselection generate a command
684 // event too? No! It'll just generate a setselection event.
685 // But we still can't have this being called whenever a real command
686 // is generated, because it sets the selection, which will already
687 // have been done! (Unless we have an optional argument for calling
688 // by the actual window system, or a separate function, ProcessCommand)
689 void wxListBox::Command (wxCommandEvent
& event
)
691 if (event
.m_extraLong
)
692 SetSelection (event
.m_commandInt
);
695 Deselect (event
.m_commandInt
);
698 ProcessCommand (event
);
701 WXHBRUSH
wxListBox::OnCtlColor(WXHDC pDC
, WXHWND pWnd
, WXUINT nCtlColor
,
702 WXUINT message
, WXWPARAM wParam
, WXLPARAM lParam
)
707 HBRUSH hbrush
= Ctl3dCtlColorEx(message
, wParam
, lParam
);
708 return (WXHBRUSH
) hbrush
;
712 if (GetParent()->GetTransparentBackground())
713 SetBkMode((HDC
) pDC
, TRANSPARENT
);
715 SetBkMode((HDC
) pDC
, OPAQUE
);
717 ::SetBkColor((HDC
) pDC
, RGB(GetBackgroundColour().Red(), GetBackgroundColour().Green(), GetBackgroundColour().Blue()));
718 ::SetTextColor((HDC
) pDC
, RGB(GetForegroundColour().Red(), GetForegroundColour().Green(), GetForegroundColour().Blue()));
720 wxBrush
*backgroundBrush
= wxTheBrushList
->FindOrCreateBrush(GetBackgroundColour(), wxSOLID
);
722 // Note that this will be cleaned up in wxApp::OnIdle, if backgroundBrush
723 // has a zero usage count.
724 backgroundBrush
->RealizeResource();
725 return (WXHBRUSH
) backgroundBrush
->GetResourceHandle();
728 long wxListBox::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
730 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
733 #if wxUSE_OWNER_DRAWN
738 // space beneath/above each row in pixels
739 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
740 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
742 // the height is the same for all items
743 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
745 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
746 // message is not yet sent when we get here!
747 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
749 // only owner-drawn control should receive this message
750 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
752 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
755 dc
.SetHDC((WXHDC
)CreateIC(_T("DISPLAY"), NULL
, NULL
, 0));
756 dc
.SetFont(wxSystemSettings::GetSystemFont(wxSYS_ANSI_VAR_FONT
));
758 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
759 pStruct
->itemWidth
= dc
.GetCharWidth();
764 // forward the message to the appropriate item
765 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
767 // only owner-drawn control should receive this message
768 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
770 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
772 long data
= ListBox_GetItemData(GetHwnd(), pStruct
->itemID
);
774 wxCHECK( data
&& (data
!= LB_ERR
), FALSE
);
776 wxListBoxItem
*pItem
= (wxListBoxItem
*)data
;
779 dc
.SetHDC((WXHDC
)pStruct
->hDC
, FALSE
);
780 wxRect
rect(wxPoint(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
),
781 wxPoint(pStruct
->rcItem
.right
, pStruct
->rcItem
.bottom
));
783 return pItem
->OnDrawItem(dc
, rect
,
784 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
785 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);