1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/slider.cpp
3 // Purpose: wxSlider, using the Win95 (and later) trackbar control
4 // Author: Julian Smart
7 // Copyright: (c) Julian Smart 1998
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
29 #include "wx/slider.h"
32 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
36 #include "wx/msw/subwin.h"
38 // ----------------------------------------------------------------------------
40 // ----------------------------------------------------------------------------
45 // indices of labels in wxSlider::m_labels
54 // the gaps between the slider and the labels, in pixels
57 // the width of the borders including white space
58 const int BORDERPAD
= 8;
59 // these 2 values are arbitrary:
63 } // anonymous namespace
65 // ============================================================================
66 // wxSlider implementation
67 // ============================================================================
69 // ----------------------------------------------------------------------------
71 // ----------------------------------------------------------------------------
86 bool wxSlider::Create(wxWindow
*parent
,
94 const wxValidator
& validator
,
97 wxCHECK_MSG( minValue
< maxValue
, false,
98 wxT("Slider minimum must be strictly less than the maximum.") );
100 // our styles are redundant: wxSL_LEFT/RIGHT imply wxSL_VERTICAL and
101 // wxSL_TOP/BOTTOM imply wxSL_HORIZONTAL, but for backwards compatibility
102 // reasons we can't really change it, instead try to infer the orientation
103 // from the flags given to us here
104 switch ( style
& (wxSL_LEFT
| wxSL_RIGHT
| wxSL_TOP
| wxSL_BOTTOM
) )
108 style
|= wxSL_VERTICAL
;
113 style
|= wxSL_HORIZONTAL
;
117 // no specific direction, do we have at least the orientation?
118 if ( !(style
& (wxSL_HORIZONTAL
| wxSL_VERTICAL
)) )
120 // no, choose default
121 style
|= wxSL_BOTTOM
| wxSL_HORIZONTAL
;
125 wxASSERT_MSG( !(style
& wxSL_VERTICAL
) || !(style
& wxSL_HORIZONTAL
),
126 wxT("incompatible slider direction and orientation") );
129 // initialize everything
130 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
133 // ensure that we have correct values for GetLabelsSize()
134 m_rangeMin
= minValue
;
135 m_rangeMax
= maxValue
;
137 // create the labels first, so that our DoGetBestSize() could take them
140 // note that we could simply create 3 wxStaticTexts here but it could
141 // result in some observable side effects at wx level (e.g. the parent of
142 // wxSlider would have 3 more children than expected) and so we prefer not
143 // to do it like this
144 if ( m_windowStyle
& wxSL_LABELS
)
146 m_labels
= new wxSubwindows(SliderLabel_Last
);
148 HWND hwndParent
= GetHwndOf(parent
);
149 for ( size_t n
= 0; n
< SliderLabel_Last
; n
++ )
151 wxWindowIDRef lblid
= NewControlId();
153 HWND wnd
= ::CreateWindow
157 WS_CHILD
| WS_VISIBLE
| SS_CENTER
,
160 (HMENU
)wxUIntToPtr(lblid
.GetValue()),
165 m_labels
->Set(n
, wnd
, lblid
);
167 m_labels
->SetFont(GetFont());
170 // now create the main control too
171 if ( !MSWCreateControl(TRACKBAR_CLASS
, wxEmptyString
, pos
, size
) )
174 // and initialize everything
175 SetRange(minValue
, maxValue
);
177 SetPageSize( wxMax(1, (maxValue
- minValue
)/10) );
179 // we need to position the labels correctly if we have them and if
180 // SetSize() hadn't been called before (when best size was determined by
181 // MSWCreateControl()) as in this case they haven't been put in place yet
182 if ( m_labels
&& size
.x
!= wxDefaultCoord
&& size
.y
!= wxDefaultCoord
)
190 WXDWORD
wxSlider::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
192 WXDWORD msStyle
= wxControl::MSWGetStyle(style
, exstyle
);
194 // TBS_HORZ, TBS_RIGHT and TBS_BOTTOM are 0 but do include them for clarity
195 msStyle
|= style
& wxSL_VERTICAL
? TBS_VERT
: TBS_HORZ
;
197 if ( style
& wxSL_BOTH
)
199 // this fully specifies the style combined with TBS_VERT/HORZ above
202 else // choose one direction
204 if ( style
& wxSL_LEFT
)
206 else if ( style
& wxSL_RIGHT
)
207 msStyle
|= TBS_RIGHT
;
208 else if ( style
& wxSL_TOP
)
210 else if ( style
& wxSL_BOTTOM
)
211 msStyle
|= TBS_BOTTOM
;
214 if ( style
& wxSL_AUTOTICKS
)
215 msStyle
|= TBS_AUTOTICKS
;
217 msStyle
|= TBS_NOTICKS
;
219 if ( style
& wxSL_SELRANGE
)
220 msStyle
|= TBS_ENABLESELRANGE
;
225 wxSlider::~wxSlider()
230 // ----------------------------------------------------------------------------
232 // ----------------------------------------------------------------------------
234 bool wxSlider::MSWOnScroll(int WXUNUSED(orientation
),
236 WXWORD
WXUNUSED(pos
),
239 wxEventType scrollEvent
;
243 scrollEvent
= wxEVT_SCROLL_TOP
;
247 scrollEvent
= wxEVT_SCROLL_BOTTOM
;
251 scrollEvent
= wxEVT_SCROLL_LINEUP
;
255 scrollEvent
= wxEVT_SCROLL_LINEDOWN
;
259 scrollEvent
= wxEVT_SCROLL_PAGEUP
;
263 scrollEvent
= wxEVT_SCROLL_PAGEDOWN
;
267 scrollEvent
= wxEVT_SCROLL_THUMBTRACK
;
271 case SB_THUMBPOSITION
:
274 scrollEvent
= wxEVT_SCROLL_THUMBRELEASE
;
275 m_isDragging
= false;
279 // this seems to only happen when the mouse wheel is used: in
280 // this case, as it might be unexpected to get THUMBRELEASE
281 // without preceding THUMBTRACKs, we don't generate it at all
282 // but generate CHANGED event because the control itself does
283 // not send us SB_ENDSCROLL for whatever reason when mouse
285 scrollEvent
= wxEVT_SCROLL_CHANGED
;
290 scrollEvent
= wxEVT_SCROLL_CHANGED
;
294 // unknown scroll event?
298 int newPos
= ValueInvertOrNot((int) ::SendMessage((HWND
) control
, TBM_GETPOS
, 0, 0));
299 if ( (newPos
< GetMin()) || (newPos
> GetMax()) )
301 // out of range - but we did process it
307 wxScrollEvent
event(scrollEvent
, m_windowId
);
308 event
.SetPosition(newPos
);
309 event
.SetEventObject( this );
310 HandleWindowEvent(event
);
312 wxCommandEvent
cevent( wxEVT_SLIDER
, GetId() );
313 cevent
.SetInt( newPos
);
314 cevent
.SetEventObject( this );
316 return HandleWindowEvent( cevent
);
319 void wxSlider::Command (wxCommandEvent
& event
)
321 SetValue (event
.GetInt());
322 ProcessCommand (event
);
325 // ----------------------------------------------------------------------------
327 // ----------------------------------------------------------------------------
329 wxRect
wxSlider::GetBoundingBox() const
331 // take care not to call our own functions which would call us recursively
333 wxSliderBase::DoGetPosition(&x
, &y
);
334 wxSliderBase::DoGetSize(&w
, &h
);
336 wxRect
rect(x
, y
, w
, h
);
339 wxRect lrect
= m_labels
->GetBoundingBox();
340 GetParent()->ScreenToClient(&lrect
.x
, &lrect
.y
);
347 void wxSlider::DoGetSize(int *width
, int *height
) const
349 wxRect rect
= GetBoundingBox();
354 *height
= rect
.height
;
357 void wxSlider::DoGetPosition(int *x
, int *y
) const
359 wxRect rect
= GetBoundingBox();
367 int wxSlider::GetLabelsSize(int *widthMin
, int *widthMax
) const
369 if ( widthMin
&& widthMax
)
371 *widthMin
= GetTextExtent(Format(m_rangeMin
)).x
;
372 *widthMax
= GetTextExtent(Format(m_rangeMax
)).x
;
374 if ( HasFlag(wxSL_INVERSE
) )
376 wxSwap(*widthMin
, *widthMax
);
380 return HasFlag(wxSL_LABELS
) ? GetCharHeight() : 0;
383 void wxSlider::DoMoveWindow(int x
, int y
, int width
, int height
)
385 // all complications below are because we need to position the labels,
386 // without them everything is easy
389 wxSliderBase::DoMoveWindow(x
, y
, width
, height
);
395 const int labelHeight
= GetLabelsSize(&minLabelWidth
, &maxLabelWidth
);
396 const int longestLabelWidth
= wxMax(minLabelWidth
, maxLabelWidth
);
397 if ( !HasFlag(wxSL_MIN_MAX_LABELS
) )
404 if ( HasFlag(wxSL_TICKS
))
406 if ( HasFlag(wxSL_BOTH
))
409 // be careful to position the slider itself after moving the labels as
410 // otherwise our GetBoundingBox(), which is called from WM_SIZE handler,
411 // would return a wrong result and wrong size would be cached internally
412 if ( HasFlag(wxSL_VERTICAL
) )
417 int xLabel
= (wxMax((THUMB
+ (BORDERPAD
* 2)), longestLabelWidth
) / 2) -
418 (longestLabelWidth
/ 2) + x
;
419 if ( HasFlag(wxSL_LEFT
) )
422 holdBottomX
= xLabel
- (abs(maxLabelWidth
- minLabelWidth
) / 2);
426 holdTopX
= xLabel
+ longestLabelWidth
+ (abs(maxLabelWidth
- minLabelWidth
) / 2);
427 holdBottomX
= xLabel
+ longestLabelWidth
;
429 labelOffset
= longestLabelWidth
+ HGAP
;
432 if ( HasFlag(wxSL_MIN_MAX_LABELS
) )
434 if ( HasFlag(wxSL_INVERSE
) )
436 wxSwap(holdTopX
, holdBottomX
);
439 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Min
],
442 minLabelWidth
, labelHeight
);
443 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Max
],
445 y
+ height
- labelHeight
,
446 maxLabelWidth
, labelHeight
);
449 if ( HasFlag(wxSL_VALUE_LABEL
) )
451 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Value
],
452 x
+ ( HasFlag(wxSL_LEFT
) ? THUMB
+ tickOffset
+ HGAP
: 0 ),
453 y
+ (height
- labelHeight
)/2,
454 longestLabelWidth
, labelHeight
);
457 // position the slider itself along the left/right edge
458 wxSliderBase::DoMoveWindow(
461 THUMB
+ tickOffset
+ HGAP
,
462 height
- (labelHeight
* 2));
467 (y
+ ((THUMB
+ tickOffset
) / 2)) - (labelHeight
/ 2);
470 ((width
- (minLabelWidth
+ maxLabelWidth
)) / 2) -
471 (longestLabelWidth
/ 2);
475 if ( HasFlag(wxSL_VALUE_LABEL
) )
477 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Value
],
479 y
+ (HasFlag(wxSL_BOTTOM
) ? 0 : THUMB
+ tickOffset
),
480 longestLabelWidth
, labelHeight
);
482 if ( HasFlag(wxSL_BOTTOM
) )
484 ySlider
+= labelHeight
;
485 yLabelMinMax
+= labelHeight
;
489 if ( HasFlag(wxSL_MIN_MAX_LABELS
) )
491 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Min
],
494 minLabelWidth
, labelHeight
);
495 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Max
],
496 x
+ width
- maxLabelWidth
,
498 maxLabelWidth
, labelHeight
);
501 // position the slider itself along the top/bottom edge
502 wxSliderBase::DoMoveWindow(
503 x
+ minLabelWidth
+ VGAP
,
505 width
- (minLabelWidth
+ maxLabelWidth
+ (VGAP
*2)),
510 wxSize
wxSlider::DoGetBestSize() const
512 // this value is arbitrary:
513 static const int length
= 100;
517 if ( HasFlag(wxSL_VERTICAL
) )
527 int hLabel
= GetLabelsSize(&widthMin
, &widthMax
);
529 // account for the labels
530 if ( HasFlag(wxSL_MIN_MAX_LABELS
) )
531 size
.x
+= HGAP
+ wxMax(widthMin
, widthMax
);
533 // labels are indented relative to the slider itself
545 int labelSize
= GetLabelsSize();
547 // Min/max labels are compensated by the ticks so we don't need
548 // extra space for them if we're also showing ticks.
549 if ( HasFlag(wxSL_MIN_MAX_LABELS
) && !HasFlag(wxSL_TICKS
) )
552 // The value label is always on top of the control and so does need
553 // extra space in any case.
554 if ( HasFlag(wxSL_VALUE_LABEL
) )
559 // need extra space to show ticks
560 if ( HasFlag(wxSL_TICKS
) )
563 // and maybe twice as much if we show them on both sides
564 if ( HasFlag(wxSL_BOTH
) )
570 // ----------------------------------------------------------------------------
571 // slider-specific methods
572 // ----------------------------------------------------------------------------
574 int wxSlider::GetValue() const
576 return ValueInvertOrNot(::SendMessage(GetHwnd(), TBM_GETPOS
, 0, 0));
579 void wxSlider::SetValue(int value
)
581 ::SendMessage(GetHwnd(), TBM_SETPOS
, (WPARAM
)TRUE
, (LPARAM
)ValueInvertOrNot(value
));
585 ::SetWindowText((*m_labels
)[SliderLabel_Value
], Format(value
).t_str());
589 void wxSlider::SetRange(int minValue
, int maxValue
)
591 // Remember the old logical value if we need to update the physical control
592 // value after changing its range in wxSL_INVERSE case (and avoid an
593 // unnecessary call to GetValue() otherwise as it's just not needed).
594 const int valueOld
= HasFlag(wxSL_INVERSE
) ? GetValue() : 0;
596 m_rangeMin
= minValue
;
597 m_rangeMax
= maxValue
;
599 ::SendMessage(GetHwnd(), TBM_SETRANGEMIN
, TRUE
, m_rangeMin
);
600 ::SendMessage(GetHwnd(), TBM_SETRANGEMAX
, TRUE
, m_rangeMax
);
604 ::SetWindowText((*m_labels
)[SliderLabel_Min
],
605 Format(ValueInvertOrNot(m_rangeMin
)).t_str());
606 ::SetWindowText((*m_labels
)[SliderLabel_Max
],
607 Format(ValueInvertOrNot(m_rangeMax
)).t_str());
610 // When emulating wxSL_INVERSE style in wxWidgets, we need to update the
611 // value after changing the range to ensure that the value seen by the user
612 // code, i.e. the one returned by GetValue(), does not change.
613 if ( HasFlag(wxSL_INVERSE
) )
615 ::SendMessage(GetHwnd(), TBM_SETPOS
, TRUE
, ValueInvertOrNot(valueOld
));
619 void wxSlider::DoSetTickFreq(int n
)
622 ::SendMessage( GetHwnd(), TBM_SETTICFREQ
, (WPARAM
) n
, (LPARAM
) 0 );
625 void wxSlider::SetPageSize(int pageSize
)
627 ::SendMessage( GetHwnd(), TBM_SETPAGESIZE
, (WPARAM
) 0, (LPARAM
) pageSize
);
628 m_pageSize
= pageSize
;
631 int wxSlider::GetPageSize() const
636 void wxSlider::ClearSel()
638 ::SendMessage(GetHwnd(), TBM_CLEARSEL
, (WPARAM
) TRUE
, (LPARAM
) 0);
641 void wxSlider::ClearTicks()
643 ::SendMessage(GetHwnd(), TBM_CLEARTICS
, (WPARAM
) TRUE
, (LPARAM
) 0);
646 void wxSlider::SetLineSize(int lineSize
)
648 m_lineSize
= lineSize
;
649 ::SendMessage(GetHwnd(), TBM_SETLINESIZE
, (WPARAM
) 0, (LPARAM
) lineSize
);
652 int wxSlider::GetLineSize() const
654 return (int)::SendMessage(GetHwnd(), TBM_GETLINESIZE
, 0, 0);
657 int wxSlider::GetSelEnd() const
659 return (int)::SendMessage(GetHwnd(), TBM_GETSELEND
, 0, 0);
662 int wxSlider::GetSelStart() const
664 return (int)::SendMessage(GetHwnd(), TBM_GETSELSTART
, 0, 0);
667 void wxSlider::SetSelection(int minPos
, int maxPos
)
669 ::SendMessage(GetHwnd(), TBM_SETSEL
,
670 (WPARAM
) TRUE
/* redraw */,
671 (LPARAM
) MAKELONG( minPos
, maxPos
) );
674 void wxSlider::SetThumbLength(int len
)
676 ::SendMessage(GetHwnd(), TBM_SETTHUMBLENGTH
, (WPARAM
) len
, (LPARAM
) 0);
679 int wxSlider::GetThumbLength() const
681 return (int)::SendMessage( GetHwnd(), TBM_GETTHUMBLENGTH
, 0, 0);
684 void wxSlider::SetTick(int tickPos
)
686 ::SendMessage( GetHwnd(), TBM_SETTIC
, (WPARAM
) 0, (LPARAM
) tickPos
);
689 // ----------------------------------------------------------------------------
690 // composite control methods
691 // ----------------------------------------------------------------------------
693 WXHWND
wxSlider::GetStaticMin() const
695 return m_labels
? (WXHWND
)(*m_labels
)[SliderLabel_Min
] : NULL
;
698 WXHWND
wxSlider::GetStaticMax() const
700 return m_labels
? (WXHWND
)(*m_labels
)[SliderLabel_Max
] : NULL
;
703 WXHWND
wxSlider::GetEditValue() const
705 return m_labels
? (WXHWND
)(*m_labels
)[SliderLabel_Value
] : NULL
;
708 WX_FORWARD_STD_METHODS_TO_SUBWINDOWS(wxSlider
, wxSliderBase
, m_labels
)
710 #endif // wxUSE_SLIDER