File: System\Windows\Input\AccessKeyManager.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// 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.Collections;
using System.Globalization;
using System.Windows.Interop;
using MS.Internal;
 
namespace System.Windows.Input
{
    /// <summary>
    ///   AccessKeyManager object is created on demand and it is one per thread.
    /// It attached an event handler for PostProcessInput on InputManager and expose registration and 
    /// unregistration of access keys. When the access key is pressed in calls OnAccessKey method on the target element
    /// </summary>
    public sealed class AccessKeyManager
    {
        #region Public API
        /// <summary>
        ///   Register the access key binding to the element that is the accesskey.
        /// </summary>
        /// <param name="key">When the key is pressed the element OnAccessKey method is called</param>
        /// <param name="element">The registration element</param>
        public static void Register(string key, IInputElement element)
        {
            ArgumentNullException.ThrowIfNull(element);
            key = NormalizeKey(key);
 
            AccessKeyManager akm = AccessKeyManager.Current;
 
            lock (akm._keyToElements)
            {
                ArrayList elements = (ArrayList)akm._keyToElements[key];
 
                if (elements == null)
                {
                    elements = new ArrayList(1);
                    akm._keyToElements[key] = elements;
                }
                else
                {
                    // There were some elements there, remove dead ones
                    PurgeDead(elements, null);
                }
 
                elements.Add(new WeakReference(element));
            }
        }
 
        /// <summary>
        /// Unregister one key bound to a particular element
        /// </summary>
        /// <param name="key"></param>
        /// <param name="element"></param>
        public static void Unregister(string key, IInputElement element)
        {
            ArgumentNullException.ThrowIfNull(element);
            key = NormalizeKey(key);
 
            AccessKeyManager akm = AccessKeyManager.Current;
 
            lock (akm._keyToElements)
            {
                // Get all elements bound to this key and remove this element
                ArrayList elements = (ArrayList)akm._keyToElements[key];
 
                if (elements != null)
                {
                    PurgeDead(elements, element);
                    if (elements.Count == 0)
                    {
                        akm._keyToElements.Remove(key);
                    }
                }
            }
        }
 
        /// <summary>
        /// Tells if there is a particular key registered at the global scope in this Context.
        /// </summary>
        /// <param name="scope">Scope to query (for example the PresentationSource of the visual)</param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool IsKeyRegistered(object scope, string key)
        {
            key = NormalizeKey(key);
 
            AccessKeyManager akm = AccessKeyManager.Current;
            List<IInputElement> targets = akm.GetTargetsForScope(scope, key, null, AccessKeyInformation.Empty);
            return (targets != null && targets.Count > 0);
        }
 
        /// <summary>
        /// Process the given key as if the key were pressed at the global scope in this context.
        /// </summary>
        /// <param name="scope">scope in which to invoke the access key</param>
        /// <param name="key">character being pressed</param>
        /// <param name="isMultiple">True if this key has multiple matches</param>
        /// <returns>false if there are no more keys that match, true otherwise</returns>
        /// <summary>
        ///     Executes the command on the given command source.
        /// </summary>
        public static bool ProcessKey(object scope, string key, bool isMultiple)
        {
            key = NormalizeKey(key);
 
            AccessKeyManager akm = AccessKeyManager.Current;
            return (akm.ProcessKeyForScope(scope, key, isMultiple,false) == ProcessKeyResult.MoreMatches);
        }
 
        /// <summary>
        /// Returns StringInfo.GetNextTextElement(key).ToUpperInvariant() throwing exceptions for null
        /// and multi-char strings.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string NormalizeKey(string key)
        {
            ArgumentNullException.ThrowIfNull(key);
 
            string firstCharacter = StringInfo.GetNextTextElement(key);
 
            if (key != firstCharacter)
            {
                throw new ArgumentException(SR.Format(SR.AccessKeyManager_NotAUnicodeCharacter, "key"));
            }
 
            return firstCharacter.ToUpperInvariant();
        }
 
        /// <summary>
        /// This event is used by elements that want to define a scope for accesskeys, such as Menu and Popup.
        /// This event will never be raised, it is used to identify classes that define new scopes.
        /// </summary>
        public static readonly RoutedEvent AccessKeyPressedEvent = EventManager.RegisterRoutedEvent(
            "AccessKeyPressed", RoutingStrategy.Bubble, typeof(AccessKeyPressedEventHandler), typeof(AccessKeyManager));
 
