PageRenderTime 101ms CodeModel.GetById 75ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 0ms

/MahApps.Metro.Controls/ToggleSwitch.cs

https://bitbucket.org/aeoth/mahapps.metro/
C# | 683 lines | 311 code | 74 blank | 298 comment | 47 complexity | 3383aed761582445dd0a23426011cadd MD5 | raw file
  1// (c) Copyright Microsoft Corporation.
  2// This source is subject to the Microsoft Public License (Ms-PL).
  3// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  4// All other rights reserved.
  5
  6using System;
  7using System.ComponentModel;
  8using System.Globalization;
  9using System.Windows;
 10using System.Windows.Controls;
 11using System.Windows.Controls.Primitives;
 12using System.Windows.Data;
 13using System.Windows.Media;
 14
 15namespace MahApps.Metro.Controls
 16{
 17    /// <summary>
 18    /// Represents a switch that can be toggled between two states.
 19    /// </summary>
 20    [TemplateVisualState(Name = NormalState, GroupName = CommonStates)]
 21    [TemplateVisualState(Name = DisabledState, GroupName = CommonStates)]
 22    [TemplatePart(Name = SwitchPart, Type = typeof(ToggleButton))]
 23    public class ToggleSwitch : ContentControl
 24    {
 25        /// <summary>
 26        /// Common visual states.
 27        /// </summary>
 28        private const string CommonStates = "CommonStates";
 29
 30        /// <summary>
 31        /// Normal visual state.
 32        /// </summary>
 33        private const string NormalState = "Normal";
 34
 35        /// <summary>
 36        /// Disabled visual state.
 37        /// </summary>
 38        private const string DisabledState = "Disabled";
 39
 40        /// <summary>
 41        /// The ToggleButton that drives this.
 42        /// </summary>
 43        private const string SwitchPart = "Switch";
 44
 45        /// <summary>
 46        /// Identifies the Header DependencyProperty.
 47        /// </summary>
 48        public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(object), typeof(ToggleSwitch), new PropertyMetadata(null));
 49
 50        /// <summary>
 51        /// Gets or sets the header.
 52        /// </summary>
 53        public object Header
 54        {
 55            get { return GetValue(HeaderProperty); }
 56            set { SetValue(HeaderProperty, value); }
 57        }
 58
 59        /// <summary>
 60        /// Identifies the HeaderTemplate DependencyProperty.
 61        /// </summary>
 62        public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(ToggleSwitch), new PropertyMetadata(null));
 63
 64        /// <summary>
 65        /// Gets or sets the template used to display the control's header.
 66        /// </summary>
 67        public DataTemplate HeaderTemplate
 68        {
 69            get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
 70            set { SetValue(HeaderTemplateProperty, value); }
 71        }
 72
 73        /// <summary>
 74        /// Identifies the SwitchForeground DependencyProperty.
 75        /// </summary>
 76        public static readonly DependencyProperty SwitchForegroundProperty = DependencyProperty.Register("SwitchForeground", typeof(Brush), typeof(ToggleSwitch), null);
 77        
 78        /// <summary>
 79        /// Gets or sets the switch foreground.
 80        /// </summary>
 81        public Brush SwitchForeground
 82        {
 83            get { return (Brush)GetValue(SwitchForegroundProperty); }
 84            set
 85            {
 86                SetValue(SwitchForegroundProperty, value);
 87            }
 88        }
 89
 90        /// <summary>
 91        /// Gets or sets whether the ToggleSwitch is checked.
 92        /// </summary>
 93        [TypeConverter(typeof(NullableBoolConverter))]
 94        public bool? IsChecked
 95        {
 96            get { return (bool?)GetValue(IsCheckedProperty); }
 97            set { SetValue(IsCheckedProperty, value); }
 98        }
 99
