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"
28 #include "wx/msw/private.h"
34 #include <wx/msw/gnuwin32/extra.h>
42 #include "wx/ownerdrw.h"
45 #include "wx/dynarray.h"
48 #if !USE_SHARED_LIBRARY
49 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
52 // ============================================================================
53 // list box item declaration and implementation
54 // ============================================================================
58 class wxListBoxItem
: public wxOwnerDrawn
61 wxListBoxItem(const wxString
& str
= "");
64 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
66 // no bitmaps/checkmarks
70 wxOwnerDrawn
*wxListBox::CreateItem(size_t n
)
72 return new wxListBoxItem();
75 #endif //USE_OWNER_DRAWN
77 // ============================================================================
78 // list box control implementation
79 // ============================================================================
81 // this macro is dangerous but still better than tons of (HWND)GetHWND()
82 #define hwnd (HWND)GetHWND()
84 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
87 if (param == LBN_SELCANCEL)
89 event.extraLong = FALSE;
92 if (param
== LBN_SELCHANGE
)
94 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, m_windowId
);
95 wxArrayInt aSelections
;
96 int count
= GetSelections(aSelections
);
99 event
.m_commandInt
= aSelections
[0] ;
100 event
.m_clientData
= GetClientData(event
.m_commandInt
);
101 wxString
str(GetString(event
.m_commandInt
));
103 event
.m_commandString
= copystring((char *)(const char *)str
);
107 event
.m_commandInt
= -1 ;
108 event
.m_commandString
= copystring("") ;
111 event
.SetEventObject( this );
112 ProcessCommand(event
);
113 if (event
.m_commandString
)
114 delete[] event
.m_commandString
;
117 else if (param
== LBN_DBLCLK
)
119 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, m_windowId
);
120 event
.SetEventObject( this );
121 GetEventHandler()->ProcessEvent(event
) ;
125 #if WXWIN_COMPATIBILITY
126 wxWindow *parent = (wxWindow *)GetParent();
128 parent->GetEventHandler()->OnDefaultAction(this);
138 wxListBox::wxListBox(void)
144 bool wxListBox::Create(wxWindow
*parent
, wxWindowID id
,
147 int n
, const wxString choices
[],
149 const wxValidator
& validator
,
150 const wxString
& name
)
157 SetValidator(validator
);
159 if (parent
) parent
->AddChild(this);
161 wxSystemSettings settings
;
162 SetBackgroundColour(settings
.GetSystemColour(wxSYS_COLOUR_WINDOW
));
163 SetForegroundColour(parent
->GetForegroundColour());
165 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
171 m_windowStyle
= style
;
173 DWORD wstyle
= WS_VSCROLL
| WS_TABSTOP
| LBS_NOTIFY
| LBS_HASSTRINGS
;
174 if (m_windowStyle
& wxLB_MULTIPLE
)
175 wstyle
|= LBS_MULTIPLESEL
;
176 else if (m_windowStyle
& wxLB_EXTENDED
)
177 wstyle
|= LBS_EXTENDEDSEL
;
179 if (m_windowStyle
& wxLB_ALWAYS_SB
)
180 wstyle
|= LBS_DISABLENOSCROLL
;
181 if (m_windowStyle
& wxLB_HSCROLL
)
182 wstyle
|= WS_HSCROLL
;
183 if (m_windowStyle
& wxLB_SORT
)
186 #if wxUSE_OWNER_DRAWN
187 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
188 // we don't support LBS_OWNERDRAWVARIABLE yet
189 wstyle
|= LBS_OWNERDRAWFIXED
;
192 // Without this style, you get unexpected heights, so e.g. constraint layout
193 // doesn't work properly
194 wstyle
|= LBS_NOINTEGRALHEIGHT
;
197 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
) ;
199 // Even with extended styles, need to combine with WS_BORDER
200 // for them to look right.
201 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
206 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, "LISTBOX", NULL
,
209 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
210 wxGetInstance(), NULL
);
212 wxCHECK_MSG( m_hWnd
, FALSE
, "Failed to create listbox" );
217 Ctl3dSubclassCtl(hwnd
);
222 // Subclass again to catch messages
226 for (ui
= 0; ui
< (size_t)n
; ui
++) {
230 #if wxUSE_OWNER_DRAWN
231 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
232 for (ui
= 0; ui
< (size_t)n
; ui
++) {
233 // create new item which will process WM_{DRAW|MEASURE}ITEM messages
234 wxOwnerDrawn
*pNewItem
= CreateItem(ui
);
235 pNewItem
->SetName(choices
[ui
]);
236 m_aItems
.Add(pNewItem
);
237 ListBox_SetItemData(hwnd
, ui
, pNewItem
);
242 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
243 SendMessage(hwnd
, LB_SETCURSEL
, 0, 0);
245 SetFont(* parent
->GetFont());
247 SetSize(x
, y
, width
, height
);
254 wxListBox::~wxListBox(void)
256 #if wxUSE_OWNER_DRAWN
257 size_t uiCount
= m_aItems
.Count();
258 while ( uiCount
-- != 0 ) {
259 delete m_aItems
[uiCount
];
264 void wxListBox::SetupColours(void)
266 SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW
));
267 SetForegroundColour(GetParent()->GetForegroundColour());
270 void wxListBox::SetFirstItem(int N
)
272 SendMessage(hwnd
,LB_SETTOPINDEX
,(WPARAM
)N
,(LPARAM
)0) ;
275 void wxListBox::SetFirstItem(const wxString
& s
)
277 int N
= FindString(s
) ;
283 void wxListBox::Delete(int N
)
285 SendMessage(hwnd
, LB_DELETESTRING
, N
, 0);
287 SetHorizontalExtent("");
290 void wxListBox::Append(const wxString
& item
)
292 int index
= ListBox_AddString(hwnd
, item
);
295 #if wxUSE_OWNER_DRAWN
296 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
297 wxOwnerDrawn
*pNewItem
= CreateItem(index
); // dummy argument
298 pNewItem
->SetName(item
);
299 m_aItems
.Add(pNewItem
);
300 ListBox_SetItemData(hwnd
, index
, pNewItem
);
304 SetHorizontalExtent(item
);
307 void wxListBox::Append(const wxString
& item
, char *Client_data
)
309 int index
= ListBox_AddString(hwnd
, item
);
312 #if wxUSE_OWNER_DRAWN
313 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
314 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
315 // in OnMeasure/OnDraw.
316 wxFAIL_MSG("Can't use client data with owner-drawn listboxes");
320 ListBox_SetItemData(hwnd
, index
, Client_data
);
322 SetHorizontalExtent(item
);
325 void wxListBox::Set(int n
, const wxString
*choices
, char** clientData
)
327 ShowWindow(hwnd
, SW_HIDE
);
328 ListBox_ResetContent(hwnd
);
330 for (i
= 0; i
< n
; i
++)
332 ListBox_AddString(hwnd
, choices
[i
]);
334 ListBox_SetItemData(hwnd
, i
, clientData
[i
]);
338 #if wxUSE_OWNER_DRAWN
339 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
340 // first delete old items
341 size_t ui
= m_aItems
.Count();
342 while ( ui
-- != 0 ) {
347 // then create new ones
348 for (ui
= 0; ui
< (size_t)n
; ui
++) {
349 wxOwnerDrawn
*pNewItem
= CreateItem(ui
);
350 pNewItem
->SetName(choices
[ui
]);
351 m_aItems
.Add(pNewItem
);
352 ListBox_SetItemData(hwnd
, ui
, pNewItem
);
354 wxASSERT_MSG(clientData
[ui
] == NULL
,
355 "Can't use client data with owner-drawn listboxes");
360 SetHorizontalExtent("");
361 ShowWindow(hwnd
, SW_SHOW
);
364 int wxListBox::FindString(const wxString
& s
) const
366 int pos
= ListBox_FindStringExact(hwnd
, (WPARAM
)-1, s
);
373 void wxListBox::Clear(void)
375 ListBox_ResetContent(hwnd
);
378 ListBox_GetHorizontalExtent(hwnd
);
381 void wxListBox::SetSelection(int N
, bool select
)
383 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
384 SendMessage(hwnd
, LB_SETSEL
, select
, N
);
390 SendMessage(hwnd
, LB_SETCURSEL
, N1
, 0);
394 bool wxListBox::Selected(int N
) const
396 return SendMessage(hwnd
, LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
399 void wxListBox::Deselect(int N
)
401 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
402 SendMessage(hwnd
, LB_SETSEL
, FALSE
, N
);
405 char *wxListBox::GetClientData(int N
) const
407 return (char *)SendMessage(hwnd
, LB_GETITEMDATA
, N
, 0);
410 void wxListBox::SetClientData(int N
, char *Client_data
)
412 if ( ListBox_SetItemData(hwnd
, N
, Client_data
) == LB_ERR
)
413 wxLogDebug("LB_SETITEMDATA failed");
416 // Return number of selections and an array of selected integers
417 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
421 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
423 int no_sel
= ListBox_GetSelCount(hwnd
);
425 int *selections
= new int[no_sel
];
426 if ( ListBox_GetSelItems(hwnd
, no_sel
, selections
) == LB_ERR
) {
427 wxFAIL_MSG("This listbox can't have single-selection style!");
430 aSelections
.Alloc(no_sel
);
431 for ( int n
= 0; n
< no_sel
; n
++ )
432 aSelections
.Add(selections
[n
]);
434 delete [] selections
;
439 else // single-selection listbox
441 aSelections
.Add(ListBox_GetCurSel(hwnd
));
447 // Get single selection, for single choice list items
448 int wxListBox::GetSelection() const
450 wxCHECK_MSG( !(m_windowStyle
& wxLB_MULTIPLE
) &&
451 !(m_windowStyle
& wxLB_EXTENDED
),
453 "GetSelection() can't be used with multiple-selection "
454 "listboxes, use GetSelections() instead." );
456 return ListBox_GetCurSel(hwnd
);
459 // Find string for position
460 wxString
wxListBox::GetString(int N
) const
462 if (N
< 0 || N
> m_noItems
)
465 int len
= (int)SendMessage(hwnd
, LB_GETTEXT
, N
, (LONG
)wxBuffer
);
467 return wxString(wxBuffer
);
470 void wxListBox::SetSize(int x
, int y
, int width
, int height
, int sizeFlags
)
472 int currentX
, currentY
;
473 GetPosition(¤tX
, ¤tY
);
480 if (x
== -1 || (sizeFlags
& wxSIZE_ALLOW_MINUS_ONE
))
482 if (y
== -1 || (sizeFlags
& wxSIZE_ALLOW_MINUS_ONE
))
485 AdjustForParentClientOrigin(x1
, y1
, sizeFlags
);
487 // If we're prepared to use the existing size, then...
488 if (width
== -1 && height
== -1 && ((sizeFlags
& wxSIZE_AUTO
) != wxSIZE_AUTO
))
493 int cx
; // button font dimensions
496 wxGetCharSize(GetHWND(), &cx
, &cy
,GetFont());
498 float control_width
, control_height
, control_x
, control_y
;
500 // Deal with default size (using -1 values)
502 w1
= DEFAULT_ITEM_WIDTH
;
505 h1
= DEFAULT_ITEM_HEIGHT
;
507 control_x
= (float)x1
;
508 control_y
= (float)y1
;
509 control_width
= (float)w1
;
510 control_height
= (float)h1
;
512 // Calculations may have made size too small
513 if (control_height
<= 0)
514 control_height
= (float)DEFAULT_ITEM_HEIGHT
;
516 if (control_width
<= 0)
517 control_width
= (float)DEFAULT_ITEM_WIDTH
;
519 // wxDebugMsg("About to set the listbox height to %d", (int)control_height);
520 MoveWindow(hwnd
, (int)control_x
, (int)control_y
,
521 (int)control_width
, (int)control_height
, TRUE
);
525 // Windows-specific code to set the horizontal extent of
526 // the listbox, if necessary. If s is non-NULL, it's
527 // used to calculate the horizontal extent.
528 // Otherwise, all strings are used.
529 void wxListBox::SetHorizontalExtent(const wxString
& s
)
531 // Only necessary if we want a horizontal scrollbar
532 if (!(m_windowStyle
& wxHSCROLL
))
534 TEXTMETRIC lpTextMetric
;
538 int existingExtent
= (int)SendMessage(hwnd
, LB_GETHORIZONTALEXTENT
, 0, 0L);
539 HDC dc
= GetWindowDC(hwnd
);
541 if (GetFont() && GetFont()->GetResourceHandle())
542 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont()->GetResourceHandle());
544 GetTextMetrics(dc
, &lpTextMetric
);
546 ::GetTextExtentPoint(dc
, (LPSTR
) (const char *)s
, s
.Length(), &extentXY
);
547 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
550 ::SelectObject(dc
, oldFont
);
553 if (extentX
> existingExtent
)
554 SendMessage(hwnd
, LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
559 int largestExtent
= 0;
560 HDC dc
= GetWindowDC(hwnd
);
562 if (GetFont() && GetFont()->GetResourceHandle())
563 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont()->GetResourceHandle());
565 GetTextMetrics(dc
, &lpTextMetric
);
567 for (i
= 0; i
< m_noItems
; i
++)
569 int len
= (int)SendMessage(hwnd
, LB_GETTEXT
, i
, (LONG
)wxBuffer
);
572 ::GetTextExtentPoint(dc
, (LPSTR
)wxBuffer
, len
, &extentXY
);
573 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
574 if (extentX
> largestExtent
)
575 largestExtent
= extentX
;
578 ::SelectObject(dc
, oldFont
);
581 SendMessage(hwnd
, LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
586 wxListBox::InsertItems(int nItems
, const wxString items
[], int pos
)
589 for (i
= 0; i
< nItems
; i
++)
590 ListBox_InsertString(hwnd
, i
+ pos
, items
[i
]);
593 #if wxUSE_OWNER_DRAWN
594 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
595 for ( i
= 0; i
< nItems
; i
++ ) {
596 wxOwnerDrawn
*pNewItem
= CreateItem((size_t)(pos
+ i
));
597 pNewItem
->SetName(items
[i
]);
598 m_aItems
.Insert(pNewItem
, (size_t)(pos
+ i
));
599 ListBox_SetItemData(hwnd
, i
, pNewItem
);
604 SetHorizontalExtent("");
607 void wxListBox::SetString(int N
, const wxString
& s
)
610 if (!(m_windowStyle
& wxLB_MULTIPLE
) && !(m_windowStyle
& wxLB_EXTENDED
))
611 sel
= GetSelection();
613 char *oldData
= (char *)wxListBox::GetClientData(N
);
615 SendMessage(hwnd
, LB_DELETESTRING
, N
, 0);
618 if (N
== (m_noItems
- 1))
621 SendMessage(hwnd
, LB_INSERTSTRING
, newN
, (LPARAM
) (const char *)s
);
623 wxListBox::SetClientData(N
, oldData
);
625 // Selection may have changed
629 #if wxUSE_OWNER_DRAWN
630 if ( m_windowStyle
& wxLB_OWNERDRAW
)
631 // update item's text
632 m_aItems
[N
]->SetName(s
);
633 #endif //USE_OWNER_DRAWN
636 int wxListBox::Number (void) const
641 // For single selection items only
642 wxString
wxListBox::GetStringSelection (void) const
644 int sel
= GetSelection ();
646 return this->GetString (sel
);
651 bool wxListBox::SetStringSelection (const wxString
& s
, bool flag
)
653 int sel
= FindString (s
);
656 SetSelection (sel
, flag
);
663 // Is this the right thing? Won't setselection generate a command
664 // event too? No! It'll just generate a setselection event.
665 // But we still can't have this being called whenever a real command
666 // is generated, because it sets the selection, which will already
667 // have been done! (Unless we have an optional argument for calling
668 // by the actual window system, or a separate function, ProcessCommand)
669 void wxListBox::Command (wxCommandEvent
& event
)
671 if (event
.m_extraLong
)
672 SetSelection (event
.m_commandInt
);
675 Deselect (event
.m_commandInt
);
678 ProcessCommand (event
);
681 WXHBRUSH
wxListBox::OnCtlColor(WXHDC pDC
, WXHWND pWnd
, WXUINT nCtlColor
,
682 WXUINT message
, WXWPARAM wParam
, WXLPARAM lParam
)
687 HBRUSH hbrush
= Ctl3dCtlColorEx(message
, wParam
, lParam
);
688 return (WXHBRUSH
) hbrush
;
692 if (GetParent()->GetTransparentBackground())
693 SetBkMode((HDC
) pDC
, TRANSPARENT
);
695 SetBkMode((HDC
) pDC
, OPAQUE
);
697 ::SetBkColor((HDC
) pDC
, RGB(GetBackgroundColour().Red(), GetBackgroundColour().Green(), GetBackgroundColour().Blue()));
698 ::SetTextColor((HDC
) pDC
, RGB(GetForegroundColour().Red(), GetForegroundColour().Green(), GetForegroundColour().Blue()));
700 wxBrush
*backgroundBrush
= wxTheBrushList
->FindOrCreateBrush(GetBackgroundColour(), wxSOLID
);
702 // Note that this will be cleaned up in wxApp::OnIdle, if backgroundBrush
703 // has a zero usage count.
704 backgroundBrush
->RealizeResource();
705 return (WXHBRUSH
) backgroundBrush
->GetResourceHandle();
708 long wxListBox::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
719 case WM_QUERYDRAGICON
:
723 case WM_RBUTTONDBLCLK
:
726 case WM_MBUTTONDBLCLK
:
729 case WM_LBUTTONDBLCLK
:
735 case WM_INITMENUPOPUP
:
740 case WM_CHAR
: // Always an ASCII character
745 case WM_CTLCOLORLISTBOX
:
746 case WM_CTLCOLORMSGBOX
:
747 case WM_CTLCOLORSCROLLBAR
:
748 case WM_CTLCOLORSTATIC
:
749 case WM_CTLCOLOREDIT
:
750 case WM_SYSCOLORCHANGE
:
754 case WM_QUERYENDSESSION
:
756 case WM_GETMINMAXINFO
:
758 return MSWDefWindowProc(nMsg
, wParam
, lParam
);
762 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
765 #if wxUSE_OWNER_DRAWN
770 // space beneath/above each row in pixels
771 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
772 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
774 // the height is the same for all items
775 // ## should be changed for LBS_OWNERDRAWVARIABLE style listboxes
776 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
777 // message is not yet sent when we get here!
778 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
780 // only owner-drawn control should receive this message
781 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
783 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
786 dc
.SetHDC((WXHDC
)CreateIC("DISPLAY", NULL
, NULL
, 0));
787 dc
.SetFont(wxSystemSettings::GetSystemFont(wxSYS_ANSI_VAR_FONT
));
789 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
790 pStruct
->itemWidth
= dc
.GetCharWidth();
795 // forward the message to the appropriate item
796 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
798 // only owner-drawn control should receive this message
799 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
801 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
802 wxListBoxItem
*pItem
= (wxListBoxItem
*)SendMessage(hwnd
, LB_GETITEMDATA
,
805 wxCHECK( (int)pItem
!= LB_ERR
, FALSE
);
808 dc
.SetHDC((WXHDC
)pStruct
->hDC
, FALSE
);
809 wxRect
rect(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
,
810 pStruct
->rcItem
.right
- pStruct
->rcItem
.left
,
811 pStruct
->rcItem
.bottom
- pStruct
->rcItem
.top
);
813 return pItem
->OnDrawItem(dc
, rect
,
814 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
815 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);