// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Windows;
using System.Diagnostics;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.Windows.Automation.Peers;
using System.Windows.Media;
namespace System.Windows.Controls.Primitives{
/// <summary>
/// Control that raises its Click event repeatedly from the time it is
/// pressed until it is released.
/// </summary>
[TemplateVisualState(Name = RepeatButton.StateNormal, GroupName = RepeatButton.GroupCommon)]
[TemplateVisualState(Name = RepeatButton.StateMouseOver, GroupName = RepeatButton.GroupCommon)]
[TemplateVisualState(Name = RepeatButton.StatePressed, GroupName = RepeatButton.GroupCommon)]
[TemplateVisualState(Name = RepeatButton.StateDisabled, GroupName = RepeatButton.GroupCommon)]
[TemplateVisualState(Name = RepeatButton.StateUnfocused, GroupName = RepeatButton.GroupFocus)]
[TemplateVisualState(Name = RepeatButton.StateFocused, GroupName = RepeatButton.GroupFocus)]
public sealed partial class RepeatButton : ButtonBase
{
#region Delay
/// <summary>
/// Gets or sets the amount of time, in milliseconds, the RepeatButton
/// waits while it is pressed before it starts repeating. The value must
/// be non-negative.
/// </summary>
public int Delay
{
get { return (int)GetValue(DelayProperty); }
set { SetValue(DelayProperty, value); }
}
/// <summary>
/// Identifies the Delay dependency property.
/// </summary>
public static readonly DependencyProperty DelayProperty =
DependencyProperty.RegisterCore(
"Delay",
typeof(int),
typeof(RepeatButton),
new PropertyMetadata(500, OnDelayPropertyChanged));
/// <summary>
/// DelayProperty property changed handler.
/// </summary>
/// <param name="d">RepeatButton that changed its Delay.</param>
/// <param name="e">DependencyPropertyChangedEventArgs.</param>
private static void OnDelayPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RepeatButton repeatButton = d as RepeatButton;
Debug.Assert(repeatButton != null);
int delay = (int)e.NewValue;
if (delay < 0)
{
throw new ArgumentException();
}
}
#endregion Delay
#region Interval
/// <summary>
/// Gets or sets the amount of time, in milliseconds, between repeats
/// once repeating starts. The value must be non-negative.
/// </summary>
public int Interval
{
get { return (int)GetValue(IntervalProperty); }
set { SetValue(IntervalProperty, value); }
}
/// <summary>
/// Identifies the Interval dependency property.
/// </summary>
public static readonly DependencyProperty IntervalProperty =
DependencyProperty.RegisterCore(
"Interval",
typeof(int),
typeof(RepeatButton),
new PropertyMetadata(33, OnIntervalPropertyChanged));
/// <summary>
/// IntervalProperty property changed handler.
/// </summary>
/// <param name="d">RepeatButton that changed its Interval.</param>
/// <param name="e">DependencyPropertyChangedEventArgs.</param>
private static void OnIntervalPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RepeatButton repeatButton = d as RepeatButton;
Debug.Assert(repeatButton != null);
int interval = (int)e.NewValue;
if (interval <= 0)
{
throw new ArgumentException();
}
}
#endregion Interval
#region Timer
/// <summary>
/// Timer listening for the button to repeat.
/// </summary>
private DispatcherTimer _timer;
/// <summary>
/// Start the timer.
/// </summary>
private void StartTimer()
{
if (_timer == null)
{
_timer = new DispatcherTimer();
_timer.Tick += new EventHandler(OnTimeout);
}
else if (_timer.IsEnabled)
{
return;
}
_timer.Interval = TimeSpan.FromMilliseconds(Delay);
_timer.Start();
}
/// <summary>
/// Stop the timer.
/// </summary>
private void StopTimer()
{
if (_timer != null)
{
_timer.Stop();
}
}
/// <summary>
/// Handler timer ticks.
/// </summary>
/// <param name="sender">Timer.</param>
/// <param name="e">EventArgs.</param>
private void OnTimeout(object sender, EventArgs e)
{
int interval = Interval;
if (_timer.Interval.Milliseconds != interval)
{
_timer.Interval = TimeSpan.FromMilliseconds(interval);
}
if (IsPressed)
{
if (_keyboardCausingRepeat)
{
// if the timeout occurred when the space key was down
// trigger the click directly without running a hit test.
OnClick();
}
else
{
// this is a workaround to check the HitTest to see
// whether the mouse is still over the button, since
// Silverlight has a bug where MouseEnter/MouseLeave
// states are not detected until the mouse has moved.
foreach (UIElement element in VisualTreeHelper.FindElementsInHostCoordinates(_mousePosition, this))
{
if (element == _elementRoot)
OnClick();
}
}
}
}
/// <summary>
/// Starts or stops the repeating timer based on whether the
/// mouse or keyboard is being used to cause the repeats.
/// </summary>
private void UpdateRepeatState()
{
if (_mouseCausingRepeat || _keyboardCausingRepeat)
{
StartTimer();
}
else
{
StopTimer();
}
}
/// <summary>
/// Called when the IsEnabled property changes.
/// </summary>
/// <param name="e">Property changed args</param>
private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// kill the repeat state timer
_keyboardCausingRepeat = false;
_mouseCausingRepeat = false;
UpdateRepeatState();
}
/// <summary>
/// Responds to the LostFocus event.
/// </summary>
/// <param name="e">The event data for the LostFocus event.</param>
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (ClickMode != ClickMode.Hover)
{
// kill the repeat state timer
_keyboardCausingRepeat = false;
_mouseCausingRepeat = false;
UpdateRepeatState();
}
}
#endregion Timer
#region Template Parts
/// <summary>
/// Root template part of the Button.
/// </summary>
/// <remarks>This field is marked internal for unit testing.</remarks>
internal FrameworkElement _elementRoot;
internal const string ElementRoot = "Root";
#endregion Template Parts
#region Constructor
/// <summary>
/// Initializes a new instance of the RepeatButton class.
/// </summary>
public RepeatButton()
{
DefaultStyleKey = typeof(RepeatButton);
ClickMode = ClickMode.Press;
IsEnabledChanged += OnIsEnabledChanged;
}
/// <summary>
/// Apply a template to the RepeatButton.
/// </summary>
public override void OnApplyTemplate()
{
// get the parts
_elementRoot = GetTemplateChild(ElementRoot) as FrameworkElement;
// Sync the logical and visual states of the control
UpdateVisualState(false);
}
#endregion Constructor
#region Change State
/// <summary>
/// Change to the correct visual state for the repeatbutton.
/// </summary>
/// <param name="useTransitions">
/// true to use transitions when updating the visual state, false to
/// snap directly to the new visual state.
/// </param>
internal override void ChangeVisualState(bool useTransitions)
{
if (!IsEnabled)
{
GoToState(useTransitions, StateDisabled);
}
else if (IsPressed)
{
GoToState(useTransitions, StatePressed);
}
else if (IsMouseOver)
{
GoToState(useTransitions, StateMouseOver);
}
else
{
GoToState(useTransitions, StateNormal);
}
if (IsFocused && IsEnabled)
{
GoToState(useTransitions, StateFocused);
}
else
{
GoToState(useTransitions, StateUnfocused);
}
}
#endregion Change State
#region Mouse Handlers
/// <summary>
/// Overriding the handler for the MouseLeftButtonDown event.
/// </summary>
/// <param name="e">MouseButtonEventArgs.</param>
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (e == null)
{
throw new ArgumentNullException("e");
}
else if (e.Handled)
{
return;
}
base.OnMouseLeftButtonDown(e);
if (ClickMode != ClickMode.Hover)
{
_mouseCausingRepeat = true;
UpdateRepeatState();
}
}
/// <summary>
/// Overriding the handler for the MouseLeftButtonUp event.
/// </summary>
/// <param name="e">MouseButtonEventArgs.</param>
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
if (e == null)
{
throw new ArgumentNullException("e");
}
else if (e.Handled)
{
return;
}
base.OnMouseLeftButtonUp(e);
if (ClickMode != ClickMode.Hover)
{
_mouseCausingRepeat = false;
UpdateRepeatState();
}
UpdateVisualState();
}
/// <summary>
/// Overriding the handler for the MouseEnter event.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
if (ClickMode == ClickMode.Hover)
{
_mouseCausingRepeat = true;
UpdateRepeatState();
}
UpdateVisualState();
// this code will cache the mouse position relative to the top level UIElement
// in the Silverlight page.
object parent = this;
while (true)
{
FrameworkElement element = parent as FrameworkElement;
if (element == null)
{
break;
}
parent = element.Parent;
}
_mousePosition = e.GetPosition(parent as UIElement);
}
/// <summary>
/// Overriding the handler for the MouseLeave event.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
if (ClickMode == ClickMode.Hover)
{
_mouseCausingRepeat = false;
UpdateRepeatState();
}
UpdateVisualState();
}
/// <summary>
/// Overriding the handler for the MouseMove event.
/// </summary>
/// <param name="e"></param>
/// <remarks>
/// The reason I am overriding this function without calling
/// base.OnMouseMove is that the ButtonBase class sometimes
/// sets IsPressed to false based on mouse position. This
/// interferes with the RepeatButton functionality.
/// </remarks>
protected override void OnMouseMove(MouseEventArgs e)
{
if (e == null)
{
throw new ArgumentNullException("e");
}
// this code will cache the mouse position relative to the top level UIElement
// in the Silverlight page.
object parent = this;
while (true)
{
FrameworkElement element = parent as FrameworkElement;
if (element == null)
{
break;
}
parent = element.Parent;
}
_mousePosition = e.GetPosition(parent as UIElement);
}
// Match signature from SL2 final
protected override void OnClick ()
{
base.OnClick ();
}
#region Keyboard Handlers
/// <summary>
/// Overriding the OnKeyDown handler to start the repeat timer.
/// </summary>
/// <param name="e">KeyEventArgs</param>
protected override void OnKeyDown(KeyEventArgs e)
{
if ((e.Key == Key.Space) && (ClickMode != ClickMode.Hover))
{
_keyboardCausingRepeat = true;
UpdateRepeatState();
}
base.OnKeyDown(e);
}
/// <summary>
/// Overriding the OnKeyUp handler to stop the repeat timer.
/// </summary>
/// <param name="e">KeyEventArgs</param>
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if ((e.Key == Key.Space) && (ClickMode != ClickMode.Hover))
{
_keyboardCausingRepeat = false;
UpdateRepeatState();
}
UpdateVisualState();
}
#endregion
#endregion Mouse Handlers
#region Private Members
// True if keyboard was used to initiate the repeat button, false otherwise.
private bool _keyboardCausingRepeat;
// True if mouse was used to initiate the repeat button, false otherwise.
private bool _mouseCausingRepeat;
#endregion
#region AutomationPeer section
protected override AutomationPeer OnCreateAutomationPeer ()
{
return new RepeatButtonAutomationPeer (this);
}
#endregion
}
}
|