File: System\Windows\Controls\Primitives\DataGridRowHeader.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 System.ComponentModel;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Automation;
 
namespace System.Windows.Controls.Primitives
{
    /// <summary>
    /// Represents the header for each row of the DataGrid
    /// </summary>
    [TemplatePart(Name = "PART_TopHeaderGripper", Type = typeof(Thumb))]
    [TemplatePart(Name = "PART_BottomHeaderGripper", Type = typeof(Thumb))]
    public class DataGridRowHeader : ButtonBase
    {
        #region Constants
 
        private const byte DATAGRIDROWHEADER_stateMouseOverCode = 0;
        private const byte DATAGRIDROWHEADER_stateMouseOverCurrentRowCode = 1;
        private const byte DATAGRIDROWHEADER_stateMouseOverEditingRowCode = 2;
        private const byte DATAGRIDROWHEADER_stateMouseOverEditingRowFocusedCode = 3;
        private const byte DATAGRIDROWHEADER_stateMouseOverSelectedCode = 4;
        private const byte DATAGRIDROWHEADER_stateMouseOverSelectedCurrentRowCode = 5;
        private const byte DATAGRIDROWHEADER_stateMouseOverSelectedCurrentRowFocusedCode = 6;
        private const byte DATAGRIDROWHEADER_stateMouseOverSelectedFocusedCode = 7;
        private const byte DATAGRIDROWHEADER_stateNormalCode = 8;
        private const byte DATAGRIDROWHEADER_stateNormalCurrentRowCode = 9;
        private const byte DATAGRIDROWHEADER_stateNormalEditingRowCode = 10;
        private const byte DATAGRIDROWHEADER_stateNormalEditingRowFocusedCode = 11;
        private const byte DATAGRIDROWHEADER_stateSelectedCode = 12;
        private const byte DATAGRIDROWHEADER_stateSelectedCurrentRowCode = 13;
        private const byte DATAGRIDROWHEADER_stateSelectedCurrentRowFocusedCode = 14;
        private const byte DATAGRIDROWHEADER_stateSelectedFocusedCode = 15;
        private const byte DATAGRIDROWHEADER_stateNullCode = 255;
 
        private static ReadOnlySpan<byte> FallbackStateMapping => [
            DATAGRIDROWHEADER_stateNormalCode,
            DATAGRIDROWHEADER_stateNormalCurrentRowCode,
            DATAGRIDROWHEADER_stateMouseOverEditingRowFocusedCode,
            DATAGRIDROWHEADER_stateNormalEditingRowFocusedCode,
            DATAGRIDROWHEADER_stateMouseOverSelectedFocusedCode,
            DATAGRIDROWHEADER_stateMouseOverSelectedCurrentRowFocusedCode,
            DATAGRIDROWHEADER_stateSelectedFocusedCode,
            DATAGRIDROWHEADER_stateSelectedFocusedCode,
            DATAGRIDROWHEADER_stateNullCode,
            DATAGRIDROWHEADER_stateNormalCode,
            DATAGRIDROWHEADER_stateNormalEditingRowFocusedCode,
            DATAGRIDROWHEADER_stateSelectedCurrentRowFocusedCode,
            DATAGRIDROWHEADER_stateSelectedFocusedCode,
            DATAGRIDROWHEADER_stateSelectedCurrentRowFocusedCode,
            DATAGRIDROWHEADER_stateNormalCurrentRowCode,
            DATAGRIDROWHEADER_stateNormalCode,
        ];
 
