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