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;
}
}
}
}
}