|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using System.Windows.Markup;
using MS.Internal.PresentationCore;
namespace System.Windows.Input
{
/// <summary>
/// A command that causes handlers associated with it to be called.
/// </summary>
[TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=" + BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")]
[ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=" + BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")]
public class RoutedCommand : ICommand
{
#region Constructors
/// <summary>
/// Default Constructor - needed to allow markup creation
/// </summary>
public RoutedCommand()
{
_name = String.Empty;
_ownerType = null;
_inputGestureCollection = null;
}
/// <summary>
/// RoutedCommand Constructor with Name and OwnerType
/// </summary>
/// <param name="name">Declared Name of the RoutedCommand for Serialization</param>
/// <param name="ownerType">Type that is registering the property</param>
public RoutedCommand(string name, Type ownerType) : this(name, ownerType, null)
{
}
/// <summary>
/// RoutedCommand Constructor with Name and OwnerType
/// </summary>
/// <param name="name">Declared Name of the RoutedCommand for Serialization</param>
/// <param name="ownerType">Type that is registering the property</param>
/// <param name="inputGestures">Default Input Gestures associated</param>
public RoutedCommand(string name, Type ownerType, InputGestureCollection inputGestures)
{
ArgumentNullException.ThrowIfNull(name);
if (name.Length == 0)
{
throw new ArgumentException(SR.StringEmpty, "name");
}
ArgumentNullException.ThrowIfNull(ownerType);
_name = name;
_ownerType = ownerType;
_inputGestureCollection = inputGestures;
}
/// <summary>
/// RoutedCommand Constructor with Name and OwnerType and command identifier.
/// </summary>
/// <param name="name">Declared Name of the RoutedCommand for Serialization</param>
/// <param name="ownerType">Type that is registering the property</param>
/// <param name="commandId">Byte identifier for the command assigned by the owning type</param>
internal RoutedCommand(string name, Type ownerType, byte commandId) : this(name, ownerType, null)
{
_commandId = commandId;
}
#endregion
#region ICommand
/// <summary>
/// Executes the command with the given parameter on the currently focused element.
/// </summary>
/// <param name="parameter">Parameter to pass to any command handlers.</param>
void ICommand.Execute(object parameter)
{
Execute(parameter, FilterInputElement(Keyboard.FocusedElement));
}
/// <summary>
/// Whether the command can be executed with the given parameter on the currently focused element.
/// </summary>
/// <param name="parameter">Parameter to pass to any command handlers.</param>
/// <returns>true if the command can be executed, false otherwise.</returns>
bool ICommand.CanExecute(object parameter)
{
bool unused;
return CanExecuteImpl(parameter, FilterInputElement(Keyboard.FocusedElement), false, out unused);
}
/// <summary>
/// Raised when CanExecute should be requeried on commands.
/// Since commands are often global, it will only hold onto the handler as a weak reference.
/// Users of this event should keep a strong reference to their event handler to avoid
/// it being garbage collected. This can be accomplished by having a private field
/// and assigning the handler as the value before or after attaching to this event.
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
#endregion
#region Public Methods
/// <summary>
/// Executes the command with the given parameter on the given target.
/// </summary>
/// <param name="parameter">Parameter to be passed to any command handlers.</param>
/// <param name="target">Element at which to begin looking for command handlers.</param>
public void Execute(object parameter, IInputElement target)
{
// We only support UIElement, ContentElement and UIElement3D
if ((target != null) && !InputElement.IsValid(target))
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, target.GetType()));
}
if (target == null)
{
target = FilterInputElement(Keyboard.FocusedElement);
}
ExecuteImpl(parameter, target, false);
}
/// <summary>
/// Whether the command can be executed with the given parameter on the given target.
/// </summary>
/// <param name="parameter">Parameter to be passed to any command handlers.</param>
/// <param name="target">The target element on which to begin looking for command handlers.</param>
/// <returns>true if the command can be executed, false otherwise.</returns>
public bool CanExecute(object parameter, IInputElement target)
{
bool unused;
return CriticalCanExecute(parameter, target, false, out unused);
}
/// <summary>
/// Whether the command can be executed with the given parameter on the given target.
/// </summary>
/// <param name="parameter">Parameter to be passed to any command handlers.</param>
/// <param name="target">The target element on which to begin looking for command handlers.</param>
/// <param name="trusted">Determines whether this call will elevate for userinitiated input or not.</param>
/// <param name="continueRouting">Determines whether the input event (if any) that caused this command should continue its route.</param>
/// <returns>true if the command can be executed, false otherwise.</returns>
internal bool CriticalCanExecute(object parameter, IInputElement target, bool trusted, out bool continueRouting)
{
// We only support UIElement, ContentElement and UIElement3D
if ((target != null) && !InputElement.IsValid(target))
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, target.GetType()));
}
if (target == null)
{
target = FilterInputElement(Keyboard.FocusedElement);
}
return CanExecuteImpl(parameter, target, trusted, out continueRouting);
}
#endregion
#region Public Properties
/// <summary>
/// Name - Declared time Name of the property/field where it is
/// defined, for serialization/debug purposes only.
/// Ex: public static RoutedCommand New { get { new RoutedCommand("New", .... ) } }
/// public static RoutedCommand New = new RoutedCommand("New", ... ) ;
/// </summary>
public string Name
{
get
{
return _name;
}
}
/// <summary>
/// Owning type of the property
/// </summary>
public Type OwnerType
{
get
{
return _ownerType;
}
}
/// <summary>
/// Identifier assigned by the owning Type. Note that this is not a global command identifier.
/// </summary>
internal byte CommandId
{
get
{
return _commandId;
}
}
/// <summary>
/// Input Gestures associated with RoutedCommand
/// </summary>
public InputGestureCollection InputGestures
{
get
{
if(InputGesturesInternal == null)
{
_inputGestureCollection = new InputGestureCollection();
}
return _inputGestureCollection;
}
}
internal InputGestureCollection InputGesturesInternal
{
get
{
if(_inputGestureCollection == null && AreInputGesturesDelayLoaded)
{
_inputGestureCollection = GetInputGestures();
AreInputGesturesDelayLoaded = false;
}
return _inputGestureCollection;
}
}
/// <summary>
/// Fetches the default input gestures for the command by invoking the LoadDefaultGestureFromResource function on the owning type.
/// </summary>
/// <returns>collection of input gestures for the command</returns>
private InputGestureCollection GetInputGestures()
{
if(OwnerType == typeof(ApplicationCommands))
{
return ApplicationCommands.LoadDefaultGestureFromResource(_commandId);
}
else if(OwnerType == typeof(NavigationCommands))
{
return NavigationCommands.LoadDefaultGestureFromResource(_commandId);
}
else if(OwnerType == typeof(MediaCommands))
{
return MediaCommands.LoadDefaultGestureFromResource(_commandId);
}
else if(OwnerType == typeof(ComponentCommands))
{
return ComponentCommands.LoadDefaultGestureFromResource(_commandId);
}
return new InputGestureCollection();
}
/// <summary>
/// Rights Management Enabledness
/// Will be set by Rights Management code.
/// </summary>
/// <value></value>
internal bool IsBlockedByRM
{
get
{
return ReadPrivateFlag(PrivateFlags.IsBlockedByRM);
}
set
{
WritePrivateFlag(PrivateFlags.IsBlockedByRM, value);
}
}
internal bool AreInputGesturesDelayLoaded
{
get
{
return ReadPrivateFlag(PrivateFlags.AreInputGesturesDelayLoaded);
}
set
{
WritePrivateFlag(PrivateFlags.AreInputGesturesDelayLoaded, value);
}
}
#endregion
#region Implementation
private static IInputElement FilterInputElement(IInputElement elem)
{
// We only support UIElement, ContentElement, and UIElement3D
if ((elem != null) && InputElement.IsValid(elem))
{
return elem;
}
return null;
}
/// <param name="parameter"></param>
/// <param name="target"></param>
/// <param name="trusted"></param>
/// <param name="continueRouting"></param>
/// <returns></returns>
private bool CanExecuteImpl(object parameter, IInputElement target, bool trusted, out bool continueRouting)
{
// If blocked by rights-management fall through and return false
if ((target != null) && !IsBlockedByRM)
{
// Raise the Preview Event, check the Handled value, and raise the regular event.
CanExecuteRoutedEventArgs args = new CanExecuteRoutedEventArgs(this, parameter);
args.RoutedEvent = CommandManager.PreviewCanExecuteEvent;
CriticalCanExecuteWrapper(parameter, target, trusted, args);
if (!args.Handled)
{
args.RoutedEvent = CommandManager.CanExecuteEvent;
CriticalCanExecuteWrapper(parameter, target, trusted, args);
}
continueRouting = args.ContinueRouting;
return args.CanExecute;
}
else
{
continueRouting = false;
return false;
}
}
private void CriticalCanExecuteWrapper(object parameter, IInputElement target, bool trusted, CanExecuteRoutedEventArgs args)
{
// This cast is ok since we are already testing for UIElement, ContentElement, or UIElement3D
// both of which derive from DO
DependencyObject targetAsDO = (DependencyObject)target;
if (targetAsDO is UIElement uie)
{
uie.RaiseEvent(args, trusted);
}
else if (targetAsDO is ContentElement ce)
{
ce.RaiseEvent(args, trusted);
}
else if (targetAsDO is UIElement3D uie3D)
{
uie3D.RaiseEvent(args, trusted);
}
}
internal bool ExecuteCore(object parameter, IInputElement target, bool userInitiated)
{
if (target == null)
{
target = FilterInputElement(Keyboard.FocusedElement);
}
return ExecuteImpl(parameter, target, userInitiated);
}
private bool ExecuteImpl(object parameter, IInputElement target, bool userInitiated)
{
// If blocked by rights-management fall through and return false
if ((target != null) && !IsBlockedByRM)
{
UIElement targetUIElement = target as UIElement;
ContentElement targetAsContentElement = null;
UIElement3D targetAsUIElement3D = null;
// Raise the Preview Event and check for Handled value, and
// Raise the regular ExecuteEvent.
ExecutedRoutedEventArgs args = new ExecutedRoutedEventArgs(this, parameter);
args.RoutedEvent = CommandManager.PreviewExecutedEvent;
if (targetUIElement != null)
{
targetUIElement.RaiseEvent(args, userInitiated);
}
else
{
targetAsContentElement = target as ContentElement;
if (targetAsContentElement != null)
{
targetAsContentElement.RaiseEvent(args, userInitiated);
}
else
{
targetAsUIElement3D = target as UIElement3D;
if (targetAsUIElement3D != null)
{
targetAsUIElement3D.RaiseEvent(args, userInitiated);
}
}
}
if (!args.Handled)
{
args.RoutedEvent = CommandManager.ExecutedEvent;
if (targetUIElement != null)
{
targetUIElement.RaiseEvent(args, userInitiated);
}
else if (targetAsContentElement != null)
{
targetAsContentElement.RaiseEvent(args, userInitiated);
}
else if (targetAsUIElement3D != null)
{
targetAsUIElement3D.RaiseEvent(args, userInitiated);
}
}
return args.Handled;
}
return false;
}
#endregion
#region PrivateMethods
private void WritePrivateFlag(PrivateFlags bit, bool value)
{
if (value)
{
_flags |= bit;
}
else
{
_flags &= ~bit;
}
}
private bool ReadPrivateFlag(PrivateFlags bit)
{
return (_flags & bit) != 0;
}
#endregion PrivateMethods
#region Data
private string _name;
private PrivateFlags _flags;
private enum PrivateFlags : byte
{
IsBlockedByRM = 0x01,
AreInputGesturesDelayLoaded = 0x02
}
private Type _ownerType;
private InputGestureCollection _inputGestureCollection;
private byte _commandId; //Note that this is NOT a global command identifier. It is specific to the owning type.
//it represents one of the CommandID enums defined by each of the command types and will be cast to one of them when used.
#endregion
}
}
|