jellyfin/MediaBrowser.UI.Controls/ExtendedListBox.cs
2013-02-20 20:33:05 -05:00

261 lines
9 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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