        /// <summary>
        ///     Adds a handler for the AccessKeyPressed attached event
        /// </summary>
        /// <param name="element">UIElement or ContentElement that listens to this event</param>
        /// <param name="handler">Event Handler to be added</param>
        public static void AddAccessKeyPressedHandler(DependencyObject element, AccessKeyPressedEventHandler handler)
        {
            UIElement.AddHandler(element, AccessKeyPressedEvent, handler);
        }
 
        /// <summary>
        ///     Removes a handler for the AccessKeyPressed attached event
        /// </summary>
        /// <param name="element">UIElement or ContentElement that listens to this event</param>
        /// <param name="handler">Event Handler to be removed</param>
        public static void RemoveAccessKeyPressedHandler(DependencyObject element, AccessKeyPressedEventHandler handler)
        {
            UIElement.RemoveHandler(element, AccessKeyPressedEvent, handler);
        }
 
        #endregion
        
        #region Constructor
        private AccessKeyManager()
        {
            InputManager.Current.PostProcessInput += new ProcessInputEventHandler(PostProcessInput);
        }
 
        #endregion
 
        #region Properties
        
        /// <summary>
        /// Access to the current context's AccessKeyManager class
        /// </summary>
        private static AccessKeyManager Current
        {
            get 
            {
                if (_accessKeyManager == null)
                    _accessKeyManager = new AccessKeyManager();
                return _accessKeyManager;
            }
        }
 
        #endregion
 
        #region PostProcessInput Event Handlers
 
        private enum ProcessKeyResult
        {
            NoMatch,
            MoreMatches,
            LastMatch
        }
 
        private void PostProcessInput(object sender, ProcessInputEventArgs e)
        {
            if (e.StagingItem.Input.Handled) return;
 
            if (e.StagingItem.Input.RoutedEvent == Keyboard.KeyDownEvent)
            {
                OnKeyDown((KeyEventArgs)e.StagingItem.Input);
            }
            else if (e.StagingItem.Input.RoutedEvent == TextCompositionManager.TextInputEvent)
            {
                OnText((TextCompositionEventArgs)e.StagingItem.Input);
            }
}
 
        // Assumes key is already a single unicode character
        private ProcessKeyResult ProcessKeyForSender(object sender, string key, bool existsElsewhere, bool userInitiated)
        {
            // This comes from OnKeyDown or OnText and though it is a single character it might not be uppercased.
            key = key.ToUpperInvariant();
 
            IInputElement inputElementSender = sender as IInputElement;
            List<IInputElement> targets = GetTargetsForSender(inputElementSender, key);
 
            return ProcessKey(targets, key, existsElsewhere, userInitiated);
        }
 
        // Assumes key is already a single unicode character AND is uppercased
        private ProcessKeyResult ProcessKeyForScope(object scope, string key, bool existsElsewhere, bool userInitiated)
        {
            List<IInputElement> targets = GetTargetsForScope(scope, key, null, AccessKeyInformation.Empty);
 
            return ProcessKey(targets, key, existsElsewhere, userInitiated);
        }
 
        private ProcessKeyResult ProcessKey(List<IInputElement> targets, string key, bool existsElsewhere, bool userInitiated)
        {
            if (targets != null)
            {
                bool oneUIElement = true;
                UIElement invokeUIElement = null;
                bool lastWasAccessed = false;
 
                int chosenIndex = 0;
                for (int i = 0; i < targets.Count; i++)
                {
                    UIElement target = targets[i] as UIElement;
                    Debug.Assert(target != null, "Targets should only be UIElements");
                    if (!target.IsEnabled)
                        continue;
 
                    if (invokeUIElement == null)
                    {
                        invokeUIElement = target;
                        chosenIndex = i;
                    }
                    else
                    {
                        if (lastWasAccessed)
                        {
                            invokeUIElement = target;
                            chosenIndex = i;
                        }
 
                        oneUIElement = false;
                    }
 
                    // 
                    lastWasAccessed = target.HasEffectiveKeyboardFocus;
                }
 
                if (invokeUIElement != null)
                {
                    AccessKeyEventArgs args = new AccessKeyEventArgs(key, !oneUIElement || existsElsewhere /* == isMultiple */,userInitiated);
                    try
                    {
                        invokeUIElement.InvokeAccessKey(args);
                    }
                    finally
                    {
                        args.ClearUserInitiated();
                    }
 
                    return (chosenIndex == targets.Count - 1) ? ProcessKeyResult.LastMatch : ProcessKeyResult.MoreMatches;
                }
            }
 
            return ProcessKeyResult.NoMatch;
        }
 
        private void OnText(TextCompositionEventArgs e)
        {
            // AccessKeyManager handles both text and system text.
            string text = e.Text;
            if ((text == null) || (text.Length == 0))
            {
                text = e.SystemText;
            }
 
            if ((text != null) && (text.Length > 0))
            {
                if (ProcessKeyForSender(e.OriginalSource, text, false /* existsElsewhere */,e.UserInitiated) != ProcessKeyResult.NoMatch)
                {
                    e.Handled = true;
                }
            }
        }
 
        private void OnKeyDown(KeyEventArgs e)
        {
            KeyboardDevice keyboard = (KeyboardDevice)e.Device;
 
            string text = null;
            switch (e.RealKey)
            {
                case Key.Enter :
                     text = "\x000D";
                     break;
 
                case Key.Escape :
                     text = "\x001B";
                     break;
            }
 
            if (text != null)
            {
                if (ProcessKeyForSender(e.OriginalSource, text, false /* existsElsewhere */,e.UserInitiated) != ProcessKeyResult.NoMatch)
                {
                    e.Handled = true;
                }
            }
        }
 
        /// <summary>
        /// Get the list of access key targets for the sender of the keyboard event.  If sender is null, 
        /// pretend key was pressed in the active window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private List<IInputElement> GetTargetsForSender(IInputElement sender, string key)
        {
            // Find the scope for the sender -- will be matched against the possible targets' scopes
            AccessKeyInformation senderInfo = GetInfoForElement(sender, key);
 
            return GetTargetsForScope(senderInfo.Scope, key, sender, senderInfo);
        }
        
        private List<IInputElement> GetTargetsForScope(object scope, string key, IInputElement sender, AccessKeyInformation senderInfo)
        {
            // null scope defaults to the active window
            if (scope == null)
            {
                scope = CriticalGetActiveSource();
 
                // if there is no active scope then give up
                if (scope == null)
                {
                    return null;
                }
            }
 
            if (CoreCompatibilityPreferences.GetIsAltKeyRequiredInAccessKeyDefaultScope() && 
                (scope is PresentationSource) && (Keyboard.Modifiers & ModifierKeys.Alt) != ModifierKeys.Alt)
            {
                // If AltKey is required and it isnt pressed then dont match against any targets
                return null;
            }
            
            //Scoping:
            //    1) When key is pressed, find matching AKs -> S
            //    3) find scope for keyevent.Source
            //    4) find scope for everything in S. throw away those that don't match.
            //    5) Final selection uses S.  yay!
            // 
            // 
            List<IInputElement> possibleElements;
            lock (_keyToElements)
            {
                possibleElements = CopyAndPurgeDead(_keyToElements[key] as ArrayList);
            }
 
            if (possibleElements == null) return null;
 
            List<IInputElement> finalTargets = new List<IInputElement>(1);
 
            // Go through all the possible elements, find the interesting candidates
            for (int i = 0; i < possibleElements.Count;  i++)
            {
                IInputElement element = possibleElements[i];
                if (element != sender)
                {
                    if (IsTargetable(element))
                    {
                        AccessKeyInformation elementInfo = GetInfoForElement(element, key);
 
                        if (elementInfo.target == null) continue;
 
                        if (scope == elementInfo.Scope)
                        {
                            finalTargets.Add(elementInfo.target);
                        }
                    }
                }
                else
                {
                    // This is the same element that sent the event so it must be in the same scope.  
                    // Just add it to the final targets
                    if (senderInfo.target != null)
                    {
                        finalTargets.Add(senderInfo.target);
                    }
                }
            }
 
            return finalTargets;
        }
        
