aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.UI.Controls/ExtendedListBox.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.UI.Controls/ExtendedListBox.cs')
-rw-r--r--MediaBrowser.UI.Controls/ExtendedListBox.cs260
1 files changed, 260 insertions, 0 deletions
diff --git a/MediaBrowser.UI.Controls/ExtendedListBox.cs b/MediaBrowser.UI.Controls/ExtendedListBox.cs
new file mode 100644
index 000000000..fb6738939
--- /dev/null
+++ b/MediaBrowser.UI.Controls/ExtendedListBox.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Extends the ListBox to provide auto-focus behavior when items are moused over
+ /// This also adds an ItemInvoked event that is fired when an item is clicked or invoked using the enter key
+ /// </summary>
+ public class ExtendedListBox : ListBox
+ {
+ /// <summary>
+ /// Fired when an item is clicked or invoked using the enter key
+ /// </summary>
+ public event EventHandler<ItemEventArgs<object>> ItemInvoked;
+
+ /// <summary>
+ /// Called when [item invoked].
+ /// </summary>
+ /// <param name="boundObject">The bound object.</param>
+ protected virtual void OnItemInvoked(object boundObject)
+ {
+ if (ItemInvoked != null)
+ {
+ ItemInvoked(this, new ItemEventArgs<object> { Argument = boundObject });
+ }
+ }
+
+ /// <summary>
+ /// The _auto focus
+ /// </summary>
+ private bool _autoFocus = true;
+ /// <summary>
+ /// Gets or sets a value indicating if the first list item should be auto-focused on load
+ /// </summary>
+ /// <value><c>true</c> if [auto focus]; otherwise, <c>false</c>.</value>
+ public bool AutoFocus
+ {
+ get { return _autoFocus; }
+ set
+ {
+ _autoFocus = value;
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ExtendedListBox" /> class.
+ /// </summary>
+ public ExtendedListBox()
+ : base()
+ {
+ ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged;
+ }
+
+ /// <summary>
+ /// The mouse down object
+ /// </summary>
+ private object mouseDownObject;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <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>
+ protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
+ {
+ base.OnPreviewMouseDown(e);
+
+ // Get the item that the mouse down event occurred on
+ mouseDownObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <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>
+ protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
+ {
+ base.OnMouseLeftButtonUp(e);
+
+ // If the mouse up event occurred on the same item as the mousedown event, then fire ItemInvoked
+ if (mouseDownObject != null)
+ {
+ var boundObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
+
+ if (mouseDownObject == boundObject)
+ {
+ mouseDownObject = null;
+ OnItemInvoked(boundObject);
+ }
+ }
+ }
+
+ /// <summary>
+ /// The key down object
+ /// </summary>
+ private object keyDownObject;
+
+ /// <summary>
+ /// Responds to the <see cref="E:System.Windows.UIElement.KeyDown" /> event.
+ /// </summary>
+ /// <param name="e">Provides data for <see cref="T:System.Windows.Input.KeyEventArgs" />.</param>
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ if (e.Key == Key.Enter)
+ {
+ if (!e.IsRepeat)
+ {
+ // Get the item that the keydown event occurred on
+ keyDownObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
+ }
+
+ e.Handled = true;
+ }
+
+ base.OnKeyDown(e);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="e">The <see cref="T:System.Windows.Input.KeyEventArgs" /> that contains the event data.</param>
+ protected override void OnKeyUp(KeyEventArgs e)
+ {
+ base.OnKeyUp(e);
+
+ // Fire ItemInvoked when enter is pressed on an item
+ if (e.Key == Key.Enter)
+ {
+ if (!e.IsRepeat)
+ {
+ // If the keyup event occurred on the same item as the keydown event, then fire ItemInvoked
+ if (keyDownObject != null)
+ {
+ var boundObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
+
+ if (keyDownObject == boundObject)
+ {
+ keyDownObject = null;
+ OnItemInvoked(boundObject);
+ }
+ }
+ }
+
+ e.Handled = true;
+ }
+ }
+
+ /// <summary>
+ /// The _last mouse move point
+ /// </summary>
+ private Point? _lastMouseMovePoint;
+
+ /// <summary>
+ /// Handles OnMouseMove to auto-select the item that's being moused over
+ /// </summary>
+ /// <param name="e">Provides data for <see cref="T:System.Windows.Input.MouseEventArgs" />.</param>
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+
+ var window = this.GetWindow();
+
+ // If the cursor is currently hidden, don't bother reacting to it
+ if (Cursor == Cursors.None || window.Cursor == Cursors.None)
+ {
+ return;
+ }
+
+ // Store the last position for comparison purposes
+ // Even if the mouse is not moving this event will fire as elements are showing and hiding
+ var pos = e.GetPosition(window);
+
+ if (!_lastMouseMovePoint.HasValue)
+ {
+ _lastMouseMovePoint = pos;
+ return;
+ }
+
+ if (pos == _lastMouseMovePoint)
+ {
+ return;
+ }
+
+ _lastMouseMovePoint = pos;
+
+ var dep = (DependencyObject)e.OriginalSource;
+
+ while ((dep != null) && !(dep is ListBoxItem))
+ {
+ dep = VisualTreeHelper.GetParent(dep);
+ }
+
+ if (dep != null)
+ {
+ var listBoxItem = dep as ListBoxItem;
+
+ if (!listBoxItem.IsFocused)
+ {
+ listBoxItem.Focus();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the datacontext for a given ListBoxItem
+ /// </summary>
+ /// <param name="dep">The dep.</param>
+ /// <returns>System.Object.</returns>
+ private object GetBoundListItemObject(DependencyObject dep)
+ {
+ while ((dep != null) && !(dep is ListBoxItem))
+ {
+ dep = VisualTreeHelper.GetParent(dep);
+ }
+
+ if (dep == null)
+ {
+ return null;
+ }
+
+ return ItemContainerGenerator.ItemFromContainer(dep);
+ }
+
+ /// <summary>
+ /// Autofocuses the first list item when the list is loaded
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
+ void ItemContainerGeneratorStatusChanged(object sender, EventArgs e)
+ {
+ if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated && AutoFocus)
+ {
+ Dispatcher.InvokeAsync(OnContainersGenerated);
+ }
+ }
+
+ /// <summary>
+ /// Called when [containers generated].
+ /// </summary>
+ void OnContainersGenerated()
+ {
+ var index = 0;
+
+ if (index >= 0)
+ {
+ var item = ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
+
+ if (item != null)
+ {
+ item.Focus();
+ ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorStatusChanged;
+ }
+ }
+ }
+ }
+}