100        /// <summary>
101        /// Identifies the IsChecked DependencyProperty.
102        /// </summary>
103        public static readonly DependencyProperty IsCheckedProperty =
104            DependencyProperty.Register("IsChecked", typeof(bool?), typeof(ToggleSwitch), new PropertyMetadata(false, OnIsCheckedChanged));
105
106        /// <summary>
107        /// Invoked when the IsChecked DependencyProperty is changed.
108        /// </summary>
109        /// <param name="d">The event sender.</param>
110        /// <param name="e">The event information.</param>
111        private static void OnIsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
112        {
113            ToggleSwitch toggleSwitch = (ToggleSwitch)d;
114            if (toggleSwitch._toggleButton != null)
115            {
116                toggleSwitch._toggleButton.IsChecked = (bool?)e.NewValue;
117            }
118        }
119
120        /// <summary>
121        /// Occurs when the
122        /// <see cref="T:MahApps.Metro.Controls.ToggleSwitch"/>
123        /// is checked.
124        /// </summary>
125        public event EventHandler<RoutedEventArgs> Checked;
126
127        /// <summary>
128        /// Occurs when the
129        /// <see cref="T:MahApps.Metro.Controls.ToggleSwitch"/>
130        /// is unchecked.
131        /// </summary>
132        public event EventHandler<RoutedEventArgs> Unchecked;
133
134        /// <summary>
135        /// Occurs when the
136        /// <see cref="T:MahApps.Metro.Controls.ToggleSwitch"/>
137        /// is indeterminate.
138        /// </summary>
139        public event EventHandler<RoutedEventArgs> Indeterminate;
140
141        /// <summary>
142        /// Occurs when the
143        /// <see cref="System.Windows.Controls.Primitives.ToggleButton"/>
144        /// is clicked.
145        /// </summary>
146        public event EventHandler<RoutedEventArgs> Click;
147
148        /// <summary>
149        /// The
150        /// <see cref="System.Windows.Controls.Primitives.ToggleButton"/>
151        /// template part.
152        /// </summary>
153        private ToggleButton _toggleButton;
154
155        /// <summary>
156        /// Whether the content was set.
157        /// </summary>
158        private bool _wasContentSet;
159
160        /// <summary>
161        /// Initializes a new instance of the ToggleSwitch class.
162        /// </summary>
163        public ToggleSwitch()
164        {
165            DefaultStyleKey = typeof(ToggleSwitch);
166            //DefaultStyleKeyProperty.OverrideMetadata(typeof(ToggleSwitch),
167            //                                       new FrameworkPropertyMetadata(typeof(ToggleSwitch)));
168        }
169
170        /// <summary>
171        /// Makes the content an "Off" or "On" string to match the state.
172        /// </summary>
173        private void SetDefaultContent()
174        {
175            Binding binding = new Binding("IsChecked") { Source = this, Converter = new OffOnConverter() };
176            SetBinding(ContentProperty, binding);
177        }
178
179        /// <summary>
180        /// Change the visual state.
181        /// </summary>
182        /// <param name="useTransitions">Indicates whether to use animation transitions.</param>
183        private void ChangeVisualState(bool useTransitions)
184        {
185            if (IsEnabled)
186            {
187                VisualStateManager.GoToState(this, NormalState, useTransitions);
188            }
189            else
190            {
191                VisualStateManager.GoToState(this, DisabledState, useTransitions);
192            }
193        }
194
195        /// <summary>
196        /// Makes the content an "Off" or "On" string to match the state if the content is set to null in the design tool.
197        /// </summary>
198        /// <param name="oldContent">The old content.</param>
199        /// <param name="newContent">The new content.</param>
200        protected override void OnContentChanged(object oldContent, object newContent)
201        {
202            base.OnContentChanged(oldContent, newContent);
203            _wasContentSet = true;
204            /*if (DesignerProperties.IsInDesignTool && newContent == null && GetBindingExpression(ContentProperty) == null)
205            {
206                SetDefaultContent();
207            }*/
208        }
209
210        /// <summary>
211        /// Gets all the template parts and initializes the corresponding state.
212        /// </summary>
213        public override void OnApplyTemplate()
214        {
215            base.OnApplyTemplate();
216
217            if (!_wasContentSet && GetBindingExpression(ContentProperty) == null)
218            {
219                SetDefaultContent();
220            }
221
222            if (_toggleButton != null)
223            {
224                _toggleButton.Checked -= CheckedHandler;
225                _toggleButton.Unchecked -= UncheckedHandler;
226                _toggleButton.Indeterminate -= IndeterminateHandler;
227                _toggleButton.Click -= ClickHandler;
228            }
229            _toggleButton = GetTemplateChild(SwitchPart) as ToggleButton;
230            if (_toggleButton != null)
231            {
232                _toggleButton.Checked += CheckedHandler;
233                _toggleButton.Unchecked += UncheckedHandler;
234                _toggleButton.Indeterminate += IndeterminateHandler;
235                _toggleButton.Click += ClickHandler;
236                _toggleButton.IsChecked = IsChecked;
237            }
238            ChangeVisualState(false);
239        }
240
241        /// <summary>
242        /// Mirrors the
243        /// <see cref="E:System.Windows.Controls.Primitives.ToggleButton.Checked"/>
244        /// event.
245        /// </summary>
246        /// <param name="sender">The event sender.</param>
247        /// <param name="e">The event information.</param>
248        private void CheckedHandler(object sender, RoutedEventArgs e)
249        {
250            IsChecked = true;
251            SafeRaise.Raise(Checked, this, e);
252        }
253
254        /// <summary>
255        /// Mirrors the
256        /// <see cref="E:System.Windows.Controls.Primitives.ToggleButton.Unchecked"/>
257        /// event.
258        /// </summary>
259        /// <param name="sender">The event sender.</param>
260        /// <param name="e">The event information.</param>
261        private void UncheckedHandler(object sender, RoutedEventArgs e)
262        {
263            IsChecked = false;
264            SafeRaise.Raise(Unchecked, this, e);
265        }
266
267        /// <summary>
268        /// Mirrors the
269        /// <see cref="E:System.Windows.Controls.Primitives.ToggleButton.Indeterminate"/>
270        /// event.
271        /// </summary>
272        /// <param name="sender">The event sender.</param>
273        /// <param name="e">The event information.</param>
274        private void IndeterminateHandler(object sender, RoutedEventArgs e)
275        {
276            IsChecked = null;
277            SafeRaise.Raise(Indeterminate, this, e);
278        }
279
280        /// <summary>
281        /// Mirrors the 
282        /// <see cref="E:System.Windows.Controls.Primitives.ToggleButton.Click"/>
283        /// event.
284        /// </summary>
285        /// <param name="sender">The event sender.</param>
286        /// <param name="e">The event information.</param>
287        private void ClickHandler(object sender, RoutedEventArgs e)
288        {
289            SafeRaise.Raise(Click, this, e);
290        }
291
292        /// <summary>
293        /// Returns a
294        /// <see cref="T:System.String"/>
295        /// that represents the current
296        /// <see cref="T:System.Object"/>
297        /// .
298        /// </summary>
299        /// <returns></returns>
300        public override string ToString()
301        {
302            return string.Format(
303                CultureInfo.InvariantCulture,
304                "{{ToggleSwitch IsChecked={0}, Content={1}}}",
305                IsChecked,
306                Content
307            );
308        }
309    }
310
311    /// <summary>
312    /// A helper class for raising events safely.
313    /// </summary>
314    internal static class SafeRaise
315    {
316        /// <summary>
317        /// Raises an event in a thread-safe manner, also does the null check.
318        /// </summary>
319        /// <param name="eventToRaise">The event to raise.</param>
320        /// <param name="sender">The event sender.</param>
321        public static void Raise(EventHandler eventToRaise, object sender)
322        {
323            if (eventToRaise != null)
324            {
325                eventToRaise(sender, EventArgs.Empty);
326            }
327        }
328
329        /// <summary>
330        /// Raises an event in a thread-safe manner, also does the null check.
331        /// </summary>
332        /// <param name="eventToRaise">The event to raise.</param>
333        /// <param name="sender">The event sender.</param>
334        public static void Raise(EventHandler<EventArgs> eventToRaise, object sender)
335        {
336            Raise(eventToRaise, sender, EventArgs.Empty);
337        }
338
339        /// <summary>
340        /// Raises an event in a thread-safe manner, also does the null check.
341        /// </summary>
342        /// <typeparam name="T">The event args type.</typeparam>
343        /// <param name="eventToRaise">The event to raise.</param>
344        /// <param name="sender">The event sender.</param>
345        /// <param name="args">The event args.</param>
346        public static void Raise<T>(EventHandler<T> eventToRaise, object sender, T args) where T : EventArgs
347        {
348            if (eventToRaise != null)
349            {
350                eventToRaise(sender, args);
351            }
352        }
353
354        // Lazy event args creation example:
355        //
356        // public class MyEventArgs : EventArgs
357        // {
358        //     public MyEventArgs(int x) { X = x; }
359        //     public int X { get; set; }
360        // }
361        //
362        // event EventHandler<MyEventArgs> Foo;
363        //
364        // public void Bar()
365        // {
366        //     int y = 2;
367        //     Raise(Foo, null, () => { return new MyEventArgs(y); });
368        // }
369
370        /// <summary>
371        /// This is a method that returns event args, used for lazy creation.
372        /// </summary>
373        /// <typeparam name="T">The event type.</typeparam>
374        /// <returns></returns>
375        public delegate T GetEventArgs<T>() where T : EventArgs;
376
377        /// <summary>
378        /// Raise an event in a thread-safe manner, with the required null check. Lazily creates event args.
379        /// </summary>
380        /// <typeparam name="T">The event args type.</typeparam>
381        /// <param name="eventToRaise">The event to raise.</param>
382        /// <param name="sender">The event sender.</param>
383        /// <param name="getEventArgs">The delegate to return the event args if needed.</param>
384        public static void Raise<T>(EventHandler<T> eventToRaise, object sender, GetEventArgs<T> getEventArgs) where T : EventArgs
385        {
386            if (eventToRaise != null)
387            {
388                eventToRaise(sender, getEventArgs());
389            }
390        }
391    }
392
393    public class OffOnConverter : IValueConverter
394    {
395        /// <summary>
396        /// Converts a value.
397        /// </summary>
398        /// <param name="value">The value produced by the binding source.</param>
399        /// <param name="targetType">The type of the binding target property.</param>
400        /// <param name="parameter">The converter parameter to use.</param>
401        /// <param name="culture">The culture to use in the converter.</param>
402        /// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
403        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
404        {
405            if (value is bool? || value == null)
406            {
407                return (bool?)value == true ? "On" : "Off";
408            }
409            return "Off";
410        }
411
412        /// <summary>
413        /// Converts a value.
414        /// </summary>
415        /// <param name="value">The value produced by the binding source.</param>
416        /// <param name="targetType">The type of the binding target property.</param>
417        /// <param name="parameter">The converter parameter to use.</param>
418        /// <param name="culture">The culture to use in the converter.</param>
419        /// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
420        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
421        {
422            throw new NotImplementedException();
423        }
424    }
425
426
427    /// <summary>
428    /// Represents a switch that can be toggled between two states.
429    /// </summary>
430    [TemplateVisualState(Name = NormalState, GroupName = CommonStates)]
431    [TemplateVisualState(Name = DisabledState, GroupName = CommonStates)]
432    [TemplateVisualState(Name = CheckedState, GroupName = CheckStates)]
433    [TemplateVisualState(Name = DraggingState, GroupName = CheckStates)]
434    [TemplateVisualState(Name = UncheckedState, GroupName = CheckStates)]
435    [TemplatePart(Name = SwitchRootPart, Type = typeof(Grid))]
436    [TemplatePart(Name = SwitchBackgroundPart, Type = typeof(UIElement))]
437    [TemplatePart(Name = SwitchTrackPart, Type = typeof(Grid))]
438    [TemplatePart(Name = SwitchThumbPart, Type = typeof(FrameworkElement))]
439    public class ToggleSwitchButton : ToggleButton
440    {
441        /// <summary>
442        /// Common visual states.
443        /// </summary>
444        private const string CommonStates = "CommonStates";
445
446        /// <summary>
447        /// Normal visual state.
448        /// </summary>
449        private const string NormalState = "Normal";
450
451        /// <summary>
452        /// Disabled visual state.
453        /// </summary>
454        private const string DisabledState = "Disabled";
455
456        /// <summary>
457        /// Check visual states.
458        /// </summary>
459        private const string CheckStates = "CheckStates";
460
461        /// <summary>
462        /// Checked visual state.
463        /// </summary>
464        private const string CheckedState = "Checked";
465
466        /// <summary>
467        /// Dragging visual state.
468        /// </summary>
469        private const string DraggingState = "Dragging";
470
471        /// <summary>
472        /// Unchecked visual state.
473        /// </summary>
474        private const string UncheckedState = "Unchecked";
475
476        /// <summary>
477        /// Switch root template part name.
478        /// </summary>
479        private const string SwitchRootPart = "SwitchRoot";
480
481        /// <summary>
482        /// Switch background template part name.
483        /// </summary>
484        private const string SwitchBackgroundPart = "SwitchBackground";
485
486        /// <summary>
487        /// Switch track template part name.
488        /// </summary>
489        private const string SwitchTrackPart = "SwitchTrack";
490
491        /// <summary>
492        /// Switch thumb template part name.
493        /// </summary>
494        private const string SwitchThumbPart = "SwitchThumb";
495
496        /// <summary>
497        /// Identifies the SwitchForeground dependency property.
498        /// </summary>
499        public static readonly DependencyProperty SwitchForegroundProperty =
500            DependencyProperty.Register("SwitchForeground", typeof(Brush), typeof(ToggleSwitchButton), new PropertyMetadata(null));
501
502        /// <summary>
503        /// Gets or sets the switch foreground.
504        /// </summary>
505        public Brush SwitchForeground
506        {
507            get
508            {
509                return (Brush)GetValue(SwitchForegroundProperty);
510            }
511            set
512            {
513                SetValue(SwitchForegroundProperty, value);
514            }
515        }
516
517        /// <summary>
518        /// The background TranslateTransform.
519        /// </summary>
520        private TranslateTransform _backgroundTranslation;
521
522        /// <summary>
523        /// The thumb TranslateTransform.
524        /// </summary>
525        private TranslateTransform _thumbTranslation;
526
527        /// <summary>
528        /// The root template part.
529        /// </summary>
530        private Grid _root;
531
532        /// <summary>
533        /// The track template part.
534        /// </summary>
535        private Grid _track;
536
537        /// <summary>
538        /// The thumb template part.
539        /// </summary>
540        private FrameworkElement _thumb;
541
542        /// <summary>
543        /// The minimum translation.
544        /// </summary>
545        private const double _uncheckedTranslation = 0;
546
547        /// <summary>
548        /// The maximum translation.
549        /// </summary>
550        private double _checkedTranslation;
551
552        /// <summary>
553        /// The drag translation.
554        /// </summary>
555        private double _dragTranslation;
556
557        /// <summary>
558        /// Whether the translation ever changed during the drag.
559        /// </summary>
560        private bool _wasDragged;
561
562        /// <summary>
563        /// Whether the dragging state is current.
564        /// </summary>
565        private bool _isDragging;
566
567        /// <summary>
568        /// Initializes a new instance of the ToggleSwitch class.
569        /// </summary>
570        public ToggleSwitchButton()
571        {
572            DefaultStyleKey = typeof(ToggleSwitchButton);
573        }
574
575        /// <summary>
576        /// Gets and sets the thumb and background translation.
577        /// </summary>
578        /// <returns>The translation.</returns>
579        private double Translation
580        {
581            get
582            {
583                return _backgroundTranslation == null ? _thumbTranslation.X : _backgroundTranslation.X;
584            }
585            set
586            {
587                if (_backgroundTranslation != null)
588                {
589                    _backgroundTranslation.X = value;
590                }
591
592                if (_thumbTranslation != null)
593                {
594                    _thumbTranslation.X = value;
595                }
596            }
597        }
598
599        /// <summary>
600        /// Change the visual state.
601        /// </summary>
602        /// <param name="useTransitions">Indicates whether to use animation transitions.</param>
603        private void ChangeVisualState(bool useTransitions)
604        {
605            if (IsEnabled)
606            {
607                VisualStateManager.GoToState(this, NormalState, useTransitions);
608            }
609            else
610            {
611                VisualStateManager.GoToState(this, DisabledState, useTransitions);
612            }
613
614            if (_isDragging)
615            {
616                VisualStateManager.GoToState(this, DraggingState, useTransitions);
617            }
618            else if (IsChecked == true)
619            {
620                VisualStateManager.GoToState(this, CheckedState, useTransitions);
621            }
622            else
623            {
624                VisualStateManager.GoToState(this, UncheckedState, useTransitions);
625            }
626        }
627
628        /// <summary>
629        /// Called by the OnClick method to implement toggle behavior.
630        /// </summary>
631        protected override void OnToggle()
632        {
633            IsChecked = IsChecked == true ? false : true;
634            ChangeVisualState(true);
635        }
636
637        /// <summary>
638        /// Gets all the template parts and initializes the corresponding state.
639        /// </summary>
640        public override void OnApplyTemplate()
641        {
642            if (_track != null)
643            {
644                _track.SizeChanged -= SizeChangedHandler;
645            }
646            if (_thumb != null)
647            {
648                _thumb.SizeChanged -= SizeChangedHandler;
649            }
650            base.OnApplyTemplate();
651            _root = GetTemplateChild(SwitchRootPart) as Grid;
652            UIElement background = GetTemplateChild(SwitchBackgroundPart) as UIElement;
653            _backgroundTranslation = background == null ? null : background.RenderTransform as TranslateTransform;
654            _track = GetTemplateChild(SwitchTrackPart) as Grid;
655            _thumb = GetTemplateChild(SwitchThumbPart) as Border;
656            _thumbTranslation = _thumb == null ? null : _thumb.RenderTransform as TranslateTransform;
657            if (_root != null && _track != null && _thumb != null && (_backgroundTranslation != null || _thumbTranslation != null))
658            {
659                /*GestureListener gestureListener = GestureService.GetGestureListener(_root);
660                gestureListener.DragStarted += DragStartedHandler;
661                gestureListener.DragDelta += DragDeltaHandler;
662                gestureListener.DragCompleted += DragCompletedHandler;*/
663                _track.SizeChanged += SizeChangedHandler;
664                _thumb.SizeChanged += SizeChangedHandler;
665            }
666            ChangeVisualState(false);
667        }
668
669        /// <summary>
670        /// Handles changed sizes for the track and the thumb.
671        /// Sets the clip of the track and computes the indeterminate and checked translations.
672        /// </summary>
673        /// <param name="sender">The event sender.</param>
674        /// <param name="e">The event information.</param>
675        private void SizeChangedHandler(object sender, SizeChangedEventArgs e)
676        {
677            _track.Clip = new RectangleGeometry { Rect = new Rect(0, 0, _track.ActualWidth, _track.ActualHeight) };
678            _checkedTranslation = _track.ActualWidth - _thumb.ActualWidth - _thumb.Margin.Left - _thumb.Margin.Right;
679        }
680    }
681}
682
683