//--------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; using EGC = ExtendedGrid.Microsoft.Windows.Controls; namespace ExtendedGrid.Microsoft.Windows.Controls { public class DataGridDetailsPresenter : ContentPresenter { static DataGridDetailsPresenter() { DefaultStyleKeyProperty.OverrideMetadata(typeof(EGC.DataGridDetailsPresenter), new FrameworkPropertyMetadata(typeof(EGC.DataGridDetailsPresenter))); ContentTemplateProperty.OverrideMetadata(typeof(EGC.DataGridDetailsPresenter), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceContentTemplate)); ContentTemplateSelectorProperty.OverrideMetadata(typeof(EGC.DataGridDetailsPresenter), new FrameworkPropertyMetadata(OnNotifyPropertyChanged, OnCoerceContentTemplateSelector)); } /// /// Instantiates a new instance of this class. /// public DataGridDetailsPresenter() { } #region Automation protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() { return new EGC.DataGridDetailsPresenterAutomationPeer(this); } #endregion #region Coercion /// /// Coerces the ContentTemplate property. /// private static object OnCoerceContentTemplate(DependencyObject d, object baseValue) { var details = d as EGC.DataGridDetailsPresenter; var row = details.DataGridRowOwner; var dataGrid = row != null ? row.DataGridOwner : null; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( details, baseValue, ContentTemplateProperty, row, EGC.DataGridRow.DetailsTemplateProperty, dataGrid, EGC.DataGrid.RowDetailsTemplateProperty); } /// /// Coerces the ContentTemplateSelector property. /// private static object OnCoerceContentTemplateSelector(DependencyObject d, object baseValue) { var details = d as EGC.DataGridDetailsPresenter; var row = details.DataGridRowOwner; var dataGrid = row != null ? row.DataGridOwner : null; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( details, baseValue, ContentTemplateSelectorProperty, row, EGC.DataGridRow.DetailsTemplateSelectorProperty, dataGrid, EGC.DataGrid.RowDetailsTemplateSelectorProperty); } #endregion #region Row Communication protected override void OnVisualParentChanged(DependencyObject oldParent) { base.OnVisualParentChanged(oldParent); // DataGridRow.DetailsPresenter is used by automation peers // Give the Row a pointer to the RowHeader so that it can propagate down change notifications EGC.DataGridRow owner = DataGridRowOwner; if (owner != null) { owner.DetailsPresenter = this; // At the time that a Row is prepared we can't Sync because the DetailsPresenter isn't created yet. // Doing it here ensures that the DetailsPresenter is in the visual tree. SyncProperties(); } } protected override void OnPreviewMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e) { base.OnPreviewMouseLeftButtonDown(e); EGC.DataGridRow rowOwner = DataGridRowOwner; EGC.DataGrid dataGridOwner = rowOwner != null ? rowOwner.DataGridOwner : null; if ((dataGridOwner != null) && (rowOwner != null)) { EGC.DataGridCellInfo previousCell = dataGridOwner.CurrentCell; dataGridOwner.HandleSelectionForRowHeaderAndDetailsInput(rowOwner, /* startDragging = */ true); // HandleSelectionForRowHeaderAndDetailsInput above sets the CurrentCell // of datagrid to the cell with displayindex 0 in the row. // This implicitly queues a request to MakeVisible command // of ScrollViewer. The command handler calls MakeVisible method of // VirtualizingStackPanel (of rows presenter) which works only // when the visual's parent layout is clean. DataGridCellsPanel layout is // not clean as per MakeVisible of VSP becuase we distribute the layout of cells for the // sake of row headers and hence it fails. VSP.MakeVisible method requeues a request to // ScrollViewer.MakeVisible command hence resulting into an infinite loop. // The workaround is to bring the concerned cell into the view by calling // ScrollIntoView so that by the time MakeVisible handler of ScrollViewer is // executed the cell is already visible and the handler succeeds. EGC.DataGridCellInfo currentCell = dataGridOwner.CurrentCell; if (currentCell != previousCell && currentCell != EGC.DataGridCellInfo.Unset) { dataGridOwner.ScrollIntoView(currentCell.Item, currentCell.Column); } } } internal FrameworkElement DetailsElement { get { var childrenCount = VisualTreeHelper.GetChildrenCount(this); if (childrenCount > 0) { return VisualTreeHelper.GetChild(this, 0) as FrameworkElement; } return null; } } /// /// Update all properties that get a value from the DataGrid /// /// /// See comment on DataGridRow.OnDataGridChanged /// internal void SyncProperties() { EGC.DataGridRow owner = DataGridRowOwner; Content = owner != null ? owner.Item : null; EGC.DataGridHelper.TransferProperty(this, ContentTemplateProperty); EGC.DataGridHelper.TransferProperty(this, ContentTemplateSelectorProperty); } #endregion #region Notification Propagation /// /// Notifies parts that respond to changes in the properties. /// private static void OnNotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((EGC.DataGridDetailsPresenter)d).NotifyPropertyChanged(d, e); } internal void NotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (e.Property == EGC.DataGrid.RowDetailsTemplateProperty || e.Property == EGC.DataGridRow.DetailsTemplateProperty || e.Property == ContentTemplateProperty) { EGC.DataGridHelper.TransferProperty(this, ContentTemplateProperty); } else if (e.Property == EGC.DataGrid.RowDetailsTemplateSelectorProperty || e.Property == EGC.DataGridRow.DetailsTemplateSelectorProperty || e.Property == ContentTemplateSelectorProperty) { EGC.DataGridHelper.TransferProperty(this, ContentTemplateSelectorProperty); } } #endregion #region GridLines // Different parts of the DataGrid draw different pieces of the GridLines. // Rows draw a single horizontal line on the bottom. The DataGridDetailsPresenter is the element that handles it. /// /// Measure. This is overridden so that the row can extend its size to account for a grid line on the bottom. /// /// /// protected override Size MeasureOverride(Size availableSize) { // Make space for the GridLine on the bottom. // Remove space from the constraint (since it implicitly includes the GridLine's thickness), // call the base implementation, and add the thickness back for the returned size. var row = DataGridRowOwner; Debug.Assert(row != null, "DetailsPresenter should have a DataGridRowOwner when measure is called."); var dataGrid = row.DataGridOwner; Debug.Assert(dataGrid != null, "DetailsPresenter should have a DataGridOwner when measure is called."); if (row.DetailsPresenterDrawsGridLines && EGC.DataGridHelper.IsGridLineVisible(dataGrid, /*isHorizontal = */ true)) { double thickness = dataGrid.HorizontalGridLineThickness; Size desiredSize = base.MeasureOverride(EGC.DataGridHelper.SubtractFromSize(availableSize, thickness, /*height = */ true)); desiredSize.Height += thickness; return desiredSize; } else { return base.MeasureOverride(availableSize); } } /// /// Arrange. This is overriden so that the row can position its content to account for a grid line on the bottom. /// /// Arrange size protected override Size ArrangeOverride(Size finalSize) { // We don't need to adjust the Arrange position of the content. By default it is arranged at 0,0 and we're // adding a line to the bottom. All we have to do is compress and extend the size, just like Measure. var row = DataGridRowOwner; Debug.Assert(row != null, "DetailsPresenter should have a DataGridRowOwner when arrange is called."); var dataGrid = row.DataGridOwner; Debug.Assert(dataGrid != null, "DetailsPresenter should have a DataGridOwner when arrange is called."); if (row.DetailsPresenterDrawsGridLines && EGC.DataGridHelper.IsGridLineVisible(dataGrid, /*isHorizontal = */ true)) { double thickness = dataGrid.HorizontalGridLineThickness; Size returnSize = base.ArrangeOverride(EGC.DataGridHelper.SubtractFromSize(finalSize, thickness, /*height = */ true)); returnSize.Height += thickness; return returnSize; } else { return base.ArrangeOverride(finalSize); } } /// /// OnRender. Overriden to draw a horizontal line underneath the content. /// /// protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); var row = DataGridRowOwner; Debug.Assert(row != null, "DetailsPresenter should have a DataGridRowOwner when OnRender is called."); var dataGrid = row.DataGridOwner; Debug.Assert(dataGrid != null, "DetailsPresenter should have a DataGridOwner when OnRender is called."); if (row.DetailsPresenterDrawsGridLines && EGC.DataGridHelper.IsGridLineVisible(dataGrid, /*isHorizontal = */ true)) { double thickness = dataGrid.HorizontalGridLineThickness; Rect rect = new Rect(new Size(RenderSize.Width, thickness)); rect.Y = RenderSize.Height - thickness; drawingContext.DrawRectangle(dataGrid.HorizontalGridLinesBrush, null, rect); } } #endregion #region Helpers /// /// The DataGrid that owns this control /// private EGC.DataGrid DataGridOwner { get { EGC.DataGridRow owner = DataGridRowOwner; if (owner != null) { return owner.DataGridOwner; } return null; } } /// /// The DataGridRow that owns this control. /// internal EGC.DataGridRow DataGridRowOwner { get { return EGC.DataGridHelper.FindParent(this); } } #endregion } }