//--------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Windows; using System.Windows.Automation.Peers; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Threading; using EGC = ExtendedGrid.Microsoft.Windows.Controls; using MS.Internal; namespace ExtendedGrid.Microsoft.Windows.Controls { /// /// A control for displaying a row of the DataGrid. /// A row represents a data item in the DataGrid. /// A row displays a cell for each column of the DataGrid. /// /// The data item for the row is added n times to the row's Items collection, /// where n is the number of columns in the DataGrid. /// public class DataGridRow : Control { #region Constructors /// /// Instantiates global information. /// static DataGridRow() { VisibilityProperty.OverrideMetadata(typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, OnCoerceVisibility)); DefaultStyleKeyProperty.OverrideMetadata(typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(typeof(EGC.DataGridRow))); ItemsPanelProperty.OverrideMetadata(typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(new ItemsPanelTemplate(new FrameworkElementFactory(typeof(EGC.DataGridCellsPanel))))); FocusableProperty.OverrideMetadata(typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(false)); BackgroundProperty.OverrideMetadata(typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, OnNotifyRowPropertyChanged, OnCoerceBackground)); BindingGroupProperty.OverrideMetadata(typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(OnNotifyRowPropertyChanged)); // Set SnapsToDevicePixels to true so that this element can draw grid lines. The metadata options are so that the property value doesn't inherit down the tree from here. SnapsToDevicePixelsProperty.OverrideMetadata(typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange)); } /// /// Instantiates a new instance of this class. /// public DataGridRow() { _tracker = new EGC.ContainerTracking(this); } #endregion #region Data Item /// /// The item that the row represents. This item is an entry in the list of items from the DataGrid. /// From this item, cells are generated for each column in the DataGrid. /// public object Item { get { return GetValue(ItemProperty); } set { SetValue(ItemProperty, value); } } /// /// The DependencyProperty for the Item property. /// public static readonly DependencyProperty ItemProperty = DependencyProperty.Register("Item", typeof(object), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnNotifyRowPropertyChanged))); /// /// Called when the value of the Item property changes. /// /// The old value of Item. /// The new value of Item. protected virtual void OnItemChanged(object oldItem, object newItem) { EGC.DataGridCellsPresenter cellsPresenter = CellsPresenter; if (cellsPresenter != null) { cellsPresenter.Item = newItem; } // Update the event source if AutomationPeer exist EGC.DataGridRowAutomationPeer peer = UIElementAutomationPeer.FromElement(this) as EGC.DataGridRowAutomationPeer; if (peer != null) { peer.UpdateEventSource(); } } #endregion #region Template /// /// A template that will generate the panel that arranges the cells in this row. /// /// /// The template for the row should contain an ItemsControl that template binds to this property. /// public ItemsPanelTemplate ItemsPanel { get { return (ItemsPanelTemplate)GetValue(ItemsPanelProperty); } set { SetValue(ItemsPanelProperty, value); } } /// /// The DependencyProperty that represents the ItemsPanel property. /// public static readonly DependencyProperty ItemsPanelProperty = ItemsControl.ItemsPanelProperty.AddOwner(typeof(EGC.DataGridRow)); /// /// Clears the CellsPresenter and DetailsPresenter references on Template change. /// protected override void OnTemplateChanged(ControlTemplate oldTemplate, ControlTemplate newTemplate) { base.OnTemplateChanged(oldTemplate, newTemplate); CellsPresenter = null; DetailsPresenter = null; } #endregion #region Row Header /// /// The object representing the Row Header. /// public object Header { get { return GetValue(HeaderProperty); } set { SetValue(HeaderProperty, value); } } /// /// The DependencyProperty for the Header property. /// public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(object), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnNotifyRowPropertyChanged))); /// /// Called when the value of the Header property changes. /// /// The old value of Header /// The new value of Header protected virtual void OnHeaderChanged(object oldHeader, object newHeader) { } /// /// The object representing the Row Header style. /// public Style HeaderStyle { get { return (Style)GetValue(HeaderStyleProperty); } set { SetValue(HeaderStyleProperty, value); } } /// /// The DependencyProperty for the HeaderStyle property. /// public static readonly DependencyProperty HeaderStyleProperty = DependencyProperty.Register("HeaderStyle", typeof(Style), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, OnNotifyRowAndRowHeaderPropertyChanged, OnCoerceHeaderStyle)); /// /// The object representing the Row Header template. /// public DataTemplate HeaderTemplate { get { return (DataTemplate)GetValue(HeaderTemplateProperty); } set { SetValue(HeaderTemplateProperty, value); } } /// /// The DependencyProperty for the HeaderTemplate property. /// public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, OnNotifyRowAndRowHeaderPropertyChanged, OnCoerceHeaderTemplate)); /// /// The object representing the Row Header template selector. /// public DataTemplateSelector HeaderTemplateSelector { get { return (DataTemplateSelector)GetValue(HeaderTemplateSelectorProperty); } set { SetValue(HeaderTemplateSelectorProperty, value); } } /// /// The DependencyProperty for the HeaderTemplateSelector property. /// public static readonly DependencyProperty HeaderTemplateSelectorProperty = DependencyProperty.Register("HeaderTemplateSelector", typeof(DataTemplateSelector), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, OnNotifyRowAndRowHeaderPropertyChanged, OnCoerceHeaderTemplateSelector)); /// /// Template used to visually indicate an error in row Validation. /// public ControlTemplate ValidationErrorTemplate { get { return (ControlTemplate)GetValue(ValidationErrorTemplateProperty); } set { SetValue(ValidationErrorTemplateProperty, value); } } /// /// DependencyProperty for the ValidationErrorTemplate property. /// public static readonly DependencyProperty ValidationErrorTemplateProperty = DependencyProperty.Register("ValidationErrorTemplate", typeof(ControlTemplate), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, OnNotifyRowPropertyChanged, OnCoerceValidationErrorTemplate)); #endregion #region Row Details /// /// The object representing the Row Details template. /// public DataTemplate DetailsTemplate { get { return (DataTemplate)GetValue(DetailsTemplateProperty); } set { SetValue(DetailsTemplateProperty, value); } } /// /// The DependencyProperty for the DetailsTemplate property. /// public static readonly DependencyProperty DetailsTemplateProperty = DependencyProperty.Register("DetailsTemplate", typeof(DataTemplate), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, OnNotifyRowAndDetailsPropertyChanged, OnCoerceDetailsTemplate)); /// /// The object representing the Row Details template selector. /// public DataTemplateSelector DetailsTemplateSelector { get { return (DataTemplateSelector)GetValue(DetailsTemplateSelectorProperty); } set { SetValue(DetailsTemplateSelectorProperty, value); } } /// /// The DependencyProperty for the DetailsTemplateSelector property. /// public static readonly DependencyProperty DetailsTemplateSelectorProperty = DependencyProperty.Register("DetailsTemplateSelector", typeof(DataTemplateSelector), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(null, OnNotifyRowAndDetailsPropertyChanged, OnCoerceDetailsTemplateSelector)); /// /// The Visibility of the Details presenter /// public Visibility DetailsVisibility { get { return (Visibility)GetValue(DetailsVisibilityProperty); } set { SetValue(DetailsVisibilityProperty, value); } } /// /// The DependencyProperty for the DetailsVisibility property. /// public static readonly DependencyProperty DetailsVisibilityProperty = DependencyProperty.Register("DetailsVisibility", typeof(Visibility), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(Visibility.Collapsed, OnNotifyDetailsVisibilityChanged, OnCoerceDetailsVisibility)); internal enum RowDetailsEventStatus : byte { Disabled, // LoadingRowDetails should not be fired in response to changes to DetailsVisibilty. Pending, // LoadingRowDetails can be called if the row is loaded or if DetailsVisibilty becomes visible. Loaded, // LoadingRowDetails has been called and UnloadingRowDetails can be called. } internal RowDetailsEventStatus DetailsEventStatus { get { return _detailsEventStatus; } set { _detailsEventStatus = value; } } #endregion #region Row Generation /// /// We can't override the metadata for a read only property, so we'll get the property change notification for AlternationIndexProperty this way instead. /// protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); if (e.Property == AlternationIndexProperty) { NotifyPropertyChanged(this, e, NotificationTarget.Rows); } } /// /// Prepares a row container for active use. /// /// /// Instantiates or updates a MultipleCopiesCollection ItemsSource in /// order that cells be generated. /// /// The data item that the row represents. /// The DataGrid owner. internal void PrepareRow(object item, EGC.DataGrid owningDataGrid) { bool fireOwnerChanged = (_owner != owningDataGrid); Debug.Assert(_owner == null || _owner == owningDataGrid, "_owner should be null before PrepareRow is called or the same as the owningDataGrid."); bool forcePrepareCells = false; _owner = owningDataGrid; if (this != item) { if (Item != item) { Item = item; } else { forcePrepareCells = true; } } if (IsEditing) { // If IsEditing was left on and this container was recycled, reset it here. IsEditing = false; } // Since we just changed _owner we need to invalidate all child properties that rely on a value supplied by the DataGrid. // A common scenario is when a recycled Row was detached from the visual tree and has just been reattached (we always clear out the // owner when recycling a container). if (fireOwnerChanged) { SyncProperties(forcePrepareCells); } // Re-run validation, but wait until Binding has occured. Dispatcher.BeginInvoke(new DispatcherOperationCallback(DelayedValidateWithoutUpdate), DispatcherPriority.DataBind, BindingGroup); } /// /// Clears the row of references. /// internal void ClearRow(EGC.DataGrid owningDataGrid) { Debug.Assert(_owner == owningDataGrid, "_owner should be the same as the DataGrid that is clearing the row."); var cellsPresenter = CellsPresenter; if (cellsPresenter != null) { PersistAttachedItemValue(cellsPresenter, EGC.DataGridCellsPresenter.HeightProperty); } PersistAttachedItemValue(this, DetailsVisibilityProperty); _owner = null; } private void PersistAttachedItemValue(DependencyObject objectWithProperty, DependencyProperty property) { ValueSource valueSource = DependencyPropertyHelper.GetValueSource(objectWithProperty, property); if (valueSource.BaseValueSource == BaseValueSource.Local) { // attach the local value to the item so it can be restored later. _owner.ItemAttachedStorage.SetValue(Item, property, objectWithProperty.GetValue(property)); objectWithProperty.ClearValue(property); } } private void RestoreAttachedItemValue(DependencyObject objectWithProperty, DependencyProperty property) { object value; if (_owner.ItemAttachedStorage.TryGetValue(Item, property, out value)) { objectWithProperty.SetValue(property, value); } } /// /// Used by the DataGrid owner to send notifications to the row container. /// internal EGC.ContainerTracking Tracker { get { return _tracker; } } #endregion #region Row Resizing internal void OnRowResizeStarted() { var cellsPresenter = CellsPresenter; if (cellsPresenter != null) { _cellsPresenterResizeHeight = cellsPresenter.Height; } } internal void OnRowResize(double changeAmount) { var cellsPresenter = CellsPresenter; if (cellsPresenter != null) { double newHeight = cellsPresenter.ActualHeight + changeAmount; // clamp the CellsPresenter size to the RowHeader size or MinHeight because the header wont shrink any smaller. double minHeight = Math.Max(RowHeader.DesiredSize.Height, MinHeight); if (DoubleUtil.LessThan(newHeight, minHeight)) { newHeight = minHeight; } // clamp the CellsPresenter size to the MaxHeight of Row, because row wouldn't grow any larger double maxHeight = MaxHeight; if (DoubleUtil.GreaterThan(newHeight, maxHeight)) { newHeight = maxHeight; } cellsPresenter.Height = newHeight; } } internal void OnRowResizeCompleted(bool canceled) { var cellsPresenter = CellsPresenter; if (cellsPresenter != null && canceled) { cellsPresenter.Height = _cellsPresenterResizeHeight; } } internal void OnRowResizeReset() { var cellsPresenter = CellsPresenter; if (cellsPresenter != null) { cellsPresenter.ClearValue(EGC.DataGridCellsPresenter.HeightProperty); if (_owner != null) { _owner.ItemAttachedStorage.ClearValue(Item, EGC.DataGridCellsPresenter.HeightProperty); } } } #endregion #region Columns Notification /// /// Notification from the DataGrid that the columns collection has changed. /// /// The columns collection. /// The event arguments from the collection's change event. protected internal virtual void OnColumnsChanged(ObservableCollection columns, NotifyCollectionChangedEventArgs e) { EGC.DataGridCellsPresenter cellsPresenter = CellsPresenter; if (cellsPresenter != null) { cellsPresenter.OnColumnsChanged(columns, e); } } #endregion #region Property Coercion private static object OnCoerceHeaderStyle(DependencyObject d, object baseValue) { var row = (EGC.DataGridRow)d; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( row, baseValue, HeaderStyleProperty, row.DataGridOwner, EGC.DataGrid.RowHeaderStyleProperty); } private static object OnCoerceHeaderTemplate(DependencyObject d, object baseValue) { var row = (EGC.DataGridRow)d; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( row, baseValue, HeaderTemplateProperty, row.DataGridOwner, EGC.DataGrid.RowHeaderTemplateProperty); } private static object OnCoerceHeaderTemplateSelector(DependencyObject d, object baseValue) { var row = (EGC.DataGridRow)d; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( row, baseValue, HeaderTemplateSelectorProperty, row.DataGridOwner, EGC.DataGrid.RowHeaderTemplateSelectorProperty); } private static object OnCoerceBackground(DependencyObject d, object baseValue) { var row = (EGC.DataGridRow)d; object coercedValue = baseValue; switch (row.AlternationIndex) { case 0: coercedValue = EGC.DataGridHelper.GetCoercedTransferPropertyValue( row, baseValue, BackgroundProperty, row.DataGridOwner, EGC.DataGrid.RowBackgroundProperty); break; case 1: coercedValue = EGC.DataGridHelper.GetCoercedTransferPropertyValue( row, baseValue, BackgroundProperty, row.DataGridOwner, EGC.DataGrid.AlternatingRowBackgroundProperty); break; } return coercedValue; } private static object OnCoerceValidationErrorTemplate(DependencyObject d, object baseValue) { var row = (EGC.DataGridRow)d; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( row, baseValue, ValidationErrorTemplateProperty, row.DataGridOwner, EGC.DataGrid.RowValidationErrorTemplateProperty); } private static object OnCoerceDetailsTemplate(DependencyObject d, object baseValue) { var row = (EGC.DataGridRow)d; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( row, baseValue, DetailsTemplateProperty, row.DataGridOwner, EGC.DataGrid.RowDetailsTemplateProperty); } private static object OnCoerceDetailsTemplateSelector(DependencyObject d, object baseValue) { var row = (EGC.DataGridRow)d; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( row, baseValue, DetailsTemplateSelectorProperty, row.DataGridOwner, EGC.DataGrid.RowDetailsTemplateSelectorProperty); } private static object OnCoerceDetailsVisibility(DependencyObject d, object baseValue) { var row = (EGC.DataGridRow)d; object visibility = EGC.DataGridHelper.GetCoercedTransferPropertyValue( row, baseValue, DetailsVisibilityProperty, row.DataGridOwner, EGC.DataGrid.RowDetailsVisibilityModeProperty); if (visibility is EGC.DataGridRowDetailsVisibilityMode) { var visibilityMode = (EGC.DataGridRowDetailsVisibilityMode)visibility; var hasDetailsTemplate = row.DetailsTemplate != null || row.DetailsTemplateSelector != null; var isRealItem = row.Item != CollectionView.NewItemPlaceholder; switch (visibilityMode) { case EGC.DataGridRowDetailsVisibilityMode.Collapsed: visibility = Visibility.Collapsed; break; case EGC.DataGridRowDetailsVisibilityMode.Visible: visibility = hasDetailsTemplate && isRealItem ? Visibility.Visible : Visibility.Collapsed; break; case EGC.DataGridRowDetailsVisibilityMode.VisibleWhenSelected: visibility = row.IsSelected && hasDetailsTemplate && isRealItem ? Visibility.Visible : Visibility.Collapsed; break; default: visibility = Visibility.Collapsed; break; } } return visibility; } /// /// Coerces Visibility so that the NewItemPlaceholder doesn't show up while you're entering a new Item /// private static object OnCoerceVisibility(DependencyObject d, object baseValue) { var row = (EGC.DataGridRow)d; var owningDataGrid = row.DataGridOwner; if (row.Item == CollectionView.NewItemPlaceholder && owningDataGrid != null) { return owningDataGrid.PlaceholderVisibility; } else { return baseValue; } } #endregion #region Notification Propagation private static void OnNotifyRowPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as EGC.DataGridRow).NotifyPropertyChanged(d, e, NotificationTarget.Rows); } private static void OnNotifyRowAndRowHeaderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as EGC.DataGridRow).NotifyPropertyChanged(d, e, NotificationTarget.Rows | NotificationTarget.RowHeaders); } private static void OnNotifyRowAndDetailsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as EGC.DataGridRow).NotifyPropertyChanged(d, e, NotificationTarget.Rows | NotificationTarget.DetailsPresenter); } private static void OnNotifyDetailsVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var row = (EGC.DataGridRow)d; // Notify the DataGrid at Loaded priority so the template has time to expland. Dispatcher.CurrentDispatcher.BeginInvoke(new DispatcherOperationCallback(DelayedRowDetailsVisibilityChanged), DispatcherPriority.Loaded, row); row.NotifyPropertyChanged(d, e, NotificationTarget.Rows | NotificationTarget.DetailsPresenter); } /// /// Notifies the DataGrid that the visibility is changed. This is intended to be Invoked at lower than Layout priority to give the template time to expand. /// private static object DelayedRowDetailsVisibilityChanged(object arg) { var row = (EGC.DataGridRow)arg; var dataGrid = row.DataGridOwner; var detailsElement = row.DetailsPresenter != null ? row.DetailsPresenter.DetailsElement : null; if (dataGrid != null) { var detailsEventArgs = new EGC.DataGridRowDetailsEventArgs(row, detailsElement); dataGrid.OnRowDetailsVisibilityChanged(detailsEventArgs); } return null; } /// /// Set by the CellsPresenter when it is created. Used by the Row to send down property change notifications. /// internal EGC.DataGridCellsPresenter CellsPresenter { get { return _cellsPresenter; } set { _cellsPresenter = value; } } /// /// Set by the DetailsPresenter when it is created. Used by the Row to send down property change notifications. /// internal EGC.DataGridDetailsPresenter DetailsPresenter { get { return _detailsPresenter; } set { _detailsPresenter = value; } } /// /// Set by the RowHeader when it is created. Used by the Row to send down property change notifications. /// internal EGC.DataGridRowHeader RowHeader { get { return _rowHeader; } set { _rowHeader = value; } } /// /// General notification for DependencyProperty changes from the grid or from columns. /// internal void NotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e, NotificationTarget target) { NotifyPropertyChanged(d, string.Empty, e, target); } /// /// General notification for DependencyProperty changes from the grid or from columns. /// internal void NotifyPropertyChanged(DependencyObject d, string propertyName, DependencyPropertyChangedEventArgs e, NotificationTarget target) { if (EGC.DataGridHelper.ShouldNotifyRows(target)) { if (e.Property == EGC.DataGrid.RowBackgroundProperty || e.Property == EGC.DataGrid.AlternatingRowBackgroundProperty || e.Property == BackgroundProperty || e.Property == AlternationIndexProperty) { EGC.DataGridHelper.TransferProperty(this, BackgroundProperty); } else if (e.Property == EGC.DataGrid.RowHeaderStyleProperty || e.Property == HeaderStyleProperty) { EGC.DataGridHelper.TransferProperty(this, HeaderStyleProperty); } else if (e.Property == EGC.DataGrid.RowHeaderTemplateProperty || e.Property == HeaderTemplateProperty) { EGC.DataGridHelper.TransferProperty(this, HeaderTemplateProperty); } else if (e.Property == EGC.DataGrid.RowHeaderTemplateSelectorProperty || e.Property == HeaderTemplateSelectorProperty) { EGC.DataGridHelper.TransferProperty(this, HeaderTemplateSelectorProperty); } else if (e.Property == EGC.DataGrid.RowValidationErrorTemplateProperty || e.Property == ValidationErrorTemplateProperty) { EGC.DataGridHelper.TransferProperty(this, ValidationErrorTemplateProperty); } else if (e.Property == EGC.DataGrid.RowDetailsTemplateProperty || e.Property == DetailsTemplateProperty) { EGC.DataGridHelper.TransferProperty(this, DetailsTemplateProperty); EGC.DataGridHelper.TransferProperty(this, DetailsVisibilityProperty); } else if (e.Property == EGC.DataGrid.RowDetailsTemplateSelectorProperty || e.Property == DetailsTemplateSelectorProperty) { EGC.DataGridHelper.TransferProperty(this, DetailsTemplateSelectorProperty); EGC.DataGridHelper.TransferProperty(this, DetailsVisibilityProperty); } else if (e.Property == EGC.DataGrid.RowDetailsVisibilityModeProperty || e.Property == DetailsVisibilityProperty || e.Property == IsSelectedProperty) { EGC.DataGridHelper.TransferProperty(this, DetailsVisibilityProperty); } else if (e.Property == ItemProperty) { OnItemChanged(e.OldValue, e.NewValue); } else if (e.Property == HeaderProperty) { OnHeaderChanged(e.OldValue, e.NewValue); } else if (e.Property == BindingGroupProperty) { // Re-run validation, but wait until Binding has occured. Dispatcher.BeginInvoke(new DispatcherOperationCallback(DelayedValidateWithoutUpdate), DispatcherPriority.DataBind, e.NewValue); } } if (EGC.DataGridHelper.ShouldNotifyDetailsPresenter(target)) { if (DetailsPresenter != null) { DetailsPresenter.NotifyPropertyChanged(d, e); } } if (EGC.DataGridHelper.ShouldNotifyCellsPresenter(target) || EGC.DataGridHelper.ShouldNotifyCells(target) || EGC.DataGridHelper.ShouldRefreshCellContent(target)) { EGC.DataGridCellsPresenter cellsPresenter = CellsPresenter; if (cellsPresenter != null) { cellsPresenter.NotifyPropertyChanged(d, propertyName, e, target); } } if (EGC.DataGridHelper.ShouldNotifyRowHeaders(target) && RowHeader != null) { RowHeader.NotifyPropertyChanged(d, e); } } private object DelayedValidateWithoutUpdate(object arg) { // Only validate if we have an Item. var bindingGroup = (BindingGroup)arg; if (bindingGroup != null && bindingGroup.Items.Count > 0) { bindingGroup.ValidateWithoutUpdate(); } return null; } /// /// Fired when the Row is attached to the DataGrid. The scenario here is if the user is scrolling and /// the Row is a recycled container that was just added back to the visual tree. Properties that rely on a value from /// the Grid should be reevaluated because they may be stale. /// /// /// Properties can obviously be stale if the DataGrid's value changes while the row is disconnected. They can also /// be stale for unobvious reasons. /// /// For example, the Style property is invalidated when we detect a new Visual parent. This happens for /// elements in the row (such as the RowHeader) before Prepare is called on the Row. The coercion callback /// will thus be unable to find the DataGrid and will return the wrong value. /// /// There is a potential for perf work here. If we know a DP isn't invalidated when the visual tree is reconnected /// and we know that the Grid hasn't modified that property then its value is likely fine. We could also cache whether /// or not the Grid's property is the one that's winning. If not, no need to redo the coercion. This notification /// is pretty fast already and thus not worth the work for now. /// private void SyncProperties(bool forcePrepareCells) { // Coerce all properties on Row that depend on values from the DataGrid // Style is ok since it's equivalent to ItemContainerStyle and has already been invalidated. EGC.DataGridHelper.TransferProperty(this, BackgroundProperty); EGC.DataGridHelper.TransferProperty(this, HeaderStyleProperty); EGC.DataGridHelper.TransferProperty(this, HeaderTemplateProperty); EGC.DataGridHelper.TransferProperty(this, HeaderTemplateSelectorProperty); EGC.DataGridHelper.TransferProperty(this, ValidationErrorTemplateProperty); EGC.DataGridHelper.TransferProperty(this, DetailsTemplateProperty); EGC.DataGridHelper.TransferProperty(this, DetailsTemplateSelectorProperty); EGC.DataGridHelper.TransferProperty(this, DetailsVisibilityProperty); CoerceValue(VisibilityProperty); // Handle NewItemPlaceholder case RestoreAttachedItemValue(this, DetailsVisibilityProperty); var cellsPresenter = CellsPresenter; if (cellsPresenter != null) { cellsPresenter.SyncProperties(forcePrepareCells); RestoreAttachedItemValue(cellsPresenter, EGC.DataGridCellsPresenter.HeightProperty); } if (DetailsPresenter != null) { DetailsPresenter.SyncProperties(); } if (RowHeader != null) { RowHeader.SyncProperties(); } } #endregion #region Alternation /// /// AlternationIndex is set on containers generated for an ItemsControl, when /// the ItemsControl's AlternationCount property is positive. The AlternationIndex /// lies in the range [0, AlternationCount), and adjacent containers always get /// assigned different values. /// /// /// Exposes ItemsControl.AlternationIndexProperty attached property as a direct property. /// public int AlternationIndex { get { return (int)GetValue(AlternationIndexProperty); } } /// /// DependencyProperty for AlternationIndex. /// /// /// Same as ItemsControl.AlternationIndexProperty. /// public static readonly DependencyProperty AlternationIndexProperty = ItemsControl.AlternationIndexProperty.AddOwner(typeof(EGC.DataGridRow)); #endregion #region Selection /// /// Indicates whether this DataGridRow is selected. /// /// /// When IsSelected is set to true, an InvalidOperationException may be /// thrown if the value of the SelectionUnit property on the parent DataGrid /// prevents selection or rows. /// [Bindable(true), Category("Appearance")] public bool IsSelected { get { return (bool)GetValue(IsSelectedProperty); } set { SetValue(IsSelectedProperty, value); } } /// /// The DependencyProperty for the IsSelected property. /// public static readonly DependencyProperty IsSelectedProperty = Selector.IsSelectedProperty.AddOwner( typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, new PropertyChangedCallback(OnIsSelectedChanged))); private static void OnIsSelectedChanged(object sender, DependencyPropertyChangedEventArgs e) { EGC.DataGridRow row = (EGC.DataGridRow)sender; bool isSelected = (bool)e.NewValue; if (isSelected && !row.IsSelectable) { throw new InvalidOperationException(SR.Get(SRID.DataGridRow_CannotSelectRowWhenCells)); } EGC.DataGrid grid = row.DataGridOwner; if (grid != null && row.DataContext != null) { EGC.DataGridAutomationPeer gridPeer = UIElementAutomationPeer.FromElement(grid) as EGC.DataGridAutomationPeer; if (gridPeer != null) { EGC.DataGridItemAutomationPeer rowItemPeer = gridPeer.GetOrCreateItemPeer(row.DataContext); if (rowItemPeer != null) { rowItemPeer.RaisePropertyChangedEvent( System.Windows.Automation.SelectionItemPatternIdentifiers.IsSelectedProperty, (bool)e.OldValue, isSelected); } } } // Update the header's IsRowSelected property row.NotifyPropertyChanged(row, e, NotificationTarget.Rows | NotificationTarget.RowHeaders); // This will raise the appropriate selection event, which will // bubble to the DataGrid. The base class Selector code will listen // for these events and will update SelectedItems as necessary. row.RaiseSelectionChangedEvent(isSelected); } private void RaiseSelectionChangedEvent(bool isSelected) { if (isSelected) { OnSelected(new RoutedEventArgs(SelectedEvent, this)); } else { OnUnselected(new RoutedEventArgs(UnselectedEvent, this)); } } /// /// Raised when the item's IsSelected property becomes true. /// public static readonly RoutedEvent SelectedEvent = Selector.SelectedEvent.AddOwner(typeof(EGC.DataGridRow)); /// /// Raised when the item's IsSelected property becomes true. /// public event RoutedEventHandler Selected { add { AddHandler(SelectedEvent, value); } remove { RemoveHandler(SelectedEvent, value); } } /// /// Called when IsSelected becomes true. Raises the Selected event. /// /// Empty event arguments. protected virtual void OnSelected(RoutedEventArgs e) { RaiseEvent(e); } /// /// Raised when the item's IsSelected property becomes false. /// public static readonly RoutedEvent UnselectedEvent = Selector.UnselectedEvent.AddOwner(typeof(EGC.DataGridRow)); /// /// Raised when the item's IsSelected property becomes false. /// public event RoutedEventHandler Unselected { add { AddHandler(UnselectedEvent, value); } remove { RemoveHandler(UnselectedEvent, value); } } /// /// Called when IsSelected becomes false. Raises the Unselected event. /// /// Empty event arguments. protected virtual void OnUnselected(RoutedEventArgs e) { RaiseEvent(e); } /// /// Determines if a row can be selected, based on the DataGrid's SelectionUnit property. /// private bool IsSelectable { get { EGC.DataGrid dataGrid = DataGridOwner; if (dataGrid != null) { EGC.DataGridSelectionUnit unit = dataGrid.SelectionUnit; return (unit == EGC.DataGridSelectionUnit.FullRow) || (unit == EGC.DataGridSelectionUnit.CellOrRowHeader); } return true; } } #endregion #region Editing /// /// Whether the row is in editing mode. /// public bool IsEditing { get { return (bool)GetValue(IsEditingProperty); } internal set { SetValue(IsEditingPropertyKey, value); } } private static readonly DependencyPropertyKey IsEditingPropertyKey = DependencyProperty.RegisterReadOnly("IsEditing", typeof(bool), typeof(EGC.DataGridRow), new FrameworkPropertyMetadata(false)); /// /// The DependencyProperty for IsEditing. /// public static readonly DependencyProperty IsEditingProperty = IsEditingPropertyKey.DependencyProperty; #endregion #region Automation protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() { return new EGC.DataGridRowAutomationPeer(this); } #endregion #region Column Virtualization /// /// Method which tries to scroll a cell for given index into the scroll view /// /// internal void ScrollCellIntoView(int index) { EGC.DataGridCellsPresenter cellsPresenter = CellsPresenter; if (cellsPresenter != null) { cellsPresenter.ScrollCellIntoView(index); } } #endregion #region Layout /// /// Arrange /// protected override Size ArrangeOverride(Size arrangeBounds) { EGC.DataGrid dataGrid = DataGridOwner; if (dataGrid != null) { dataGrid.QueueInvalidateCellsPanelHorizontalOffset(); } return base.ArrangeOverride(arrangeBounds); } #endregion #region Helpers /// /// Returns the index of this row within the DataGrid's list of item containers. /// /// /// This method performs a linear search. /// /// The index, if found, -1 otherwise. public int GetIndex() { EGC.DataGrid dataGridOwner = DataGridOwner; if (dataGridOwner != null) { return dataGridOwner.ItemContainerGenerator.IndexFromContainer(this); } return -1; } /// /// Searchs up the visual parent chain from the given element until /// a DataGridRow element is found. /// /// The descendent of a DataGridRow. /// /// The first ancestor DataGridRow of the element parameter. /// Returns null of none is found. /// public static EGC.DataGridRow GetRowContainingElement(FrameworkElement element) { return EGC.DataGridHelper.FindVisualParent(element); } internal EGC.DataGrid DataGridOwner { get { return _owner; } } /// /// Returns true if the DetailsPresenter is supposed to draw gridlines for the row. Only true /// if the DetailsPresenter hooked itself up properly to the Row. /// internal bool DetailsPresenterDrawsGridLines { get { return _detailsPresenter != null && _detailsPresenter.Visibility == Visibility.Visible; } } /// /// Acceses the CellsPresenter and attempts to get the cell at the given index. /// This is not necessarily the display order. /// internal EGC.DataGridCell TryGetCell(int index) { EGC.DataGridCellsPresenter cellsPresenter = CellsPresenter; if (cellsPresenter != null) { return cellsPresenter.ItemContainerGenerator.ContainerFromIndex(index) as EGC.DataGridCell; } return null; } #endregion #region Data private EGC.DataGrid _owner; private EGC.DataGridCellsPresenter _cellsPresenter; private EGC.DataGridDetailsPresenter _detailsPresenter; private EGC.DataGridRowHeader _rowHeader; private EGC.ContainerTracking _tracker; private double _cellsPresenterResizeHeight; private RowDetailsEventStatus _detailsEventStatus; #endregion } }