SplitButton.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. using System.ComponentModel;
  2. using System.Windows.Controls.Primitives;
  3. using System.Windows.Controls;
  4. using System.Windows.Markup;
  5. using System.Windows;
  6. using System.Windows.Media.Animation;
  7. using System.Windows.Media;
  8. using System;
  9. namespace CyberX8_Themes.CustomControls
  10. {
  11. /// <summary>
  12. /// Implemetation of a Split Button
  13. /// </summary>
  14. [TemplatePart(Name = "PART_DropDown", Type = typeof(Button))]
  15. [ContentProperty("Items")]
  16. [DefaultProperty("Items")]
  17. public class SplitButton : Button
  18. {
  19. // AddOwner Dependency properties
  20. public static readonly DependencyProperty HorizontalOffsetProperty;
  21. public static readonly DependencyProperty IsContextMenuOpenProperty;
  22. public static readonly DependencyProperty ModeProperty;
  23. public static readonly DependencyProperty PlacementProperty;
  24. public static readonly DependencyProperty PlacementRectangleProperty;
  25. public static readonly DependencyProperty VerticalOffsetProperty;
  26. /// <summary>
  27. /// Static Constructor
  28. /// </summary>
  29. static SplitButton()
  30. {
  31. DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitButton), new FrameworkPropertyMetadata(typeof(SplitButton)));
  32. IsContextMenuOpenProperty = DependencyProperty.Register("IsContextMenuOpen", typeof(bool), typeof(SplitButton), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsContextMenuOpenChanged)));
  33. ModeProperty = DependencyProperty.Register("Mode", typeof(SplitButtonMode), typeof(SplitButton), new FrameworkPropertyMetadata(SplitButtonMode.Split));
  34. // AddOwner properties from the ContextMenuService class, we need callbacks from these properties
  35. // to update the Buttons ContextMenu properties
  36. PlacementProperty = ContextMenuService.PlacementProperty.AddOwner(typeof(SplitButton), new FrameworkPropertyMetadata(PlacementMode.Bottom, new PropertyChangedCallback(OnPlacementChanged)));
  37. PlacementRectangleProperty = ContextMenuService.PlacementRectangleProperty.AddOwner(typeof(SplitButton), new FrameworkPropertyMetadata(Rect.Empty, new PropertyChangedCallback(OnPlacementRectangleChanged)));
  38. HorizontalOffsetProperty = ContextMenuService.HorizontalOffsetProperty.AddOwner(typeof(SplitButton), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnHorizontalOffsetChanged)));
  39. VerticalOffsetProperty = ContextMenuService.VerticalOffsetProperty.AddOwner(typeof(SplitButton), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnVerticalOffsetChanged)));
  40. }
  41. /*
  42. * Overrides
  43. *
  44. */
  45. /// <summary>
  46. /// OnApplyTemplate override, set up the click event for the dropdown if present in the template
  47. /// </summary>
  48. public override void OnApplyTemplate()
  49. {
  50. base.OnApplyTemplate();
  51. // set up the click event handler for the dropdown button
  52. ButtonBase dropDown = this.Template.FindName("PART_DropDown", this) as ButtonBase;
  53. if (dropDown != null)
  54. dropDown.Click += Dropdown_Click;
  55. }
  56. /// <summary>
  57. /// Handles the Base Buttons OnClick event
  58. /// </summary>
  59. protected override void OnClick()
  60. {
  61. switch (Mode)
  62. {
  63. case SplitButtonMode.Dropdown:
  64. OnDropdown();
  65. break;
  66. default:
  67. base.OnClick(); // forward on the Click event to the user
  68. break;
  69. }
  70. }
  71. /*
  72. * Properties
  73. *
  74. */
  75. /// <summary>
  76. /// The Split Button's Items property maps to the base classes ContextMenu.Items property
  77. /// </summary>
  78. public ItemCollection Items
  79. {
  80. get
  81. {
  82. EnsureContextMenuIsValid();
  83. return this.ContextMenu.Items;
  84. }
  85. }
  86. /*
  87. * DependencyProperty CLR wrappers
  88. *
  89. */
  90. /// <summary>
  91. /// Gets or sets the IsContextMenuOpen property.
  92. /// </summary>
  93. public bool IsContextMenuOpen
  94. {
  95. get { return (bool)GetValue(IsContextMenuOpenProperty); }
  96. set { SetValue(IsContextMenuOpenProperty, value); }
  97. }
  98. /// <summary>
  99. /// Placement of the Context menu
  100. /// </summary>
  101. public PlacementMode Placement
  102. {
  103. get { return (PlacementMode)GetValue(PlacementProperty); }
  104. set { SetValue(PlacementProperty, value); }
  105. }
  106. /// <summary>
  107. /// PlacementRectangle of the Context menu
  108. /// </summary>
  109. public Rect PlacementRectangle
  110. {
  111. get { return (Rect)GetValue(PlacementRectangleProperty); }
  112. set { SetValue(PlacementRectangleProperty, value); }
  113. }
  114. /// <summary>
  115. /// HorizontalOffset of the Context menu
  116. /// </summary>
  117. public double HorizontalOffset
  118. {
  119. get { return (double)GetValue(HorizontalOffsetProperty); }
  120. set { SetValue(HorizontalOffsetProperty, value); }
  121. }
  122. /// <summary>
  123. /// VerticalOffset of the Context menu
  124. /// </summary>
  125. public double VerticalOffset
  126. {
  127. get { return (double)GetValue(VerticalOffsetProperty); }
  128. set { SetValue(VerticalOffsetProperty, value); }
  129. }
  130. /// <summary>
  131. /// Defines the Mode of operation of the Button
  132. /// </summary>
  133. /// <remarks>
  134. /// The SplitButton two Modes are
  135. /// Split (default), - the button has two parts, a normal button and a dropdown which exposes the ContextMenu
  136. /// Dropdown - the button acts like a combobox, clicking anywhere on the button opens the Context Menu
  137. /// </remarks>
  138. public SplitButtonMode Mode
  139. {
  140. get { return (SplitButtonMode)GetValue(ModeProperty); }
  141. set { SetValue(ModeProperty, value); }
  142. }
  143. /*
  144. * DependencyPropertyChanged callbacks
  145. *
  146. */
  147. private static void OnIsContextMenuOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  148. {
  149. SplitButton s = (SplitButton)d;
  150. s.EnsureContextMenuIsValid();
  151. if (!s.ContextMenu.HasItems)
  152. return;
  153. bool value = (bool)e.NewValue;
  154. if (value && !s.ContextMenu.IsOpen)
  155. s.ContextMenu.IsOpen = true;
  156. else if (!value && s.ContextMenu.IsOpen)
  157. s.ContextMenu.IsOpen = false;
  158. }
  159. /// <summary>
  160. /// Placement Property changed callback, pass the value through to the buttons context menu
  161. /// </summary>
  162. private static void OnPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  163. {
  164. SplitButton s = d as SplitButton;
  165. if (s == null) return;
  166. s.EnsureContextMenuIsValid();
  167. s.ContextMenu.Placement = (PlacementMode)e.NewValue;
  168. }
  169. /// <summary>
  170. /// PlacementRectangle Property changed callback, pass the value through to the buttons context menu
  171. /// </summary>
  172. private static void OnPlacementRectangleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  173. {
  174. SplitButton s = d as SplitButton;
  175. if (s == null) return;
  176. s.EnsureContextMenuIsValid();
  177. s.ContextMenu.PlacementRectangle = (Rect)e.NewValue;
  178. }
  179. /// <summary>
  180. /// HorizontalOffset Property changed callback, pass the value through to the buttons context menu
  181. /// </summary>
  182. private static void OnHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  183. {
  184. SplitButton s = d as SplitButton;
  185. if (s == null) return;
  186. s.EnsureContextMenuIsValid();
  187. s.ContextMenu.HorizontalOffset = (double)e.NewValue;
  188. }
  189. /// <summary>
  190. /// VerticalOffset Property changed callback, pass the value through to the buttons context menu
  191. /// </summary>
  192. private static void OnVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  193. {
  194. SplitButton s = d as SplitButton;
  195. if (s == null) return;
  196. s.EnsureContextMenuIsValid();
  197. s.ContextMenu.VerticalOffset = (double)e.NewValue;
  198. }
  199. /*
  200. * Helper Methods
  201. *
  202. */
  203. /// <summary>
  204. /// Make sure the Context menu is not null
  205. /// </summary>
  206. private void EnsureContextMenuIsValid()
  207. {
  208. if (this.ContextMenu == null)
  209. {
  210. this.ContextMenu = new ContextMenu();
  211. this.ContextMenu.PlacementTarget = this;
  212. this.ContextMenu.Placement = Placement;
  213. this.ContextMenu.Opened += ((sender, routedEventArgs) => IsContextMenuOpen = true);
  214. this.ContextMenu.Closed += ((sender, routedEventArgs) => IsContextMenuOpen = false);
  215. }
  216. }
  217. private void OnDropdown()
  218. {
  219. EnsureContextMenuIsValid();
  220. if (!this.ContextMenu.HasItems)
  221. return;
  222. this.ContextMenu.IsOpen = !IsContextMenuOpen; // open it if closed, close it if open
  223. }
  224. /*
  225. * Events
  226. *
  227. */
  228. /// <summary>
  229. /// Event Handler for the Drop Down Button's Click event
  230. /// </summary>
  231. /// <param name="sender"></param>
  232. /// <param name="e"></param>
  233. private void Dropdown_Click(object sender, RoutedEventArgs e)
  234. {
  235. OnDropdown();
  236. e.Handled = true;
  237. }
  238. }
  239. public enum SplitButtonMode
  240. {
  241. Split, Dropdown, Button
  242. }
  243. }