File: System\Windows\Controls\TabItem.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using MS.Internal;
using MS.Internal.KnownBoxes;
using System.ComponentModel;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Input;
using System.Windows.Data;
 
using System.Windows.Controls.Primitives;
 
// Disable CS3001: Warning as Error: not CLS-compliant
#pragma warning disable 3001
 
namespace System.Windows.Controls
{
    /// <summary>
    ///     A child item of TabControl.
    /// </summary>
    [DefaultEvent("IsSelectedChanged")]
    public class TabItem : HeaderedContentControl
    {
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        ///     Default DependencyObject constructor
        /// </summary>
        /// <remarks>
        ///     Automatic determination of current Dispatcher. Use alternative constructor
        ///     that accepts a Dispatcher for best performance.
        /// </remarks>
        public TabItem() : base()
        {
        }
 
        static TabItem()
        {
            EventManager.RegisterClassHandler(typeof(TabItem), AccessKeyManager.AccessKeyPressedEvent, new AccessKeyPressedEventHandler(OnAccessKeyPressed));
 
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TabItem), new FrameworkPropertyMetadata(typeof(TabItem)));
            _dType = DependencyObjectType.FromSystemTypeInternal(typeof(TabItem));
            KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(TabItem), new FrameworkPropertyMetadata(KeyboardNavigationMode.Contained));
            KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(TabItem), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local));
 
            IsEnabledProperty.OverrideMetadata(typeof(TabItem), new UIPropertyMetadata(new PropertyChangedCallback(OnVisualStatePropertyChanged)));
            IsMouseOverPropertyKey.OverrideMetadata(typeof(TabItem), new UIPropertyMetadata(new PropertyChangedCallback(OnVisualStatePropertyChanged)));
            AutomationProperties.IsOffscreenBehaviorProperty.OverrideMetadata(typeof(TabItem), new FrameworkPropertyMetadata(IsOffscreenBehavior.FromClip));
        }
 
        #endregion
 
        //-------------------------------------------------------------------
        //
        //  Properties
        //
        //-------------------------------------------------------------------
 
        #region Properties
        /// <summary>
        ///     Indicates whether this TabItem is selected.
        /// </summary>
        public static readonly DependencyProperty IsSelectedProperty =
                Selector.IsSelectedProperty.AddOwner(typeof(TabItem),
                        new FrameworkPropertyMetadata(BooleanBoxes.FalseBox,
                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.Journal,
                                new PropertyChangedCallback(OnIsSelectedChanged)));
 
        /// <summary>
        ///     Indicates whether this TabItem is selected.
        /// </summary>
        [Bindable(true), Category("Appearance")]
        public bool IsSelected
        {
            get { return (bool) GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, BooleanBoxes.Box(value)); }
        }
 
        private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TabItem tabItem = d as TabItem;
 
            bool isSelected = (bool)e.NewValue;
 
            TabControl parentTabControl = tabItem.TabControlParent;
            if (parentTabControl != null)
            {
                parentTabControl.RaiseIsSelectedChangedAutomationEvent(tabItem, isSelected);
            }
 
            if (isSelected)
            {
                tabItem.OnSelected(new RoutedEventArgs(Selector.SelectedEvent, tabItem));
            }
            else
            {
                tabItem.OnUnselected(new RoutedEventArgs(Selector.UnselectedEvent, tabItem));
            }
 
 
            // KeyboardNavigation use bounding box reduced with DirectionalNavigationMargin when calculating the next element in directional navigation
            // Because TabItem use negative margins some TabItems overlap which would changes the directional navigation if we don't reduce the bounding box
            if (isSelected)
            {
                Binding binding = new Binding("Margin");
                binding.Source = tabItem;
                BindingOperations.SetBinding(tabItem, KeyboardNavigation.DirectionalNavigationMarginProperty, binding);
            }
            else
            {
                BindingOperations.ClearBinding(tabItem, KeyboardNavigation.DirectionalNavigationMarginProperty);
            }
 
            tabItem.UpdateVisualState();
        }
 
        /// <summary>
        ///     Event indicating that the IsSelected property is now true.
        /// </summary>
        /// <param name="e">Event arguments</param>
        protected virtual void OnSelected(RoutedEventArgs e)
        {
            HandleIsSelectedChanged(true, e);
        }
 
        /// <summary>
        ///     Event indicating that the IsSelected property is now false.
        /// </summary>
        /// <param name="e">Event arguments</param>
        protected virtual void OnUnselected(RoutedEventArgs e)
        {
            HandleIsSelectedChanged(false, e);
        }
 
        private void HandleIsSelectedChanged(bool newValue, RoutedEventArgs e)
        {
#if OLD_AUTOMATION
            if (AutomationProvider.IsActive)
            {
                RaiseAutomationIsSelectedChanged(!newValue, newValue);
            }
#endif
 
            RaiseEvent(e);
        }
 