        /// <summary>
        /// Returns scope for the given element.
        /// </summary>
        /// <param name="element"></param>
        /// <param name="key"></param>
        /// <returns>Scope for the given element, null means the context global scope</returns>
        private AccessKeyInformation GetInfoForElement(IInputElement element, string key)
        {
            AccessKeyInformation info = new AccessKeyInformation();
            if (element != null)
            {
                AccessKeyPressedEventArgs args = new AccessKeyPressedEventArgs(key);
 
                element.RaiseEvent(args);
                info.Scope = args.Scope;
                info.target = args.Target;
                if (info.Scope == null)
                {
                    info.Scope = GetSourceForElement(element);
                }
            }
            else
            {
                info.Scope = CriticalGetActiveSource();
            }
            return info;
        }
 
        private PresentationSource GetSourceForElement(IInputElement element)
        {
            PresentationSource source = null;
            DependencyObject elementDO = element as DependencyObject;
 
            // Use internal helpers to try to find the source of the element.
            // Because IInputElements can move around without notification we need to
            // look up the source every time.
            if (elementDO != null)
            {
                DependencyObject containingVisual = InputElement.GetContainingVisual(elementDO);
 
                if (containingVisual != null)
                {
                    source = PresentationSource.CriticalFromVisual(containingVisual);
                }
            }
            
            // NOTE: source can be null but IsTargetable(element) == true if the
            // element is in an orphaned tree but the tree has not yet been garbage collected.  
            return source;
        }
 
        private PresentationSource GetActiveSource()
        {
            IntPtr hwnd = MS.Win32.UnsafeNativeMethods.GetActiveWindow();
            if (hwnd != IntPtr.Zero)
                return HwndSource.FromHwnd(hwnd);
 
            return null;
        }
 
        private PresentationSource CriticalGetActiveSource()
        {
            IntPtr hwnd = MS.Win32.UnsafeNativeMethods.GetActiveWindow();
            if (hwnd != IntPtr.Zero)
                return HwndSource.CriticalFromHwnd(hwnd);
 
            return null;
        }
 
        
        private bool IsTargetable(IInputElement element)
        {
            DependencyObject uielement = InputElement.GetContainingUIElement((DependencyObject)element);
 
            // For an element to be a valid target it must be visible and enabled
            if (uielement != null 
                && IsVisible(uielement)
                && IsEnabled(uielement))
            {
                return true;
            }
 
            return false;
        }
 
        private static bool IsVisible(DependencyObject element)
        {
            while (element != null)
            {
                Visibility visibility;
                UIElement uiElem = element as UIElement;
                UIElement3D uiElem3D = element as UIElement3D;
                
                if (uiElem != null)
 
                {
                    visibility = uiElem.Visibility;
                }
                else
                {
                    visibility = uiElem3D.Visibility;                    
                }
                
                if (visibility != Visibility.Visible)
                {
                    return false;
                }
                
                element = UIElementHelper.GetUIParent(element);
            }
            
            return true;
        }
 
        // returns whether the given DO is enabled or not
        private static bool IsEnabled(DependencyObject element)
        {
            return ((bool)element.GetValue(UIElement.IsEnabledProperty));                               
        }
 
        private struct AccessKeyInformation
        {
            public object Scope
            {
                get 
                {
                    return _scope;
                }
                set 
                {
                    _scope = value;
                }
            }
 
            
            public UIElement target;
 
            private static AccessKeyInformation _empty = new AccessKeyInformation();
            public static AccessKeyInformation Empty
            {
                get
                {
                    return _empty;
                }
            }
 
            private object _scope;
        }
 
        private static void PurgeDead(ArrayList elements, object elementToRemove)
        {
            for (int i = 0; i < elements.Count; )
            {
                WeakReference weakReference = (WeakReference)elements[i];
                object element = weakReference.Target;
 
                if (element == null || element == elementToRemove)
                {
                    elements.RemoveAt(i);
                }
                else
                {
                    i++;
                }
            }
        }
 
