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"
37 #include <wx/msw/gnuwin32/extra.h>
45 #include "wx/ownerdrw.h"
48 #include "wx/dynarray.h"
51 #if !USE_SHARED_LIBRARY
52 IMPLEMENT_DYNAMIC_CLASS(wxListBox
, wxControl
)
55 // ============================================================================
56 // list box item declaration and implementation
57 // ============================================================================
61 class wxListBoxItem
: public wxOwnerDrawn
64 wxListBoxItem(const wxString
& str
= "");
67 wxListBoxItem::wxListBoxItem(const wxString
& str
) : wxOwnerDrawn(str
, FALSE
)
69 // no bitmaps/checkmarks
73 wxOwnerDrawn
*wxListBox::CreateItem(size_t n
)
75 return new wxListBoxItem();
78 #endif //USE_OWNER_DRAWN
80 // ============================================================================
81 // list box control implementation
82 // ============================================================================
84 // this macro is dangerous but still better than tons of (HWND)GetHWND()
85 #define hwnd (HWND)GetHWND()
87 bool wxListBox::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
90 if (param == LBN_SELCANCEL)
92 event.extraLong = FALSE;
95 if (param
== LBN_SELCHANGE
)
97 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_SELECTED
, m_windowId
);
98 wxArrayInt aSelections
;
99 int count
= GetSelections(aSelections
);
102 event
.m_commandInt
= aSelections
[0] ;
103 event
.m_clientData
= GetClientData(event
.m_commandInt
);
104 wxString
str(GetString(event
.m_commandInt
));
106 event
.m_commandString
= copystring((char *)(const char *)str
);
110 event
.m_commandInt
= -1 ;
111 event
.m_commandString
= copystring("") ;
114 event
.SetEventObject( this );
115 ProcessCommand(event
);
116 if (event
.m_commandString
)
117 delete[] event
.m_commandString
;
120 else if (param
== LBN_DBLCLK
)
122 wxCommandEvent
event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED
, m_windowId
);
123 event
.SetEventObject( this );
124 GetEventHandler()->ProcessEvent(event
) ;
128 #if WXWIN_COMPATIBILITY
129 wxWindow *parent = (wxWindow *)GetParent();
131 parent->GetEventHandler()->OnDefaultAction(this);
141 wxListBox::wxListBox(void)
147 bool wxListBox::Create(wxWindow
*parent
, wxWindowID id
,
150 int n
, const wxString choices
[],
152 const wxValidator
& validator
,
153 const wxString
& name
)
160 SetValidator(validator
);
162 if (parent
) parent
->AddChild(this);
164 wxSystemSettings settings
;
165 SetBackgroundColour(settings
.GetSystemColour(wxSYS_COLOUR_WINDOW
));
166 SetForegroundColour(parent
->GetForegroundColour());
168 m_windowId
= ( id
== -1 ) ? (int)NewControlId() : id
;
174 m_windowStyle
= style
;
176 DWORD wstyle
= WS_VSCROLL
| WS_TABSTOP
| LBS_NOTIFY
| LBS_HASSTRINGS
;
177 if (m_windowStyle
& wxLB_MULTIPLE
)
178 wstyle
|= LBS_MULTIPLESEL
;
179 else if (m_windowStyle
& wxLB_EXTENDED
)
180 wstyle
|= LBS_EXTENDEDSEL
;
182 if (m_windowStyle
& wxLB_ALWAYS_SB
)
183 wstyle
|= LBS_DISABLENOSCROLL
;
184 if (m_windowStyle
& wxLB_HSCROLL
)
185 wstyle
|= WS_HSCROLL
;
186 if (m_windowStyle
& wxLB_SORT
)
189 #if wxUSE_OWNER_DRAWN
190 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
191 // we don't support LBS_OWNERDRAWVARIABLE yet
192 wstyle
|= LBS_OWNERDRAWFIXED
;
195 // Without this style, you get unexpected heights, so e.g. constraint layout
196 // doesn't work properly
197 wstyle
|= LBS_NOINTEGRALHEIGHT
;
200 WXDWORD exStyle
= Determine3DEffects(WS_EX_CLIENTEDGE
, &want3D
) ;
202 // Even with extended styles, need to combine with WS_BORDER
203 // for them to look right.
204 if ( want3D
|| wxStyleHasBorder(m_windowStyle
) )
209 m_hWnd
= (WXHWND
)::CreateWindowEx(exStyle
, "LISTBOX", NULL
,
212 (HWND
)parent
->GetHWND(), (HMENU
)m_windowId
,
213 wxGetInstance(), NULL
);
215 wxCHECK_MSG( m_hWnd
, FALSE
, "Failed to create listbox" );
220 Ctl3dSubclassCtl(hwnd
);
225 // Subclass again to catch messages
229 for (ui
= 0; ui
< (size_t)n
; ui
++) {
233 /* Not needed -- done in Append
234 #if wxUSE_OWNER_DRAWN
235 if ( m_windowStyle & wxLB_OWNERDRAW ) {
236 for (ui = 0; ui < (size_t)n; ui++) {
237 // create new item which will process WM_{DRAW|MEASURE}ITEM messages
238 wxOwnerDrawn *pNewItem = CreateItem(ui);
239 pNewItem->SetName(choices[ui]);
240 m_aItems.Add(pNewItem);
241 ListBox_SetItemData(hwnd, ui, pNewItem);
247 if ( (m_windowStyle
& wxLB_MULTIPLE
) == 0 )
248 SendMessage(hwnd
, LB_SETCURSEL
, 0, 0);
250 SetFont(parent
->GetFont());
252 SetSize(x
, y
, width
, height
);
259 wxListBox::~wxListBox(void)
261 #if wxUSE_OWNER_DRAWN
262 size_t uiCount
= m_aItems
.Count();
263 while ( uiCount
-- != 0 ) {
264 delete m_aItems
[uiCount
];
269 void wxListBox::SetupColours(void)
271 SetBackgroundColour(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOW
));
272 SetForegroundColour(GetParent()->GetForegroundColour());
275 void wxListBox::SetFirstItem(int N
)
277 SendMessage(hwnd
,LB_SETTOPINDEX
,(WPARAM
)N
,(LPARAM
)0) ;
280 void wxListBox::SetFirstItem(const wxString
& s
)
282 int N
= FindString(s
) ;
288 void wxListBox::Delete(int N
)
290 SendMessage(hwnd
, LB_DELETESTRING
, N
, 0);
292 SetHorizontalExtent("");
295 void wxListBox::Append(const wxString
& item
)
297 int index
= ListBox_AddString(hwnd
, item
);
300 #if wxUSE_OWNER_DRAWN
301 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
302 wxOwnerDrawn
*pNewItem
= CreateItem(index
); // dummy argument
303 pNewItem
->SetName(item
);
304 m_aItems
.Add(pNewItem
);
305 ListBox_SetItemData(hwnd
, index
, pNewItem
);
309 SetHorizontalExtent(item
);
312 void wxListBox::Append(const wxString
& item
, char *Client_data
)
314 int index
= ListBox_AddString(hwnd
, item
);
317 #if wxUSE_OWNER_DRAWN
318 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
319 // client data must be pointer to wxOwnerDrawn, otherwise we would crash
320 // in OnMeasure/OnDraw.
321 wxFAIL_MSG("Can't use client data with owner-drawn listboxes");
325 ListBox_SetItemData(hwnd
, index
, Client_data
);
327 SetHorizontalExtent(item
);
330 void wxListBox::Set(int n
, const wxString
*choices
, char** clientData
)
332 ShowWindow(hwnd
, SW_HIDE
);
333 ListBox_ResetContent(hwnd
);
335 for (i
= 0; i
< n
; i
++)
337 ListBox_AddString(hwnd
, choices
[i
]);
339 ListBox_SetItemData(hwnd
, i
, clientData
[i
]);
343 #if wxUSE_OWNER_DRAWN
344 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
345 // first delete old items
346 size_t ui
= m_aItems
.Count();
347 while ( ui
-- != 0 ) {
352 // then create new ones
353 for (ui
= 0; ui
< (size_t)n
; ui
++) {
354 wxOwnerDrawn
*pNewItem
= CreateItem(ui
);
355 pNewItem
->SetName(choices
[ui
]);
356 m_aItems
.Add(pNewItem
);
357 ListBox_SetItemData(hwnd
, ui
, pNewItem
);
359 wxASSERT_MSG(clientData
[ui
] == NULL
,
360 "Can't use client data with owner-drawn listboxes");
365 SetHorizontalExtent("");
366 ShowWindow(hwnd
, SW_SHOW
);
369 int wxListBox::FindString(const wxString
& s
) const
371 int pos
= ListBox_FindStringExact(hwnd
, (WPARAM
)-1, s
);
378 void wxListBox::Clear(void)
380 ListBox_ResetContent(hwnd
);
383 ListBox_GetHorizontalExtent(hwnd
);
386 void wxListBox::SetSelection(int N
, bool select
)
388 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
389 SendMessage(hwnd
, LB_SETSEL
, select
, N
);
395 SendMessage(hwnd
, LB_SETCURSEL
, N1
, 0);
399 bool wxListBox::Selected(int N
) const
401 return SendMessage(hwnd
, LB_GETSEL
, N
, 0) == 0 ? FALSE
: TRUE
;
404 void wxListBox::Deselect(int N
)
406 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
407 SendMessage(hwnd
, LB_SETSEL
, FALSE
, N
);
410 char *wxListBox::GetClientData(int N
) const
412 return (char *)SendMessage(hwnd
, LB_GETITEMDATA
, N
, 0);
415 void wxListBox::SetClientData(int N
, char *Client_data
)
417 if ( ListBox_SetItemData(hwnd
, N
, Client_data
) == LB_ERR
)
418 wxLogDebug("LB_SETITEMDATA failed");
421 // Return number of selections and an array of selected integers
422 int wxListBox::GetSelections(wxArrayInt
& aSelections
) const
426 if ((m_windowStyle
& wxLB_MULTIPLE
) || (m_windowStyle
& wxLB_EXTENDED
))
428 int no_sel
= ListBox_GetSelCount(hwnd
);
430 int *selections
= new int[no_sel
];
431 if ( ListBox_GetSelItems(hwnd
, no_sel
, selections
) == LB_ERR
) {
432 wxFAIL_MSG("This listbox can't have single-selection style!");
435 aSelections
.Alloc(no_sel
);
436 for ( int n
= 0; n
< no_sel
; n
++ )
437 aSelections
.Add(selections
[n
]);
439 delete [] selections
;
444 else // single-selection listbox
446 aSelections
.Add(ListBox_GetCurSel(hwnd
));
452 // Get single selection, for single choice list items
453 int wxListBox::GetSelection() const
455 wxCHECK_MSG( !(m_windowStyle
& wxLB_MULTIPLE
) &&
456 !(m_windowStyle
& wxLB_EXTENDED
),
458 "GetSelection() can't be used with multiple-selection "
459 "listboxes, use GetSelections() instead." );
461 return ListBox_GetCurSel(hwnd
);
464 // Find string for position
465 wxString
wxListBox::GetString(int N
) const
467 if (N
< 0 || N
> m_noItems
)
470 int len
= (int)SendMessage(hwnd
, LB_GETTEXT
, N
, (LONG
)wxBuffer
);
472 return wxString(wxBuffer
);
475 void wxListBox::SetSize(int x
, int y
, int width
, int height
, int sizeFlags
)
477 int currentX
, currentY
;
478 GetPosition(¤tX
, ¤tY
);
485 if (x
== -1 || (sizeFlags
& wxSIZE_ALLOW_MINUS_ONE
))
487 if (y
== -1 || (sizeFlags
& wxSIZE_ALLOW_MINUS_ONE
))
490 AdjustForParentClientOrigin(x1
, y1
, sizeFlags
);
492 // If we're prepared to use the existing size, then...
493 if (width
== -1 && height
== -1 && ((sizeFlags
& wxSIZE_AUTO
) != wxSIZE_AUTO
))
498 int cx
; // button font dimensions
501 wxGetCharSize(GetHWND(), &cx
, &cy
, & this->GetFont());
503 float control_width
, control_height
, control_x
, control_y
;
505 // Deal with default size (using -1 values)
507 w1
= DEFAULT_ITEM_WIDTH
;
510 h1
= DEFAULT_ITEM_HEIGHT
;
512 control_x
= (float)x1
;
513 control_y
= (float)y1
;
514 control_width
= (float)w1
;
515 control_height
= (float)h1
;
517 // Calculations may have made size too small
518 if (control_height
<= 0)
519 control_height
= (float)DEFAULT_ITEM_HEIGHT
;
521 if (control_width
<= 0)
522 control_width
= (float)DEFAULT_ITEM_WIDTH
;
524 // wxDebugMsg("About to set the listbox height to %d", (int)control_height);
525 MoveWindow(hwnd
, (int)control_x
, (int)control_y
,
526 (int)control_width
, (int)control_height
, TRUE
);
530 // Windows-specific code to set the horizontal extent of
531 // the listbox, if necessary. If s is non-NULL, it's
532 // used to calculate the horizontal extent.
533 // Otherwise, all strings are used.
534 void wxListBox::SetHorizontalExtent(const wxString
& s
)
536 // Only necessary if we want a horizontal scrollbar
537 if (!(m_windowStyle
& wxHSCROLL
))
539 TEXTMETRIC lpTextMetric
;
543 int existingExtent
= (int)SendMessage(hwnd
, LB_GETHORIZONTALEXTENT
, 0, 0L);
544 HDC dc
= GetWindowDC(hwnd
);
546 if (GetFont().Ok() && GetFont().GetResourceHandle())
547 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
549 GetTextMetrics(dc
, &lpTextMetric
);
551 ::GetTextExtentPoint(dc
, (LPSTR
) (const char *)s
, s
.Length(), &extentXY
);
552 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
555 ::SelectObject(dc
, oldFont
);
558 if (extentX
> existingExtent
)
559 SendMessage(hwnd
, LB_SETHORIZONTALEXTENT
, LOWORD(extentX
), 0L);
564 int largestExtent
= 0;
565 HDC dc
= GetWindowDC(hwnd
);
567 if (GetFont().Ok() && GetFont().GetResourceHandle())
568 oldFont
= (HFONT
) ::SelectObject(dc
, (HFONT
) GetFont().GetResourceHandle());
570 GetTextMetrics(dc
, &lpTextMetric
);
572 for (i
= 0; i
< m_noItems
; i
++)
574 int len
= (int)SendMessage(hwnd
, LB_GETTEXT
, i
, (LONG
)wxBuffer
);
577 ::GetTextExtentPoint(dc
, (LPSTR
)wxBuffer
, len
, &extentXY
);
578 int extentX
= (int)(extentXY
.cx
+ lpTextMetric
.tmAveCharWidth
);
579 if (extentX
> largestExtent
)
580 largestExtent
= extentX
;
583 ::SelectObject(dc
, oldFont
);
586 SendMessage(hwnd
, LB_SETHORIZONTALEXTENT
, LOWORD(largestExtent
), 0L);
591 wxListBox::InsertItems(int nItems
, const wxString items
[], int pos
)
594 for (i
= 0; i
< nItems
; i
++)
595 ListBox_InsertString(hwnd
, i
+ pos
, items
[i
]);
598 #if wxUSE_OWNER_DRAWN
599 if ( m_windowStyle
& wxLB_OWNERDRAW
) {
600 for ( i
= 0; i
< nItems
; i
++ ) {
601 wxOwnerDrawn
*pNewItem
= CreateItem((size_t)(pos
+ i
));
602 pNewItem
->SetName(items
[i
]);
603 m_aItems
.Insert(pNewItem
, (size_t)(pos
+ i
));
604 ListBox_SetItemData(hwnd
, i
, pNewItem
);
609 SetHorizontalExtent("");
612 void wxListBox::SetString(int N
, const wxString
& s
)
615 if (!(m_windowStyle
& wxLB_MULTIPLE
) && !(m_windowStyle
& wxLB_EXTENDED
))
616 sel
= GetSelection();
618 char *oldData
= (char *)wxListBox::GetClientData(N
);
620 SendMessage(hwnd
, LB_DELETESTRING
, N
, 0);
623 if (N
== (m_noItems
- 1))
626 SendMessage(hwnd
, LB_INSERTSTRING
, newN
, (LPARAM
) (const char *)s
);
628 wxListBox::SetClientData(N
, oldData
);
630 // Selection may have changed
634 #if wxUSE_OWNER_DRAWN
635 if ( m_windowStyle
& wxLB_OWNERDRAW
)
636 // update item's text
637 m_aItems
[N
]->SetName(s
);
638 #endif //USE_OWNER_DRAWN
641 int wxListBox::Number (void) const
646 // For single selection items only
647 wxString
wxListBox::GetStringSelection (void) const
649 int sel
= GetSelection ();
651 return this->GetString (sel
);
656 bool wxListBox::SetStringSelection (const wxString
& s
, bool flag
)
658 int sel
= FindString (s
);
661 SetSelection (sel
, flag
);
668 // Is this the right thing? Won't setselection generate a command
669 // event too? No! It'll just generate a setselection event.
670 // But we still can't have this being called whenever a real command
671 // is generated, because it sets the selection, which will already
672 // have been done! (Unless we have an optional argument for calling
673 // by the actual window system, or a separate function, ProcessCommand)
674 void wxListBox::Command (wxCommandEvent
& event
)
676 if (event
.m_extraLong
)
677 SetSelection (event
.m_commandInt
);
680 Deselect (event
.m_commandInt
);
683 ProcessCommand (event
);
686 WXHBRUSH
wxListBox::OnCtlColor(WXHDC pDC
, WXHWND pWnd
, WXUINT nCtlColor
,
687 WXUINT message
, WXWPARAM wParam
, WXLPARAM lParam
)
692 HBRUSH hbrush
= Ctl3dCtlColorEx(message
, wParam
, lParam
);
693 return (WXHBRUSH
) hbrush
;
697 if (GetParent()->GetTransparentBackground())
698 SetBkMode((HDC
) pDC
, TRANSPARENT
);
700 SetBkMode((HDC
) pDC
, OPAQUE
);
702 ::SetBkColor((HDC
) pDC
, RGB(GetBackgroundColour().Red(), GetBackgroundColour().Green(), GetBackgroundColour().Blue()));
703 ::SetTextColor((HDC
) pDC
, RGB(GetForegroundColour().Red(), GetForegroundColour().Green(), GetForegroundColour().Blue()));
705 wxBrush
*backgroundBrush
= wxTheBrushList
->FindOrCreateBrush(GetBackgroundColour(), wxSOLID
);
707 // Note that this will be cleaned up in wxApp::OnIdle, if backgroundBrush
708 // has a zero usage count.
709 backgroundBrush
->RealizeResource();
710 return (WXHBRUSH
) backgroundBrush
->GetResourceHandle();
713 long wxListBox::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
724 case WM_QUERYDRAGICON
:
728 case WM_RBUTTONDBLCLK
:
731 case WM_MBUTTONDBLCLK
:
734 case WM_LBUTTONDBLCLK
:
740 case WM_INITMENUPOPUP
:
745 case WM_CHAR
: // Always an ASCII character
750 case WM_CTLCOLORLISTBOX
:
751 case WM_CTLCOLORMSGBOX
:
752 case WM_CTLCOLORSCROLLBAR
:
753 case WM_CTLCOLORSTATIC
:
754 case WM_CTLCOLOREDIT
:
755 case WM_SYSCOLORCHANGE
:
759 case WM_QUERYENDSESSION
:
761 case WM_GETMINMAXINFO
:
763 return MSWDefWindowProc(nMsg
, wParam
, lParam
);
767 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
770 #if wxUSE_OWNER_DRAWN
775 // space beneath/above each row in pixels
776 // "standard" checklistbox use 1 here, some might prefer 2. 0 is ugly.
777 #define OWNER_DRAWN_LISTBOX_EXTRA_SPACE (1)
779 // the height is the same for all items
780 // ## should be changed for LBS_OWNERDRAWVARIABLE style listboxes
781 // NB: can't forward this to wxListBoxItem because LB_SETITEMDATA
782 // message is not yet sent when we get here!
783 bool wxListBox::MSWOnMeasure(WXMEASUREITEMSTRUCT
*item
)
785 // only owner-drawn control should receive this message
786 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
788 MEASUREITEMSTRUCT
*pStruct
= (MEASUREITEMSTRUCT
*)item
;
791 dc
.SetHDC((WXHDC
)CreateIC("DISPLAY", NULL
, NULL
, 0));
792 dc
.SetFont(wxSystemSettings::GetSystemFont(wxSYS_ANSI_VAR_FONT
));
794 pStruct
->itemHeight
= dc
.GetCharHeight() + 2*OWNER_DRAWN_LISTBOX_EXTRA_SPACE
;
795 pStruct
->itemWidth
= dc
.GetCharWidth();
800 // forward the message to the appropriate item
801 bool wxListBox::MSWOnDraw(WXDRAWITEMSTRUCT
*item
)
803 // only owner-drawn control should receive this message
804 wxCHECK( ((m_windowStyle
& wxLB_OWNERDRAW
) == wxLB_OWNERDRAW
), FALSE
);
806 DRAWITEMSTRUCT
*pStruct
= (DRAWITEMSTRUCT
*)item
;
807 wxListBoxItem
*pItem
= (wxListBoxItem
*)SendMessage(hwnd
, LB_GETITEMDATA
,
810 wxCHECK( (int)pItem
!= LB_ERR
, FALSE
);
813 dc
.SetHDC((WXHDC
)pStruct
->hDC
, FALSE
);
814 wxRect
rect(pStruct
->rcItem
.left
, pStruct
->rcItem
.top
,
815 pStruct
->rcItem
.right
- pStruct
->rcItem
.left
,
816 pStruct
->rcItem
.bottom
- pStruct
->rcItem
.top
);
818 return pItem
->OnDrawItem(dc
, rect
,
819 (wxOwnerDrawn::wxODAction
)pStruct
->itemAction
,
820 (wxOwnerDrawn::wxODStatus
)pStruct
->itemState
);