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>
43 #include "wx/ownerdrw.h"
46 #include "wx/dynarray.h"
49 #if !USE_SHARED_LIBRARY
50 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
53 // ============================================================================
54 // list box item declaration and implementation
55 // ============================================================================
59 class wxListBoxItem
: public wxOwnerDrawn
62 wxListBoxItem(const wxString
& str
= "");
65 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
67 // no bitmaps/checkmarks
71 wxOwnerDrawn
*wxListBox::CreateItem(size_t n
)
73 return new wxListBoxItem();
76 #endif //USE_OWNER_DRAWN
78 // ============================================================================
79 // list box control implementation
80 // ============================================================================
82 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
85 if (param == LBN_SELCANCEL)
87 event.extraLong = FALSE;
90 if (param
== LBN_SELCHANGE
)
92 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, m_windowId
);
93 wxArrayInt aSelections
;
94 int count
= GetSelections(aSelections
);
97 event
.m_commandInt
= aSelections
[0] ;
98 event
.m_clientData
= GetClientData(event
.m_commandInt
);
99 wxString
str(GetString(event
.m_commandInt
));
102 event
.m_commandString
= str
;
107 event
.m_commandInt
= -1 ;
108 event
.m_commandString
.Empty();
111 event
.SetEventObject( this );
112 ProcessCommand(event
);
115 else if (param
== LBN_DBLCLK
)
117 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, m_windowId
);
118 event
.SetEventObject( this );
119 GetEventHandler()->ProcessEvent(event
) ;
127 wxListBox::wxListBox()
133 bool wxListBox::Create(wxWindow
*parent
,
137 int n
, const wxString choices
[],
139 const wxValidator
& validator
,
140 const wxString
& name
)
147 SetValidator(validator
);
150 parent
->AddChild(this);
152 wxSystemSettings settings
;
153 SetBackgroundColour(settings
.GetSystemColour(wxSYS_COLOUR_WINDOW
));
154 SetForegroundColour(parent
->GetForegroundColour());
156 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
162 m_windowStyle
= style
;
164 DWORD wstyle
= WS_VISIBLE
| WS_VSCROLL
| WS_TABSTOP
|
165 LBS_NOTIFY
| LBS_HASSTRINGS
;
166 if (m_windowStyle
& wxLB_MULTIPLE
)
167 wstyle
|= LBS_MULTIPLESEL
;
168 else if (m_windowStyle
& wxLB_EXTENDED
)
169 wstyle
|= LBS_EXTENDEDSEL
;
171 if (m_windowStyle
& wxLB_ALWAYS_SB
)
172 wstyle
|= LBS_DISABLENOSCROLL
;
173 if (m_windowStyle
& wxLB_HSCROLL
)
174 wstyle
|= WS_HSCROLL
;
175 if (m_windowStyle
& wxLB_SORT
)
178 #if wxUSE_OWNER_DRAWN
179 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
180 // we don't support LBS_OWNERDRAWVARIABLE yet
181 wstyle
|= LBS_OWNERDRAWFIXED
;
185 // Without this style, you get unexpected heights, so e.g. constraint layout
186 // doesn't work properly
187 wstyle
|= LBS_NOINTEGRALHEIGHT
;
190 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
) ;
192 // Even with extended styles, need to combine with WS_BORDER
193 // for them to look right.
194 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
199 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, "LISTBOX", NULL
,
202 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
203 wxGetInstance(), NULL
);
205 wxCHECK_MSG( m_hWnd
, FALSE
, "Failed to create listbox" );
210 Ctl3dSubclassCtl(GetHwnd());
215 // Subclass again to catch messages
219 for (ui
= 0; ui
< (size_t)n
; ui
++) {
223 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
224 SendMessage(GetHwnd(), LB_SETCURSEL
, 0, 0);
226 SetFont(parent
->GetFont());
228 SetSize(x
, y
, width
, height
);
235 wxListBox::~wxListBox()
237 #if wxUSE_OWNER_DRAWN
238 size_t uiCount
= m_aItems
.Count();
239 while ( uiCount
-- != 0 ) {
240 delete m_aItems
[uiCount
];
242 #endif // wxUSE_OWNER_DRAWN
245 void wxListBox::SetupColours()
247 SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW
));
248 SetForegroundColour(GetParent()->GetForegroundColour());
251 void wxListBox::SetFirstItem(int N
)
253 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
254 "invalid index in wxListBox::SetFirstItem" );
256 SendMessage(GetHwnd(),LB_SETTOPINDEX
,(WPARAM
)N
,(LPARAM
)0) ;
259 void wxListBox::SetFirstItem(const wxString
& s
)
261 int N
= FindString(s
) ;
267 void wxListBox::Delete(int N
)
269 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
270 "invalid index in wxListBox::Delete" );
272 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
275 SetHorizontalExtent("");
278 void wxListBox::Append(const wxString
& item
)
280 int index
= ListBox_AddString(GetHwnd(), item
);
283 #if wxUSE_OWNER_DRAWN
284 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
285 wxOwnerDrawn
*pNewItem
= CreateItem(index
); // dummy argument
286 pNewItem
->SetName(item
);
287 m_aItems
.Add(pNewItem
);
288 ListBox_SetItemData(GetHwnd(), index
, pNewItem
);
292 SetHorizontalExtent(item
);
295 void wxListBox::Append(const wxString
& item
, char *Client_data
)
297 int index
= ListBox_AddString(GetHwnd(), item
);
300 #if wxUSE_OWNER_DRAWN
301 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
302 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
303 // in OnMeasure/OnDraw.
304 wxFAIL_MSG("Can't use client data with owner-drawn listboxes");
309 ListBox_SetItemData(GetHwnd(), index
, Client_data
);
311 SetHorizontalExtent(item
);
314 void wxListBox::Set(int n
, const wxString
*choices
, char** clientData
)
316 ShowWindow(GetHwnd(), SW_HIDE
);
317 ListBox_ResetContent(GetHwnd());
319 for (i
= 0; i
< n
; i
++)
321 ListBox_AddString(GetHwnd(), choices
[i
]);
323 ListBox_SetItemData(GetHwnd(), i
, clientData
[i
]);
327 #if wxUSE_OWNER_DRAWN
328 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
329 // first delete old items
330 size_t ui
= m_aItems
.Count();
331 while ( ui
-- != 0 ) {
336 // then create new ones
337 for (ui
= 0; ui
< (size_t)n
; ui
++) {
338 wxOwnerDrawn
*pNewItem
= CreateItem(ui
);
339 pNewItem
->SetName(choices
[ui
]);
340 m_aItems
.Add(pNewItem
);
341 ListBox_SetItemData(GetHwnd(), ui
, pNewItem
);
343 wxASSERT_MSG(clientData
[ui
] == NULL
,
344 "Can't use client data with owner-drawn listboxes");
349 SetHorizontalExtent("");
350 ShowWindow(GetHwnd(), SW_SHOW
);
353 int wxListBox::FindString(const wxString
& s
) const
355 int pos
= ListBox_FindStringExact(GetHwnd(), (WPARAM
)-1, s
);
362 void wxListBox::Clear()
364 ListBox_ResetContent(GetHwnd());
366 #if wxUSE_OWNER_DRAWN
367 size_t uiCount
= m_aItems
.Count();
368 while ( uiCount
-- != 0 ) {
369 delete m_aItems
[uiCount
];
373 #endif // wxUSE_OWNER_DRAWN
376 ListBox_GetHorizontalExtent(GetHwnd());
379 void wxListBox::SetSelection(int N
, bool select
)
381 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
382 "invalid index in wxListBox::SetSelection" );
384 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
385 SendMessage(GetHwnd(), LB_SETSEL
, select
, N
);
391 SendMessage(GetHwnd(), LB_SETCURSEL
, N1
, 0);
395 bool wxListBox::Selected(int N
) const
397 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, FALSE
,
398 "invalid index in wxListBox::Selected" );
400 return SendMessage(GetHwnd(), LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
403 void wxListBox::Deselect(int N
)
405 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
406 "invalid index in wxListBox::Deselect" );
408 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
409 SendMessage(GetHwnd(), LB_SETSEL
, FALSE
, N
);
412 char *wxListBox::GetClientData(int N
) const
414 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, NULL
,
415 "invalid index in wxListBox::GetClientData" );
417 return (char *)SendMessage(GetHwnd(), LB_GETITEMDATA
, N
, 0);
420 void wxListBox::SetClientData(int N
, char *Client_data
)
422 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
423 "invalid index in wxListBox::SetClientData" );
425 if ( ListBox_SetItemData(GetHwnd(), N
, Client_data
) == LB_ERR
)
426 wxLogDebug("LB_SETITEMDATA failed");
429 // Return number of selections and an array of selected integers
430 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
434 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
436 int no_sel
= ListBox_GetSelCount(GetHwnd());
438 int *selections
= new int[no_sel
];
439 if ( ListBox_GetSelItems(GetHwnd(), no_sel
, selections
) == LB_ERR
) {
440 wxFAIL_MSG("This listbox can't have single-selection style!");
443 aSelections
.Alloc(no_sel
);
444 for ( int n
= 0; n
< no_sel
; n
++ )
445 aSelections
.Add(selections
[n
]);
447 delete [] selections
;
452 else // single-selection listbox
454 aSelections
.Add(ListBox_GetCurSel(GetHwnd()));
460 // Get single selection, for single choice list items
461 int wxListBox::GetSelection() const
463 wxCHECK_MSG( !(m_windowStyle
& wxLB_MULTIPLE
) &&
464 !(m_windowStyle
& wxLB_EXTENDED
),
466 "GetSelection() can't be used with multiple-selection "
467 "listboxes, use GetSelections() instead." );
469 return ListBox_GetCurSel(GetHwnd());
472 // Find string for position
473 wxString
wxListBox::GetString(int N
) const
475 wxCHECK_MSG( N
>= 0 && N
< m_noItems
, "",
476 "invalid index in wxListBox::GetClientData" );
478 int len
= ListBox_GetTextLen(GetHwnd(), N
);
480 // +1 for terminating NUL
482 ListBox_GetText(GetHwnd(), N
, result
.GetWriteBuf(len
+ 1));
483 result
.UngetWriteBuf();
488 void wxListBox::DoSetSize(int x
, int y
, int width
, int height
, int sizeFlags
)
490 int currentX
, currentY
;
491 GetPosition(¤tX
, ¤tY
);
498 if (x
== -1 || (sizeFlags
& wxSIZE_ALLOW_MINUS_ONE
))
500 if (y
== -1 || (sizeFlags
& wxSIZE_ALLOW_MINUS_ONE
))
503 AdjustForParentClientOrigin(x1
, y1
, sizeFlags
);
505 // If we're prepared to use the existing size, then...
506 if (width
== -1 && height
== -1 && ((sizeFlags
& wxSIZE_AUTO
) != wxSIZE_AUTO
))
511 int cx
; // button font dimensions
514 wxGetCharSize(GetHWND(), &cx
, &cy
, & this->GetFont());
516 float control_width
, control_height
, control_x
, control_y
;
518 // Deal with default size (using -1 values)
520 w1
= DEFAULT_ITEM_WIDTH
;
523 h1
= DEFAULT_ITEM_HEIGHT
;
525 control_x
= (float)x1
;
526 control_y
= (float)y1
;
527 control_width
= (float)w1
;
528 control_height
= (float)h1
;
530 // Calculations may have made size too small
531 if (control_height
<= 0)
532 control_height
= (float)DEFAULT_ITEM_HEIGHT
;
534 if (control_width
<= 0)
535 control_width
= (float)DEFAULT_ITEM_WIDTH
;
537 MoveWindow(GetHwnd(),
538 (int)control_x
, (int)control_y
,
539 (int)control_width
, (int)control_height
,
544 // Windows-specific code to set the horizontal extent of
545 // the listbox, if necessary. If s is non-NULL, it's
546 // used to calculate the horizontal extent.
547 // Otherwise, all strings are used.
548 void wxListBox::SetHorizontalExtent(const wxString
& s
)
550 // Only necessary if we want a horizontal scrollbar
551 if (!(m_windowStyle
& wxHSCROLL
))
553 TEXTMETRIC lpTextMetric
;
557 int existingExtent
= (int)SendMessage(GetHwnd(), LB_GETHORIZONTALEXTENT
, 0, 0L);
558 HDC dc
= GetWindowDC(GetHwnd());
560 if (GetFont().Ok() && GetFont().GetResourceHandle())
561 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
563 GetTextMetrics(dc
, &lpTextMetric
);
565 ::GetTextExtentPoint(dc
, (LPSTR
) (const char *)s
, s
.Length(), &extentXY
);
566 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
569 ::SelectObject(dc
, oldFont
);
571 ReleaseDC(GetHwnd(), dc
);
572 if (extentX
> existingExtent
)
573 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
578 int largestExtent
= 0;
579 HDC dc
= GetWindowDC(GetHwnd());
581 if (GetFont().Ok() && GetFont().GetResourceHandle())
582 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
584 GetTextMetrics(dc
, &lpTextMetric
);
586 for (i
= 0; i
< m_noItems
; i
++)
588 int len
= (int)SendMessage(GetHwnd(), LB_GETTEXT
, i
, (LONG
)wxBuffer
);
591 ::GetTextExtentPoint(dc
, (LPSTR
)wxBuffer
, len
, &extentXY
);
592 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
593 if (extentX
> largestExtent
)
594 largestExtent
= extentX
;
597 ::SelectObject(dc
, oldFont
);
599 ReleaseDC(GetHwnd(), dc
);
600 SendMessage(GetHwnd(), LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
605 wxListBox::InsertItems(int nItems
, const wxString items
[], int pos
)
607 wxCHECK_RET( pos
>= 0 && pos
<= m_noItems
,
608 "invalid index in wxListBox::InsertItems" );
611 for (i
= 0; i
< nItems
; i
++)
612 ListBox_InsertString(GetHwnd(), i
+ pos
, items
[i
]);
615 SetHorizontalExtent("");
618 void wxListBox::SetString(int N
, const wxString
& s
)
620 wxCHECK_RET( N
>= 0 && N
< m_noItems
,
621 "invalid index in wxListBox::SetString" );
624 if (!(m_windowStyle
& wxLB_MULTIPLE
) && !(m_windowStyle
& wxLB_EXTENDED
))
625 sel
= GetSelection();
627 char *oldData
= (char *)wxListBox::GetClientData(N
);
629 SendMessage(GetHwnd(), LB_DELETESTRING
, N
, 0);
632 if (N
== (m_noItems
- 1))
635 SendMessage(GetHwnd(), LB_INSERTSTRING
, newN
, (LPARAM
) (const char *)s
);
637 wxListBox::SetClientData(N
, oldData
);
639 // Selection may have changed
643 #if wxUSE_OWNER_DRAWN
644 if ( m_windowStyle
& wxLB_OWNERDRAW
)
645 // update item's text
646 m_aItems
[N
]->SetName(s
);
647 #endif //USE_OWNER_DRAWN
650 int wxListBox::Number () const
655 // For single selection items only
656 wxString
wxListBox::GetStringSelection () const
658 int sel
= GetSelection ();
660 return this->GetString (sel
);
665 bool wxListBox::SetStringSelection (const wxString
& s
, bool flag
)
667 int sel
= FindString (s
);
670 SetSelection (sel
, flag
);
677 // Is this the right thing? Won't setselection generate a command
678 // event too? No! It'll just generate a setselection event.
679 // But we still can't have this being called whenever a real command
680 // is generated, because it sets the selection, which will already
681 // have been done! (Unless we have an optional argument for calling
682 // by the actual window system, or a separate function, ProcessCommand)
683 void wxListBox::Command (wxCommandEvent
& event
)
685 if (event
.m_extraLong
)
686 SetSelection (event
.m_commandInt
);
689 Deselect (event
.m_commandInt
);
692 ProcessCommand (event
);
695 WXHBRUSH
wxListBox::OnCtlColor(WXHDC pDC
, WXHWND pWnd
, WXUINT nCtlColor
,
696 WXUINT message
, WXWPARAM wParam
, WXLPARAM lParam
)
701 HBRUSH hbrush
= Ctl3dCtlColorEx(message
, wParam
, lParam
);
702 return (WXHBRUSH
) hbrush
;
706 if (GetParent()->GetTransparentBackground())
707 SetBkMode((HDC
) pDC
, TRANSPARENT
);
709 SetBkMode((HDC
) pDC
, OPAQUE
);
711 ::SetBkColor((HDC
) pDC
, RGB(GetBackgroundColour().Red(), GetBackgroundColour().Green(), GetBackgroundColour().Blue()));
712 ::SetTextColor((HDC
) pDC
, RGB(GetForegroundColour().Red(), GetForegroundColour().Green(), GetForegroundColour().Blue()));
714 wxBrush
*backgroundBrush
= wxTheBrushList
->FindOrCreateBrush(GetBackgroundColour(), wxSOLID
);
716 // Note that this will be cleaned up in wxApp::OnIdle, if backgroundBrush
717 // has a zero usage count.
718 backgroundBrush
->RealizeResource();
719 return (WXHBRUSH
) backgroundBrush
->GetResourceHandle();
722 long wxListBox::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
724 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
727 #if wxUSE_OWNER_DRAWN
732 // space beneath/above each row in pixels
733 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
734 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
736 // the height is the same for all items
737 // TODO should be changed for LBS_OWNERDRAWVARIABLE style listboxes
739 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
740 // message is not yet sent when we get here!
741 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
743 // only owner-drawn control should receive this message
744 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
746 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
749 dc
.SetHDC((WXHDC
)CreateIC("DISPLAY", NULL
, NULL
, 0));
750 dc
.SetFont(wxSystemSettings::GetSystemFont(wxSYS_ANSI_VAR_FONT
));
752 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
753 pStruct
->itemWidth
= dc
.GetCharWidth();
758 // forward the message to the appropriate item
759 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
761 // only owner-drawn control should receive this message
762 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
764 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
766 long data
= ListBox_GetItemData(GetHwnd(), pStruct
->itemID
);
768 wxCHECK( data
&& (data
!= LB_ERR
), FALSE
);
770 wxListBoxItem
*pItem
= (wxListBoxItem
*)data
;
773 dc
.SetHDC((WXHDC
)pStruct
->hDC
, FALSE
);
774 wxRect
rect(wxPoint(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
),
775 wxPoint(pStruct
->rcItem
.right
, pStruct
->rcItem
.bottom
));
777 return pItem
->OnDrawItem(dc
, rect
,
778 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
779 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);