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 { /// /// 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 /// public class ExtendedListBox : ListBox { /// /// Fired when an item is clicked or invoked using the enter key /// public event EventHandler> ItemInvoked; /// /// Called when [item invoked]. /// /// The bound object. protected virtual void OnItemInvoked(object boundObject) { if (ItemInvoked != null) { ItemInvoked(this, new ItemEventArgs { Argument = boundObject }); } } /// /// The _auto focus /// private bool _autoFocus = true; /// /// Gets or sets a value indicating if the first list item should be auto-focused on load /// /// true if [auto focus]; otherwise, false. public bool AutoFocus { get { return _autoFocus; } set { _autoFocus = value; } } /// /// Initializes a new instance of the class. /// public ExtendedListBox() : base() { ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged; } /// /// The mouse down object /// private object mouseDownObject; /// /// Invoked when an unhandled 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. /// /// The that contains the event data. The event data reports that one or more mouse buttons were pressed. protected override void OnPreviewMouseDown(MouseButtonEventArgs e) { base.OnPreviewMouseDown(e); // Get the item that the mouse down event occurred on mouseDownObject = GetBoundListItemObject((DependencyObject)e.OriginalSource); } /// /// Invoked when an unhandled  routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. /// /// The that contains the event data. The event data reports that the left mouse button was released. 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); } } } /// /// The key down object /// private object keyDownObject; /// /// Responds to the event. /// /// Provides data for . 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); } /// /// Invoked when an unhandled  attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. /// /// The that contains the event data. 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; } } /// /// The _last mouse move point /// private Point? _lastMouseMovePoint; /// /// Handles OnMouseMove to auto-select the item that's being moused over /// /// Provides data for . 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(); } } } /// /// Gets the datacontext for a given ListBoxItem /// /// The dep. /// System.Object. private object GetBoundListItemObject(DependencyObject dep) { while ((dep != null) && !(dep is ListBoxItem)) { dep = VisualTreeHelper.GetParent(dep); } if (dep == null) { return null; } return ItemContainerGenerator.ItemFromContainer(dep); } /// /// Autofocuses the first list item when the list is loaded /// /// The sender. /// The instance containing the event data. void ItemContainerGeneratorStatusChanged(object sender, EventArgs e) { if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated && AutoFocus) { Dispatcher.InvokeAsync(OnContainersGenerated); } } /// /// Called when [containers generated]. /// void OnContainersGenerated() { var index = 0; if (index >= 0) { var item = ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem; if (item != null) { item.Focus(); ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorStatusChanged; } } } } }