        /// <summary>
        ///     Takes an ArrayList of WeakReferences, removes the dead references and returns
        ///     a generic List of IInputElements (strong references)
        /// </summary>
        private static List<IInputElement> CopyAndPurgeDead(ArrayList elements)
        {
            if (elements == null)
            {
                return null;
            }
 
            List<IInputElement> copy = new List<IInputElement>(elements.Count);
 
            for (int i = 0; i < elements.Count; )
            {
                WeakReference weakReference = (WeakReference)elements[i];
                object element = weakReference.Target;
 
                if (element == null)
                {
                    elements.RemoveAt(i);
                }
                else
                {
                    Debug.Assert(element is IInputElement, "Element in AccessKeyManager store was not of type IInputElement");
                    copy.Add((IInputElement)element);
                    i++;
                }
            }
 
            return copy;
        }
 
        #endregion
 
        #region Private Properties
        
        /////////////////////////////////////////////////////////////////////////////////
        // Overview: Algorithm to look up access key from the element for which it is a target.
        //           
        //     When the AccessKeyCharacter for an element is requested we see if there
        //     is a corresponding AccessKeyElement stashed on the element.  If there is,
        //     raise the AccessKeyPressed event on it to see if that element is  still the 
        //     target for it.  If not, go through all registered accesskeys and get their 
        //     targets until we find the desired element.  The "primary" access key character
        //     is the first one we find.
        //     
        //     Note: The algorithm ends up being O(n) for each request for AccessKeyCharacter 
        //     because there is no mapping from AccessKeyElement to its "primary" character.
        //     Maintaining this would require storing a hash from AccessKeyElement to character
        //     or requiring that each element registered implement an interface or some other
        //     kind of contract.  Because we don't keep track of this or enforce this, to find 
        //     the "primary" character we must go through all registered pairs of 
        //     (character, element) to find the character -- O(n).
        //
        //     This ends up being just fine, because in any given context there shouldn't be
        //     so many elements registered that this cost is at all noticable.
        //
        /////////////////////////////////////////////////////////////////////////////////
 
        /// <summary>
        ///     The primary access key element for an element.  This is stored as a WeakReference.
        /// </summary>
        private static readonly DependencyProperty AccessKeyElementProperty =
            DependencyProperty.RegisterAttached("AccessKeyElement", typeof(WeakReference), typeof(AccessKeyManager));
 
        #endregion
 
        #region Private Methods for UIAutomation
 
        internal static string InternalGetAccessKeyCharacter(DependencyObject d)
        {
            return Current.GetAccessKeyCharacter(d);
        }
 
        private string GetAccessKeyCharacter(DependencyObject d)
        {
            // See what the local value for AccessKeyElement is first and start with that.
            WeakReference cachedElementWeakRef = (WeakReference)d.GetValue(AccessKeyElementProperty);
            IInputElement accessKeyElement = (cachedElementWeakRef != null) ? (IInputElement)cachedElementWeakRef.Target : null;
 
            if (accessKeyElement != null)
            {
                // First figure out if the target of accessKeyElement is still "d", then go find
                // the "primary" character for the accessKeyElement.  
 
                AccessKeyPressedEventArgs accessKeyPressedEventArgs = new AccessKeyPressedEventArgs();
                accessKeyElement.RaiseEvent(accessKeyPressedEventArgs);
                if (accessKeyPressedEventArgs.Target == d)
                {
                    // Because there is no way to get at the access key element's character from the
                    // element (there is no interface or anything) we have to go through all registered 
                    // access keys and see if this access key element is still registered and what its
                    // "primary" character is.
                
                    foreach (DictionaryEntry entry in Current._keyToElements)
                    {
                        ArrayList elements = (ArrayList)entry.Value;
                        for (int i = 0; i < elements.Count; i++)
                        {
                            // If this element matches accessKeyElement, then return the current character
                            WeakReference currentElementWeakRef = (WeakReference)elements[i];
 
                            if (currentElementWeakRef.Target == accessKeyElement)
                            {
                                return (string)entry.Key;
                            }
                        }
                    }
                }
            }
 
 
            // There was no access key stored or it no longer matched.  Clear out the cache and figure it out again.
            d.ClearValue(AccessKeyElementProperty);
 
            foreach (DictionaryEntry entry in Current._keyToElements)
            {
                ArrayList elements = (ArrayList)entry.Value;
                for (int i = 0; i < elements.Count; i++)
                {
                    // Determine the target for this element.  Cache the weak reference for the element on the target.
                    WeakReference currentElementWeakRef = (WeakReference)elements[i];
                    IInputElement currentElement = (IInputElement)currentElementWeakRef.Target;
 
                    if (currentElement != null)
                    {
                        AccessKeyPressedEventArgs accessKeyPressedEventArgs = new AccessKeyPressedEventArgs();
                        currentElement.RaiseEvent(accessKeyPressedEventArgs);
 
                        // If the target was non-null, cache the access key element on the target.
                        // if the target matches "d", return the current character.
                        if (accessKeyPressedEventArgs.Target != null)
                        {
                            accessKeyPressedEventArgs.Target.SetValue(AccessKeyElementProperty, currentElementWeakRef);
 
                            if (accessKeyPressedEventArgs.Target == d)
                            {
                                return (string)entry.Key;
                            }
                        }
                    }
                }
            }
 
 
            return String.Empty;
        }
 
