//--------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; using MS.Internal; using EGC = ExtendedGrid.Microsoft.Windows.Controls; namespace ExtendedGrid.Microsoft.Windows.Controls { /// /// Represents the header for each row of the DataGrid /// [TemplatePart(Name = "PART_TopHeaderGripper", Type = typeof(Thumb))] [TemplatePart(Name = "PART_BottomHeaderGripper", Type = typeof(Thumb))] public class DataGridRowHeader : ButtonBase { static DataGridRowHeader() { DefaultStyleKeyProperty.OverrideMetadata(typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(typeof(EGC.DataGridRowHeader))); ContentProperty.OverrideMetadata(typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceContent)); ContentTemplateProperty.OverrideMetadata(typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceContentTemplate)); ContentTemplateSelectorProperty.OverrideMetadata(typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceContentTemplateSelector)); StyleProperty.OverrideMetadata(typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceStyle)); WidthProperty.OverrideMetadata(typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceWidth)); ClickModeProperty.OverrideMetadata(typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(ClickMode.Press)); FocusableProperty.OverrideMetadata(typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(false)); } #region Automation protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() { return new EGC.DataGridRowHeaderAutomationPeer(this); } #endregion #region Layout /// /// Property that indicates the brush to use when drawing seperators between headers. /// public Brush SeparatorBrush { get { return (Brush)GetValue(SeparatorBrushProperty); } set { SetValue(SeparatorBrushProperty, value); } } /// /// DependencyProperty for SeperatorBrush. /// public static readonly DependencyProperty SeparatorBrushProperty = DependencyProperty.Register("SeparatorBrush", typeof(Brush), typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(null)); /// /// Property that indicates the Visibility for the header seperators. /// public Visibility SeparatorVisibility { get { return (Visibility)GetValue(SeparatorVisibilityProperty); } set { SetValue(SeparatorVisibilityProperty, value); } } /// /// DependencyProperty for SeperatorBrush. /// public static readonly DependencyProperty SeparatorVisibilityProperty = DependencyProperty.Register("SeparatorVisibility", typeof(Visibility), typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(Visibility.Visible)); /// /// Measure this element and it's child elements. /// /// /// DataGridRowHeader needs to update the DataGrid's RowHeaderActualWidth & use this as it's width so that they all end up the /// same size. /// /// /// protected override Size MeasureOverride(Size availableSize) { var baseSize = base.MeasureOverride(availableSize); if (DoubleUtil.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 EGC.DataGridRow parent = ParentRow; if (parent != null) { parent.RowHeader = this; SyncProperties(); } // Grippers will now be in the Visual tree. HookupGripperEvents(); } /// /// Update all properties that get a value from the DataGrid /// /// /// See comment on DataGridRow.OnDataGridChanged /// internal void SyncProperties() { EGC.DataGridHelper.TransferProperty(this, ContentProperty); EGC.DataGridHelper.TransferProperty(this, StyleProperty); EGC.DataGridHelper.TransferProperty(this, ContentTemplateProperty); EGC.DataGridHelper.TransferProperty(this, ContentTemplateSelectorProperty); EGC.DataGridHelper.TransferProperty(this, WidthProperty); CoerceValue(IsRowSelectedProperty); // We could be the first row now, so reset the thumb visibility. OnCanUserResizeRowsChanged(); } #endregion #region Property Change Notification /// /// Notifies parts that respond to changes in the properties. /// private static void OnNotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((EGC.DataGridRowHeader)d).NotifyPropertyChanged(d, e); } /// /// Notification for column header-related DependencyProperty changes from the grid or from columns. /// internal void NotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (e.Property == EGC.DataGridRow.HeaderProperty || e.Property == ContentProperty) { EGC.DataGridHelper.TransferProperty(this, ContentProperty); } else if (e.Property == EGC.DataGrid.RowHeaderStyleProperty || e.Property == EGC.DataGridRow.HeaderStyleProperty || e.Property == StyleProperty) { EGC.DataGridHelper.TransferProperty(this, StyleProperty); } else if (e.Property == EGC.DataGrid.RowHeaderTemplateProperty || e.Property == EGC.DataGridRow.HeaderTemplateProperty || e.Property == ContentTemplateProperty) { EGC.DataGridHelper.TransferProperty(this, ContentTemplateProperty); } else if (e.Property == EGC.DataGrid.RowHeaderTemplateSelectorProperty || e.Property == EGC.DataGridRow.HeaderTemplateSelectorProperty || e.Property == ContentTemplateSelectorProperty) { EGC.DataGridHelper.TransferProperty(this, ContentTemplateSelectorProperty); } else if (e.Property == EGC.DataGrid.RowHeaderWidthProperty || e.Property == WidthProperty) { EGC.DataGridHelper.TransferProperty(this, WidthProperty); } else if (e.Property == EGC.DataGridRow.IsSelectedProperty) { CoerceValue(IsRowSelectedProperty); } else if (e.Property == EGC.DataGrid.CanUserResizeRowsProperty) { OnCanUserResizeRowsChanged(); } else if (e.Property == EGC.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(); } } } #endregion #region Property Coercion callbacks /// /// Coerces the Content property. We're choosing a value between Row.Header and the Content property on RowHeader. /// private static object OnCoerceContent(DependencyObject d, object baseValue) { var header = d as EGC.DataGridRowHeader; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( header, baseValue, ContentProperty, header.ParentRow, EGC.DataGridRow.HeaderProperty); } /// /// Coerces the ContentTemplate property. /// private static object OnCoerceContentTemplate(DependencyObject d, object baseValue) { var header = d as EGC.DataGridRowHeader; var row = header.ParentRow; var dataGrid = row != null ? row.DataGridOwner : null; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( header, baseValue, ContentTemplateProperty, row, EGC.DataGridRow.HeaderTemplateProperty, dataGrid, EGC.DataGrid.RowHeaderTemplateProperty); } /// /// Coerces the ContentTemplateSelector property. /// private static object OnCoerceContentTemplateSelector(DependencyObject d, object baseValue) { var header = d as EGC.DataGridRowHeader; var row = header.ParentRow; var dataGrid = row != null ? row.DataGridOwner : null; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( header, baseValue, ContentTemplateSelectorProperty, row, EGC.DataGridRow.HeaderTemplateSelectorProperty, dataGrid, EGC.DataGrid.RowHeaderTemplateSelectorProperty); } /// /// Coerces the Style property. /// private static object OnCoerceStyle(DependencyObject d, object baseValue) { var header = d as EGC.DataGridRowHeader; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( header, baseValue, StyleProperty, header.ParentRow, EGC.DataGridRow.HeaderStyleProperty, header.DataGridOwner, EGC.DataGrid.RowHeaderStyleProperty); } /// /// Coerces the Width property. /// private static object OnCoerceWidth(DependencyObject d, object baseValue) { var header = d as EGC.DataGridRowHeader; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( header, baseValue, WidthProperty, header.DataGridOwner, EGC.DataGrid.RowHeaderWidthProperty); } #endregion #region Selection /// /// Indicates whether the owning DataGridRow is selected. /// [Bindable(true), Category("Appearance")] public bool IsRowSelected { get { return (bool)GetValue(IsRowSelectedProperty); } } private static readonly DependencyPropertyKey IsRowSelectedPropertyKey = DependencyProperty.RegisterReadOnly( "IsRowSelected", typeof(bool), typeof(EGC.DataGridRowHeader), new FrameworkPropertyMetadata(false, null, new CoerceValueCallback(OnCoerceIsRowSelected))); /// /// The DependencyProperty for the IsRowSelected property. /// public static readonly DependencyProperty IsRowSelectedProperty = IsRowSelectedPropertyKey.DependencyProperty; private static object OnCoerceIsRowSelected(DependencyObject d, object baseValue) { EGC.DataGridRowHeader header = (EGC.DataGridRowHeader)d; EGC.DataGridRow parent = header.ParentRow; if (parent != null) { return parent.IsSelected; } return baseValue; } /// /// Called when the header is clicked. /// 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(); } EGC.DataGrid dataGridOwner = DataGridOwner; EGC.DataGridRow parentRow = ParentRow; if ((dataGridOwner != null) && (parentRow != null)) { dataGridOwner.HandleSelectionForRowHeaderAndDetailsInput(parentRow, /* startDragging = */ true); } } #endregion #region Row Resizing /// /// 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. /// 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(); } } /// /// Clear gripper event /// 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) { EGC.DataGrid dataGrid = DataGridOwner; EGC.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) { EGC.DataGrid dataGrid = DataGridOwner; if (dataGrid != null && dataGrid.CanUserResizeRows) { _bottomGripper.Visibility = Visibility.Visible; } else { _bottomGripper.Visibility = Visibility.Collapsed; } } } /// /// This is the row that the top gripper should be resizing. /// private EGC.DataGridRow PreviousRow { get { EGC.DataGridRow row = ParentRow; if (row != null) { EGC.DataGrid dataGrid = row.DataGridOwner; if (dataGrid != null) { int index = dataGrid.ItemContainerGenerator.IndexFromContainer(row); if (index > 0) { return (EGC.DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index - 1); } } } return null; } } /// /// Returns either this header or the one before it depending on which Gripper fired the event. /// /// private EGC.DataGridRow RowToResize(object gripper) { return (gripper == _bottomGripper) ? this.ParentRow : PreviousRow; } // Save the original height before resize private void OnRowHeaderGripperDragStarted(object sender, DragStartedEventArgs e) { EGC.DataGridRow rowToResize = RowToResize(sender); if (rowToResize != null) { rowToResize.OnRowResizeStarted(); e.Handled = true; } } private void OnRowHeaderResize(object sender, DragDeltaEventArgs e) { EGC.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) { EGC.DataGridRow rowToResize = RowToResize(sender); if (rowToResize != null) { rowToResize.OnRowResizeCompleted(e.Canceled); e.Handled = true; } } private void OnGripperDoubleClicked(object sender, MouseButtonEventArgs e) { EGC.DataGridRow rowToResize = RowToResize(sender); if (rowToResize != null) { rowToResize.OnRowResizeReset(); e.Handled = true; } } private void OnCanUserResizeRowsChanged() { SetTopGripperVisibility(); SetBottomGripperVisibility(); } #endregion #region Helpers internal EGC.DataGridRow ParentRow { get { return EGC.DataGridHelper.FindParent(this); } } private EGC.DataGrid DataGridOwner { get { EGC.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"; } }