Selector.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / System / Windows / Controls / Primitives / Selector.cs / 3 / Selector.cs

                            //---------------------------------------------------------------------------- 
//
// Copyright (C) Microsoft Corporation.  All rights reserved.
//
//--------------------------------------------------------------------------- 

using System.ComponentModel; 
using System.Collections; 
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using System.Collections.Specialized;
using System.Windows.Threading;
using System.Windows.Data;
using System.Windows; 
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider; 
using System.Windows.Input; 
using System.Xml;
using MS.Utility; 
using MS.Internal;
using MS.Internal.Data;
using MS.Internal.KnownBoxes;
using MS.Internal.Hashing.PresentationFramework;    // HashHelper 

using System; 
using System.Diagnostics; 

// Disable CS3001: Warning as Error: not CLS-compliant 
#pragma warning disable 3001

namespace System.Windows.Controls.Primitives
{ 
    /// 
    /// The base class for controls that select items from among their children 
    ///  
    [DefaultEvent("SelectionChanged"), DefaultProperty("SelectedIndex")]
    [Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)] // cannot be read & localized as string 
    public abstract class Selector : ItemsControl
    {
        //-------------------------------------------------------------------
        // 
        //  Constructors
        // 
        //------------------------------------------------------------------- 

        #region Constructors 

        /// 
        ///     Default Selector constructor.
        ///  
        protected Selector() : base()
        { 
            Items.CurrentChanged += new EventHandler(OnCurrentChanged); 
            ItemContainerGenerator.StatusChanged += new EventHandler(OnGeneratorStatusChanged);
 
            _focusEnterMainFocusScopeEventHandler = new EventHandler(OnFocusEnterMainFocusScope);
            KeyboardNavigation.Current.FocusEnterMainFocusScope += _focusEnterMainFocusScopeEventHandler;

            ObservableCollection selectedItems = new SelectedItemCollection(this); 
            SetValue(SelectedItemsPropertyKey, selectedItems);
            selectedItems.CollectionChanged += new NotifyCollectionChangedEventHandler(OnSelectedItemsCollectionChanged); 
 
            // to prevent this inherited property from bleeding into nested selectors, set this locally to
            // false at construction time 
            SetValue(IsSelectionActivePropertyKey, BooleanBoxes.FalseBox);
        }

        static Selector() 
        {
            EventManager.RegisterClassHandler(typeof(Selector), Selector.SelectedEvent, new RoutedEventHandler(Selector.OnSelected)); 
            EventManager.RegisterClassHandler(typeof(Selector), Selector.UnselectedEvent, new RoutedEventHandler(Selector.OnUnselected)); 
        }
 
        #endregion

        //--------------------------------------------------------------------
        // 
        //  Public Events
        // 
        //------------------------------------------------------------------- 

        #region Public Events 

        /// 
        ///     An event fired when the selection changes.
        ///  
        public static readonly RoutedEvent SelectionChangedEvent = EventManager.RegisterRoutedEvent(
            "SelectionChanged", RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(Selector)); 
 
        /// 
        ///     An event fired when the selection changes. 
        /// 
        [Category("Behavior")]
        public event SelectionChangedEventHandler SelectionChanged
        { 
            add { AddHandler(SelectionChangedEvent, value); }
            remove { RemoveHandler(SelectionChangedEvent, value); } 
        } 

        ///  
        ///     An event fired by UI children when the IsSelected property changes to true.
        ///     For listening to selection state changes use  instead.
        /// 
        public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent( 
            "Selected", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Selector));
 
        ///  
        ///     Adds a handler for the SelectedEvent attached event
        ///     For listening to selection state changes use  instead. 
        /// 
        /// UIElement or ContentElement that listens to this event
        /// Event Handler to be added
        public static void AddSelectedHandler(DependencyObject element, RoutedEventHandler handler) 
        {
            FrameworkElement.AddHandler(element, SelectedEvent, handler); 
        } 

        ///  
        ///     Removes a handler for the SelectedEvent attached event
        ///     For listening to selection state changes use  instead.
        /// 
        /// UIElement or ContentElement that listens to this event 
        /// Event Handler to be removed
        public static void RemoveSelectedHandler(DependencyObject element, RoutedEventHandler handler) 
        { 
            FrameworkElement.RemoveHandler(element, SelectedEvent, handler);
        } 

        /// 
        ///     An event fired by UI children when the IsSelected property changes to false.
        ///     For listening to selection state changes use  instead. 
        /// 
        public static readonly RoutedEvent UnselectedEvent = EventManager.RegisterRoutedEvent( 
            "Unselected", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Selector)); 

        ///  
        ///     Adds a handler for the UnselectedEvent attached event
        ///     For listening to selection state changes use  instead.
        /// 
        /// UIElement or ContentElement that listens to this event 
        /// Event Handler to be added
        public static void AddUnselectedHandler(DependencyObject element, RoutedEventHandler handler) 
        { 
            FrameworkElement.AddHandler(element, UnselectedEvent, handler);
        } 

        /// 
        ///     Removes a handler for the UnselectedEvent attached event
        ///     For listening to selection state changes use  instead. 
        /// 
        /// UIElement or ContentElement that listens to this event 
        /// Event Handler to be removed 
        public static void RemoveUnselectedHandler(DependencyObject element, RoutedEventHandler handler)
        { 
            FrameworkElement.RemoveHandler(element, UnselectedEvent, handler);
        }

        #endregion 

        //-------------------------------------------------------------------- 
        // 
        //  Public Properties
        // 
        //--------------------------------------------------------------------

        #region Public Properties
 
        // -----------------------------------------------------------------
        //  Attached Properties 
        // ------------------------------------------------------------------ 

        ///  
        ///     Property key for IsSelectionActiveProperty.
        /// 
        internal static readonly DependencyPropertyKey IsSelectionActivePropertyKey =
                DependencyProperty.RegisterAttachedReadOnly( 
                        "IsSelectionActive",
                        typeof(bool), 
                        typeof(Selector), 
                        new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits));
 
        /// 
        ///     Indicates whether the keyboard focus is within the Selector.
        /// In case when focus goes to Menu/Toolbar then selection is active too.
        ///  
        public static readonly DependencyProperty IsSelectionActiveProperty =
            IsSelectionActivePropertyKey.DependencyProperty; 
 
        /// 
        ///     Get IsSelectionActive property 
        /// 
        /// 
        /// 
        public static bool GetIsSelectionActive(DependencyObject element) 
        {
            if (element == null) 
            { 
                throw new ArgumentNullException("element");
            } 
            return (bool) element.GetValue(IsSelectionActiveProperty);
        }

        ///  
        ///     Specifies whether a UI container for an item in a Selector should appear selected.
        ///  
        public static readonly DependencyProperty IsSelectedProperty = 
                DependencyProperty.RegisterAttached(
                        "IsSelected", 
                        typeof(bool),
                        typeof(Selector),
                        new FrameworkPropertyMetadata(
                                BooleanBoxes.FalseBox, 
                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
 
        ///  
        ///     Retrieves the value of the attached property.
        ///  
        /// The DependencyObject on which to query the property.
        /// The value of the attached property.
        [AttachedPropertyBrowsableForChildren()]
        public static bool GetIsSelected(DependencyObject element) 
        {
            if (element == null) 
            { 
                throw new ArgumentNullException("element");
            } 

            return (bool) element.GetValue(IsSelectedProperty);
        }
 

        ///  
        ///     Sets the value of the attached property. 
        /// 
        /// The DependencyObject on which to set the property. 
        /// The new value of the attached property.
        public static void SetIsSelected(DependencyObject element, bool isSelected)
        {
            if (element == null) 
            {
                throw new ArgumentNullException("element"); 
            } 

            element.SetValue(IsSelectedProperty, BooleanBoxes.Box(isSelected)); 
        }

        // -----------------------------------------------------------------
        //  Direct Properties 
        // -----------------------------------------------------------------
 
        ///  
        /// Whether this Selector should keep SelectedItem in [....] with the ItemCollection's current item.
        ///  
        public static readonly DependencyProperty IsSynchronizedWithCurrentItemProperty =
                DependencyProperty.Register(
                        "IsSynchronizedWithCurrentItem",
                        typeof(bool?), 
                        typeof(Selector),
                        new FrameworkPropertyMetadata( 
                                (bool?)null, 
                                new PropertyChangedCallback(OnIsSynchronizedWithCurrentItemChanged)));
 
        /// 
        /// Whether this Selector should keep SelectedItem in [....] with the ItemCollection's current item.
        /// 
        [Bindable(true), Category("Behavior")] 
        [TypeConverter("System.Windows.NullableBoolConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")]
        [Localizability(LocalizationCategory.NeverLocalize)] // not localizable 
        public bool? IsSynchronizedWithCurrentItem 
        {
            get { return (bool?) GetValue(IsSynchronizedWithCurrentItemProperty); } 
            set { SetValue(IsSynchronizedWithCurrentItemProperty, value); }
        }

        private static void OnIsSynchronizedWithCurrentItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
        {
            Selector s = (Selector)d; 
            s.SetSynchronizationWithCurrentItem(); 
        }
 
        private void SetSynchronizationWithCurrentItem()
        {
            bool? isSynchronizedWithCurrentItem = IsSynchronizedWithCurrentItem;
            bool oldSync = IsSynchronizedWithCurrentItemPrivate; 
            bool newSync;
 
            if (isSynchronizedWithCurrentItem.HasValue) 
            {
                // if there's a value, use it 
                newSync = isSynchronizedWithCurrentItem.Value;
            }
            else
            { 
                // when the value is null, synchronize iff selection mode is Single
                // and there's a non-default view. 
                SelectionMode mode = (SelectionMode)GetValue(ListBox.SelectionModeProperty); 
                newSync = (mode == SelectionMode.Single) &&
                            !CollectionViewSource.IsDefaultView(Items.CollectionView); 
            }

            IsSynchronizedWithCurrentItemPrivate = newSync;
 
            if (!oldSync && newSync)
            { 
                SetSelectedToCurrent(); 
            }
        } 

        /// 
        ///     SelectedIndex DependencyProperty
        ///  
        public static readonly DependencyProperty SelectedIndexProperty =
                DependencyProperty.Register( 
                        "SelectedIndex", 
                        typeof(int),
                        typeof(Selector), 
                        new FrameworkPropertyMetadata(
                                -1,
                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
                                new PropertyChangedCallback(OnSelectedIndexChanged), 
                                new CoerceValueCallback(CoerceSelectedIndex)),
                        new ValidateValueCallback(ValidateSelectedIndex)); 
 
        /// 
        ///     The index of the first item in the current selection or -1 if the selection is empty. 
        /// 
        [Bindable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Localizability(LocalizationCategory.NeverLocalize)] // not localizable
        public int SelectedIndex 
        {
            get { return (int) GetValue(SelectedIndexProperty); } 
            set { SetValue(SelectedIndexProperty, value); } 
        }
 
        private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Selector s = (Selector) d;
 
            // If we're in the middle of a selection change, ignore all changes
            if (!s.SelectionChange.IsActive) 
            { 
                int newIndex = (int) e.NewValue;
                object item = (newIndex == -1) ? null : s.Items[newIndex]; 
                s.SelectionChange.SelectJustThisItem(item, true /* assumeInItemsCollection */);
            }
        }
 
        private static bool ValidateSelectedIndex(object o)
        { 
            return ((int) o) >= -1; 
        }
 
        private static object CoerceSelectedIndex(DependencyObject d, object value)
        {
            Selector s = (Selector) d;
            if ((value is int) && (int) value >= s.Items.Count) 
            {
                return DependencyProperty.UnsetValue; 
            } 

            return value; 
        }


 
        /// 
        ///     SelectedItem DependencyProperty 
        ///  
        public static readonly DependencyProperty SelectedItemProperty =
                DependencyProperty.Register( 
                        "SelectedItem",
                        typeof(object),
                        typeof(Selector),
                        new FrameworkPropertyMetadata( 
                                null,
                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 
                                new PropertyChangedCallback(OnSelectedItemChanged), 
                                new CoerceValueCallback(CoerceSelectedItem)));
 
        /// 
        ///  The first item in the current selection, or null if the selection is empty.
        /// 
        [Bindable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
        public object SelectedItem
        { 
            get { return GetValue(SelectedItemProperty); } 
            set { SetValue(SelectedItemProperty, value); }
        } 

        private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Selector s = (Selector) d; 

            if (!s.SelectionChange.IsActive) 
            { 
                s.SelectionChange.SelectJustThisItem(e.NewValue, false /* assumeInItemsCollection */);
            } 
        }

        private static object CoerceSelectedItem(DependencyObject d, object value)
        { 
            Selector s = (Selector) d;
            if (value == null || s.SkipCoerceSelectedItemCheck) 
                 return value; 

            int selectedIndex = s.SelectedIndex; 

            if ( (selectedIndex > -1 && selectedIndex < s.Items.Count && s.Items[selectedIndex] == value)
                || s.Items.Contains(value))
            { 
                return value;
            } 
 
            return DependencyProperty.UnsetValue;
        } 



        ///  
        ///     SelectedValue DependencyProperty
        ///  
        public static readonly DependencyProperty SelectedValueProperty = 
                DependencyProperty.Register(
                        "SelectedValue", 
                        typeof(object),
                        typeof(Selector),
                        new FrameworkPropertyMetadata(
                                null, 
                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                new PropertyChangedCallback(OnSelectedValueChanged), 
                                new CoerceValueCallback(CoerceSelectedValue))); 

        ///  
        ///  The value of the SelectedItem, obtained using the SelectedValuePath.
        /// 
        /// 
        /// 

Setting SelectedValue to some value x attempts to select an item whose /// "value" evaluates to x, using the current setting of . /// If no such item can be found, the selection is cleared.

/// ///

Getting the value of SelectedValue returns the "value" of the , /// using the current setting of , or null /// if there is no selection.

/// ///

Note that these rules imply that getting SelectedValue immediately after /// setting it to x will not necessarily return x. It might return null, /// if no item with value x can be found.

///
[Bindable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Localizability(LocalizationCategory.NeverLocalize)] // not localizable public object SelectedValue { get { return GetValue(SelectedValueProperty); } set { SetValue(SelectedValueProperty, value); } } /// /// This could happen when SelectedValuePath has changed, /// SelectedItem has changed, or someone is setting SelectedValue. /// private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { } // Select an item whose value matches the given value private object SelectItemWithValue(object value) { _cacheValid[(int)CacheBits.SelectedValueDrivesSelection] = true; // look through the items for one whose value matches the given value object item = FindItemWithValue(value); // We can assume it's in the collection because we just searched // through the collection to find it. SelectionChange.SelectJustThisItem(item, true /* assumeInItemsCollection */); // if there are no items, protect SelectedValue from being overwritten // until items show up. This enables a SelectedValue set from markup // to set the initial selection when the items eventually appear. // Otherwise, allow SelectedValue to follow SelectedItem. if (item != DependencyProperty.UnsetValue || HasItems) { _cacheValid[(int)CacheBits.SelectedValueDrivesSelection] = false; } return item; } private object FindItemWithValue(object value) { if (!HasItems) return DependencyProperty.UnsetValue; // use first item to determine which kind of binding to use (XML vs. CLR) BindingExpression bindingExpr = PrepareItemValueBinding(Items[0]); // optimize for case where there is no SelectedValuePath (meaning // that the value of the item is the item itself, or the InnerText // of the item) if (string.IsNullOrEmpty(SelectedValuePath)) { // when there's no SelectedValuePath, the binding's Path // is either empty (CLR) or "/InnerText" (XML) string path = bindingExpr.ParentBinding.Path.Path; Debug.Assert(String.IsNullOrEmpty(path) || path == "/InnerText"); if (string.IsNullOrEmpty(path)) { // CLR - item is its own selected value if (Items.Contains(value)) return value; else return DependencyProperty.UnsetValue; } else { // XML - use the InnerText as the selected value return FindXmlNodeWithInnerText(value); } } Type selectedType = (value != null) ? value.GetType() : null; object selectedValue = value; DynamicValueConverter converter = new DynamicValueConverter(false); foreach (object current in Items) { bindingExpr.Activate(current); object itemValue = bindingExpr.Value; if (VerifyEqual(value, selectedType, itemValue, converter)) { bindingExpr.Deactivate(); return current; } } bindingExpr.Deactivate(); return DependencyProperty.UnsetValue; } private bool VerifyEqual(object knownValue, Type knownType, object itemValue, DynamicValueConverter converter) { object tempValue = knownValue; if (knownType != null && itemValue != null) { Type itemType = itemValue.GetType(); // determine if selectedValue is comparable to itemValue, convert if necessary // using a DefaultValueConverter if (!knownType.IsAssignableFrom(itemType)) { tempValue = converter.Convert(knownValue, itemType); if (tempValue == DependencyProperty.UnsetValue) { // can't convert, keep original value for the following object comparison tempValue = knownValue; } } } return Object.Equals(tempValue, itemValue); } // separate function to avoid loading System.Xml until we have a good reason [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private object FindXmlNodeWithInnerText(object innerText) { string innerTextString = innerText as string; if (innerTextString != null) { foreach (object item in Items) { XmlNode node = item as XmlNode; if (node != null && node.InnerText == innerTextString) return node; } } return DependencyProperty.UnsetValue; } private static object CoerceSelectedValue(DependencyObject d, object value) { Selector s = (Selector)d; if (s.SelectionChange.IsActive) { // If we're in the middle of a selection change, accept the value s._cacheValid[(int)CacheBits.SelectedValueDrivesSelection] = false; } else { // Otherwise, this is a user-initiated change to SelectedValue. // Find the corresponding item. object item = s.SelectItemWithValue(value); // if the search fails, coerce the value to null. Unless there // are no items at all, in which case wait for the items to appear // and search again. if (item == DependencyProperty.UnsetValue && s.HasItems) { value = null; } } return value; } /// /// SelectedValuePath DependencyProperty /// public static readonly DependencyProperty SelectedValuePathProperty = DependencyProperty.Register( "SelectedValuePath", typeof(string), typeof(Selector), new FrameworkPropertyMetadata( String.Empty, new PropertyChangedCallback(OnSelectedValuePathChanged))); /// /// The path used to retrieve the SelectedValue from the SelectedItem /// [Bindable(true), Category("Appearance")] [Localizability(LocalizationCategory.NeverLocalize)] // not localizable public string SelectedValuePath { get { return (string) GetValue(SelectedValuePathProperty); } set { SetValue(SelectedValuePathProperty, value); } } private static void OnSelectedValuePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Selector s = (Selector)d; // discard the current ItemValue binding ItemValueBindingExpression.ClearValue(s); // select the corresponding item if (s.SelectedValue != null) { s.SelectItemWithValue(s.SelectedValue); } } /// /// Prepare the binding on the ItemValue property, creating it if necessary. /// Use the item to decide what kind of binding (XML vs. CLR) to use. /// /// private BindingExpression PrepareItemValueBinding(object item) { if (item == null) return null; Binding binding; bool useXml = XmlHelper.IsXmlNode(item); BindingExpression bindingExpr = ItemValueBindingExpression.GetValue(this); // replace existing binding if it's the wrong kind if (bindingExpr != null) { binding = bindingExpr.ParentBinding; bool usesXml = (binding.XPath != null); if ((!usesXml && useXml) || (usesXml && !useXml)) { ItemValueBindingExpression.ClearValue(this); bindingExpr = null; } } if (bindingExpr == null) { // create the binding binding = new Binding(); // Set source to null so binding does not use ambient DataContext binding.Source = null; if (useXml) { binding.XPath = SelectedValuePath; binding.Path = new PropertyPath("/InnerText"); } else { binding.Path = new PropertyPath(SelectedValuePath); } bindingExpr = (BindingExpression)BindingExpression.CreateUntargetedBindingExpression(this, binding); ItemValueBindingExpression.SetValue(this, bindingExpr); } return bindingExpr; } /// /// The key needed set a read-only property. /// private static readonly DependencyPropertyKey SelectedItemsPropertyKey = DependencyProperty.RegisterReadOnly( "SelectedItems", typeof(IList), typeof(Selector), new FrameworkPropertyMetadata( (IList) null)); /// /// A read-only IList containing the currently selected items /// internal static readonly DependencyProperty SelectedItemsImplProperty = SelectedItemsPropertyKey.DependencyProperty; /// /// The currently selected items. /// internal IList SelectedItemsImpl { get { return (IList)GetValue(SelectedItemsImplProperty); } } /// /// Select multiple items. /// /// Collection of items to be selected. /// true if all items have been selected. internal bool SetSelectedItemsImpl(IEnumerable selectedItems) { bool succeeded = false; if (!SelectionChange.IsActive) { SelectionChange.Begin(); SelectionChange.CleanupDeferSelection(); ObservableCollection oldSelectedItems = (ObservableCollection) GetValue(SelectedItemsImplProperty); try { // Unselect everything in oldSelectedItems. if (oldSelectedItems != null) { foreach (object currentlySelectedItem in oldSelectedItems) { SelectionChange.Unselect(currentlySelectedItem); } } if (selectedItems != null) { // Make sure that we can select every items. foreach (object item in selectedItems) { if (!SelectionChange.Select(item, false /* assumeInItemsCollection */)) { SelectionChange.Cancel(); return false; } } } SelectionChange.End(); succeeded = true; } finally { if (!succeeded) { SelectionChange.Cancel(); } } } return succeeded; } private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Ignore selection changes we're causing. if (SelectionChange.IsActive) { return; } if (!CanSelectMultiple) { throw new InvalidOperationException(SR.Get(SRID.ChangingCollectionNotSupported)); } SelectionChange.Begin(); bool succeeded=false; try { // get the affected item object item = null; switch (e.Action) { case NotifyCollectionChangedAction.Add: if (e.NewItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); item = e.NewItems[0]; break; case NotifyCollectionChangedAction.Remove: if (e.OldItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); item = e.OldItems[0]; break; case NotifyCollectionChangedAction.Reset: break; case NotifyCollectionChangedAction.Replace: if (e.NewItems.Count != 1 || e.OldItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); break; default: throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action)); } switch (e.Action) { case NotifyCollectionChangedAction.Add: { SelectionChange.Select(item, false /* assumeInItemsCollection */); break; } case NotifyCollectionChangedAction.Remove: { SelectionChange.Unselect(item); break; } case NotifyCollectionChangedAction.Reset: { SelectionChange.CleanupDeferSelection(); for (int i = 0; i < _selectedItems.Count; i++) { SelectionChange.Unselect(_selectedItems[i]); } ObservableCollection userSelectedItems = (ObservableCollection)sender; for (int i = 0; i < userSelectedItems.Count; i++) { SelectionChange.Select(userSelectedItems[i], false /* assumeInItemsCollection */); } break; } case NotifyCollectionChangedAction.Replace: { SelectionChange.Unselect(e.OldItems[0]); SelectionChange.Select(e.NewItems[0], false /* assumeInItemsCollection */); break; } } SelectionChange.End(); succeeded = true; } finally { if (!succeeded) { SelectionChange.Cancel(); } } } #endregion //------------------------------------------------------------------- // // Internal Properties // //-------------------------------------------------------------------- #region Internal Properties /// /// Whether this Selector can select more than one item at once /// internal bool CanSelectMultiple { get { return _cacheValid[(int)CacheBits.CanSelectMultiple]; } set { if (_cacheValid[(int)CacheBits.CanSelectMultiple] != value) { _cacheValid[(int)CacheBits.CanSelectMultiple] = value; if (!value && (_selectedItems.Count > 1)) { SelectionChange.Validate(); } } } } #endregion //------------------------------------------------------------------- // // Internal Methods // //-------------------------------------------------------------------- #region Internal Methods /// /// Clear the IsSelected property from containers that are no longer used. This is done for container recycling; /// If we ever reuse a container with a stale IsSelected value the UI will incorrectly display it as selected. /// protected override void ClearContainerForItemOverride(DependencyObject element, object item) { base.ClearContainerForItemOverride(element, item); element.ClearValue(IsSelectedProperty); } internal void RaiseIsSelectedChangedAutomationEvent(DependencyObject container, bool isSelected) { SelectorAutomationPeer selectorPeer = UIElementAutomationPeer.FromElement(this) as SelectorAutomationPeer; if (selectorPeer != null && selectorPeer.ItemPeers != null) { object item = GetItemOrContainerFromContainer(container); if (item != null) { SelectorItemAutomationPeer itemPeer = selectorPeer.ItemPeers[item] as SelectorItemAutomationPeer; if (itemPeer != null) itemPeer.RaiseAutomationIsSelectedChanged(isSelected); } } } internal void SetInitialMousePosition() { _lastMousePosition = Mouse.GetPosition(this); } // Tracks mouse movement. // Returns true if the mouse moved from the last time this method was called. internal bool DidMouseMove() { Point newPosition = Mouse.GetPosition(this); if (newPosition != _lastMousePosition) { _lastMousePosition = newPosition; return true; } return false; } internal void ResetLastMousePosition() { _lastMousePosition = new Point(); } /// /// Select all items in the collection. /// Assumes that CanSelectMultiple is true /// internal void SelectAllImpl() { Debug.Assert(CanSelectMultiple, "CanSelectMultiple should be true when calling SelectAllImpl"); SelectionChange.Begin(); SelectionChange.CleanupDeferSelection(); try { foreach (object current in Items) { SelectionChange.Select(current, true /* assumeInItemsCollection */); } } finally { SelectionChange.End(); } } /// /// Unselect all items in the collection. /// internal virtual void UnselectAllImpl() { SelectionChange.Begin(); SelectionChange.CleanupDeferSelection(); try { object selectedItem = InternalSelectedItem; foreach (object current in _selectedItems) { SelectionChange.Unselect(current); } } finally { SelectionChange.End(); } } #endregion //-------------------------------------------------------------------- // // Protected Methods // //------------------------------------------------------------------- #region Protected Methods /// /// Updates the current selection when Items has changed /// /// Information about what has changed protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { // When items become available, reevaluate the choice of algorithm // used by _selectedItems. if (e.Action == NotifyCollectionChangedAction.Reset || (e.Action == NotifyCollectionChangedAction.Add && e.NewStartingIndex == 0)) { ResetSelectedItemsAlgorithm(); } base.OnItemsChanged(e); // Do not coerce the SelectedIndexProperty if it holds a DeferredSelectedIndexReference // because this deferred reference object is guaranteed to produce a pre-coerced value. // Also if you did coerce it then you will lose the attempted performance optimization // because it will get dereferenced immediately in order to supply a baseValue for coersion. EffectiveValueEntry entry = GetValueEntry( LookupEntry(SelectedIndexProperty.GlobalIndex), SelectedIndexProperty, null, RequestFlags.DeferredReferences); if (!entry.IsDeferredReference || !(entry.Value is DeferredSelectedIndexReference)) { CoerceValue(SelectedIndexProperty); } CoerceValue(SelectedItemProperty); if (_cacheValid[(int)CacheBits.SelectedValueDrivesSelection] && !Object.Equals(SelectedValue, InternalSelectedValue)) { // This sets the selection from SelectedValue when SelectedValue // was set prior to the arrival of any items to select, provided // that SelectedIndex or SelectedItem didn't already do it. SelectItemWithValue(SelectedValue); } switch (e.Action) { case NotifyCollectionChangedAction.Add: { SelectionChange.Begin(); try { object element = e.NewItems[0]; // If we added something, see if it was set be selected and [....]. if (ItemGetIsSelected(element)) { SelectionChange.Select(element, true /* assumeInItemsCollection */); } } finally { SelectionChange.End(); } break; } case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Replace: { SelectionChange.Begin(); try { // if they removed something in a selection, remove it. // When End() commits the changes it will update SelectedIndex. object element = e.OldItems[0]; if (_selectedItems.Contains(element)) { SelectionChange.Unselect(element); } } finally { // Here SelectedIndex will be fixed to point to the first thing in _selectedItems, so // the case of removing something before SelectedIndex is taken care of. SelectionChange.End(); } break; } case NotifyCollectionChangedAction.Move: { SelectionChange.Validate(); break; } case NotifyCollectionChangedAction.Reset: { // catastrophic update -- need to resynchronize everything. // If we remove all the items we clear the defered selection if (Items.IsEmpty) SelectionChange.CleanupDeferSelection(); // This is to support the MasterDetail scenario. // When the Items is refreshed, Items.Current could be the old selection for this view. if (Items.CurrentItem != null && IsSynchronizedWithCurrentItemPrivate == true) { // SetSelectedToCurrent(); } else { SelectionChange.Begin(); try { // Throw away all the things we don't have anymore // remove from _selectedItems anything not in Items. for (int i = 0; i < _selectedItems.Count; i++) { object item = _selectedItems[i]; if (!Items.Contains(item)) // PERF: potentially in need of optimization SelectionChange.Unselect(item); } // Select everything in Items that is selected but isn't in the _selectedItems. if (ItemsSource == null) { for (int i = 0; i < Items.Count; i++) { object item = Items[i]; // This only works for items that know they're selected: // items that are UI elements or items that have had their UI generated. if (IndexGetIsSelected(i, item)) { if (!_selectedItems.Contains(item)) { SelectionChange.Select(item, true /* assumeInItemsCollection */); } } } } } finally { SelectionChange.End(); } } break; } default: throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action)); } } /// /// A virtual function that is called when the selection is changed. Default behavior /// is to raise a SelectionChangedEvent /// /// The inputs for this event. Can be raised (default behavior) or processed /// in some other way. protected virtual void OnSelectionChanged(SelectionChangedEventArgs e) { RaiseEvent(e); } /// /// An event reporting that the IsKeyboardFocusWithin property changed. /// protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e) { base.OnIsKeyboardFocusWithinChanged(e); // When focus within changes we need to update the value of IsSelectionActive property // In case focus is within the selector then IsSelectionActive is true // In case focus is within the current visual root and one the Menu/Toolbar (or any element that does not track focus) // then IsSelectionActive is true // In all other cases IsSelectionActive is false bool isSelectionActive = false; if ((bool)e.NewValue) { isSelectionActive = true; } else { DependencyObject currentFocus = Keyboard.FocusedElement as DependencyObject; if (currentFocus != null) { UIElement root = KeyboardNavigation.GetVisualRoot(this) as UIElement; if (root != null && root.IsKeyboardFocusWithin) { if (FocusManager.GetFocusScope(currentFocus) != root) { isSelectionActive = true; } } } } if (isSelectionActive) { SetValue(IsSelectionActivePropertyKey, BooleanBoxes.TrueBox); } else { SetValue(IsSelectionActivePropertyKey, BooleanBoxes.FalseBox); } } private void OnFocusEnterMainFocusScope(object sender, EventArgs e) { // When KeyboardFocus comes back to the main focus scope and the Selector does not have focus within - clear IsSelectionActivePrivateProperty if (!IsKeyboardFocusWithin) { ClearValue(IsSelectionActivePropertyKey); } } /// /// Called when the value of ItemsSource changes. /// protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { SetSynchronizationWithCurrentItem(); } #endregion //-------------------------------------------------------------------- // // Implementation // //------------------------------------------------------------------- #region Implementation // used to retrieve the value of an item, according to the SelectedValuePath private static readonly BindingExpressionUncommonField ItemValueBindingExpression = new BindingExpressionUncommonField(); // True if we're really synchronizing selection and current item private bool IsSynchronizedWithCurrentItemPrivate { get { return _cacheValid[(int)CacheBits.IsSynchronizedWithCurrentItem]; } set { _cacheValid[(int)CacheBits.IsSynchronizedWithCurrentItem] = value; } } private bool SkipCoerceSelectedItemCheck { get { return _cacheValid[(int)CacheBits.SkipCoerceSelectedItemCheck]; } set { _cacheValid[(int)CacheBits.SkipCoerceSelectedItemCheck] = value; } } #endregion #region Private Methods /// /// Adds/Removes the given item to the collection. Assumes the item is in the collection. /// private void SetSelectedHelper(object item, FrameworkElement UI, bool selected) { Debug.Assert(!SelectionChange.IsActive, "SelectionChange is already active -- use SelectionChange.Select or Unselect"); bool selectable; selectable = ItemGetIsSelectable(item); if (selectable == false && selected) { throw new InvalidOperationException(SR.Get(SRID.CannotSelectNotSelectableItem)); } SelectionChange.Begin(); try { if (selected) { SelectionChange.Select(item, true /* assumeInItemsCollection */); } else { SelectionChange.Unselect(item); } } finally { SelectionChange.End(); } } private void OnCurrentChanged(object sender, EventArgs e) { // if (IsSynchronizedWithCurrentItemPrivate) SetSelectedToCurrent(); } private void OnGeneratorStatusChanged(object sender, EventArgs e) { if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { if (HasItems) { Debug.Assert(!((SelectedIndex >= 0) && (_selectedItems.Count == 0)), "SelectedIndex >= 0 implies _selectedItems nonempty"); SelectionChange.Begin(); try { // Things could have been added to _selectedItems before the containers were generated, so now push // the IsSelected state down onto those items. for (int i = 0; i < _selectedItems.Count; i++) { // This could send messages back from the children, but we will ignore them b/c the selectionchange is active. ItemSetIsSelected(_selectedItems[i], true); } } finally { SelectionChange.Cancel(); } } } } private void SetSelectedToCurrent() { Debug.Assert(IsSynchronizedWithCurrentItemPrivate); if (!_cacheValid[(int)CacheBits.SyncingSelectionAndCurrency]) { _cacheValid[(int)CacheBits.SyncingSelectionAndCurrency] = true; try { object item = Items.CurrentItem; if (item != null && ItemGetIsSelectable(item)) { SelectionChange.SelectJustThisItem(item, true /* assumeInItemsCollection */); } else { // Select nothing if Currency is not set. SelectionChange.SelectJustThisItem(null, false); } } finally { _cacheValid[(int)CacheBits.SyncingSelectionAndCurrency] = false; } } } private void SetCurrentToSelected() { Debug.Assert(IsSynchronizedWithCurrentItemPrivate); if (!_cacheValid[(int)CacheBits.SyncingSelectionAndCurrency]) { _cacheValid[(int)CacheBits.SyncingSelectionAndCurrency] = true; try { if (_selectedItems.Count == 0) { // this avoid treating null as an item Items.MoveCurrentToPosition(-1); } else { Items.MoveCurrentTo(InternalSelectedItem); } } finally { _cacheValid[(int)CacheBits.SyncingSelectionAndCurrency] = false; } } } private void UpdateSelectedItems() { // Update SelectedItems. We don't want to invalidate the property // because that defeats the ability of bindings to be able to listen // for collection changes on that collection. Instead we just want // to add all the items which are not already in the collection. // Note: This is currently only called from SelectionChanger where SC.IsActive will be true. // If this is ever called from another location, ensure that SC.IsActive is true. Debug.Assert(SelectionChange.IsActive, "SelectionChange.IsActive should be true"); IList userSelectedItems = SelectedItemsImpl; if (userSelectedItems != null) { InternalSelectedItemsStorage userSelectedItemsTable = new InternalSelectedItemsStorage(userSelectedItems.Count); userSelectedItemsTable.UsesItemHashCodes = _selectedItems.UsesItemHashCodes; for (int i = 0; i < userSelectedItems.Count; i++) { object userSelectedItem = userSelectedItems[i]; if (_selectedItems.Contains(userSelectedItem) && !userSelectedItemsTable.Contains(userSelectedItem)) { // cache the user's selected items into a table with O(1) lookup. userSelectedItemsTable.Add(userSelectedItem); } else { // Remove each thing that is in SelectedItems that's not in _selectedItems. // Remove all duplicate items from userSelectedItems userSelectedItems.RemoveAt(i); i--; } } // Add to SelectedItems everything missing that's in _selectedItems. foreach (object selectedItem in _selectedItems) { if (!userSelectedItemsTable.Contains(selectedItem)) { userSelectedItems.Add(selectedItem); } } } } // called by SelectionChanger internal void UpdatePublicSelectionProperties() { EffectiveValueEntry entry = GetValueEntry( LookupEntry(SelectedIndexProperty.GlobalIndex), SelectedIndexProperty, null, RequestFlags.DeferredReferences); object selectedIndexValue = entry.Value; if (!entry.IsDeferredReference) { // these are important checks to make before calling SetValue -- they // ensure that we are not going to clobber a coerced value int selectedIndex = (int)selectedIndexValue; if ((selectedIndex > Items.Count - 1) || (selectedIndex == -1 && _selectedItems.Count > 0) || (selectedIndex > -1 && (_selectedItems.Count == 0 || Items[selectedIndex] != _selectedItems[0]))) { // Use a DeferredSelectedIndexReference instead of calculating the new // value now for better performance. Most of the time no // one cares what the new is, and calculating InternalSelectedIndex // be expensive because Items.IndexOf call SetDeferredValue(SelectedIndexProperty, new DeferredSelectedIndexReference(this)); } } if (SelectedItem != InternalSelectedItem) { try { // We know that InternalSelectedItem is a correct value for SelectedItemProperty and // should skip the coerce callback because it is expensive to call IndexOf and Contains SkipCoerceSelectedItemCheck = true; SetValue(SelectedItemProperty, InternalSelectedItem); } finally { SkipCoerceSelectedItemCheck = false; } } if (!_cacheValid[(int)CacheBits.SelectedValueDrivesSelection]) { object desiredSelectedValue = InternalSelectedValue; if (desiredSelectedValue == DependencyProperty.UnsetValue) { desiredSelectedValue = null; } if (!Object.Equals(SelectedValue, desiredSelectedValue)) { SetValue(SelectedValueProperty, desiredSelectedValue); } } UpdateSelectedItems(); } /// /// Raise the SelectionChanged event. /// private void InvokeSelectionChanged(List unselectedItems, List selectedItems) { SelectionChangedEventArgs selectionChanged = new SelectionChangedEventArgs(unselectedItems, selectedItems); selectionChanged.Source=this; OnSelectionChanged(selectionChanged); } /// /// Returns true if FrameworkElement representing this item has Selector.IsSelectedProperty set to true. /// /// /// private bool ItemGetIsSelected(object item) { if (item == null) return false; return ContainerGetIsSelected(ItemContainerGenerator.ContainerFromItem(item), item); } /// /// Returns true if FrameworkElement representing the item at the given index /// has Selector.IsSelectedProperty set to true. /// /// /// /// private bool IndexGetIsSelected(int index, object item) { return ContainerGetIsSelected(ItemContainerGenerator.ContainerFromIndex(index), item); } /// /// Returns true if FrameworkElement (container) representing the item /// has Selector.IsSelectedProperty set to true. /// /// /// /// private static bool ContainerGetIsSelected(DependencyObject container, object item) { if (container != null) { return (bool)container.GetValue(Selector.IsSelectedProperty); } // In the case where the elements added *are* the containers, read it off the item could work too // DependencyObject element = item as DependencyObject; if (element != null) { return (bool)element.GetValue(Selector.IsSelectedProperty); } return false; } /// /// Set an Item to be selected. Sets Selector.IsSelectedProperty on the FrameworkElement representing the item. /// /// /// private void ItemSetIsSelected(object item, bool value) { if (item == null) return; DependencyObject container = ItemContainerGenerator.ContainerFromItem(item); if (container != null) { // First check that the value is different and then set it. if (GetIsSelected(container) != value) { container.SetValue(Selector.IsSelectedProperty, BooleanBoxes.Box(value)); } } else { // In the case where the elements added *are* the containers, set it on the item instead of doing nothing // DependencyObject element = item as DependencyObject; if (element != null) { if (GetIsSelected(element) != value) { element.SetValue(Selector.IsSelectedProperty, BooleanBoxes.Box(value)); } } } } /// /// Returns false if FrameworkElement representing this item has Selector.SelectableProperty set to false. True otherwise. /// /// /// internal static bool ItemGetIsSelectable(object item) { if (item != null) { return !(item is Separator); } return false; } internal static bool UiGetIsSelectable(DependencyObject o) { if (o != null) { if (!ItemGetIsSelectable(o)) { return false; } else { // Check the data item ItemsControl itemsControl = ItemsControl.ItemsControlFromItemContainer(o); if (itemsControl != null) { object data = itemsControl.ItemContainerGenerator.ItemFromContainer(o); if (data != o) { return ItemGetIsSelectable(data); } else { return true; } } } } return false; } private static void OnSelected(object sender, RoutedEventArgs e) { ((Selector)sender).NotifyIsSelectedChanged(e.OriginalSource as FrameworkElement, true, e); } private static void OnUnselected(object sender, RoutedEventArgs e) { ((Selector)sender).NotifyIsSelectedChanged(e.OriginalSource as FrameworkElement, false, e); } /// /// Called by handlers of Selected/Unselected or CheckedChanged events to indicate that the selection state /// on the item has changed and selector needs to update accordingly. /// /// /// /// /// internal void NotifyIsSelectedChanged(FrameworkElement container, bool selected, RoutedEventArgs e) { // The selectionchanged event will fire at the end of the selection change. // We are here because this change was requested within the SelectionChange. // If there isn't a selection change going on now, we should do a SelectionChange. if (!SelectionChange.IsActive) { if (container != null) { object item = GetItemOrContainerFromContainer(container); if (item != DependencyProperty.UnsetValue) { SetSelectedHelper(item, container, selected); e.Handled = true; } } } else { // We cause this property to change, so mark it as handled e.Handled = true; } } /// /// Allows batch processing of selection changes so that only one SelectionChanged event is fired and /// SelectedIndex is changed only once (if necessary). /// internal SelectionChanger SelectionChange { get { if (_selectionChangeInstance == null) { _selectionChangeInstance = new SelectionChanger(this); } return _selectionChangeInstance; } } // use the first item to decide whether items support hashing correctly. // Reset the algorithm used by _selectedItems accordingly. void ResetSelectedItemsAlgorithm() { if (!Items.IsEmpty) { _selectedItems.UsesItemHashCodes = Items.CollectionView.HasReliableHashCodes(); } } #region Private Properties // /* // Journaling the selection state is more complex than just a property. // For one thing, the selection properties may never be referenced, and // thus they might not have a value in the local store. Second, one // property might not be sufficient (say, SelectedIndex) and another might // fail serialization (i.e. SelectedItems). With a DP that has a // ReadLocalValueOverride, it will be enumerated by the LocalValueEnumerator // and the value can have custom serialization logic. private static readonly DependencyProperty PrivateJournaledSelectionProperty = DependencyProperty.Register("PrivateJournaledSelection", typeof(object), typeof(Selector), PrivateJournaledSelectionPropertyMetadata); private static FrameworkPropertyMetadata PrivateJournaledSelectionPropertyMetadata { get { FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Journal); fpm.ReadLocalValueOverride = new ReadLocalValueOverride(ReadPrivateJournaledSelection); fpm.WriteLocalValueOverride = new WriteLocalValueOverride(WritePrivateJournaledSelection); return fpm; } } private static object ReadPrivateJournaledSelection(DependencyObject d) { // For now, just do what we were doing before -- journal SelectedIndex return ((Selector)d).InternalSelectedIndex; } private static void WritePrivateJournaledSelection(DependencyObject d, object value) { Selector s = (Selector)d; // Issue: This could throw an exception if things aren't set up in time. s.SelectedIndex = (int)value; } */ #endregion //------------------------------------------------------------------- // // Data // //------------------------------------------------------------------- #region Private Members // The selected items that we interact with. Most of the time when SelectedItems // is in use, this is identical to the value of the SelectedItems property, but // differs in type, and will differ in content in the case where you set or modify // SelectedItems and we need to switch our selection to what was just provided. // This is our internal representation of the selection and generally should be modified // only by SelectionChanger. Internal classes may read this for efficiency's sake // to avoid putting SelectedItems "in use" but we can't really expose this externally. internal InternalSelectedItemsStorage _selectedItems = new InternalSelectedItemsStorage(1); // Gets the selected item but doesn't use SelectedItem (avoids putting it "in use") internal object InternalSelectedItem { get { return (_selectedItems.Count == 0) ? null : _selectedItems[0]; } } /// /// Index of the first item in SelectedItems or (-1) if SelectedItems is empty. /// /// internal int InternalSelectedIndex { get { return (_selectedItems.Count == 0) ? -1 : Items.IndexOf(_selectedItems[0]); } } private object InternalSelectedValue { get { object item = InternalSelectedItem; object selectedValue; if (item != null) { BindingExpression bindingExpr = PrepareItemValueBinding(item); if (String.IsNullOrEmpty(SelectedValuePath)) { // when there's no SelectedValuePath, the binding's Path // is either empty (CLR) or "/InnerText" (XML) string path = bindingExpr.ParentBinding.Path.Path; Debug.Assert(String.IsNullOrEmpty(path) || path == "/InnerText"); if (string.IsNullOrEmpty(path)) { selectedValue = item; // CLR - the item is its own selected value } else { selectedValue = GetInnerText(item); // XML - use the InnerText as the selected value } } else { // apply the SelectedValuePath to the item bindingExpr.Activate(item); selectedValue = bindingExpr.Value; bindingExpr.Deactivate(); } } else { // no selected item - use UnsetValue (to distinguish from null, a legitimate value for the SVP) selectedValue = DependencyProperty.UnsetValue; } return selectedValue; } } // separate method to avoid loading System.Xml until necessary [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private object GetInnerText(object item) { XmlNode node = item as XmlNode; if (node != null) { return node.InnerText; } else { return null; } } // Used by ListBox and ComboBox to determine if the mouse actually entered the // List/ComboBoxItem before it focus which calls BringIntoView private Point _lastMousePosition = new Point(); // see comment on SelectionChange property private SelectionChanger _selectionChangeInstance; // Condense boolean bits. Constructor takes the default value, and will resize to access up to 32 bits. private BitVector32 _cacheValid = new BitVector32((int)CacheBits.CanSelectMultiple); [Flags] private enum CacheBits { // This flag is true while syncing the selection and the currency. It // is used to avoid reentrancy: e.g. when the currency changes we want // to change the selection accordingly, but that selection change should // not try to change currency. SyncingSelectionAndCurrency = 0x00000001, CanSelectMultiple = 0x00000002, IsSynchronizedWithCurrentItem = 0x00000004, SkipCoerceSelectedItemCheck = 0x00000008, SelectedValueDrivesSelection = 0x00000010, } private EventHandler _focusEnterMainFocusScopeEventHandler; #endregion #region Helper Classes #region SelectionChanger /// /// Helper class for selection change batching. /// internal class SelectionChanger { /// /// Create a new SelectionChangeHelper -- there should only be one instance per Selector. /// /// internal SelectionChanger(Selector s) { _owner = s; _active = false; _toSelect = new InternalSelectedItemsStorage(1); _toUnselect = new InternalSelectedItemsStorage(1); _toDeferSelect = new InternalSelectedItemsStorage(1); } /// /// True if there is a SelectionChange currently in progress. /// internal bool IsActive { get { return _active; } } /// /// Begin tracking selection changes. /// internal void Begin() { Debug.Assert(_owner.CheckAccess()); Debug.Assert(!_active, SR.Get(SRID.SelectionChangeActive)); _active = true; _toSelect.Clear(); _toUnselect.Clear(); } /// /// Commit selection changes. /// internal void End() { Debug.Assert(_owner.CheckAccess()); Debug.Assert(_active, "There must be a selection change active when you call SelectionChange.End()"); List unselected = new List(); List selected = new List(); // We might have been asked to make changes that will put us in an invalid state. Correct for this. try { ApplyCanSelectMultiple(); CreateDeltaSelectionChange(unselected, selected); _owner.UpdatePublicSelectionProperties(); } finally { // End the selection change -- IsActive will be false after this Cleanup(); } // only raise the event if there were actually any changes applied if (unselected.Count > 0 || selected.Count > 0) { // see bug 1459509: update Current AFTER selection change and before raising event if (_owner.IsSynchronizedWithCurrentItemPrivate) _owner.SetCurrentToSelected(); _owner.InvokeSelectionChanged(unselected, selected); } } private void ApplyCanSelectMultiple() { if (!_owner.CanSelectMultiple) { Debug.Assert(_toSelect.Count <= 1, "_toSelect.Count was > 1"); if (_toSelect.Count == 1) // this is all that should be selected, unselect _selectedItems { _toUnselect = new InternalSelectedItemsStorage(_owner._selectedItems); } else // _toSelect.Count == 0, and unselect all but one of _selectedItems { // This is when CanSelectMultiple changes from true to false. if (_owner._selectedItems.Count > 1 && _owner._selectedItems.Count != _toUnselect.Count + 1) { // they didn't deselect enough; force deselection object selectedItem = _owner._selectedItems[0]; _toUnselect.Clear(); foreach (object o in _owner._selectedItems) { if (o != selectedItem) { _toUnselect.Add(o); } } } } } } private void CreateDeltaSelectionChange(List unselectedItems, List selectedItems) { for (int i = 0; i < _toDeferSelect.Count; i++) { object o = _toDeferSelect[i]; // If defered selected item exis in Items - move it to _toSelect if (_owner.Items.Contains(o)) { _toSelect.Add(o); _toDeferSelect.Remove(o); i--; } } if (_toUnselect.Count > 0 || _toSelect.Count > 0) { if (_owner._selectedItems.UsesItemHashCodes) { // Step 1: Keep current selection temp storage InternalSelectedItemsStorage currentSelectedItems = new InternalSelectedItemsStorage(_owner._selectedItems); // Step 2: Clear the selection storage _owner._selectedItems.Clear(); // STep 3: Process the items that need to be unselected foreach (object o in _toUnselect) { _owner.ItemSetIsSelected(o, false); if (currentSelectedItems.Contains(o)) { unselectedItems.Add(o); } } // Step 4: Add back items from the temp storage that are not in _toUnselect foreach (object o in currentSelectedItems) { if (!_toUnselect.Contains(o)) { _owner._selectedItems.Add(o); } } } else { foreach (object o in _toUnselect) { _owner.ItemSetIsSelected(o, false); if (_owner._selectedItems.Contains(o)) { _owner._selectedItems.Remove(o); unselectedItems.Add(o); } } } foreach (object o in _toSelect) { _owner.ItemSetIsSelected(o, true); if (!_owner._selectedItems.Contains(o)) { _owner._selectedItems.Add(o); selectedItems.Add(o); } } } } #if never private void SynchronizeSelectedIndexToSelectedItem() { if (_owner._selectedItems.Count == 0) { _owner.SelectedIndex = -1; } else { object selectedItem = _owner.SelectedItem; object firstSelection = _owner._selectedItems[0]; // This check is only just to slightly improve perf by checking if it's in selected items before doing a reverse lookup if (selectedItem == null || firstSelection != selectedItem) { _owner.SelectedIndex = _owner.Items.IndexOf(firstSelection); } } } #endif /// /// Queue something to be added to the selection. Does nothing if the item is already selected. /// /// /// /// true if the Selection was queued internal bool Select(object o, bool assumeInItemsCollection) { Debug.Assert(_owner.CheckAccess()); Debug.Assert(_active, SR.Get(SRID.SelectionChangeNotActive)); Debug.Assert(o != null, "parameter o should not be null"); // Disallow selecting !IsSelectable things if (!ItemGetIsSelectable(o)) return false; // Disallow selecting things not in Items.FlatView if (!assumeInItemsCollection) { if (!_owner.Items.Contains(o)) { // If user selected item is not in the Items yet - defer the selection if (!_toDeferSelect.Contains(o)) _toDeferSelect.Add(o); return false; } } // To support Unselect(o) / Select(o) where o is already selected. if (_toUnselect.Remove(o)) { return true; } // Ignore if the item is already selected if (_owner._selectedItems.Contains(o)) return false; // Ignore if the item has already been requested to be selected. if (_toSelect.Contains(o)) return false; // enforce that we only select one thing in the CanSelectMultiple=false case. if (!_owner.CanSelectMultiple && _toSelect.Count > 0) { // If it was the item telling us this, turn around and set IsSelected = false // This will basically only happen in a Refresh situation where multiple items in the collection were selected but // CanSelectMultiple = false. foreach (object item in _toSelect) { _owner.ItemSetIsSelected(item, false); } _toSelect.Clear(); } _toSelect.Add(o); return true; } /// /// Queue something to be removed from the selection. Does nothing if the item is not already selected. /// /// /// true if the item was queued for unselection. internal bool Unselect(object o) { Debug.Assert(_owner.CheckAccess()); Debug.Assert(_active, SR.Get(SRID.SelectionChangeNotActive)); Debug.Assert(o != null, "parameter o should not be null"); _toDeferSelect.Remove(o); // To support Select(o) / Unselect(o) where o is not already selected. if (_toSelect.Remove(o)) { return true; } // Ignore if the item is not already selected if (!_owner._selectedItems.Contains(o)) return false; // Ignore if the item has already been queued for unselection. if (_toUnselect.Contains(o)) return false; _toUnselect.Add(o); return true; } /// /// Makes sure that the current selection is valid; Performs a SelectionChange it if it's not. /// internal void Validate() { Begin(); End(); } /// /// Cancels the currently active SelectionChange. /// internal void Cancel() { Debug.Assert(_owner.CheckAccess()); Cleanup(); } internal void CleanupDeferSelection() { if (_toDeferSelect.Count > 0) { _toDeferSelect.Clear(); } } internal void Cleanup() { _active = false; if (_toSelect.Count > 0) { _toSelect.Clear(); } if (_toUnselect.Count > 0) { _toUnselect.Clear(); } } /// /// Select just this item; all other items in SelectedItems will be removed. /// /// /// internal void SelectJustThisItem(object item, bool assumeInItemsCollection) { Begin(); CleanupDeferSelection(); try { // was this item already in the selection? bool isSelected = false; // go backwards in case a selection is rejected; then they'll still have the same SelectedItem for (int i = _owner._selectedItems.Count - 1; i >= 0; i--) { if (item != _owner._selectedItems[i]) { Unselect(_owner._selectedItems[i]); } else { isSelected = true; } } if (!isSelected && item != null && item != DependencyProperty.UnsetValue) { Select(item, assumeInItemsCollection); } } finally { End(); } } private Selector _owner; private InternalSelectedItemsStorage _toSelect; private InternalSelectedItemsStorage _toUnselect; private InternalSelectedItemsStorage _toDeferSelect; // Keep the items that cannot be selected because they are not in _owner.Items private bool _active; } #endregion #region InternalSelectedItemsStorage internal class InternalSelectedItemsStorage : IEnumerable { internal InternalSelectedItemsStorage(int capacity) { _list = new ArrayList(capacity); _set = new Dictionary(capacity); } internal InternalSelectedItemsStorage(InternalSelectedItemsStorage collection) { _list = new ArrayList(collection._list); if (collection.UsesItemHashCodes) { _set = new Dictionary(collection._set); } } public void Add(object t) { if (_set != null) { _set.Add(t, null); } _list.Add(t); } public bool Remove(object t) { if (_set != null) { if (_set.Remove(t)) { _list.Remove(t); return true; } } else { int index = _list.IndexOf(t); if (index >= 0) { _list.RemoveAt(index); return true; } } return false; } public bool Contains(object t) { if (_set != null) { return _set.ContainsKey(t); } else { return _list.Contains(t); } } public object this[int index] { get { return _list[index]; } } public void Clear() { _list.Clear(); if (_set != null) { _set.Clear(); } } public int Count { get { return _list.Count; } } IEnumerator IEnumerable.GetEnumerator() { return _list.GetEnumerator(); } // If the underlying items don't implement GetHashCode according to // guidelines (i.e. if an item's hashcode can change during the item's // lifetime) we can't use any hash-based data structures like Dictionary, // Hashtable, etc. The principal offender is DataRowView. (bug 1583080) public bool UsesItemHashCodes { get { return _set != null; } set { if (value == true && _set == null) { _set = new Dictionary(_list.Count); for (int i=0; i<_list.Count; ++i) { _set.Add(_list[i], null); } } else if (value == false) { _set = null; } } } private ArrayList _list; private Dictionary _set; } #endregion InternalSelectedItemsStorage #endregion #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System.ComponentModel; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Windows.Threading; using System.Windows.Data; using System.Windows; using System.Windows.Automation.Peers; using System.Windows.Automation.Provider; using System.Windows.Input; using System.Xml; using MS.Utility; using MS.Internal; using MS.Internal.Data; using MS.Internal.KnownBoxes; using MS.Internal.Hashing.PresentationFramework; // HashHelper using System; using System.Diagnostics; // Disable CS3001: Warning as Error: not CLS-compliant #pragma warning disable 3001 namespace System.Windows.Controls.Primitives { /// /// The base class for controls that select items from among their children /// [DefaultEvent("SelectionChanged"), DefaultProperty("SelectedIndex")] [Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)] // cannot be read & localized as string public abstract class Selector : ItemsControl { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors /// /// Default Selector constructor. /// protected Selector() : base() { Items.CurrentChanged += new EventHandler(OnCurrentChanged); ItemContainerGenerator.StatusChanged += new EventHandler(OnGeneratorStatusChanged); _focusEnterMainFocusScopeEventHandler = new EventHandler(OnFocusEnterMainFocusScope); KeyboardNavigation.Current.FocusEnterMainFocusScope += _focusEnterMainFocusScopeEventHandler; ObservableCollection selectedItems = new SelectedItemCollection(this); SetValue(SelectedItemsPropertyKey, selectedItems); selectedItems.CollectionChanged += new NotifyCollectionChangedEventHandler(OnSelectedItemsCollectionChanged); // to prevent this inherited property from bleeding into nested selectors, set this locally to // false at construction time SetValue(IsSelectionActivePropertyKey, BooleanBoxes.FalseBox); } static Selector() { EventManager.RegisterClassHandler(typeof(Selector), Selector.SelectedEvent, new RoutedEventHandler(Selector.OnSelected)); EventManager.RegisterClassHandler(typeof(Selector), Selector.UnselectedEvent, new RoutedEventHandler(Selector.OnUnselected)); } #endregion //-------------------------------------------------------------------- // // Public Events // //------------------------------------------------------------------- #region Public Events /// /// An event fired when the selection changes. /// public static readonly RoutedEvent SelectionChangedEvent = EventManager.RegisterRoutedEvent( "SelectionChanged", RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(Selector)); /// /// An event fired when the selection changes. /// [Category("Behavior")] public event SelectionChangedEventHandler SelectionChanged { add { AddHandler(SelectionChangedEvent, value); } remove { RemoveHandler(SelectionChangedEvent, value); } } /// /// An event fired by UI children when the IsSelected property changes to true. /// For listening to selection state changes use instead. /// public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent( "Selected", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Selector)); /// /// Adds a handler for the SelectedEvent attached event /// For listening to selection state changes use instead. /// /// UIElement or ContentElement that listens to this event /// Event Handler to be added public static void AddSelectedHandler(DependencyObject element, RoutedEventHandler handler) { FrameworkElement.AddHandler(element, SelectedEvent, handler); } /// /// Removes a handler for the SelectedEvent attached event /// For listening to selection state changes use instead. /// /// UIElement or ContentElement that listens to this event /// Event Handler to be removed public static void RemoveSelectedHandler(DependencyObject element, RoutedEventHandler handler) { FrameworkElement.RemoveHandler(element, SelectedEvent, handler); } /// /// An event fired by UI children when the IsSelected property changes to false. /// For listening to selection state changes use instead. /// public static readonly RoutedEvent UnselectedEvent = EventManager.RegisterRoutedEvent( "Unselected", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Selector)); /// /// Adds a handler for the UnselectedEvent attached event /// For listening to selection state changes use instead. /// /// UIElement or ContentElement that listens to this event /// Event Handler to be added public static void AddUnselectedHandler(DependencyObject element, RoutedEventHandler handler) { FrameworkElement.AddHandler(element, UnselectedEvent, handler); } /// /// Removes a handler for the UnselectedEvent attached event /// For listening to selection state changes use instead. /// /// UIElement or ContentElement that listens to this event /// Event Handler to be removed public static void RemoveUnselectedHandler(DependencyObject element, RoutedEventHandler handler) { FrameworkElement.RemoveHandler(element, UnselectedEvent, handler); } #endregion //-------------------------------------------------------------------- // // Public Properties // //-------------------------------------------------------------------- #region Public Properties // ----------------------------------------------------------------- // Attached Properties // ------------------------------------------------------------------ /// /// Property key for IsSelectionActiveProperty. /// internal static readonly DependencyPropertyKey IsSelectionActivePropertyKey = DependencyProperty.RegisterAttachedReadOnly( "IsSelectionActive", typeof(bool), typeof(Selector), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits)); /// /// Indicates whether the keyboard focus is within the Selector. /// In case when focus goes to Menu/Toolbar then selection is active too. /// public static readonly DependencyProperty IsSelectionActiveProperty = IsSelectionActivePropertyKey.DependencyProperty; /// /// Get IsSelectionActive property /// /// /// public static bool GetIsSelectionActive(DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return (bool) element.GetValue(IsSelectionActiveProperty); } /// /// Specifies whether a UI container for an item in a Selector should appear selected. /// public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached( "IsSelected", typeof(bool), typeof(Selector), new FrameworkPropertyMetadata( BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); /// /// Retrieves the value of the attached property. /// /// The DependencyObject on which to query the property. /// The value of the attached property. [AttachedPropertyBrowsableForChildren()] public static bool GetIsSelected(DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return (bool) element.GetValue(IsSelectedProperty); } /// /// Sets the value of the attached property. /// /// The DependencyObject on which to set the property. /// The new value of the attached property. public static void SetIsSelected(DependencyObject element, bool isSelected) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(IsSelectedProperty, BooleanBoxes.Box(isSelected)); } // ----------------------------------------------------------------- // Direct Properties // ----------------------------------------------------------------- /// /// Whether this Selector should keep SelectedItem in [....] with the ItemCollection's current item. /// public static readonly DependencyProperty IsSynchronizedWithCurrentItemProperty = DependencyProperty.Register( "IsSynchronizedWithCurrentItem", typeof(bool?), typeof(Selector), new FrameworkPropertyMetadata( (bool?)null, new PropertyChangedCallback(OnIsSynchronizedWithCurrentItemChanged))); /// /// Whether this Selector should keep SelectedItem in [....] with the ItemCollection's current item. /// [Bindable(true), Category("Behavior")] [TypeConverter("System.Windows.NullableBoolConverter, PresentationFramework, Version=" + Microsoft.Internal.BuildInfo.WCP_VERSION + ", Culture=neutral, PublicKeyToken=" + Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_TOKEN + ", Custom=null")] [Localizability(LocalizationCategory.NeverLocalize)] // not localizable public bool? IsSynchronizedWithCurrentItem { get { return (bool?) GetValue(IsSynchronizedWithCurrentItemProperty); } set { SetValue(IsSynchronizedWithCurrentItemProperty, value); } } private static void OnIsSynchronizedWithCurrentItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Selector s = (Selector)d; s.SetSynchronizationWithCurrentItem(); } private void SetSynchronizationWithCurrentItem() { bool? isSynchronizedWithCurrentItem = IsSynchronizedWithCurrentItem; bool oldSync = IsSynchronizedWithCurrentItemPrivate; bool newSync; if (isSynchronizedWithCurrentItem.HasValue) { // if there's a value, use it newSync = isSynchronizedWithCurrentItem.Value; } else { // when the value is null, synchronize iff selection mode is Single // and there's a non-default view. SelectionMode mode = (SelectionMode)GetValue(ListBox.SelectionModeProperty); newSync = (mode == SelectionMode.Single) && !CollectionViewSource.IsDefaultView(Items.CollectionView); } IsSynchronizedWithCurrentItemPrivate = newSync; if (!oldSync && newSync) { SetSelectedToCurrent(); } } /// /// SelectedIndex DependencyProperty /// public static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register( "SelectedIndex", typeof(int), typeof(Selector), new FrameworkPropertyMetadata( -1, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, new PropertyChangedCallback(OnSelectedIndexChanged), new CoerceValueCallback(CoerceSelectedIndex)), new ValidateValueCallback(ValidateSelectedIndex)); /// /// The index of the first item in the current selection or -1 if the selection is empty. /// [Bindable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Localizability(LocalizationCategory.NeverLocalize)] // not localizable public int SelectedIndex { get { return (int) GetValue(SelectedIndexProperty); } set { SetValue(SelectedIndexProperty, value); } } private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Selector s = (Selector) d; // If we're in the middle of a selection change, ignore all changes if (!s.SelectionChange.IsActive) { int newIndex = (int) e.NewValue; object item = (newIndex == -1) ? null : s.Items[newIndex]; s.SelectionChange.SelectJustThisItem(item, true /* assumeInItemsCollection */); } } private static bool ValidateSelectedIndex(object o) { return ((int) o) >= -1; } private static object CoerceSelectedIndex(DependencyObject d, object value) { Selector s = (Selector) d; if ((value is int) && (int) value >= s.Items.Count) { return DependencyProperty.UnsetValue; } return value; } /// /// SelectedItem DependencyProperty /// public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register( "SelectedItem", typeof(object), typeof(Selector), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnSelectedItemChanged), new CoerceValueCallback(CoerceSelectedItem))); /// /// The first item in the current selection, or null if the selection is empty. /// [Bindable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public object SelectedItem { get { return GetValue(SelectedItemProperty); } set { SetValue(SelectedItemProperty, value); } } private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Selector s = (Selector) d; if (!s.SelectionChange.IsActive) { s.SelectionChange.SelectJustThisItem(e.NewValue, false /* assumeInItemsCollection */); } } private static object CoerceSelectedItem(DependencyObject d, object value) { Selector s = (Selector) d; if (value == null || s.SkipCoerceSelectedItemCheck) return value; int selectedIndex = s.SelectedIndex; if ( (selectedIndex > -1 && selectedIndex < s.Items.Count && s.Items[selectedIndex] == value) || s.Items.Contains(value)) { return value; } return DependencyProperty.UnsetValue; } /// /// SelectedValue DependencyProperty /// public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register( "SelectedValue", typeof(object), typeof(Selector), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnSelectedValueChanged), new CoerceValueCallback(CoerceSelectedValue))); /// /// The value of the SelectedItem, obtained using the SelectedValuePath. /// /// ///

Setting SelectedValue to some value x attempts to select an item whose /// "value" evaluates to x, using the current setting of . /// If no such item can be found, the selection is cleared.

/// ///

Getting the value of SelectedValue returns the "value" of the , /// using the current setting of , or null /// if there is no selection.

/// ///

Note that these rules imply that getting SelectedValue immediately after /// setting it to x will not necessarily return x. It might return null, /// if no item with value x can be found.

///
[Bindable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Localizability(LocalizationCategory.NeverLocalize)] // not localizable public object SelectedValue { get { return GetValue(SelectedValueProperty); } set { SetValue(SelectedValueProperty, value); } } /// /// This could happen when SelectedValuePath has changed, /// SelectedItem has changed, or someone is setting SelectedValue. /// private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { } // Select an item whose value matches the given value private object SelectItemWithValue(object value) { _cacheValid[(int)CacheBits.SelectedValueDrivesSelection] = true; // look through the items for one whose value matches the given value object item = FindItemWithValue(value); // We can assume it's in the collection because we just searched // through the collection to find it. SelectionChange.SelectJustThisItem(item, true /* assumeInItemsCollection */); // if there are no items, protect SelectedValue from being overwritten // until items show up. This enables a SelectedValue set from markup // to set the initial selection when the items eventually appear. // Otherwise, allow SelectedValue to follow SelectedItem. if (item != DependencyProperty.UnsetValue || HasItems) { _cacheValid[(int)CacheBits.SelectedValueDrivesSelection] = false; } return item; } private object FindItemWithValue(object value) { if (!HasItems) return DependencyProperty.UnsetValue; // use first item to determine which kind of binding to use (XML vs. CLR) BindingExpression bindingExpr = PrepareItemValueBinding(Items[0]); // optimize for case where there is no SelectedValuePath (meaning // that the value of the item is the item itself, or the InnerText // of the item) if (string.IsNullOrEmpty(SelectedValuePath)) { // when there's no SelectedValuePath, the binding's Path // is either empty (CLR) or "/InnerText" (XML) string path = bindingExpr.ParentBinding.Path.Path; Debug.Assert(String.IsNullOrEmpty(path) || path == "/InnerText"); if (string.IsNullOrEmpty(path)) { // CLR - item is its own selected value if (Items.Contains(value)) return value; else return DependencyProperty.UnsetValue; } else { // XML - use the InnerText as the selected value return FindXmlNodeWithInnerText(value); } } Type selectedType = (value != null) ? value.GetType() : null; object selectedValue = value; DynamicValueConverter converter = new DynamicValueConverter(false); foreach (object current in Items) { bindingExpr.Activate(current); object itemValue = bindingExpr.Value; if (VerifyEqual(value, selectedType, itemValue, converter)) { bindingExpr.Deactivate(); return current; } } bindingExpr.Deactivate(); return DependencyProperty.UnsetValue; } private bool VerifyEqual(object knownValue, Type knownType, object itemValue, DynamicValueConverter converter) { object tempValue = knownValue; if (knownType != null && itemValue != null) { Type itemType = itemValue.GetType(); // determine if selectedValue is comparable to itemValue, convert if necessary // using a DefaultValueConverter if (!knownType.IsAssignableFrom(itemType)) { tempValue = converter.Convert(knownValue, itemType); if (tempValue == DependencyProperty.UnsetValue) { // can't convert, keep original value for the following object comparison tempValue = knownValue; } } } return Object.Equals(tempValue, itemValue); } // separate function to avoid loading System.Xml until we have a good reason [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private object FindXmlNodeWithInnerText(object innerText) { string innerTextString = innerText as string; if (innerTextString != null) { foreach (object item in Items) { XmlNode node = item as XmlNode; if (node != null && node.InnerText == innerTextString) return node; } } return DependencyProperty.UnsetValue; } private static object CoerceSelectedValue(DependencyObject d, object value) { Selector s = (Selector)d; if (s.SelectionChange.IsActive) { // If we're in the middle of a selection change, accept the value s._cacheValid[(int)CacheBits.SelectedValueDrivesSelection] = false; } else { // Otherwise, this is a user-initiated change to SelectedValue. // Find the corresponding item. object item = s.SelectItemWithValue(value); // if the search fails, coerce the value to null. Unless there // are no items at all, in which case wait for the items to appear // and search again. if (item == DependencyProperty.UnsetValue && s.HasItems) { value = null; } } return value; } /// /// SelectedValuePath DependencyProperty /// public static readonly DependencyProperty SelectedValuePathProperty = DependencyProperty.Register( "SelectedValuePath", typeof(string), typeof(Selector), new FrameworkPropertyMetadata( String.Empty, new PropertyChangedCallback(OnSelectedValuePathChanged))); /// /// The path used to retrieve the SelectedValue from the SelectedItem /// [Bindable(true), Category("Appearance")] [Localizability(LocalizationCategory.NeverLocalize)] // not localizable public string SelectedValuePath { get { return (string) GetValue(SelectedValuePathProperty); } set { SetValue(SelectedValuePathProperty, value); } } private static void OnSelectedValuePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Selector s = (Selector)d; // discard the current ItemValue binding ItemValueBindingExpression.ClearValue(s); // select the corresponding item if (s.SelectedValue != null) { s.SelectItemWithValue(s.SelectedValue); } } /// /// Prepare the binding on the ItemValue property, creating it if necessary. /// Use the item to decide what kind of binding (XML vs. CLR) to use. /// /// private BindingExpression PrepareItemValueBinding(object item) { if (item == null) return null; Binding binding; bool useXml = XmlHelper.IsXmlNode(item); BindingExpression bindingExpr = ItemValueBindingExpression.GetValue(this); // replace existing binding if it's the wrong kind if (bindingExpr != null) { binding = bindingExpr.ParentBinding; bool usesXml = (binding.XPath != null); if ((!usesXml && useXml) || (usesXml && !useXml)) { ItemValueBindingExpression.ClearValue(this); bindingExpr = null; } } if (bindingExpr == null) { // create the binding binding = new Binding(); // Set source to null so binding does not use ambient DataContext binding.Source = null; if (useXml) { binding.XPath = SelectedValuePath; binding.Path = new PropertyPath("/InnerText"); } else { binding.Path = new PropertyPath(SelectedValuePath); } bindingExpr = (BindingExpression)BindingExpression.CreateUntargetedBindingExpression(this, binding); ItemValueBindingExpression.SetValue(this, bindingExpr); } return bindingExpr; } /// /// The key needed set a read-only property. /// private static readonly DependencyPropertyKey SelectedItemsPropertyKey = DependencyProperty.RegisterReadOnly( "SelectedItems", typeof(IList), typeof(Selector), new FrameworkPropertyMetadata( (IList) null)); /// /// A read-only IList containing the currently selected items /// internal static readonly DependencyProperty SelectedItemsImplProperty = SelectedItemsPropertyKey.DependencyProperty; /// /// The currently selected items. /// internal IList SelectedItemsImpl { get { return (IList)GetValue(SelectedItemsImplProperty); } } /// /// Select multiple items. /// /// Collection of items to be selected. /// true if all items have been selected. internal bool SetSelectedItemsImpl(IEnumerable selectedItems) { bool succeeded = false; if (!SelectionChange.IsActive) { SelectionChange.Begin(); SelectionChange.CleanupDeferSelection(); ObservableCollection oldSelectedItems = (ObservableCollection) GetValue(SelectedItemsImplProperty); try { // Unselect everything in oldSelectedItems. if (oldSelectedItems != null) { foreach (object currentlySelectedItem in oldSelectedItems) { SelectionChange.Unselect(currentlySelectedItem); } } if (selectedItems != null) { // Make sure that we can select every items. foreach (object item in selectedItems) { if (!SelectionChange.Select(item, false /* assumeInItemsCollection */)) { SelectionChange.Cancel(); return false; } } } SelectionChange.End(); succeeded = true; } finally { if (!succeeded) { SelectionChange.Cancel(); } } } return succeeded; } private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Ignore selection changes we're causing. if (SelectionChange.IsActive) { return; } if (!CanSelectMultiple) { throw new InvalidOperationException(SR.Get(SRID.ChangingCollectionNotSupported)); } SelectionChange.Begin(); bool succeeded=false; try { // get the affected item object item = null; switch (e.Action) { case NotifyCollectionChangedAction.Add: if (e.NewItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); item = e.NewItems[0]; break; case NotifyCollectionChangedAction.Remove: if (e.OldItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); item = e.OldItems[0]; break; case NotifyCollectionChangedAction.Reset: break; case NotifyCollectionChangedAction.Replace: if (e.NewItems.Count != 1 || e.OldItems.Count != 1) throw new NotSupportedException(SR.Get(SRID.RangeActionsNotSupported)); break; default: throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action)); } switch (e.Action) { case NotifyCollectionChangedAction.Add: { SelectionChange.Select(item, false /* assumeInItemsCollection */); break; } case NotifyCollectionChangedAction.Remove: { SelectionChange.Unselect(item); break; } case NotifyCollectionChangedAction.Reset: { SelectionChange.CleanupDeferSelection(); for (int i = 0; i < _selectedItems.Count; i++) { SelectionChange.Unselect(_selectedItems[i]); } ObservableCollection userSelectedItems = (ObservableCollection)sender; for (int i = 0; i < userSelectedItems.Count; i++) { SelectionChange.Select(userSelectedItems[i], false /* assumeInItemsCollection */); } break; } case NotifyCollectionChangedAction.Replace: { SelectionChange.Unselect(e.OldItems[0]); SelectionChange.Select(e.NewItems[0], false /* assumeInItemsCollection */); break; } } SelectionChange.End(); succeeded = true; } finally { if (!succeeded) { SelectionChange.Cancel(); } } } #endregion //------------------------------------------------------------------- // // Internal Properties // //-------------------------------------------------------------------- #region Internal Properties /// /// Whether this Selector can select more than one item at once /// internal bool CanSelectMultiple { get { return _cacheValid[(int)CacheBits.CanSelectMultiple]; } set { if (_cacheValid[(int)CacheBits.CanSelectMultiple] != value) { _cacheValid[(int)CacheBits.CanSelectMultiple] = value; if (!value && (_selectedItems.Count > 1)) { SelectionChange.Validate(); } } } } #endregion //------------------------------------------------------------------- // // Internal Methods // //-------------------------------------------------------------------- #region Internal Methods /// /// Clear the IsSelected property from containers that are no longer used. This is done for container recycling; /// If we ever reuse a container with a stale IsSelected value the UI will incorrectly display it as selected. /// protected override void ClearContainerForItemOverride(DependencyObject element, object item) { base.ClearContainerForItemOverride(element, item); element.ClearValue(IsSelectedProperty); } internal void RaiseIsSelectedChangedAutomationEvent(DependencyObject container, bool isSelected) { SelectorAutomationPeer selectorPeer = UIElementAutomationPeer.FromElement(this) as SelectorAutomationPeer; if (selectorPeer != null && selectorPeer.ItemPeers != null) { object item = GetItemOrContainerFromContainer(container); if (item != null) { SelectorItemAutomationPeer itemPeer = selectorPeer.ItemPeers[item] as SelectorItemAutomationPeer; if (itemPeer != null) itemPeer.RaiseAutomationIsSelectedChanged(isSelected); } } } internal void SetInitialMousePosition() { _lastMousePosition = Mouse.GetPosition(this); } // Tracks mouse movement. // Returns true if the mouse moved from the last time this method was called. internal bool DidMouseMove() { Point newPosition = Mouse.GetPosition(this); if (newPosition != _lastMousePosition) { _lastMousePosition = newPosition; return true; } return false; } internal void ResetLastMousePosition() { _lastMousePosition = new Point(); } /// /// Select all items in the collection. /// Assumes that CanSelectMultiple is true /// internal void SelectAllImpl() { Debug.Assert(CanSelectMultiple, "CanSelectMultiple should be true when calling SelectAllImpl"); SelectionChange.Begin(); SelectionChange.CleanupDeferSelection(); try { foreach (object current in Items) { SelectionChange.Select(current, true /* assumeInItemsCollection */); } } finally { SelectionChange.End(); } } /// /// Unselect all items in the collection. /// internal virtual void UnselectAllImpl() { SelectionChange.Begin(); SelectionChange.CleanupDeferSelection(); try { object selectedItem = InternalSelectedItem; foreach (object current in _selectedItems) { SelectionChange.Unselect(current); } } finally { SelectionChange.End(); } } #endregion //-------------------------------------------------------------------- // // Protected Methods // //------------------------------------------------------------------- #region Protected Methods /// /// Updates the current selection when Items has changed /// /// Information about what has changed protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { // When items become available, reevaluate the choice of algorithm // used by _selectedItems. if (e.Action == NotifyCollectionChangedAction.Reset || (e.Action == NotifyCollectionChangedAction.Add && e.NewStartingIndex == 0)) { ResetSelectedItemsAlgorithm(); } base.OnItemsChanged(e); // Do not coerce the SelectedIndexProperty if it holds a DeferredSelectedIndexReference // because this deferred reference object is guaranteed to produce a pre-coerced value. // Also if you did coerce it then you will lose the attempted performance optimization // because it will get dereferenced immediately in order to supply a baseValue for coersion. EffectiveValueEntry entry = GetValueEntry( LookupEntry(SelectedIndexProperty.GlobalIndex), SelectedIndexProperty, null, RequestFlags.DeferredReferences); if (!entry.IsDeferredReference || !(entry.Value is DeferredSelectedIndexReference)) { CoerceValue(SelectedIndexProperty); } CoerceValue(SelectedItemProperty); if (_cacheValid[(int)CacheBits.SelectedValueDrivesSelection] && !Object.Equals(SelectedValue, InternalSelectedValue)) { // This sets the selection from SelectedValue when SelectedValue // was set prior to the arrival of any items to select, provided // that SelectedIndex or SelectedItem didn't already do it. SelectItemWithValue(SelectedValue); } switch (e.Action) { case NotifyCollectionChangedAction.Add: { SelectionChange.Begin(); try { object element = e.NewItems[0]; // If we added something, see if it was set be selected and [....]. if (ItemGetIsSelected(element)) { SelectionChange.Select(element, true /* assumeInItemsCollection */); } } finally { SelectionChange.End(); } break; } case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Replace: { SelectionChange.Begin(); try { // if they removed something in a selection, remove it. // When End() commits the changes it will update SelectedIndex. object element = e.OldItems[0]; if (_selectedItems.Contains(element)) { SelectionChange.Unselect(element); } } finally { // Here SelectedIndex will be fixed to point to the first thing in _selectedItems, so // the case of removing something before SelectedIndex is taken care of. SelectionChange.End(); } break; } case NotifyCollectionChangedAction.Move: { SelectionChange.Validate(); break; } case NotifyCollectionChangedAction.Reset: { // catastrophic update -- need to resynchronize everything. // If we remove all the items we clear the defered selection if (Items.IsEmpty) SelectionChange.CleanupDeferSelection(); // This is to support the MasterDetail scenario. // When the Items is refreshed, Items.Current could be the old selection for this view. if (Items.CurrentItem != null && IsSynchronizedWithCurrentItemPrivate == true) { // SetSelectedToCurrent(); } else { SelectionChange.Begin(); try { // Throw away all the things we don't have anymore // remove from _selectedItems anything not in Items. for (int i = 0; i < _selectedItems.Count; i++) { object item = _selectedItems[i]; if (!Items.Contains(item)) // PERF: potentially in need of optimization SelectionChange.Unselect(item); } // Select everything in Items that is selected but isn't in the _selectedItems. if (ItemsSource == null) { for (int i = 0; i < Items.Count; i++) { object item = Items[i]; // This only works for items that know they're selected: // items that are UI elements or items that have had their UI generated. if (IndexGetIsSelected(i, item)) { if (!_selectedItems.Contains(item)) { SelectionChange.Select(item, true /* assumeInItemsCollection */); } } } } } finally { SelectionChange.End(); } } break; } default: throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action)); } } /// /// A virtual function that is called when the selection is changed. Default behavior /// is to raise a SelectionChangedEvent /// /// The inputs for this event. Can be raised (default behavior) or processed /// in some other way. protected virtual void OnSelectionChanged(SelectionChangedEventArgs e) { RaiseEvent(e); } /// /// An event reporting that the IsKeyboardFocusWithin property changed. /// protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e) { base.OnIsKeyboardFocusWithinChanged(e); // When focus within changes we need to update the value of IsSelectionActive property // In case focus is within the selector then IsSelectionActive is true // In case focus is within the current visual root and one the Menu/Toolbar (or any element that does not track focus) // then IsSelectionActive is true // In all other cases IsSelectionActive is false bool isSelectionActive = false; if ((bool)e.NewValue) { isSelectionActive = true; } else { DependencyObject currentFocus = Keyboard.FocusedElement as DependencyObject; if (currentFocus != null) { UIElement root = KeyboardNavigation.GetVisualRoot(this) as UIElement; if (root != null && root.IsKeyboardFocusWithin) { if (FocusManager.GetFocusScope(currentFocus) != root) { isSelectionActive = true; } } } } if (isSelectionActive) { SetValue(IsSelectionActivePropertyKey, BooleanBoxes.TrueBox); } else { SetValue(IsSelectionActivePropertyKey, BooleanBoxes.FalseBox); } } private void OnFocusEnterMainFocusScope(object sender, EventArgs e) { // When KeyboardFocus comes back to the main focus scope and the Selector does not have focus within - clear IsSelectionActivePrivateProperty if (!IsKeyboardFocusWithin) { ClearValue(IsSelectionActivePropertyKey); } } /// /// Called when the value of ItemsSource changes. /// protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { SetSynchronizationWithCurrentItem(); } #endregion //-------------------------------------------------------------------- // // Implementation // //------------------------------------------------------------------- #region Implementation // used to retrieve the value of an item, according to the SelectedValuePath private static readonly BindingExpressionUncommonField ItemValueBindingExpression = new BindingExpressionUncommonField(); // True if we're really synchronizing selection and current item private bool IsSynchronizedWithCurrentItemPrivate { get { return _cacheValid[(int)CacheBits.IsSynchronizedWithCurrentItem]; } set { _cacheValid[(int)CacheBits.IsSynchronizedWithCurrentItem] = value; } } private bool SkipCoerceSelectedItemCheck { get { return _cacheValid[(int)CacheBits.SkipCoerceSelectedItemCheck]; } set { _cacheValid[(int)CacheBits.SkipCoerceSelectedItemCheck] = value; } } #endregion #region Private Methods /// /// Adds/Removes the given item to the collection. Assumes the item is in the collection. /// private void SetSelectedHelper(object item, FrameworkElement UI, bool selected) { Debug.Assert(!SelectionChange.IsActive, "SelectionChange is already active -- use SelectionChange.Select or Unselect"); bool selectable; selectable = ItemGetIsSelectable(item); if (selectable == false && selected) { throw new InvalidOperationException(SR.Get(SRID.CannotSelectNotSelectableItem)); } SelectionChange.Begin(); try { if (selected) { SelectionChange.Select(item, true /* assumeInItemsCollection */); } else { SelectionChange.Unselect(item); } } finally { SelectionChange.End(); } } private void OnCurrentChanged(object sender, EventArgs e) { // if (IsSynchronizedWithCurrentItemPrivate) SetSelectedToCurrent(); } private void OnGeneratorStatusChanged(object sender, EventArgs e) { if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { if (HasItems) { Debug.Assert(!((SelectedIndex >= 0) && (_selectedItems.Count == 0)), "SelectedIndex >= 0 implies _selectedItems nonempty"); SelectionChange.Begin(); try { // Things could have been added to _selectedItems before the containers were generated, so now push // the IsSelected state down onto those items. for (int i = 0; i < _selectedItems.Count; i++) { // This could send messages back from the children, but we will ignore them b/c the selectionchange is active. ItemSetIsSelected(_selectedItems[i], true); } } finally { SelectionChange.Cancel(); } } } } private void SetSelectedToCurrent() { Debug.Assert(IsSynchronizedWithCurrentItemPrivate); if (!_cacheValid[(int)CacheBits.SyncingSelectionAndCurrency]) { _cacheValid[(int)CacheBits.SyncingSelectionAndCurrency] = true; try { object item = Items.CurrentItem; if (item != null && ItemGetIsSelectable(item)) { SelectionChange.SelectJustThisItem(item, true /* assumeInItemsCollection */); } else { // Select nothing if Currency is not set. SelectionChange.SelectJustThisItem(null, false); } } finally { _cacheValid[(int)CacheBits.SyncingSelectionAndCurrency] = false; } } } private void SetCurrentToSelected() { Debug.Assert(IsSynchronizedWithCurrentItemPrivate); if (!_cacheValid[(int)CacheBits.SyncingSelectionAndCurrency]) { _cacheValid[(int)CacheBits.SyncingSelectionAndCurrency] = true; try { if (_selectedItems.Count == 0) { // this avoid treating null as an item Items.MoveCurrentToPosition(-1); } else { Items.MoveCurrentTo(InternalSelectedItem); } } finally { _cacheValid[(int)CacheBits.SyncingSelectionAndCurrency] = false; } } } private void UpdateSelectedItems() { // Update SelectedItems. We don't want to invalidate the property // because that defeats the ability of bindings to be able to listen // for collection changes on that collection. Instead we just want // to add all the items which are not already in the collection. // Note: This is currently only called from SelectionChanger where SC.IsActive will be true. // If this is ever called from another location, ensure that SC.IsActive is true. Debug.Assert(SelectionChange.IsActive, "SelectionChange.IsActive should be true"); IList userSelectedItems = SelectedItemsImpl; if (userSelectedItems != null) { InternalSelectedItemsStorage userSelectedItemsTable = new InternalSelectedItemsStorage(userSelectedItems.Count); userSelectedItemsTable.UsesItemHashCodes = _selectedItems.UsesItemHashCodes; for (int i = 0; i < userSelectedItems.Count; i++) { object userSelectedItem = userSelectedItems[i]; if (_selectedItems.Contains(userSelectedItem) && !userSelectedItemsTable.Contains(userSelectedItem)) { // cache the user's selected items into a table with O(1) lookup. userSelectedItemsTable.Add(userSelectedItem); } else { // Remove each thing that is in SelectedItems that's not in _selectedItems. // Remove all duplicate items from userSelectedItems userSelectedItems.RemoveAt(i); i--; } } // Add to SelectedItems everything missing that's in _selectedItems. foreach (object selectedItem in _selectedItems) { if (!userSelectedItemsTable.Contains(selectedItem)) { userSelectedItems.Add(selectedItem); } } } } // called by SelectionChanger internal void UpdatePublicSelectionProperties() { EffectiveValueEntry entry = GetValueEntry( LookupEntry(SelectedIndexProperty.GlobalIndex), SelectedIndexProperty, null, RequestFlags.DeferredReferences); object selectedIndexValue = entry.Value; if (!entry.IsDeferredReference) { // these are important checks to make before calling SetValue -- they // ensure that we are not going to clobber a coerced value int selectedIndex = (int)selectedIndexValue; if ((selectedIndex > Items.Count - 1) || (selectedIndex == -1 && _selectedItems.Count > 0) || (selectedIndex > -1 && (_selectedItems.Count == 0 || Items[selectedIndex] != _selectedItems[0]))) { // Use a DeferredSelectedIndexReference instead of calculating the new // value now for better performance. Most of the time no // one cares what the new is, and calculating InternalSelectedIndex // be expensive because Items.IndexOf call SetDeferredValue(SelectedIndexProperty, new DeferredSelectedIndexReference(this)); } } if (SelectedItem != InternalSelectedItem) { try { // We know that InternalSelectedItem is a correct value for SelectedItemProperty and // should skip the coerce callback because it is expensive to call IndexOf and Contains SkipCoerceSelectedItemCheck = true; SetValue(SelectedItemProperty, InternalSelectedItem); } finally { SkipCoerceSelectedItemCheck = false; } } if (!_cacheValid[(int)CacheBits.SelectedValueDrivesSelection]) { object desiredSelectedValue = InternalSelectedValue; if (desiredSelectedValue == DependencyProperty.UnsetValue) { desiredSelectedValue = null; } if (!Object.Equals(SelectedValue, desiredSelectedValue)) { SetValue(SelectedValueProperty, desiredSelectedValue); } } UpdateSelectedItems(); } /// /// Raise the SelectionChanged event. /// private void InvokeSelectionChanged(List unselectedItems, List selectedItems) { SelectionChangedEventArgs selectionChanged = new SelectionChangedEventArgs(unselectedItems, selectedItems); selectionChanged.Source=this; OnSelectionChanged(selectionChanged); } /// /// Returns true if FrameworkElement representing this item has Selector.IsSelectedProperty set to true. /// /// /// private bool ItemGetIsSelected(object item) { if (item == null) return false; return ContainerGetIsSelected(ItemContainerGenerator.ContainerFromItem(item), item); } /// /// Returns true if FrameworkElement representing the item at the given index /// has Selector.IsSelectedProperty set to true. /// /// /// /// private bool IndexGetIsSelected(int index, object item) { return ContainerGetIsSelected(ItemContainerGenerator.ContainerFromIndex(index), item); } /// /// Returns true if FrameworkElement (container) representing the item /// has Selector.IsSelectedProperty set to true. /// /// /// /// private static bool ContainerGetIsSelected(DependencyObject container, object item) { if (container != null) { return (bool)container.GetValue(Selector.IsSelectedProperty); } // In the case where the elements added *are* the containers, read it off the item could work too // DependencyObject element = item as DependencyObject; if (element != null) { return (bool)element.GetValue(Selector.IsSelectedProperty); } return false; } /// /// Set an Item to be selected. Sets Selector.IsSelectedProperty on the FrameworkElement representing the item. /// /// /// private void ItemSetIsSelected(object item, bool value) { if (item == null) return; DependencyObject container = ItemContainerGenerator.ContainerFromItem(item); if (container != null) { // First check that the value is different and then set it. if (GetIsSelected(container) != value) { container.SetValue(Selector.IsSelectedProperty, BooleanBoxes.Box(value)); } } else { // In the case where the elements added *are* the containers, set it on the item instead of doing nothing // DependencyObject element = item as DependencyObject; if (element != null) { if (GetIsSelected(element) != value) { element.SetValue(Selector.IsSelectedProperty, BooleanBoxes.Box(value)); } } } } /// /// Returns false if FrameworkElement representing this item has Selector.SelectableProperty set to false. True otherwise. /// /// /// internal static bool ItemGetIsSelectable(object item) { if (item != null) { return !(item is Separator); } return false; } internal static bool UiGetIsSelectable(DependencyObject o) { if (o != null) { if (!ItemGetIsSelectable(o)) { return false; } else { // Check the data item ItemsControl itemsControl = ItemsControl.ItemsControlFromItemContainer(o); if (itemsControl != null) { object data = itemsControl.ItemContainerGenerator.ItemFromContainer(o); if (data != o) { return ItemGetIsSelectable(data); } else { return true; } } } } return false; } private static void OnSelected(object sender, RoutedEventArgs e) { ((Selector)sender).NotifyIsSelectedChanged(e.OriginalSource as FrameworkElement, true, e); } private static void OnUnselected(object sender, RoutedEventArgs e) { ((Selector)sender).NotifyIsSelectedChanged(e.OriginalSource as FrameworkElement, false, e); } /// /// Called by handlers of Selected/Unselected or CheckedChanged events to indicate that the selection state /// on the item has changed and selector needs to update accordingly. /// /// /// /// /// internal void NotifyIsSelectedChanged(FrameworkElement container, bool selected, RoutedEventArgs e) { // The selectionchanged event will fire at the end of the selection change. // We are here because this change was requested within the SelectionChange. // If there isn't a selection change going on now, we should do a SelectionChange. if (!SelectionChange.IsActive) { if (container != null) { object item = GetItemOrContainerFromContainer(container); if (item != DependencyProperty.UnsetValue) { SetSelectedHelper(item, container, selected); e.Handled = true; } } } else { // We cause this property to change, so mark it as handled e.Handled = true; } } /// /// Allows batch processing of selection changes so that only one SelectionChanged event is fired and /// SelectedIndex is changed only once (if necessary). /// internal SelectionChanger SelectionChange { get { if (_selectionChangeInstance == null) { _selectionChangeInstance = new SelectionChanger(this); } return _selectionChangeInstance; } } // use the first item to decide whether items support hashing correctly. // Reset the algorithm used by _selectedItems accordingly. void ResetSelectedItemsAlgorithm() { if (!Items.IsEmpty) { _selectedItems.UsesItemHashCodes = Items.CollectionView.HasReliableHashCodes(); } } #region Private Properties // /* // Journaling the selection state is more complex than just a property. // For one thing, the selection properties may never be referenced, and // thus they might not have a value in the local store. Second, one // property might not be sufficient (say, SelectedIndex) and another might // fail serialization (i.e. SelectedItems). With a DP that has a // ReadLocalValueOverride, it will be enumerated by the LocalValueEnumerator // and the value can have custom serialization logic. private static readonly DependencyProperty PrivateJournaledSelectionProperty = DependencyProperty.Register("PrivateJournaledSelection", typeof(object), typeof(Selector), PrivateJournaledSelectionPropertyMetadata); private static FrameworkPropertyMetadata PrivateJournaledSelectionPropertyMetadata { get { FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Journal); fpm.ReadLocalValueOverride = new ReadLocalValueOverride(ReadPrivateJournaledSelection); fpm.WriteLocalValueOverride = new WriteLocalValueOverride(WritePrivateJournaledSelection); return fpm; } } private static object ReadPrivateJournaledSelection(DependencyObject d) { // For now, just do what we were doing before -- journal SelectedIndex return ((Selector)d).InternalSelectedIndex; } private static void WritePrivateJournaledSelection(DependencyObject d, object value) { Selector s = (Selector)d; // Issue: This could throw an exception if things aren't set up in time. s.SelectedIndex = (int)value; } */ #endregion //------------------------------------------------------------------- // // Data // //------------------------------------------------------------------- #region Private Members // The selected items that we interact with. Most of the time when SelectedItems // is in use, this is identical to the value of the SelectedItems property, but // differs in type, and will differ in content in the case where you set or modify // SelectedItems and we need to switch our selection to what was just provided. // This is our internal representation of the selection and generally should be modified // only by SelectionChanger. Internal classes may read this for efficiency's sake // to avoid putting SelectedItems "in use" but we can't really expose this externally. internal InternalSelectedItemsStorage _selectedItems = new InternalSelectedItemsStorage(1); // Gets the selected item but doesn't use SelectedItem (avoids putting it "in use") internal object InternalSelectedItem { get { return (_selectedItems.Count == 0) ? null : _selectedItems[0]; } } /// /// Index of the first item in SelectedItems or (-1) if SelectedItems is empty. /// /// internal int InternalSelectedIndex { get { return (_selectedItems.Count == 0) ? -1 : Items.IndexOf(_selectedItems[0]); } } private object InternalSelectedValue { get { object item = InternalSelectedItem; object selectedValue; if (item != null) { BindingExpression bindingExpr = PrepareItemValueBinding(item); if (String.IsNullOrEmpty(SelectedValuePath)) { // when there's no SelectedValuePath, the binding's Path // is either empty (CLR) or "/InnerText" (XML) string path = bindingExpr.ParentBinding.Path.Path; Debug.Assert(String.IsNullOrEmpty(path) || path == "/InnerText"); if (string.IsNullOrEmpty(path)) { selectedValue = item; // CLR - the item is its own selected value } else { selectedValue = GetInnerText(item); // XML - use the InnerText as the selected value } } else { // apply the SelectedValuePath to the item bindingExpr.Activate(item); selectedValue = bindingExpr.Value; bindingExpr.Deactivate(); } } else { // no selected item - use UnsetValue (to distinguish from null, a legitimate value for the SVP) selectedValue = DependencyProperty.UnsetValue; } return selectedValue; } } // separate method to avoid loading System.Xml until necessary [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private object GetInnerText(object item) { XmlNode node = item as XmlNode; if (node != null) { return node.InnerText; } else { return null; } } // Used by ListBox and ComboBox to determine if the mouse actually entered the // List/ComboBoxItem before it focus which calls BringIntoView private Point _lastMousePosition = new Point(); // see comment on SelectionChange property private SelectionChanger _selectionChangeInstance; // Condense boolean bits. Constructor takes the default value, and will resize to access up to 32 bits. private BitVector32 _cacheValid = new BitVector32((int)CacheBits.CanSelectMultiple); [Flags] private enum CacheBits { // This flag is true while syncing the selection and the currency. It // is used to avoid reentrancy: e.g. when the currency changes we want // to change the selection accordingly, but that selection change should // not try to change currency. SyncingSelectionAndCurrency = 0x00000001, CanSelectMultiple = 0x00000002, IsSynchronizedWithCurrentItem = 0x00000004, SkipCoerceSelectedItemCheck = 0x00000008, SelectedValueDrivesSelection = 0x00000010, } private EventHandler _focusEnterMainFocusScopeEventHandler; #endregion #region Helper Classes #region SelectionChanger /// /// Helper class for selection change batching. /// internal class SelectionChanger { /// /// Create a new SelectionChangeHelper -- there should only be one instance per Selector. /// /// internal SelectionChanger(Selector s) { _owner = s; _active = false; _toSelect = new InternalSelectedItemsStorage(1); _toUnselect = new InternalSelectedItemsStorage(1); _toDeferSelect = new InternalSelectedItemsStorage(1); } /// /// True if there is a SelectionChange currently in progress. /// internal bool IsActive { get { return _active; } } /// /// Begin tracking selection changes. /// internal void Begin() { Debug.Assert(_owner.CheckAccess()); Debug.Assert(!_active, SR.Get(SRID.SelectionChangeActive)); _active = true; _toSelect.Clear(); _toUnselect.Clear(); } /// /// Commit selection changes. /// internal void End() { Debug.Assert(_owner.CheckAccess()); Debug.Assert(_active, "There must be a selection change active when you call SelectionChange.End()"); List unselected = new List(); List selected = new List(); // We might have been asked to make changes that will put us in an invalid state. Correct for this. try { ApplyCanSelectMultiple(); CreateDeltaSelectionChange(unselected, selected); _owner.UpdatePublicSelectionProperties(); } finally { // End the selection change -- IsActive will be false after this Cleanup(); } // only raise the event if there were actually any changes applied if (unselected.Count > 0 || selected.Count > 0) { // see bug 1459509: update Current AFTER selection change and before raising event if (_owner.IsSynchronizedWithCurrentItemPrivate) _owner.SetCurrentToSelected(); _owner.InvokeSelectionChanged(unselected, selected); } } private void ApplyCanSelectMultiple() { if (!_owner.CanSelectMultiple) { Debug.Assert(_toSelect.Count <= 1, "_toSelect.Count was > 1"); if (_toSelect.Count == 1) // this is all that should be selected, unselect _selectedItems { _toUnselect = new InternalSelectedItemsStorage(_owner._selectedItems); } else // _toSelect.Count == 0, and unselect all but one of _selectedItems { // This is when CanSelectMultiple changes from true to false. if (_owner._selectedItems.Count > 1 && _owner._selectedItems.Count != _toUnselect.Count + 1) { // they didn't deselect enough; force deselection object selectedItem = _owner._selectedItems[0]; _toUnselect.Clear(); foreach (object o in _owner._selectedItems) { if (o != selectedItem) { _toUnselect.Add(o); } } } } } } private void CreateDeltaSelectionChange(List unselectedItems, List selectedItems) { for (int i = 0; i < _toDeferSelect.Count; i++) { object o = _toDeferSelect[i]; // If defered selected item exis in Items - move it to _toSelect if (_owner.Items.Contains(o)) { _toSelect.Add(o); _toDeferSelect.Remove(o); i--; } } if (_toUnselect.Count > 0 || _toSelect.Count > 0) { if (_owner._selectedItems.UsesItemHashCodes) { // Step 1: Keep current selection temp storage InternalSelectedItemsStorage currentSelectedItems = new InternalSelectedItemsStorage(_owner._selectedItems); // Step 2: Clear the selection storage _owner._selectedItems.Clear(); // STep 3: Process the items that need to be unselected foreach (object o in _toUnselect) { _owner.ItemSetIsSelected(o, false); if (currentSelectedItems.Contains(o)) { unselectedItems.Add(o); } } // Step 4: Add back items from the temp storage that are not in _toUnselect foreach (object o in currentSelectedItems) { if (!_toUnselect.Contains(o)) { _owner._selectedItems.Add(o); } } } else { foreach (object o in _toUnselect) { _owner.ItemSetIsSelected(o, false); if (_owner._selectedItems.Contains(o)) { _owner._selectedItems.Remove(o); unselectedItems.Add(o); } } } foreach (object o in _toSelect) { _owner.ItemSetIsSelected(o, true); if (!_owner._selectedItems.Contains(o)) { _owner._selectedItems.Add(o); selectedItems.Add(o); } } } } #if never private void SynchronizeSelectedIndexToSelectedItem() { if (_owner._selectedItems.Count == 0) { _owner.SelectedIndex = -1; } else { object selectedItem = _owner.SelectedItem; object firstSelection = _owner._selectedItems[0]; // This check is only just to slightly improve perf by checking if it's in selected items before doing a reverse lookup if (selectedItem == null || firstSelection != selectedItem) { _owner.SelectedIndex = _owner.Items.IndexOf(firstSelection); } } } #endif /// /// Queue something to be added to the selection. Does nothing if the item is already selected. /// /// /// /// true if the Selection was queued internal bool Select(object o, bool assumeInItemsCollection) { Debug.Assert(_owner.CheckAccess()); Debug.Assert(_active, SR.Get(SRID.SelectionChangeNotActive)); Debug.Assert(o != null, "parameter o should not be null"); // Disallow selecting !IsSelectable things if (!ItemGetIsSelectable(o)) return false; // Disallow selecting things not in Items.FlatView if (!assumeInItemsCollection) { if (!_owner.Items.Contains(o)) { // If user selected item is not in the Items yet - defer the selection if (!_toDeferSelect.Contains(o)) _toDeferSelect.Add(o); return false; } } // To support Unselect(o) / Select(o) where o is already selected. if (_toUnselect.Remove(o)) { return true; } // Ignore if the item is already selected if (_owner._selectedItems.Contains(o)) return false; // Ignore if the item has already been requested to be selected. if (_toSelect.Contains(o)) return false; // enforce that we only select one thing in the CanSelectMultiple=false case. if (!_owner.CanSelectMultiple && _toSelect.Count > 0) { // If it was the item telling us this, turn around and set IsSelected = false // This will basically only happen in a Refresh situation where multiple items in the collection were selected but // CanSelectMultiple = false. foreach (object item in _toSelect) { _owner.ItemSetIsSelected(item, false); } _toSelect.Clear(); } _toSelect.Add(o); return true; } /// /// Queue something to be removed from the selection. Does nothing if the item is not already selected. /// /// /// true if the item was queued for unselection. internal bool Unselect(object o) { Debug.Assert(_owner.CheckAccess()); Debug.Assert(_active, SR.Get(SRID.SelectionChangeNotActive)); Debug.Assert(o != null, "parameter o should not be null"); _toDeferSelect.Remove(o); // To support Select(o) / Unselect(o) where o is not already selected. if (_toSelect.Remove(o)) { return true; } // Ignore if the item is not already selected if (!_owner._selectedItems.Contains(o)) return false; // Ignore if the item has already been queued for unselection. if (_toUnselect.Contains(o)) return false; _toUnselect.Add(o); return true; } /// /// Makes sure that the current selection is valid; Performs a SelectionChange it if it's not. /// internal void Validate() { Begin(); End(); } /// /// Cancels the currently active SelectionChange. /// internal void Cancel() { Debug.Assert(_owner.CheckAccess()); Cleanup(); } internal void CleanupDeferSelection() { if (_toDeferSelect.Count > 0) { _toDeferSelect.Clear(); } } internal void Cleanup() { _active = false; if (_toSelect.Count > 0) { _toSelect.Clear(); } if (_toUnselect.Count > 0) { _toUnselect.Clear(); } } /// /// Select just this item; all other items in SelectedItems will be removed. /// /// /// internal void SelectJustThisItem(object item, bool assumeInItemsCollection) { Begin(); CleanupDeferSelection(); try { // was this item already in the selection? bool isSelected = false; // go backwards in case a selection is rejected; then they'll still have the same SelectedItem for (int i = _owner._selectedItems.Count - 1; i >= 0; i--) { if (item != _owner._selectedItems[i]) { Unselect(_owner._selectedItems[i]); } else { isSelected = true; } } if (!isSelected && item != null && item != DependencyProperty.UnsetValue) { Select(item, assumeInItemsCollection); } } finally { End(); } } private Selector _owner; private InternalSelectedItemsStorage _toSelect; private InternalSelectedItemsStorage _toUnselect; private InternalSelectedItemsStorage _toDeferSelect; // Keep the items that cannot be selected because they are not in _owner.Items private bool _active; } #endregion #region InternalSelectedItemsStorage internal class InternalSelectedItemsStorage : IEnumerable { internal InternalSelectedItemsStorage(int capacity) { _list = new ArrayList(capacity); _set = new Dictionary(capacity); } internal InternalSelectedItemsStorage(InternalSelectedItemsStorage collection) { _list = new ArrayList(collection._list); if (collection.UsesItemHashCodes) { _set = new Dictionary(collection._set); } } public void Add(object t) { if (_set != null) { _set.Add(t, null); } _list.Add(t); } public bool Remove(object t) { if (_set != null) { if (_set.Remove(t)) { _list.Remove(t); return true; } } else { int index = _list.IndexOf(t); if (index >= 0) { _list.RemoveAt(index); return true; } } return false; } public bool Contains(object t) { if (_set != null) { return _set.ContainsKey(t); } else { return _list.Contains(t); } } public object this[int index] { get { return _list[index]; } } public void Clear() { _list.Clear(); if (_set != null) { _set.Clear(); } } public int Count { get { return _list.Count; } } IEnumerator IEnumerable.GetEnumerator() { return _list.GetEnumerator(); } // If the underlying items don't implement GetHashCode according to // guidelines (i.e. if an item's hashcode can change during the item's // lifetime) we can't use any hash-based data structures like Dictionary, // Hashtable, etc. The principal offender is DataRowView. (bug 1583080) public bool UsesItemHashCodes { get { return _set != null; } set { if (value == true && _set == null) { _set = new Dictionary(_list.Count); for (int i=0; i<_list.Count; ++i) { _set.Add(_list[i], null); } } else if (value == false) { _set = null; } } } private ArrayList _list; private Dictionary _set; } #endregion InternalSelectedItemsStorage #endregion #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK