ExtendedListBox.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. using System;
  2. using System.Windows;
  3. using System.Windows.Controls;
  4. using System.Windows.Controls.Primitives;
  5. using System.Windows.Input;
  6. using System.Windows.Media;
  7. namespace MediaBrowser.UI.Controls
  8. {
  9. /// <summary>
  10. /// Extends the ListBox to provide auto-focus behavior when items are moused over
  11. /// This also adds an ItemInvoked event that is fired when an item is clicked or invoked using the enter key
  12. /// </summary>
  13. public class ExtendedListBox : ListBox
  14. {
  15. /// <summary>
  16. /// Fired when an item is clicked or invoked using the enter key
  17. /// </summary>
  18. public event EventHandler<ItemEventArgs<object>> ItemInvoked;
  19. /// <summary>
  20. /// Called when [item invoked].
  21. /// </summary>
  22. /// <param name="boundObject">The bound object.</param>
  23. protected virtual void OnItemInvoked(object boundObject)
  24. {
  25. if (ItemInvoked != null)
  26. {
  27. ItemInvoked(this, new ItemEventArgs<object> { Argument = boundObject });
  28. }
  29. }
  30. /// <summary>
  31. /// The _auto focus
  32. /// </summary>
  33. private bool _autoFocus = true;
  34. /// <summary>
  35. /// Gets or sets a value indicating if the first list item should be auto-focused on load
  36. /// </summary>
  37. /// <value><c>true</c> if [auto focus]; otherwise, <c>false</c>.</value>
  38. public bool AutoFocus
  39. {
  40. get { return _autoFocus; }
  41. set
  42. {
  43. _autoFocus = value;
  44. }
  45. }
  46. /// <summary>
  47. /// Initializes a new instance of the <see cref="ExtendedListBox" /> class.
  48. /// </summary>
  49. public ExtendedListBox()
  50. : base()
  51. {
  52. ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged;
  53. }
  54. /// <summary>
  55. /// The mouse down object
  56. /// </summary>
  57. private object mouseDownObject;
  58. /// <summary>
  59. /// Invoked when an unhandled <see cref="E:System.Windows.Input.Mouse.PreviewMouseDown" /> attached routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
  60. /// </summary>
  61. /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that contains the event data. The event data reports that one or more mouse buttons were pressed.</param>
  62. protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
  63. {
  64. base.OnPreviewMouseDown(e);
  65. // Get the item that the mouse down event occurred on
  66. mouseDownObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
  67. }
  68. /// <summary>
  69. /// Invoked when an unhandled <see cref="E:System.Windows.UIElement.MouseLeftButtonUp" /> routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
  70. /// </summary>
  71. /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that contains the event data. The event data reports that the left mouse button was released.</param>
  72. protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
  73. {
  74. base.OnMouseLeftButtonUp(e);
  75. // If the mouse up event occurred on the same item as the mousedown event, then fire ItemInvoked
  76. if (mouseDownObject != null)
  77. {
  78. var boundObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
  79. if (mouseDownObject == boundObject)
  80. {
  81. mouseDownObject = null;
  82. OnItemInvoked(boundObject);
  83. }
  84. }
  85. }
  86. /// <summary>
  87. /// The key down object
  88. /// </summary>
  89. private object keyDownObject;
  90. /// <summary>
  91. /// Responds to the <see cref="E:System.Windows.UIElement.KeyDown" /> event.
  92. /// </summary>
  93. /// <param name="e">Provides data for <see cref="T:System.Windows.Input.KeyEventArgs" />.</param>
  94. protected override void OnKeyDown(KeyEventArgs e)
  95. {
  96. if (e.Key == Key.Enter)
  97. {
  98. if (!e.IsRepeat)
  99. {
  100. // Get the item that the keydown event occurred on
  101. keyDownObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
  102. }
  103. e.Handled = true;
  104. }
  105. base.OnKeyDown(e);
  106. }
  107. /// <summary>
  108. /// Invoked when an unhandled <see cref="E:System.Windows.Input.Keyboard.KeyUp" /> attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
  109. /// </summary>
  110. /// <param name="e">The <see cref="T:System.Windows.Input.KeyEventArgs" /> that contains the event data.</param>
  111. protected override void OnKeyUp(KeyEventArgs e)
  112. {
  113. base.OnKeyUp(e);
  114. // Fire ItemInvoked when enter is pressed on an item
  115. if (e.Key == Key.Enter)
  116. {
  117. if (!e.IsRepeat)
  118. {
  119. // If the keyup event occurred on the same item as the keydown event, then fire ItemInvoked
  120. if (keyDownObject != null)
  121. {
  122. var boundObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
  123. if (keyDownObject == boundObject)
  124. {
  125. keyDownObject = null;
  126. OnItemInvoked(boundObject);
  127. }
  128. }
  129. }
  130. e.Handled = true;
  131. }
  132. }
  133. /// <summary>
  134. /// The _last mouse move point
  135. /// </summary>
  136. private Point? _lastMouseMovePoint;
  137. /// <summary>
  138. /// Handles OnMouseMove to auto-select the item that's being moused over
  139. /// </summary>
  140. /// <param name="e">Provides data for <see cref="T:System.Windows.Input.MouseEventArgs" />.</param>
  141. protected override void OnMouseMove(MouseEventArgs e)
  142. {
  143. base.OnMouseMove(e);
  144. var window = this.GetWindow();
  145. // If the cursor is currently hidden, don't bother reacting to it
  146. if (Cursor == Cursors.None || window.Cursor == Cursors.None)
  147. {
  148. return;
  149. }
  150. // Store the last position for comparison purposes
  151. // Even if the mouse is not moving this event will fire as elements are showing and hiding
  152. var pos = e.GetPosition(window);
  153. if (!_lastMouseMovePoint.HasValue)
  154. {
  155. _lastMouseMovePoint = pos;
  156. return;
  157. }
  158. if (pos == _lastMouseMovePoint)
  159. {
  160. return;
  161. }
  162. _lastMouseMovePoint = pos;
  163. var dep = (DependencyObject)e.OriginalSource;
  164. while ((dep != null) && !(dep is ListBoxItem))
  165. {
  166. dep = VisualTreeHelper.GetParent(dep);
  167. }
  168. if (dep != null)
  169. {
  170. var listBoxItem = dep as ListBoxItem;
  171. if (!listBoxItem.IsFocused)
  172. {
  173. listBoxItem.Focus();
  174. }
  175. }
  176. }
  177. /// <summary>
  178. /// Gets the datacontext for a given ListBoxItem
  179. /// </summary>
  180. /// <param name="dep">The dep.</param>
  181. /// <returns>System.Object.</returns>
  182. private object GetBoundListItemObject(DependencyObject dep)
  183. {
  184. while ((dep != null) && !(dep is ListBoxItem))
  185. {
  186. dep = VisualTreeHelper.GetParent(dep);
  187. }
  188. if (dep == null)
  189. {
  190. return null;
  191. }
  192. return ItemContainerGenerator.ItemFromContainer(dep);
  193. }
  194. /// <summary>
  195. /// Autofocuses the first list item when the list is loaded
  196. /// </summary>
  197. /// <param name="sender">The sender.</param>
  198. /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
  199. void ItemContainerGeneratorStatusChanged(object sender, EventArgs e)
  200. {
  201. if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated && AutoFocus)
  202. {
  203. Dispatcher.InvokeAsync(OnContainersGenerated);
  204. }
  205. }
  206. /// <summary>
  207. /// Called when [containers generated].
  208. /// </summary>
  209. void OnContainersGenerated()
  210. {
  211. var index = 0;
  212. if (index >= 0)
  213. {
  214. var item = ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
  215. if (item != null)
  216. {
  217. item.Focus();
  218. ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorStatusChanged;
  219. }
  220. }
  221. }
  222. }
  223. }