1
ļ»æ// Copyright Ā© Microsoft Corporation.
2
// This source is subject to the Microsoft Source License for Silverlight Controls (March 2008 Release).
3
// Please see http://go.microsoft.com/fwlink/?LinkID=111693 for details.
4
// All other rights reserved.
7
using System.Diagnostics;
8
using System.Windows.Input;
9
using System.Windows.Media.Animation;
10
using System.Windows.Controls;
12
namespace System.Windows.Controls.Primitives
15
/// Represents the base class for all Button controls.
17
public partial class ButtonBase : ContentControl
21
/// Gets or sets when the Click event should occur.
23
public ClickMode ClickMode
25
get { return (ClickMode) GetValue(ClickModeProperty); }
26
set { SetValue(ClickModeProperty, value); }
30
/// Identifies the ClickMode dependency property.
32
public static readonly DependencyProperty ClickModeProperty =
33
DependencyProperty.Register(
37
new PropertyMetadata(OnClickModePropertyChanged));
40
/// ClickModeProperty property changed handler.
42
/// <param name="d">ButtonBase that changed its ClickMode.</param>
43
/// <param name="e">DependencyPropertyChangedEventArgs.</param>
44
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Name")]
45
private static void OnClickModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
47
ButtonBase source = d as ButtonBase;
48
Debug.Assert(source != null,
49
"The source is not an instance of ButtonBase!");
51
Debug.Assert(typeof(ClickMode).IsInstanceOfType(e.NewValue),
52
"The value is not an instance of ClickMode!");
53
ClickMode value = (ClickMode) e.NewValue;
55
if (value != ClickMode.Release && value != ClickMode.Press && value != ClickMode.Hover)
57
throw new ArgumentException(Resource.ButtonBase_OnClickModePropertyChanged_InvalidValue, "value");
64
/// Gets a value that determines whether this element has logical focus.
67
/// IsFocused will not be set until OnFocus has been called. It may not
68
/// yet have been set if you check it in your own Focus event handler.
72
get { return (bool) GetValue(IsFocusedProperty); }
73
internal set { SetValue(IsFocusedProperty, value); }
77
/// Identifies the IsFocused dependency property.
79
public static readonly DependencyProperty IsFocusedProperty =
80
DependencyProperty.Register(
89
/// Gets a value indicating whether the mouse pointer is located over
93
/// IsMouseOver will not be set until MouseEnter has been called. It
94
/// may not yet have been set if you check it in your own MouseEnter
97
public bool IsMouseOver
99
get { return (bool) GetValue(IsMouseOverProperty); }
100
internal set { SetValue(IsMouseOverProperty, value); }
104
/// Identifies the IsMouseOver dependency property.
106
public static readonly DependencyProperty IsMouseOverProperty =
107
DependencyProperty.Register(
112
#endregion IsMouseOver
116
/// Gets a value that indicates whether a ButtonBase is currently
119
public bool IsPressed
121
get { return (bool) GetValue(IsPressedProperty); }
122
protected internal set { SetValue(IsPressedProperty, value); }
126
/// Identifies the IsPressed dependency property.
128
public static readonly DependencyProperty IsPressedProperty =
129
DependencyProperty.Register(
133
new PropertyMetadata(OnIsPressedPropertyChanged));
136
/// IsPressedProperty property changed handler.
138
/// <param name="d">ButtonBase that changed its IsPressed.</param>
139
/// <param name="e">DependencyPropertyChangedEventArgs.</param>
140
private static void OnIsPressedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
142
ButtonBase source = d as ButtonBase;
143
Debug.Assert(source != null,
144
"The source is not an instance of ButtonBase!");
146
source.OnIsPressedChanged(e);
151
/// True if the control has been loaded; false otherwise.
153
internal bool _isLoaded;
156
/// True if the mouse has been captured by this button, false otherwise.
158
internal bool _isMouseCaptured;
161
/// True if the SPACE key is currently pressed, false otherwise.
163
internal bool _isSpaceKeyDown;
166
/// True if the mouse's left button is currently down, false otherwise.
168
internal bool _isMouseLeftButtonDown;
171
/// Last known position of the mouse with respect to this Button.
173
internal Point _mousePosition;
176
/// Current visual state of the button.
178
internal Storyboard _currentState;
181
/// True if visual state changes are suspended; false otherwise.
183
internal bool _suspendStateChanges;
186
/// Occurs when a Button is clicked.
188
public event RoutedEventHandler Click;
191
/// Initializes a new instance of the ButtonBase class.
193
protected ButtonBase()
195
// Allow the button to respond to the ENTER key and be focused
196
KeyboardNavigation.SetAcceptsReturn(this, true);
199
// Attach the necessary events to their virtual counterparts
200
Loaded += delegate { _isLoaded = true; UpdateVisualState(); };
201
GotFocus += OnGotFocus;
202
LostFocus += OnLostFocus;
203
KeyDown += OnKeyDown;
205
MouseEnter += OnMouseEnter;
206
MouseLeave += OnMouseLeave;
207
MouseLeftButtonDown += OnMouseLeftButtonDown;
208
MouseLeftButtonUp += OnMouseLeftButtonUp;
209
MouseMove += OnMouseMove;
213
/// Update the current visual state of the button.
215
internal void UpdateVisualState()
217
if (!_suspendStateChanges)
224
/// Change to the correct visual state for the button.
226
internal virtual void ChangeVisualState()
231
/// Change the visual state of the button.
233
/// <param name="state">Next visual state of the button.</param>
235
/// This method should not be called by controls to force a change to
236
/// the current visual state. UpdateVisualState is preferred because
237
/// it properly handles suspension of state changes.
239
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "An invalid Storyboard or playing a Storyboard at the wrong time throws System.Exception.")]
240
internal void ChangeVisualState(Storyboard state)
242
// Do nothing if we're already in the right state
243
Storyboard previousState = _currentState;
244
if (state == previousState)
249
// Try to prevent transitioning to a new state before the control
250
// has been loaded by waiting for the Loaded event and checking if
251
// it has a Parent because it was added to the visual tree (since
252
// playing a Storyboard before a control is loaded will raise an
253
// Exception as per Jolt Bug 13025).
254
if (state != null && _isLoaded && Parent != null)
258
// Transition into the state
261
// Don't make the new state the current state until after we
262
// we know it didn't fail to play the transition (so the
263
// next call to UpdateVisualState will have a chance to try
265
_currentState = state;
267
// Back out of the previous state after moving to the next
268
// state to restore any values not used by the new state.
269
if (previousState != null)
271
previousState.Stop();
281
/// Capture the mouse.
283
internal void CaptureMouseInternal()
285
if (!_isMouseCaptured)
287
_isMouseCaptured = CaptureMouse();
292
/// Release mouse capture if we already had it.
294
internal void ReleaseMouseCaptureInternal()
296
ReleaseMouseCapture();
297
_isMouseCaptured = false;
301
/// Invoke OnClick for testing.
303
internal void OnClickInternal()
309
/// Raises the Click routed event.
311
protected virtual void OnClick()
313
RoutedEventHandler handler = Click;
316
handler(this, new RoutedEventArgs { OriginalSource = this });
321
/// Called when the IsEnabled property changes.
323
/// <param name="isEnabled">New value of the IsEnabled property.</param>
324
protected override void OnIsEnabledChanged(bool isEnabled)
326
base.OnIsEnabledChanged(isEnabled);
327
_suspendStateChanges = true;
334
_isMouseCaptured = false;
335
_isSpaceKeyDown = false;
336
_isMouseLeftButtonDown = false;
341
_suspendStateChanges = false;
347
/// Called when the IsPressed property changes.
350
/// The data for DependencyPropertyChangedEventArgs.
352
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
353
protected virtual void OnIsPressedChanged(DependencyPropertyChangedEventArgs e)
359
/// Handle the GotFocus event.
361
/// <param name="sender">The source of the event.</param>
362
/// <param name="e">RoutedEventArgs.</param>
363
internal void OnGotFocus(object sender, RoutedEventArgs e)
370
/// Responds to the GotFocus event.
372
/// <param name="e">The event data for the GotFocus event.</param>
373
protected virtual void OnGotFocus(RoutedEventArgs e)
377
throw new ArgumentNullException("e");
383
/// Handle the LostFocus event.
385
/// <param name="sender">The source of the event.</param>
386
/// <param name="e">RoutedEventArgs.</param>
387
internal void OnLostFocus(object sender, RoutedEventArgs e)
394
/// Responds to the LostFocus event.
396
/// <param name="e">The event data for the LostFocus event.</param>
397
protected virtual void OnLostFocus(RoutedEventArgs e)
401
throw new ArgumentNullException("e");
404
_suspendStateChanges = true;
407
if (ClickMode != ClickMode.Hover)
410
ReleaseMouseCaptureInternal();
411
_isSpaceKeyDown = false;
416
_suspendStateChanges = false;
422
/// Handle the KeyDown event.
424
/// <param name="sender">The source of the event.</param>
425
/// <param name="e">KeyEventArgs.</param>
426
internal void OnKeyDown(object sender, KeyEventArgs e)
432
/// Responds to the KeyDown event.
434
/// <param name="e">The event data for the KeyDown event.</param>
435
protected virtual void OnKeyDown(KeyEventArgs e)
439
throw new ArgumentNullException("e");
446
if (OnKeyDownInternal(e.Key))
453
/// Handles the KeyDown event for ButtonBase.
455
/// <param name="key">
456
/// The keyboard key associated with the event.
458
/// <returns>True if the event was handled, false otherwise.</returns>
460
/// This method exists for the purpose of unit testing since we can't
461
/// set KeyEventArgs.Key to simulate key press events.
463
internal virtual bool OnKeyDownInternal(Key key)
465
// True if the button will handle the event, false otherwise.
466
bool handled = false;
468
// Key presses can be ignored when disabled or in ClickMode.Hover
469
if (IsEnabled && (ClickMode != ClickMode.Hover))
471
// Hitting the SPACE key is equivalent to pressing the mouse
473
if (key == Key.Space)
475
// Ignore the SPACE key if we already have the mouse
477
if (!_isMouseCaptured)
479
_isSpaceKeyDown = true;
481
CaptureMouseInternal();
483
if (ClickMode == ClickMode.Press)
491
// The ENTER key forces a click
492
else if ((key == Key.Enter) && KeyboardNavigation.GetAcceptsReturn(this))
494
_isSpaceKeyDown = false;
496
ReleaseMouseCaptureInternal();
502
// Any other keys pressed are irrelevant
503
else if (_isSpaceKeyDown)
506
_isSpaceKeyDown = false;
507
ReleaseMouseCaptureInternal();
515
/// Handle the KeyUp event.
517
/// <param name="sender">The source of the event.</param>
518
/// <param name="e">KeyEventArgs.</param>
519
internal void OnKeyUp(object sender, KeyEventArgs e)
525
/// Responds to the KeyUp event.
527
/// <param name="e">The event data for the KeyUp event.</param>
528
protected virtual void OnKeyUp(KeyEventArgs e)
532
throw new ArgumentNullException("e");
539
if (OnKeyUpInternal(e.Key))
546
/// Handles the KeyUp event for ButtonBase.
548
/// <param name="key">The keyboard key associated with the event.</param>
549
/// <returns>True if the event was handled, false otherwise.</returns>
551
/// This method exists for the purpose of unit testing since we can't
552
/// set KeyEventArgs.Key to simulate key press events.
554
internal bool OnKeyUpInternal(Key key)
556
// True if the button will handle the event, false otherwise.
557
bool handled = false;
559
// Key presses can be ignored when disabled or in ClickMode.Hover
560
// or if any other key than SPACE was released.
561
if (IsEnabled && (ClickMode != ClickMode.Hover) && (key == Key.Space))
563
_isSpaceKeyDown = false;
565
if (!_isMouseLeftButtonDown)
567
// If the mouse isn't in use, raise the Click event if we're
568
// in the correct click mode
569
ReleaseMouseCaptureInternal();
570
if (IsPressed && (ClickMode == ClickMode.Release))
577
else if (_isMouseCaptured)
579
// Determine if the button should still be pressed based on
580
// the position of the mouse.
581
bool isValid = IsValidMousePosition();
585
ReleaseMouseCaptureInternal();
596
/// Handle the MouseEnter event.
598
/// <param name="sender">The source of the event.</param>
599
/// <param name="e">MouseEventArgs.</param>
600
internal void OnMouseEnter(object sender, MouseEventArgs e)
607
/// Responds to the MouseEnter event.
609
/// <param name="e">The event data for the MouseEnter event.</param>
610
protected virtual void OnMouseEnter(MouseEventArgs e)
614
throw new ArgumentNullException("e");
621
_suspendStateChanges = true;
624
if ((ClickMode == ClickMode.Hover) && IsEnabled)
634
_suspendStateChanges = false;
640
/// Handle the MouseLeave event.
642
/// <param name="sender">The source of the event.</param>
643
/// <param name="e">MouseEventArgs.</param>
644
internal void OnMouseLeave(object sender, MouseEventArgs e)
651
/// Responds to the MouseLeave event.
653
/// <param name="e">The event data for the MouseLeave event.</param>
654
protected virtual void OnMouseLeave(MouseEventArgs e)
658
throw new ArgumentNullException("e");
665
_suspendStateChanges = true;
668
if ((ClickMode == ClickMode.Hover) && IsEnabled)
676
_suspendStateChanges = false;
682
/// Handle the MouseLeftButtonDown event.
684
/// <param name="sender">The source of the event.</param>
685
/// <param name="e">MouseButtonEventArgs.</param>
686
internal void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
688
_isMouseLeftButtonDown = true;
689
OnMouseLeftButtonDown(e);
693
/// Responds to the MouseLeftButtonDown event.
696
/// The event data for the MouseLeftButtonDown event.
698
protected virtual void OnMouseLeftButtonDown(MouseButtonEventArgs e)
702
throw new ArgumentNullException("e");
709
if (!IsEnabled || (ClickMode == ClickMode.Hover))
715
_suspendStateChanges = true;
720
CaptureMouseInternal();
721
if (_isMouseCaptured)
728
_suspendStateChanges = false;
732
if (ClickMode == ClickMode.Press)
739
/// Handle the MouseLeftButtonUp event.
741
/// <param name="sender">The source of the event.</param>
742
/// <param name="e">MouseButtonEventArgs.</param>
743
internal void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
745
_isMouseLeftButtonDown = false;
746
OnMouseLeftButtonUp(e);
750
/// Responds to the MouseLeftButtonUp event.
753
/// The event data for the MouseLeftButtonUp event.
755
protected virtual void OnMouseLeftButtonUp(MouseButtonEventArgs e)
759
throw new ArgumentNullException("e");
766
if (!IsEnabled || (ClickMode == ClickMode.Hover))
772
if (!_isSpaceKeyDown && IsPressed && (ClickMode == ClickMode.Release))
777
if (!_isSpaceKeyDown)
779
ReleaseMouseCaptureInternal();
785
/// Handle the MouseMove event.
787
/// <param name="sender">The source of the event.</param>
788
/// <param name="e">MouseEventArgs.</param>
789
internal void OnMouseMove(object sender, MouseEventArgs e)
795
/// Responds to the MouseMove event.
797
/// <param name="e">The event data for the MouseMove event.</param>
798
protected virtual void OnMouseMove(MouseEventArgs e)
802
throw new ArgumentNullException("e");
805
// Cache the latest mouse position.
806
_mousePosition = e.GetPosition(this);
813
// Determine if the button is still pressed based on the mouse's
815
if (_isMouseLeftButtonDown &&
817
(ClickMode != ClickMode.Hover) &&
821
IsPressed = IsValidMousePosition();
827
/// Determine if the mouse is above the button based on its last known
831
/// True if the mouse is considered above the button, false otherwise.
833
internal bool IsValidMousePosition()
835
return (_mousePosition.X >= 0.0) &&
836
(_mousePosition.X <= ActualWidth) &&
837
(_mousePosition.Y >= 0.0) &&
838
(_mousePosition.Y <= ActualHeight);