        #endregion
 
        #region Data
        // Map: string -> ArrayList of WeakReferences to IInputElements
        private Hashtable _keyToElements = new Hashtable(10);
 
        [ThreadStatic] private static AccessKeyManager _accessKeyManager;
 
        #endregion
    }
 
    /// <summary>
    /// The delegate type for handling a FindScope event
    /// </summary>
    public delegate void AccessKeyPressedEventHandler(object sender, AccessKeyPressedEventArgs e);
 
    /// <summary>
    /// The inputs to an AccessKeyPressedEventHandler
    /// </summary>
    public class AccessKeyPressedEventArgs : RoutedEventArgs
    {
        #region Constructors
 
        /// <summary>
        /// The constructor for AccessKeyPressed event args
        /// </summary>
        public AccessKeyPressedEventArgs()
        {
            RoutedEvent = AccessKeyManager.AccessKeyPressedEvent;
            _key = null;
        }
 
        /// <summary>
        /// Constructor for AccessKeyPressed event args
        /// </summary>
        /// <param name="key"></param>
        public AccessKeyPressedEventArgs(string key) : this()
        {
            _key = key;
        }
 
        #endregion
 
        #region Public Properties
 
        /// <summary>
        /// The scope for the element that raised this event.
        /// </summary>
        public object Scope
        {
            get { return _scope; }
            set { _scope = value; }
        }
 
        /// <summary>
        /// Target element for the element that raised this event.
        /// </summary>
        /// <value></value>
        public UIElement Target
        {
            get { return _target; }
            set { _target = value; }
        }
 
        /// <summary>
        /// Key that was pressed
        /// </summary>
        /// <value></value>
        public string Key
        {
            get { return _key; }
        }
 
        #endregion
 
        #region Protected Methods
 
        /// <summary>
        /// </summary>
        /// <param name="genericHandler">The handler to invoke.</param>
        /// <param name="genericTarget">The current object along the event's route.</param>
        protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
        {
            AccessKeyPressedEventHandler handler = (AccessKeyPressedEventHandler)genericHandler;
 
            handler(genericTarget, this);
        }
 
        #endregion
 
        #region Data
 
        private object _scope;
        private UIElement _target;
        private string _key;
 
        #endregion
    }
 
    /// <summary>
    /// Information pertaining to when the access key associated with an element is pressed
    /// </summary>
    public class AccessKeyEventArgs : EventArgs
    {
        /// <summary>
        /// 
        /// </summary>
        internal AccessKeyEventArgs(string key, bool isMultiple, bool userInitiated)
        {
            _key = key;
            _isMultiple = isMultiple;
            _userInitiated = userInitiated;
        }
 
        internal void ClearUserInitiated()
        {
            _userInitiated = false;
        }
        /// <summary>
        /// The key that was pressed which invoked this access key
        /// </summary>
        /// <value></value>
        public string Key
        {
            get { return _key; }
        }
 
        /// <summary>
        /// Were there other elements which are also invoked by this key
        /// </summary>
        /// <value></value>
        public bool IsMultiple
        {
            get { return _isMultiple; }
        }
 
        internal bool UserInitiated
        {
            get { return _userInitiated; }
        }
        
 
        private string _key;
        private bool _isMultiple;
        private bool _userInitiated;
}
}