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"
24 #include "wx/listbox.h"
25 #include "wx/settings.h"
31 #include "wx/msw/private.h"
38 #include <wx/msw/gnuwin32/extra.h>
47 #include "wx/ownerdrw.h"
50 #include "wx/dynarray.h"
53 #if !USE_SHARED_LIBRARY
54 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
57 // ============================================================================
58 // list box item declaration and implementation
59 // ============================================================================
63 class wxListBoxItem
: public wxOwnerDrawn
66 wxListBoxItem(const wxString
& str
= "");
69 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
71 // no bitmaps/checkmarks
75 wxOwnerDrawn
*wxListBox::CreateItem(size_t n
)
77 return new wxListBoxItem();
80 #endif //USE_OWNER_DRAWN
82 // ============================================================================
83 // list box control implementation
84 // ============================================================================
86 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
89 if (param == LBN_SELCANCEL)
91 event.extraLong = FALSE;
94 if (param
== LBN_SELCHANGE
)
96 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, m_windowId
);
97 wxArrayInt aSelections
;
98 int count
= GetSelections(aSelections
);
101 event
.m_commandInt
= aSelections
[0] ;
102 event
.m_clientData
= GetClientData(event
.m_commandInt
);
103 wxString
str(GetString(event
.m_commandInt
));
106 event
.m_commandString
= str
;
111 event
.m_commandInt
= -1 ;
112 event
.m_commandString
.Empty();
115 event
.SetEventObject( this );
116 ProcessCommand(event
);
119 else if (param
== LBN_DBLCLK
)
121 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, m_windowId
);
122 event
.SetEventObject( this );
123 GetEventHandler()->ProcessEvent(event
) ;
131 wxListBox::wxListBox()
137 bool wxListBox::Create(wxWindow
*parent
,
141 int n
, const wxString choices
[],
143 const wxValidator
& validator
,
144 const wxString
& name
)
151 SetValidator(validator
);
154 parent
->AddChild(this);
156 wxSystemSettings settings
;
157 SetBackgroundColour(settings
.GetSystemColour(wxSYS_COLOUR_WINDOW
));
158 SetForegroundColour(parent
->GetForegroundColour());
160 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
166 m_windowStyle
= style
;
168 DWORD wstyle
= WS_VSCROLL
| WS_TABSTOP
| LBS_NOTIFY
| LBS_HASSTRINGS
;
169 if (m_windowStyle
& wxLB_MULTIPLE
)
170 wstyle
|= LBS_MULTIPLESEL
;
171 else if (m_windowStyle
& wxLB_EXTENDED
)
172 wstyle
|= LBS_EXTENDEDSEL
;
174 if (m_windowStyle
& wxLB_ALWAYS_SB
)
175 wstyle
|= LBS_DISABLENOSCROLL
;
176 if (m_windowStyle
& wxLB_HSCROLL
)
177 wstyle
|= WS_HSCROLL
;
178 if (m_windowStyle
& wxLB_SORT
)
181 #if wxUSE_OWNER_DRAWN
182 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
183 // we don't support LBS_OWNERDRAWVARIABLE yet
184 wstyle
|= LBS_OWNERDRAWFIXED
;
188 // Without this style, you get unexpected heights, so e.g. constraint layout
189 // doesn't work properly
190 wstyle
|= LBS_NOINTEGRALHEIGHT
;
193 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
) ;
195 // Even with extended styles, need to combine with WS_BORDER
196 // for them to look right.
197 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
202 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, "LISTBOX", NULL
,
205 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
206 wxGetInstance(), NULL
);
208 wxCHECK_MSG( m_hWnd
, FALSE
, "Failed to create listbox" );
213 Ctl3dSubclassCtl(GetHwnd());
218 // Subclass again to catch messages
222 for (ui
= 0; ui
< (size_t)n
; ui
++) {
226 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
227 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
229 SetFont(parent
->GetFont());
231 SetSize(x
, y
, width
, height
);
238 wxListBox::~wxListBox()
240 #if wxUSE_OWNER_DRAWN
241 size_t uiCount
= m_aItems
.Count();
242 while ( uiCount
-- != 0 ) {
243 delete m_aItems
[uiCount
];
245 #endif // wxUSE_OWNER_DRAWN
248 void wxListBox::SetupColours()
250 SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW
));
251 SetForegroundColour(GetParent()->GetForegroundColour());
254 void wxListBox::SetFirstItem(int N
)
256 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
257 "invalid index in wxListBox::SetFirstItem" );
259 SendMessage(GetHwnd(),LB_SETTOPINDEX
,(WPARAM
)N
,(LPARAM
)0) ;
262 void wxListBox::SetFirstItem(const wxString
& s
)
264 int N
= FindString(s
) ;
270 void wxListBox::Delete(int N
)
272 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
273 "invalid index in wxListBox::Delete" );
275 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
278 SetHorizontalExtent("");
281 void wxListBox::Append(const wxString
& item
)
283 int index
= ListBox_AddString(GetHwnd(), item
);
286 #if wxUSE_OWNER_DRAWN
287 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
288 wxOwnerDrawn
*pNewItem
= CreateItem(index
); // dummy argument
289 pNewItem
->SetName(item
);
290 m_aItems
.Add(pNewItem
);
291 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
295 SetHorizontalExtent(item
);
298 void wxListBox::Append(const wxString
& item
, char *Client_data
)
300 int index
= ListBox_AddString(GetHwnd(), item
);
303 #if wxUSE_OWNER_DRAWN
304 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
305 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
306 // in OnMeasure/OnDraw.
307 wxFAIL_MSG("Can't use client data with owner-drawn listboxes");
312 ListBox_SetItemData(GetHwnd(), index
, Client_data
);
314 SetHorizontalExtent(item
);
317 void wxListBox::Set(int n
, const wxString
*choices
, char** clientData
)
319 ShowWindow(GetHwnd(), SW_HIDE
);
320 ListBox_ResetContent(GetHwnd());
322 for (i
= 0; i
< n
; i
++)
324 ListBox_AddString(GetHwnd(), choices
[i
]);
326 ListBox_SetItemData(GetHwnd(), i
, clientData
[i
]);
330 #if wxUSE_OWNER_DRAWN
331 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
332 // first delete old items
333 size_t ui
= m_aItems
.Count();
334 while ( ui
-- != 0 ) {
339 // then create new ones
340 for (ui
= 0; ui
< (size_t)n
; ui
++) {
341 wxOwnerDrawn
*pNewItem
= CreateItem(ui
);
342 pNewItem
->SetName(choices
[ui
]);
343 m_aItems
.Add(pNewItem
);
344 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
346 wxASSERT_MSG(clientData
[ui
] == NULL
,
347 "Can't use client data with owner-drawn listboxes");
352 SetHorizontalExtent("");
353 ShowWindow(GetHwnd(), SW_SHOW
);
356 int wxListBox::FindString(const wxString
& s
) const
358 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
365 void wxListBox::Clear()
367 ListBox_ResetContent(GetHwnd());
369 #if wxUSE_OWNER_DRAWN
370 size_t uiCount
= m_aItems
.Count();
371 while ( uiCount
-- != 0 ) {
372 delete m_aItems
[uiCount
];
376 #endif // wxUSE_OWNER_DRAWN
379 ListBox_GetHorizontalExtent(GetHwnd());
382 void wxListBox::SetSelection(int N
, bool select
)
384 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
385 "invalid index in wxListBox::SetSelection" );
387 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
388 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
394 SendMessage(GetHwnd(), LB_SETCURSEL
, N1
, 0);
398 bool wxListBox::Selected(int N
) const
400 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
401 "invalid index in wxListBox::Selected" );
403 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
406 void wxListBox::Deselect(int N
)
408 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
409 "invalid index in wxListBox::Deselect" );
411 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
412 SendMessage(GetHwnd(), LB_SETSEL
, FALSE
, N
);
415 char *wxListBox::GetClientData(int N
) const
417 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, NULL
,
418 "invalid index in wxListBox::GetClientData" );
420 return (char *)SendMessage(GetHwnd(), LB_GETITEMDATA
, N
, 0);
423 void wxListBox::SetClientData(int N
, char *Client_data
)
425 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
426 "invalid index in wxListBox::SetClientData" );
428 if ( ListBox_SetItemData(GetHwnd(), N
, Client_data
) == LB_ERR
)
429 wxLogDebug("LB_SETITEMDATA failed");
432 // Return number of selections and an array of selected integers
433 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
437 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
439 int no_sel
= ListBox_GetSelCount(GetHwnd());
441 int *selections
= new int[no_sel
];
442 if ( ListBox_GetSelItems(GetHwnd(), no_sel
, selections
) == LB_ERR
) {
443 wxFAIL_MSG("This listbox can't have single-selection style!");
446 aSelections
.Alloc(no_sel
);
447 for ( int n
= 0; n
< no_sel
; n
++ )
448 aSelections
.Add(selections
[n
]);
450 delete [] selections
;
455 else // single-selection listbox
457 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
463 // Get single selection, for single choice list items
464 int wxListBox::GetSelection() const
466 wxCHECK_MSG( !(m_windowStyle
& wxLB_MULTIPLE
) &&
467 !(m_windowStyle
& wxLB_EXTENDED
),
469 "GetSelection() can't be used with multiple-selection "
470 "listboxes, use GetSelections() instead." );
472 return ListBox_GetCurSel(GetHwnd());
475 // Find string for position
476 wxString
wxListBox::GetString(int N
) const
478 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, "",
479 "invalid index in wxListBox::GetClientData" );
481 int len
= ListBox_GetTextLen(GetHwnd(), N
);
483 // +1 for terminating NUL
485 ListBox_GetText(GetHwnd(), N
, result
.GetWriteBuf(len
+ 1));
486 result
.UngetWriteBuf();
491 void wxListBox::DoSetSize(int x
, int y
, int width
, int height
, int sizeFlags
)
493 int currentX
, currentY
;
494 GetPosition(¤tX
, ¤tY
);
501 if (x
== -1 || (sizeFlags
& wxSIZE_ALLOW_MINUS_ONE
))
503 if (y
== -1 || (sizeFlags
& wxSIZE_ALLOW_MINUS_ONE
))
506 AdjustForParentClientOrigin(x1
, y1
, sizeFlags
);
508 // If we're prepared to use the existing size, then...
509 if (width
== -1 && height
== -1 && ((sizeFlags
& wxSIZE_AUTO
) != wxSIZE_AUTO
))
514 int cx
; // button font dimensions
517 wxGetCharSize(GetHWND(), &cx
, &cy
, & this->GetFont());
519 float control_width
, control_height
, control_x
, control_y
;
521 // Deal with default size (using -1 values)
523 w1
= DEFAULT_ITEM_WIDTH
;
526 h1
= DEFAULT_ITEM_HEIGHT
;
528 control_x
= (float)x1
;
529 control_y
= (float)y1
;
530 control_width
= (float)w1
;
531 control_height
= (float)h1
;
533 // Calculations may have made size too small
534 if (control_height
<= 0)
535 control_height
= (float)DEFAULT_ITEM_HEIGHT
;
537 if (control_width
<= 0)
538 control_width
= (float)DEFAULT_ITEM_WIDTH
;
540 MoveWindow(GetHwnd(),
541 (int)control_x
, (int)control_y
,
542 (int)control_width
, (int)control_height
,
547 // Windows-specific code to set the horizontal extent of
548 // the listbox, if necessary. If s is non-NULL, it's
549 // used to calculate the horizontal extent.
550 // Otherwise, all strings are used.
551 void wxListBox::SetHorizontalExtent(const wxString
& s
)
553 // Only necessary if we want a horizontal scrollbar
554 if (!(m_windowStyle
& wxHSCROLL
))
556 TEXTMETRIC lpTextMetric
;
560 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
561 HDC dc
= GetWindowDC(GetHwnd());
563 if (GetFont().Ok() && GetFont().GetResourceHandle())
564 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
566 GetTextMetrics(dc
, &lpTextMetric
);
568 ::GetTextExtentPoint(dc
, (LPSTR
) (const char *)s
, s
.Length(), &extentXY
);
569 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
572 ::SelectObject(dc
, oldFont
);
574 ReleaseDC(GetHwnd(), dc
);
575 if (extentX
> existingExtent
)
576 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
581 int largestExtent
= 0;
582 HDC dc
= GetWindowDC(GetHwnd());
584 if (GetFont().Ok() && GetFont().GetResourceHandle())
585 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
587 GetTextMetrics(dc
, &lpTextMetric
);
589 for (i
= 0; i
< m_noItems
; i
++)
591 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LONG
)wxBuffer
);
594 ::GetTextExtentPoint(dc
, (LPSTR
)wxBuffer
, len
, &extentXY
);
595 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
596 if (extentX
> largestExtent
)
597 largestExtent
= extentX
;
600 ::SelectObject(dc
, oldFont
);
602 ReleaseDC(GetHwnd(), dc
);
603 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
608 wxListBox::InsertItems(int nItems
, const wxString items
[], int pos
)
610 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
611 "invalid index in wxListBox::InsertItems" );
614 for (i
= 0; i
< nItems
; i
++)
615 ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
618 SetHorizontalExtent("");
621 void wxListBox::SetString(int N
, const wxString
& s
)
623 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
624 "invalid index in wxListBox::SetString" );
627 if (!(m_windowStyle
& wxLB_MULTIPLE
) && !(m_windowStyle
& wxLB_EXTENDED
))
628 sel
= GetSelection();
630 char *oldData
= (char *)wxListBox::GetClientData(N
);
632 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
635 if (N
== (m_noItems
- 1))
638 SendMessage(GetHwnd(), LB_INSERTSTRING
, newN
, (LPARAM
) (const char *)s
);
640 wxListBox::SetClientData(N
, oldData
);
642 // Selection may have changed
646 #if wxUSE_OWNER_DRAWN
647 if ( m_windowStyle
& wxLB_OWNERDRAW
)
648 // update item's text
649 m_aItems
[N
]->SetName(s
);
650 #endif //USE_OWNER_DRAWN
653 int wxListBox::Number () const
658 // For single selection items only
659 wxString
wxListBox::GetStringSelection () const
661 int sel
= GetSelection ();
663 return this->GetString (sel
);
668 bool wxListBox::SetStringSelection (const wxString
& s
, bool flag
)
670 int sel
= FindString (s
);
673 SetSelection (sel
, flag
);
680 // Is this the right thing? Won't setselection generate a command
681 // event too? No! It'll just generate a setselection event.
682 // But we still can't have this being called whenever a real command
683 // is generated, because it sets the selection, which will already
684 // have been done! (Unless we have an optional argument for calling
685 // by the actual window system, or a separate function, ProcessCommand)
686 void wxListBox::Command (wxCommandEvent
& event
)
688 if (event
.m_extraLong
)
689 SetSelection (event
.m_commandInt
);
692 Deselect (event
.m_commandInt
);
695 ProcessCommand (event
);
698 WXHBRUSH
wxListBox::OnCtlColor(WXHDC pDC
, WXHWND pWnd
, WXUINT nCtlColor
,
699 WXUINT message
, WXWPARAM wParam
, WXLPARAM lParam
)
704 HBRUSH hbrush
= Ctl3dCtlColorEx(message
, wParam
, lParam
);
705 return (WXHBRUSH
) hbrush
;
709 if (GetParent()->GetTransparentBackground())
710 SetBkMode((HDC
) pDC
, TRANSPARENT
);
712 SetBkMode((HDC
) pDC
, OPAQUE
);
714 ::SetBkColor((HDC
) pDC
, RGB(GetBackgroundColour().Red(), GetBackgroundColour().Green(), GetBackgroundColour().Blue()));
715 ::SetTextColor((HDC
) pDC
, RGB(GetForegroundColour().Red(), GetForegroundColour().Green(), GetForegroundColour().Blue()));
717 wxBrush
*backgroundBrush
= wxTheBrushList
->FindOrCreateBrush(GetBackgroundColour(), wxSOLID
);
719 // Note that this will be cleaned up in wxApp::OnIdle, if backgroundBrush
720 // has a zero usage count.
721 backgroundBrush
->RealizeResource();
722 return (WXHBRUSH
) backgroundBrush
->GetResourceHandle();
725 long wxListBox::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
727 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
730 #if wxUSE_OWNER_DRAWN
735 // space beneath/above each row in pixels
736 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
737 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
739 // the height is the same for all items
740 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
742 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
743 // message is not yet sent when we get here!
744 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
746 // only owner-drawn control should receive this message
747 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
749 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
752 dc
.SetHDC((WXHDC
)CreateIC("DISPLAY", NULL
, NULL
, 0));
753 dc
.SetFont(wxSystemSettings::GetSystemFont(wxSYS_ANSI_VAR_FONT
));
755 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
756 pStruct
->itemWidth
= dc
.GetCharWidth();
761 // forward the message to the appropriate item
762 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
764 // only owner-drawn control should receive this message
765 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
767 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
769 long data
= ListBox_GetItemData(GetHwnd(), pStruct
->itemID
);
771 wxCHECK( data
&& (data
!= LB_ERR
), FALSE
);
773 wxListBoxItem
*pItem
= (wxListBoxItem
*)data
;
776 dc
.SetHDC((WXHDC
)pStruct
->hDC
, FALSE
);
777 wxRect
rect(wxPoint(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
),
778 wxPoint(pStruct
->rcItem
.right
, pStruct
->rcItem
.bottom
));
780 return pItem
->OnDrawItem(dc
, rect
,
781 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
782 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);