        private static ReadOnlySpan<byte> IdealStateMapping => [
            DATAGRIDROWHEADER_stateNormalCode,
            DATAGRIDROWHEADER_stateNormalCode,
            DATAGRIDROWHEADER_stateMouseOverCode,
            DATAGRIDROWHEADER_stateMouseOverCode,
            DATAGRIDROWHEADER_stateNullCode,
            DATAGRIDROWHEADER_stateNullCode,
            DATAGRIDROWHEADER_stateNullCode,
            DATAGRIDROWHEADER_stateNullCode,
            DATAGRIDROWHEADER_stateSelectedCode,
            DATAGRIDROWHEADER_stateSelectedFocusedCode,
            DATAGRIDROWHEADER_stateMouseOverSelectedCode,
            DATAGRIDROWHEADER_stateMouseOverSelectedFocusedCode,
            DATAGRIDROWHEADER_stateNormalEditingRowCode,
            DATAGRIDROWHEADER_stateNormalEditingRowFocusedCode,
            DATAGRIDROWHEADER_stateMouseOverEditingRowCode,
            DATAGRIDROWHEADER_stateMouseOverEditingRowFocusedCode,
            DATAGRIDROWHEADER_stateNormalCurrentRowCode,
            DATAGRIDROWHEADER_stateNormalCurrentRowCode,
            DATAGRIDROWHEADER_stateMouseOverCurrentRowCode,
            DATAGRIDROWHEADER_stateMouseOverCurrentRowCode,
            DATAGRIDROWHEADER_stateNullCode,
            DATAGRIDROWHEADER_stateNullCode,
            DATAGRIDROWHEADER_stateNullCode,
            DATAGRIDROWHEADER_stateNullCode,
            DATAGRIDROWHEADER_stateSelectedCurrentRowCode,
            DATAGRIDROWHEADER_stateSelectedCurrentRowFocusedCode,
            DATAGRIDROWHEADER_stateMouseOverSelectedCurrentRowCode,
            DATAGRIDROWHEADER_stateMouseOverSelectedCurrentRowFocusedCode,
            DATAGRIDROWHEADER_stateNormalEditingRowCode,
            DATAGRIDROWHEADER_stateNormalEditingRowFocusedCode,
            DATAGRIDROWHEADER_stateMouseOverEditingRowCode,
            DATAGRIDROWHEADER_stateMouseOverEditingRowFocusedCode
        ];
 
        private static string[] _stateNames = new string[]
        {
            VisualStates.DATAGRIDROWHEADER_stateMouseOver,
            VisualStates.DATAGRIDROWHEADER_stateMouseOverCurrentRow,
            VisualStates.DATAGRIDROWHEADER_stateMouseOverEditingRow,
            VisualStates.DATAGRIDROWHEADER_stateMouseOverEditingRowFocused,
            VisualStates.DATAGRIDROWHEADER_stateMouseOverSelected,
            VisualStates.DATAGRIDROWHEADER_stateMouseOverSelectedCurrentRow,
            VisualStates.DATAGRIDROWHEADER_stateMouseOverSelectedCurrentRowFocused,
            VisualStates.DATAGRIDROWHEADER_stateMouseOverSelectedFocused,
            VisualStates.DATAGRIDROWHEADER_stateNormal,
            VisualStates.DATAGRIDROWHEADER_stateNormalCurrentRow,
            VisualStates.DATAGRIDROWHEADER_stateNormalEditingRow,
            VisualStates.DATAGRIDROWHEADER_stateNormalEditingRowFocused,
            VisualStates.DATAGRIDROWHEADER_stateSelected,
            VisualStates.DATAGRIDROWHEADER_stateSelectedCurrentRow,
            VisualStates.DATAGRIDROWHEADER_stateSelectedCurrentRowFocused,
            VisualStates.DATAGRIDROWHEADER_stateSelectedFocused
        };
 
        #endregion Constants
 
        static DataGridRowHeader()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(typeof(DataGridRowHeader)));
 
            ContentProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceContent));
            ContentTemplateProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceContentTemplate));
            ContentTemplateSelectorProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceContentTemplateSelector));
            StyleProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceStyle));
            WidthProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceWidth));
 
            ClickModeProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(ClickMode.Press));
            FocusableProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(false));
            AutomationProperties.IsOffscreenBehaviorProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(IsOffscreenBehavior.FromClip));
        }
 
        #region Automation
 
        protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
        {
            return new System.Windows.Automation.Peers.DataGridRowHeaderAutomationPeer(this);
        }
 
        #endregion
 
        #region Layout
 
        /// <summary>
        ///     Property that indicates the brush to use when drawing seperators between headers.
        /// </summary>
        public Brush SeparatorBrush
        {
            get { return (Brush)GetValue(SeparatorBrushProperty); }
            set { SetValue(SeparatorBrushProperty, value); }
        }
 
        /// <summary>
        ///     DependencyProperty for SeperatorBrush.
        /// </summary>
        public static readonly DependencyProperty SeparatorBrushProperty =
            DependencyProperty.Register("SeparatorBrush", typeof(Brush), typeof(DataGridRowHeader), new FrameworkPropertyMetadata(null));
 
        /// <summary>
        ///     Property that indicates the Visibility for the header seperators.
        /// </summary>
        public Visibility SeparatorVisibility
        {
            get { return (Visibility)GetValue(SeparatorVisibilityProperty); }
            set { SetValue(SeparatorVisibilityProperty, value); }
        }
 
        /// <summary>
        ///     DependencyProperty for SeperatorBrush.
        /// </summary>
        public static readonly DependencyProperty SeparatorVisibilityProperty =
            DependencyProperty.Register("SeparatorVisibility", typeof(Visibility), typeof(DataGridRowHeader), new FrameworkPropertyMetadata(Visibility.Visible));
 
        /// <summary>
        /// Measure this element and it's child elements.
        /// </summary>
        /// <remarks>
        /// DataGridRowHeader needs to update the DataGrid's RowHeaderActualWidth & use this as it's width so that they all end up the
        /// same size.
        /// </remarks>
        /// <param name="availableSize"></param>
        /// <returns></returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            var baseSize = base.MeasureOverride(availableSize);
 
            DataGrid dataGridOwner = DataGridOwner;
            if (dataGridOwner == null)
            {
                return baseSize;
            }
 
            if (double.IsNaN(dataGridOwner.RowHeaderWidth) &&
                baseSize.Width > dataGridOwner.RowHeaderActualWidth)
            {
                dataGridOwner.RowHeaderActualWidth = baseSize.Width;
            }
 
            // Regardless of how width the Header wants to be, we use
            // DataGridOwner.RowHeaderActualWidth to ensure they're all the same size.
            return new Size(dataGridOwner.RowHeaderActualWidth, baseSize.Height);
        }
 
        #endregion
 
        #region Row Communication
 
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
 
            // Give the Row a pointer to the RowHeader so that it can propagate down change notifications
            DataGridRow parent = ParentRow;
 
            if (parent != null)
            {
                parent.RowHeader = this;
                SyncProperties();
            }
 
            // Grippers will now be in the Visual tree.
            HookupGripperEvents();
        }
 
        /// <summary>
        ///     Update all properties that get a value from the DataGrid
        /// </summary>
        /// <remarks>
        ///     See comment on DataGridRow.OnDataGridChanged
        /// </remarks>
        internal void SyncProperties()
        {
            DataGridHelper.TransferProperty(this, ContentProperty);
            DataGridHelper.TransferProperty(this, StyleProperty);
            DataGridHelper.TransferProperty(this, ContentTemplateProperty);
            DataGridHelper.TransferProperty(this, ContentTemplateSelectorProperty);
            DataGridHelper.TransferProperty(this, WidthProperty);
            CoerceValue(IsRowSelectedProperty);
 
            // We could be the first row now, so reset the thumb visibility.
            OnCanUserResizeRowsChanged();
        }
 
        #endregion
 
        #region Property Change Notification
 
        /// <summary>
        ///     Notifies parts that respond to changes in the properties.
        /// </summary>
        private static void OnNotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((DataGridRowHeader)d).NotifyPropertyChanged(d, e);
        }
 
        /// <summary>
        ///     Notification for column header-related DependencyProperty changes from the grid or from columns.
        /// </summary>
        internal void NotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == DataGridRow.HeaderProperty || e.Property == ContentProperty)
            {
                DataGridHelper.TransferProperty(this, ContentProperty);
            }
            else if (e.Property == DataGrid.RowHeaderStyleProperty || e.Property == DataGridRow.HeaderStyleProperty || e.Property == StyleProperty)
            {
                DataGridHelper.TransferProperty(this, StyleProperty);
            }
            else if (e.Property == DataGrid.RowHeaderTemplateProperty || e.Property == DataGridRow.HeaderTemplateProperty || e.Property == ContentTemplateProperty)
            {
                DataGridHelper.TransferProperty(this, ContentTemplateProperty);
            }
            else if (e.Property == DataGrid.RowHeaderTemplateSelectorProperty || e.Property == DataGridRow.HeaderTemplateSelectorProperty || e.Property == ContentTemplateSelectorProperty)
            {
                DataGridHelper.TransferProperty(this, ContentTemplateSelectorProperty);
            }
            else if (e.Property == DataGrid.RowHeaderWidthProperty || e.Property == WidthProperty)
            {
                DataGridHelper.TransferProperty(this, WidthProperty);
            }
            else if (e.Property == DataGridRow.IsSelectedProperty)
            {
                CoerceValue(IsRowSelectedProperty);
            }
            else if (e.Property == DataGrid.CanUserResizeRowsProperty)
            {
                OnCanUserResizeRowsChanged();
            }
            else if (e.Property == DataGrid.RowHeaderActualWidthProperty)
            {
                // When the RowHeaderActualWidth changes we need to re-measure to pick up the new value for DesiredSize
                this.InvalidateMeasure();
                this.InvalidateArrange();
 
                // If the DataGrid has not run layout the headers parent may not position the cells correctly when the header size changes.
                // This will cause the cells to be out of sync with the columns. To avoid this we will force a layout of the headers parent panel.
                var parent = this.Parent as UIElement;
                if (parent != null)
                {
                    parent.InvalidateMeasure();
                    parent.InvalidateArrange();
                }
            }
            else if (e.Property == DataGrid.CurrentItemProperty    ||
                     e.Property == DataGridRow.IsEditingProperty   ||
                     e.Property == DataGridRow.IsMouseOverProperty ||
                     e.Property == DataGrid.IsKeyboardFocusWithinProperty)
            {
                UpdateVisualState();
            }
        }
 
        #endregion
 
        #region Property Coercion callbacks
 
        /// <summary>
        ///     Coerces the Content property.  We're choosing a value between Row.Header and the Content property on RowHeader.
        /// </summary>
        private static object OnCoerceContent(DependencyObject d, object baseValue)
        {
            var header = d as DataGridRowHeader;
            return DataGridHelper.GetCoercedTransferPropertyValue(
                header,
                baseValue,
                ContentProperty,
                header.ParentRow,
                DataGridRow.HeaderProperty);
        }
 
        /// <summary>
        ///     Coerces the ContentTemplate property.
        /// </summary>
        private static object OnCoerceContentTemplate(DependencyObject d, object baseValue)
        {
            var header = d as DataGridRowHeader;
            var row = header.ParentRow;
            var dataGrid = row != null ? row.DataGridOwner : null;
            return DataGridHelper.GetCoercedTransferPropertyValue(
                header,
                baseValue,
                ContentTemplateProperty,
                row,
                DataGridRow.HeaderTemplateProperty,
                dataGrid,
                DataGrid.RowHeaderTemplateProperty);
        }
 
        /// <summary>
        ///     Coerces the ContentTemplateSelector property.
        /// </summary>
        private static object OnCoerceContentTemplateSelector(DependencyObject d, object baseValue)
        {
            var header = d as DataGridRowHeader;
            var row = header.ParentRow;
            var dataGrid = row != null ? row.DataGridOwner : null;
            return DataGridHelper.GetCoercedTransferPropertyValue(
                header,
                baseValue,
                ContentTemplateSelectorProperty,
                row,
                DataGridRow.HeaderTemplateSelectorProperty,
                dataGrid,
                DataGrid.RowHeaderTemplateSelectorProperty);
        }
 
        /// <summary>
        ///     Coerces the Style property.
        /// </summary>
        private static object OnCoerceStyle(DependencyObject d, object baseValue)
        {
            var header = d as DataGridRowHeader;
            return DataGridHelper.GetCoercedTransferPropertyValue(
                header,
                baseValue,
                StyleProperty,
                header.ParentRow,
                DataGridRow.HeaderStyleProperty,
                header.DataGridOwner,
                DataGrid.RowHeaderStyleProperty);
        }
 
        /// <summary>
        ///     Coerces the Width property.
        /// </summary>
        private static object OnCoerceWidth(DependencyObject d, object baseValue)
        {
            var header = d as DataGridRowHeader;
            return DataGridHelper.GetCoercedTransferPropertyValue(
                header,
                baseValue,
                WidthProperty,
                header.DataGridOwner,
                DataGrid.RowHeaderWidthProperty);
        }
 
        #endregion
 
                #region Visual State
 
        private bool IsRowCurrent
        {
            get
            {
                var row = ParentRow;
                if (row != null)
                {
                    var dataGrid = row.DataGridOwner;
                    if (dataGrid != null)
                    {
                        return dataGrid.IsCurrent(row);
                    }
                }
 
                return false;
            }
        }
 
        private bool IsRowEditing
        {
            get
            {
                var row = ParentRow;
                if (row != null)
                {
                    return row.IsEditing;
                }
 
                return false;
            }
        }
 
        private bool IsRowMouseOver
        {
            get
            {
                var row = ParentRow;
                if (row != null)
                {
                    return row.IsMouseOver;
                }
 
                return false;
            }
        }
 
        private bool IsDataGridKeyboardFocusWithin
        {
            get
            {
                var row = ParentRow;
                if (row != null)
                {
                    var dataGrid = row.DataGridOwner;
                    if (dataGrid != null)
                    {
                        return dataGrid.IsKeyboardFocusWithin;
                    }
                }
 
                return false;
            }
        }
 
        internal override void ChangeVisualState(bool useTransitions)
        {
            byte idealStateMappingIndex = 0;
 
            if (IsRowCurrent)
            {
                idealStateMappingIndex += 16;
            }
            if (IsRowSelected || IsRowEditing)
            {
                idealStateMappingIndex += 8;
            }
            if (IsRowEditing)
            {
                idealStateMappingIndex += 4;
            }
            if (IsRowMouseOver)
            {
                idealStateMappingIndex += 2;
            }
            if (IsDataGridKeyboardFocusWithin)
            {
                idealStateMappingIndex += 1;
            }
 
            byte stateCode = IdealStateMapping[idealStateMappingIndex];
            Debug.Assert(stateCode != DATAGRIDROWHEADER_stateNullCode);
 
            string storyboardName;
            while (stateCode != DATAGRIDROWHEADER_stateNullCode)
            {
                storyboardName = _stateNames[stateCode];
                if (VisualStateManager.GoToState(this, storyboardName, useTransitions))
                {
                    break;
                }
                else
                {
                    // The state wasn't implemented so fall back to the next one
                    stateCode = FallbackStateMapping[stateCode];
                }
            }
 
            ChangeValidationVisualState(useTransitions);
        }
 
        #endregion
 
        #region Selection
 
        /// <summary>
        ///     Indicates whether the owning DataGridRow is selected.
        /// </summary>
        [Bindable(true), Category("Appearance")]
        public bool IsRowSelected
        {
            get { return (bool)GetValue(IsRowSelectedProperty); }
        }
 
        private static readonly DependencyPropertyKey IsRowSelectedPropertyKey =
            DependencyProperty.RegisterReadOnly(
                "IsRowSelected",
                typeof(bool),
                typeof(DataGridRowHeader),
                new FrameworkPropertyMetadata(false, OnVisualStatePropertyChanged, new CoerceValueCallback(OnCoerceIsRowSelected)));
 
        /// <summary>
        ///     The DependencyProperty for the IsRowSelected property.
        /// </summary>
        public static readonly DependencyProperty IsRowSelectedProperty = IsRowSelectedPropertyKey.DependencyProperty;
 
        private static object OnCoerceIsRowSelected(DependencyObject d, object baseValue)
        {
            DataGridRowHeader header = (DataGridRowHeader)d;
            DataGridRow parent = header.ParentRow;
            if (parent != null)
            {
                return parent.IsSelected;
            }
 
            return baseValue;
        }
 
        /// <summary>
        ///     Called when the header is clicked.
        /// </summary>
        protected override void OnClick()
        {
            base.OnClick();
 
            // The base implementation took capture. This prevents us from doing
            // drag selection, so release it.
            if (Mouse.Captured == this)
            {
                ReleaseMouseCapture();
            }
 
            DataGrid dataGridOwner = DataGridOwner;
            DataGridRow parentRow = ParentRow;
            if ((dataGridOwner != null) && (parentRow != null))
            {
                dataGridOwner.HandleSelectionForRowHeaderAndDetailsInput(parentRow, /* startDragging = */ true);
            }
        }
 
        #endregion
 
        #region Row Resizing
 
        /// <summary>
        /// Find grippers and register drag events
        ///
        /// The default style for DataGridRowHeader is
        /// +-------------------------------+
        /// +-------------------------------+
        /// +           Gripper             +
        /// +-------------------------------+
        /// +            Header             +
        /// +-------------------------------+
        /// +           Gripper             +
        /// +-------------------------------+
        /// +-------------------------------+
        ///
        /// The reason we have two grippers is we can't extend the bottom gripper to straddle the line between two
        /// headers; the header below would render on top of it.
        /// We resize a Row by grabbing the gripper to the bottom; the top gripper thus adjusts the height of
        /// the row above it.
        /// </summary>
        private void HookupGripperEvents()
        {
            UnhookGripperEvents();
 
            _topGripper = GetTemplateChild(TopHeaderGripperTemplateName) as Thumb;
            _bottomGripper = GetTemplateChild(BottomHeaderGripperTemplateName) as Thumb;
 
            if (_topGripper != null)
            {
                _topGripper.DragStarted += new DragStartedEventHandler(OnRowHeaderGripperDragStarted);
                _topGripper.DragDelta += new DragDeltaEventHandler(OnRowHeaderResize);
                _topGripper.DragCompleted += new DragCompletedEventHandler(OnRowHeaderGripperDragCompleted);
                _topGripper.MouseDoubleClick += new MouseButtonEventHandler(OnGripperDoubleClicked);
                SetTopGripperVisibility();
            }
 
            if (_bottomGripper != null)
            {
                _bottomGripper.DragStarted += new DragStartedEventHandler(OnRowHeaderGripperDragStarted);
                _bottomGripper.DragDelta += new DragDeltaEventHandler(OnRowHeaderResize);
                _bottomGripper.DragCompleted += new DragCompletedEventHandler(OnRowHeaderGripperDragCompleted);
                _bottomGripper.MouseDoubleClick += new MouseButtonEventHandler(OnGripperDoubleClicked);
                SetBottomGripperVisibility();
            }
        }
 
        /// <summary>
        /// Clear gripper event
        /// </summary>
        private void UnhookGripperEvents()
        {
            if (_topGripper != null)
            {
                _topGripper.DragStarted -= new DragStartedEventHandler(OnRowHeaderGripperDragStarted);
                _topGripper.DragDelta -= new DragDeltaEventHandler(OnRowHeaderResize);
                _topGripper.DragCompleted -= new DragCompletedEventHandler(OnRowHeaderGripperDragCompleted);
                _topGripper.MouseDoubleClick -= new MouseButtonEventHandler(OnGripperDoubleClicked);
                _topGripper = null;
            }
 
            if (_bottomGripper != null)
            {
                _bottomGripper.DragStarted -= new DragStartedEventHandler(OnRowHeaderGripperDragStarted);
                _bottomGripper.DragDelta -= new DragDeltaEventHandler(OnRowHeaderResize);
                _bottomGripper.DragCompleted -= new DragCompletedEventHandler(OnRowHeaderGripperDragCompleted);
                _bottomGripper.MouseDoubleClick -= new MouseButtonEventHandler(OnGripperDoubleClicked);
                _bottomGripper = null;
            }
        }
 
        private void SetTopGripperVisibility()
        {
            if (_topGripper != null)
            {
                DataGrid dataGrid = DataGridOwner;
                DataGridRow parent = ParentRow;
                if (dataGrid != null && parent != null &&
                    dataGrid.CanUserResizeRows && dataGrid.Items.Count > 1 &&
                    !object.ReferenceEquals(parent.Item, dataGrid.Items[0]))
                {
                    _topGripper.Visibility = Visibility.Visible;
                }
                else
                {
                    _topGripper.Visibility = Visibility.Collapsed;
                }
            }
        }
 
        private void SetBottomGripperVisibility()
        {
            if (_bottomGripper != null)
            {
                DataGrid dataGrid = DataGridOwner;
                if (dataGrid != null && dataGrid.CanUserResizeRows)
                {
                    _bottomGripper.Visibility = Visibility.Visible;
                }
                else
                {
                    _bottomGripper.Visibility = Visibility.Collapsed;
                }
            }
        }
 
        /// <summary>
        ///     This is the row that the top gripper should be resizing.
        /// </summary>
        private DataGridRow PreviousRow
        {
            get
            {
                DataGridRow row = ParentRow;
                if (row != null)
                {
                    DataGrid dataGrid = row.DataGridOwner;
                    if (dataGrid != null)
                    {
                        int index = dataGrid.ItemContainerGenerator.IndexFromContainer(row);
                        if (index > 0)
                        {
                            return (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index - 1);
                        }
                    }
                }
 
                return null;
            }
        }
 
        /// <summary>
        /// Returns either this header or the one before it depending on which Gripper fired the event.
        /// </summary>
        /// <param name="sender"></param>
        private DataGridRow RowToResize(object gripper)
        {
            return (gripper == _bottomGripper) ? this.ParentRow : PreviousRow;
        }
 
        // Save the original height before resize
        private void OnRowHeaderGripperDragStarted(object sender, DragStartedEventArgs e)
        {
            DataGridRow rowToResize = RowToResize(sender);
            if (rowToResize != null)
            {
                rowToResize.OnRowResizeStarted();
                e.Handled = true;
            }
        }
 
        private void OnRowHeaderResize(object sender, DragDeltaEventArgs e)
        {
            DataGridRow rowToResize = RowToResize(sender);
            if (rowToResize != null)
            {
                rowToResize.OnRowResize(e.VerticalChange);
                e.Handled = true;
            }
        }
 
        // Restores original height if canceled.
        private void OnRowHeaderGripperDragCompleted(object sender, DragCompletedEventArgs e)
        {
            DataGridRow rowToResize = RowToResize(sender);
            if (rowToResize != null)
            {
                rowToResize.OnRowResizeCompleted(e.Canceled);
                e.Handled = true;
            }
        }
 
        private void OnGripperDoubleClicked(object sender, MouseButtonEventArgs e)
        {
            DataGridRow rowToResize = RowToResize(sender);
            if (rowToResize != null)
            {
                rowToResize.OnRowResizeReset();
                e.Handled = true;
            }
        }
 
        private void OnCanUserResizeRowsChanged()
        {
            SetTopGripperVisibility();
            SetBottomGripperVisibility();
        }
 
        #endregion
 
        #region Helpers
 
        internal DataGridRow ParentRow
        {
            get
            {
                return DataGridHelper.FindParent<DataGridRow>(this);
            }
        }
 
        private DataGrid DataGridOwner
        {
            get
            {
                DataGridRow parent = ParentRow;
                if (parent != null)
                {
                    return parent.DataGridOwner;
                }
 
                return null;
            }
        }
 
        #endregion
 
        private Thumb _topGripper, _bottomGripper;
        private const string TopHeaderGripperTemplateName = "PART_TopHeaderGripper";
        private const string BottomHeaderGripperTemplateName = "PART_BottomHeaderGripper";
    }
}