#if OLD_AUTOMATION
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        private void RaiseAutomationIsSelectedChanged(bool oldValue, bool newValue)
        {
            AutomationProvider.RaiseAutomationPropertyChangedEvent(this, SelectionItemPatternIdentifiers.IsSelectedProperty, oldValue, newValue);
        }
#endif
 
        #region TabStripPlacement
        /// <summary>
        ///     Property key for TabStripPlacementProperty.
        /// </summary>
        private static readonly DependencyPropertyKey TabStripPlacementPropertyKey =
                DependencyProperty.RegisterReadOnly(
                        "TabStripPlacement",
                        typeof(Dock),
                        typeof(TabItem),
                        new FrameworkPropertyMetadata(
                                Dock.Top,
                                null,
                                new CoerceValueCallback(CoerceTabStripPlacement)));
 
        /// <summary>
        /// Specifies the placement of the TabItem
        /// </summary>
        public static readonly DependencyProperty TabStripPlacementProperty =
            TabStripPlacementPropertyKey.DependencyProperty;
 
        private static object CoerceTabStripPlacement(DependencyObject d, object value)
        {
            TabControl tabControl = ((TabItem)d).TabControlParent;
            return (tabControl != null) ? tabControl.TabStripPlacement : value;
        }
 
        /// <summary>
        /// Specifies the placement of the TabItem. This read-only property get its value from the TabControl parent
        /// </summary>
        public Dock TabStripPlacement
        {
            get
            {
                return (Dock)GetValue(TabStripPlacementProperty);
            }
        }
 
        internal override void OnAncestorChanged()
        {
            // TabStripPlacement depends on the logical parent -- so invalidate it when that changes
            CoerceValue(TabStripPlacementProperty);
        }
 
        #endregion TabStripPlacement
 
        #endregion
 
        //-------------------------------------------------------------------
        //
        //  Protected Methods
        //
        //-------------------------------------------------------------------
 
        #region Protected Methods
 
        internal override void ChangeVisualState(bool useTransitions)
        {
            if (!IsEnabled)
            {
                VisualStateManager.GoToState(this, VisualStates.StateDisabled, useTransitions);
            }
            else if (IsMouseOver)
            {
                VisualStateManager.GoToState(this, VisualStates.StateMouseOver, useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, VisualStates.StateNormal, useTransitions);
            }
 
            // Update the SelectionStates group
            if (IsSelected)
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateSelected, VisualStates.StateUnselected);
            }
            else
            {
                VisualStateManager.GoToState(this, VisualStates.StateUnselected, useTransitions);
            }
 
            if (IsKeyboardFocused)
            {
                VisualStateManager.GoToState(this, VisualStates.StateFocused, useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, VisualStates.StateUnfocused, useTransitions);
            }
 
            base.ChangeVisualState(useTransitions);
        }
 
        /// <summary>
        /// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
        /// </summary>
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new TabItemWrapperAutomationPeer(this);
        }
 
        /// <summary>
        /// This is the method that responds to the MouseLeftButtonDownEvent event.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            // We should process only the direct events in case TabItem is the selected one
            // otherwise we are getting this event when we click on TabItem content because it is in the logical subtree
            if (e.Source == this || !IsSelected)
            {
                if (SetFocus())
                    e.Handled = true;
            }
            base.OnMouseLeftButtonDown(e);
        }
 
        /// <summary>
        /// Focus event handler
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
        {
            base.OnPreviewGotKeyboardFocus(e);
            if (!e.Handled && e.NewFocus == this)
            {
                if (FrameworkAppContextSwitches.SelectionPropertiesCanLagBehindSelectionChangedEvent)
                {
                    // old ("useless") behavior - retained for app-compat
                    if (!IsSelected && TabControlParent != null)
                    {
                        SetCurrentValueInternal(IsSelectedProperty, BooleanBoxes.TrueBox);
                        // If focus moved in result of selection - handle the event to prevent setting focus back on the new item
                        if (e.OldFocus != Keyboard.FocusedElement)
                        {
                            e.Handled = true;
                        }
                        else if (GetBoolField(BoolField.SetFocusOnContent))
                        {
                            TabControl parentTabControl = TabControlParent;
                            if (parentTabControl != null)
                            {
                                // Save the parent and check for null to make sure that SetCurrentValue didn't have a change handler
                                // that removed the TabItem from the tree.
                                ContentPresenter selectedContentPresenter = parentTabControl.SelectedContentPresenter;
                                if (selectedContentPresenter != null)
                                {
                                    parentTabControl.UpdateLayout(); // Wait for layout
                                    bool success = selectedContentPresenter.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
 
                                    // If we successfully move focus inside the content then don't set focus to the header
                                    if (success)
                                        e.Handled = true;
                                }
                            }
                        }
                    }
                }
                else
                {
                    // new behavior.  Fixes the case when selection
                    // changes while focus is in the old SelectedContent
                    if (!IsSelected && TabControlParent != null)
                    {
                        SetCurrentValueInternal(IsSelectedProperty, BooleanBoxes.TrueBox);
                        // If focus moved in result of selection - handle the event to prevent setting focus back on the new item
                        if (e.OldFocus != Keyboard.FocusedElement)
                        {
                            e.Handled = true;
                        }
                    }
 
                    if (!e.Handled && GetBoolField(BoolField.SetFocusOnContent))
                    {
                        TabControl parentTabControl = TabControlParent;
                        if (parentTabControl != null)
                        {
                            // Save the parent and check for null to make sure that SetCurrentValue didn't have a change handler
                            // that removed the TabItem from the tree.
                            ContentPresenter selectedContentPresenter = parentTabControl.SelectedContentPresenter;
                            if (selectedContentPresenter != null)
                            {
                                parentTabControl.UpdateLayout(); // Wait for layout
                                bool success = selectedContentPresenter.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
 
                                // If we successfully move focus inside the content then don't set focus to the header
                                if (success)
                                {
                                    e.Handled = true;
 
                                    // However, if the focus got switched to a different focus scope,
                                    // mark the header as the one last focused in its focus scope. #8293
                                    if (Keyboard.FocusedElement is DependencyObject focusedElement)
                                    {
                                        DependencyObject thisFocusScope = FocusManager.GetFocusScope(this);
                                        if (thisFocusScope != null && Keyboard.FocusedElement is DependencyObject currentFocus)
                                        {
                                            DependencyObject currentFocusScope = FocusManager.GetFocusScope(currentFocus);
                                            if (currentFocusScope != thisFocusScope && thisFocusScope != null)
                                                FocusManager.SetFocusedElement(thisFocusScope, this);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// The Access key for this control was invoked.
        /// </summary>
        protected override void OnAccessKey(AccessKeyEventArgs e)
        {
            SetFocus();
        }
 
        /// <summary>
        ///     This method is invoked when the Content property changes.
        /// </summary>
        /// <param name="oldContent">The old value of the Content property.</param>
        /// <param name="newContent">The new value of the Content property.</param>
        protected override void OnContentChanged(object oldContent, object newContent)
        {
            base.OnContentChanged(oldContent, newContent);
 
            // If this is the selected TabItem then we should update TabControl.SelectedContent
            if (IsSelected)
            {
                TabControl tabControl = TabControlParent;
                if (tabControl != null)
                {
                    if (newContent == BindingExpressionBase.DisconnectedItem)
                    {
                        // don't let {DisconnectedItem} bleed into the UI
                        newContent = null;
                    }
 
                    tabControl.SelectedContent = newContent;
                }
            }
        }
 
        /// <summary>
        ///     This method is invoked when the ContentTemplate property changes.
        /// </summary>
        /// <param name="oldContentTemplate">The old value of the ContentTemplate property.</param>
        /// <param name="newContentTemplate">The new value of the ContentTemplate property.</param>
        protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate)
        {
            base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
 
            // If this is the selected TabItem then we should update TabControl.SelectedContentTemplate
            if (IsSelected)
            {
                TabControl tabControl = TabControlParent;
                if (tabControl != null)
                {
                    tabControl.SelectedContentTemplate = newContentTemplate;
                }
            }
        }
 
        /// <summary>
        ///     This method is invoked when the ContentTemplateSelector property changes.
        /// </summary>
        /// <param name="oldContentTemplateSelector">The old value of the ContentTemplateSelector property.</param>
        /// <param name="newContentTemplateSelector">The new value of the ContentTemplateSelector property.</param>
        protected override void OnContentTemplateSelectorChanged(DataTemplateSelector oldContentTemplateSelector, DataTemplateSelector newContentTemplateSelector)
        {
            base.OnContentTemplateSelectorChanged(oldContentTemplateSelector, newContentTemplateSelector);
 
            // If this is the selected TabItem then we should update TabControl.SelectedContentTemplateSelector
            if (IsSelected)
            {
                TabControl tabControl = TabControlParent;
                if (tabControl != null)
                {
                    tabControl.SelectedContentTemplateSelector = newContentTemplateSelector;
                }
            }
        }
 
        #endregion
 
        //-------------------------------------------------------------------
        //
        //  Private Methods
        //
        //-------------------------------------------------------------------
 
        #region Private Methods
 
        private static void OnAccessKeyPressed(object sender, AccessKeyPressedEventArgs e)
        {
            if (!e.Handled && e.Scope == null)
            {
                TabItem tabItem = sender as TabItem;
 
                if (e.Target == null)
                {
                    e.Target = tabItem;
                }
                else if (!tabItem.IsSelected) // If TabItem is not active it is a scope for its content elements
                {
                    e.Scope = tabItem;
                    e.Handled = true;
                }
            }
        }
 
        internal bool SetFocus()
        {
            bool returnValue = false;
 
            if (!GetBoolField(BoolField.SettingFocus))
            {
                TabItem currentFocus = Keyboard.FocusedElement as TabItem;
 
                // If current focus was another TabItem in the same TabControl - dont set focus on content
                bool setFocusOnContent = (FrameworkAppContextSwitches.SelectionPropertiesCanLagBehindSelectionChangedEvent || !IsKeyboardFocusWithin)
                                            && ((currentFocus == this) || (currentFocus == null) || (currentFocus.TabControlParent != this.TabControlParent));
                SetBoolField(BoolField.SettingFocus, true);
                SetBoolField(BoolField.SetFocusOnContent, setFocusOnContent);
                try
                {
                    returnValue = Focus() || setFocusOnContent;
                }
                finally
                {
                    SetBoolField(BoolField.SettingFocus, false);
                    SetBoolField(BoolField.SetFocusOnContent, false);
                }
            }
 
            return returnValue;
        }
 
        private TabControl TabControlParent
        {
            get
            {
                return ItemsControl.ItemsControlFromItemContainer(this) as TabControl;
            }
        }
 
        #endregion
 
        //-------------------------------------------------------------------
        //
        //  Private Fields
        //
        //-------------------------------------------------------------------
 
        #region Private Fields
 
        private bool GetBoolField(BoolField field)
        {
            return (_tabItemBoolFieldStore & field) != 0;
        }
 
        private void SetBoolField(BoolField field, bool value)
        {
            if (value)
            {
                _tabItemBoolFieldStore |= field;
            }
            else
            {
                _tabItemBoolFieldStore &= (~field);
            }
        }
 
        [Flags]
        private enum BoolField
        {
            SetFocusOnContent      = 0x10, // This flag determine if we want to set focus on active TabItem content
            SettingFocus           = 0x20, // This flag indicates that the TabItem is in the process of setting focus
 
            // By default ListBoxItem is selectable
            DefaultValue = 0,
        }
 
        BoolField _tabItemBoolFieldStore = BoolField.DefaultValue;
 
        #endregion Private Fields
 
        #region DTypeThemeStyleKey
 
        // Returns the DependencyObjectType for the registered ThemeStyleKey's default
        // value. Controls will override this method to return approriate types.
        internal override DependencyObjectType DTypeThemeStyleKey
        {
            get { return _dType; }
        }
 
        private static DependencyObjectType _dType;
 
        #endregion DTypeThemeStyleKey
    }
}