//--------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.ComponentModel; using System.Diagnostics; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using MS.Internal; using EGC = ExtendedGrid.Microsoft.Windows.Controls; namespace ExtendedGrid.Microsoft.Windows.Controls { /// /// A base class for specifying the column definitions. /// public abstract class DataGridColumn : DependencyObject { #region Header /// /// An object that represents the header of this column. /// public object Header { get { return (object)GetValue(HeaderProperty); } set { SetValue(HeaderProperty, value); } } /// /// The DependencyProperty that represents the Header property. /// public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(object), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnNotifyColumnHeaderPropertyChanged))); /// /// The Style for the DataGridColumnHeader /// public Style HeaderStyle { get { return (Style)GetValue(HeaderStyleProperty); } set { SetValue(HeaderStyleProperty, value); } } /// /// The DependencyProperty that represents the HeaderStyle property. /// public static readonly DependencyProperty HeaderStyleProperty = DependencyProperty.Register("HeaderStyle", typeof(Style), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null, OnNotifyColumnHeaderPropertyChanged, OnCoerceHeaderStyle)); private static object OnCoerceHeaderStyle(DependencyObject d, object baseValue) { var column = d as EGC.DataGridColumn; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( column, baseValue, HeaderStyleProperty, column.DataGridOwner, EGC.DataGrid.ColumnHeaderStyleProperty); } /// /// The string format to apply to the header. /// public string HeaderStringFormat { get { return (string)GetValue(HeaderStringFormatProperty); } set { SetValue(HeaderStringFormatProperty, value); } } /// /// The DependencyProperty that represents the HeaderStringFormat property. /// public static readonly DependencyProperty HeaderStringFormatProperty = DependencyProperty.Register("HeaderStringFormat", typeof(string), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null, OnNotifyColumnHeaderPropertyChanged)); /// /// The template that defines the visual representation of the header. /// public DataTemplate HeaderTemplate { get { return (DataTemplate)GetValue(HeaderTemplateProperty); } set { SetValue(HeaderTemplateProperty, value); } } /// /// The DependencyProperty that represents the HeaderTemplate property. /// public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null, OnNotifyColumnHeaderPropertyChanged)); /// /// DataTemplateSelector that selects which template to use for the Column Header /// public DataTemplateSelector HeaderTemplateSelector { get { return (DataTemplateSelector)GetValue(HeaderTemplateSelectorProperty); } set { SetValue(HeaderTemplateSelectorProperty, value); } } /// /// The DependencyProperty that represents the HeaderTemplateSelector property. /// public static readonly DependencyProperty HeaderTemplateSelectorProperty = DependencyProperty.Register("HeaderTemplateSelector", typeof(DataTemplateSelector), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null, OnNotifyColumnHeaderPropertyChanged)); #endregion #region Cell Container /// /// A style to apply to the container of cells in this column. /// public Style CellStyle { get { return (Style)GetValue(CellStyleProperty); } set { SetValue(CellStyleProperty, value); } } /// /// The DependencyProperty that represents the CellStyle property. /// public static readonly DependencyProperty CellStyleProperty = DependencyProperty.Register("CellStyle", typeof(Style), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null, OnNotifyCellPropertyChanged, OnCoerceCellStyle)); private static object OnCoerceCellStyle(DependencyObject d, object baseValue) { var column = d as EGC.DataGridColumn; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( column, baseValue, CellStyleProperty, column.DataGridOwner, EGC.DataGrid.CellStyleProperty); } /// /// Whether cells in this column can enter edit mode. /// public bool IsReadOnly { get { return (bool)GetValue(IsReadOnlyProperty); } set { SetValue(IsReadOnlyProperty, value); } } /// /// The DependencyProperty that represents the IsReadOnly property. /// public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(false, OnNotifyCellPropertyChanged, OnCoerceIsReadOnly)); private static object OnCoerceIsReadOnly(DependencyObject d, object baseValue) { var column = d as EGC.DataGridColumn; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( column, baseValue, IsReadOnlyProperty, column.DataGridOwner, EGC.DataGrid.IsReadOnlyProperty); } #endregion #region Width /// /// Specifies the width of the header and cells within this column. /// public EGC.DataGridLength Width { get { return (EGC.DataGridLength)GetValue(WidthProperty); } set { SetValue(WidthProperty, value); } } /// /// The DependencyProperty that represents the Width property. /// public static readonly DependencyProperty WidthProperty = DependencyProperty.Register( "Width", typeof(EGC.DataGridLength), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(EGC.DataGridLength.Auto, new PropertyChangedCallback(OnWidthPropertyChanged), new CoerceValueCallback(OnCoerceWidth))); /// /// Internal method which sets the column's width /// without actual redistribution of widths among other /// columns /// /// internal void SetWidthInternal(EGC.DataGridLength width) { bool originalValue = _ignoreRedistributionOnWidthChange; _ignoreRedistributionOnWidthChange = true; try { Width = width; } finally { _ignoreRedistributionOnWidthChange = originalValue; } } /// /// Property changed call back for Width property which notification propagation /// and does the redistribution of widths among other columns if needed /// /// /// private static void OnWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { EGC.DataGridColumn column = (EGC.DataGridColumn)d; EGC.DataGridLength oldWidth = (EGC.DataGridLength)e.OldValue; EGC.DataGridLength newWidth = (EGC.DataGridLength)e.NewValue; EGC.DataGrid dataGrid = column.DataGridOwner; if (dataGrid != null && !DoubleUtil.AreClose(oldWidth.DisplayValue, newWidth.DisplayValue)) { dataGrid.InternalColumns.InvalidateAverageColumnWidth(); } if (column._processingWidthChange) { column.CoerceValue(ActualWidthProperty); return; } column._processingWidthChange = true; try { if (dataGrid != null && (newWidth.IsStar ^ oldWidth.IsStar)) { dataGrid.InternalColumns.InvalidateHasVisibleStarColumns(); } column.NotifyPropertyChanged( d, e, NotificationTarget.ColumnCollection | NotificationTarget.Columns | NotificationTarget.Cells | NotificationTarget.ColumnHeaders | NotificationTarget.CellsPresenter | NotificationTarget.ColumnHeadersPresenter); if (dataGrid != null) { if (!column._ignoreRedistributionOnWidthChange && column.IsVisible) { if (!newWidth.IsStar && !newWidth.IsAbsolute) { EGC.DataGridLength changedWidth = column.Width; double displayValue = EGC.DataGridHelper.CoerceToMinMax(changedWidth.DesiredValue, column.MinWidth, column.MaxWidth); column.SetWidthInternal(new EGC.DataGridLength(changedWidth.Value, changedWidth.UnitType, changedWidth.DesiredValue, displayValue)); } dataGrid.InternalColumns.RedistributeColumnWidthsOnWidthChangeOfColumn(column, (EGC.DataGridLength)e.OldValue); } } } finally { column._processingWidthChange = false; } } /// /// Specifies the minimum width of the header and cells within this column. /// public double MinWidth { get { return (double)GetValue(MinWidthProperty); } set { SetValue(MinWidthProperty, value); } } /// /// The DependencyProperty that represents the MinWidth property. /// public static readonly DependencyProperty MinWidthProperty = DependencyProperty.Register( "MinWidth", typeof(double), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(20d, new PropertyChangedCallback(OnMinWidthPropertyChanged), new CoerceValueCallback(OnCoerceMinWidth)), new ValidateValueCallback(ValidateMinWidth)); /// /// Property changed call back for MinWidth property which notification propagation /// and does the redistribution of widths among other columns if needed /// /// /// private static void OnMinWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { EGC.DataGridColumn column = (EGC.DataGridColumn)d; EGC.DataGrid dataGrid = column.DataGridOwner; column.NotifyPropertyChanged(d, e, NotificationTarget.Columns); if (dataGrid != null && column.IsVisible) { dataGrid.InternalColumns.RedistributeColumnWidthsOnMinWidthChangeOfColumn(column, (double)e.OldValue); } } /// /// Specifies the maximum width of the header and cells within this column. /// public double MaxWidth { get { return (double)GetValue(MaxWidthProperty); } set { SetValue(MaxWidthProperty, value); } } /// /// The DependencyProperty that represents the MaxWidth property. /// public static readonly DependencyProperty MaxWidthProperty = DependencyProperty.Register( "MaxWidth", typeof(double), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(double.PositiveInfinity, new PropertyChangedCallback(OnMaxWidthPropertyChanged), new CoerceValueCallback(OnCoerceMaxWidth)), new ValidateValueCallback(ValidateMaxWidth)); /// /// Property changed call back for MaxWidth property which notification propagation /// and does the redistribution of widths among other columns if needed /// /// /// private static void OnMaxWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { EGC.DataGridColumn column = (EGC.DataGridColumn)d; EGC.DataGrid dataGrid = column.DataGridOwner; column.NotifyPropertyChanged(d, e, NotificationTarget.Columns); if (dataGrid != null && column.IsVisible) { dataGrid.InternalColumns.RedistributeColumnWidthsOnMaxWidthChangeOfColumn(column, (double)e.OldValue); } } /// /// Coerces the WidthProperty based on the DataGrid transferred property rules /// private static object OnCoerceWidth(DependencyObject d, object baseValue) { var column = d as EGC.DataGridColumn; EGC.DataGridLength width = (EGC.DataGridLength)EGC.DataGridHelper.GetCoercedTransferPropertyValue( column, baseValue, WidthProperty, column.DataGridOwner, EGC.DataGrid.ColumnWidthProperty); double newDisplayValue = (DoubleUtil.IsNaN(width.DisplayValue) ? width.DisplayValue : EGC.DataGridHelper.CoerceToMinMax(width.DisplayValue, column.MinWidth, column.MaxWidth)); if (DoubleUtil.IsNaN(newDisplayValue) || DoubleUtil.AreClose(newDisplayValue, width.DisplayValue)) { return width; } return new EGC.DataGridLength( width.Value, width.UnitType, width.DesiredValue, newDisplayValue); } /// /// Coerces the MinWidthProperty based on the DataGrid transferred property rules /// private static object OnCoerceMinWidth(DependencyObject d, object baseValue) { var column = d as EGC.DataGridColumn; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( column, baseValue, MinWidthProperty, column.DataGridOwner, EGC.DataGrid.MinColumnWidthProperty); } /// /// Coerces the MaxWidthProperty based on the DataGrid transferred property rules /// private static object OnCoerceMaxWidth(DependencyObject d, object baseValue) { var column = d as EGC.DataGridColumn; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( column, baseValue, MaxWidthProperty, column.DataGridOwner, EGC.DataGrid.MaxColumnWidthProperty); } /// /// Validates that the minimum width is an acceptable value /// private static bool ValidateMinWidth(object v) { double value = (double)v; return !(value < 0d || DoubleUtil.IsNaN(value) || Double.IsPositiveInfinity(value)); } /// /// Validates that the maximum width is an acceptable value /// private static bool ValidateMaxWidth(object v) { double value = (double)v; return !(value < 0d || DoubleUtil.IsNaN(value)); } /// /// This is the width that cells and headers should use in Arrange. /// public double ActualWidth { get { return (double)GetValue(ActualWidthProperty); } private set { SetValue(ActualWidthPropertyKey, value); } } public static readonly DependencyPropertyKey ActualWidthPropertyKey = DependencyProperty.RegisterReadOnly("ActualWidth", typeof(double), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(0.0, null, new CoerceValueCallback(OnCoerceActualWidth))); internal static readonly DependencyProperty ActualWidthProperty = ActualWidthPropertyKey.DependencyProperty; private static object OnCoerceActualWidth(DependencyObject d, object baseValue) { EGC.DataGridColumn column = ((EGC.DataGridColumn)d); double actualWidth = (double)baseValue; double minWidth = column.MinWidth; double maxWidth = column.MaxWidth; // If the width is an absolute pixel value, then ActualWidth should be that value EGC.DataGridLength width = column.Width; if (width.IsAbsolute) { actualWidth = width.DisplayValue; } if (actualWidth < minWidth) { actualWidth = minWidth; } else if (actualWidth > maxWidth) { actualWidth = maxWidth; } return actualWidth; } /// /// Retrieve the proper measure constraint for cells. /// /// Whether a header constraint or a normal cell constraint is requested. /// The value to use as the width when creating a measure constraint. internal double GetConstraintWidth(bool isHeader) { EGC.DataGridLength width = Width; if (!DoubleUtil.IsNaN(width.DisplayValue)) { return width.DisplayValue; } if (width.IsAbsolute || width.IsStar || (width.IsSizeToCells && isHeader) || (width.IsSizeToHeader && !isHeader)) { // In these cases, the cell's desired size does not matter. // Use the column's current width as the constraint. return ActualWidth; } else { // The element gets to size to content. return Double.PositiveInfinity; } } /// /// Notifies the column of a cell's desired width. /// Updates the actual width if necessary /// /// Whether the cell is a header or not. /// The desired size of the cell. internal void UpdateDesiredWidthForAutoColumn(bool isHeader, double pixelWidth) { EGC.DataGridLength width = Width; double minWidth = MinWidth; double maxWidth = MaxWidth; double displayWidth = EGC.DataGridHelper.CoerceToMinMax(pixelWidth, minWidth, maxWidth); if (width.IsAuto || (width.IsSizeToCells && !isHeader) || (width.IsSizeToHeader && isHeader)) { if (DoubleUtil.IsNaN(width.DesiredValue) || DoubleUtil.LessThan(width.DesiredValue, pixelWidth)) { if (DoubleUtil.IsNaN(width.DisplayValue)) { SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, pixelWidth, displayWidth)); } else { double originalDesiredValue = EGC.DataGridHelper.CoerceToMinMax(width.DesiredValue, minWidth, maxWidth); SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, pixelWidth, width.DisplayValue)); if (DoubleUtil.AreClose(originalDesiredValue, width.DisplayValue)) { DataGridOwner.InternalColumns.RecomputeColumnWidthsOnColumnResize(this, pixelWidth - width.DisplayValue, true); } } width = Width; } if (DoubleUtil.IsNaN(width.DisplayValue)) { if (ActualWidth < displayWidth) { ActualWidth = displayWidth; } } else if (!DoubleUtil.AreClose(ActualWidth, width.DisplayValue)) { ActualWidth = width.DisplayValue; } } } /// /// Notifies the column that Width="*" columns have a new actual width. /// internal void UpdateWidthForStarColumn(double displayWidth, double desiredWidth, double starValue) { Debug.Assert(Width.IsStar); EGC.DataGridLength width = Width; if (!DoubleUtil.AreClose(displayWidth, width.DisplayValue) || !DoubleUtil.AreClose(desiredWidth, width.DesiredValue) || !DoubleUtil.AreClose(width.Value, starValue)) { SetWidthInternal(new EGC.DataGridLength(starValue, width.UnitType, desiredWidth, displayWidth)); ActualWidth = displayWidth; } } #endregion #region Visual Tree Generation /// /// Retrieves the visual tree that was generated for a particular row and column. /// /// The row that corresponds to the desired cell. /// The element if found, null otherwise. public FrameworkElement GetCellContent(object dataItem) { if (dataItem == null) { throw new ArgumentNullException("dataItem"); } if (_dataGridOwner != null) { EGC.DataGridRow row = _dataGridOwner.ItemContainerGenerator.ContainerFromItem(dataItem) as EGC.DataGridRow; if (row != null) { return GetCellContent(row); } } return null; } /// /// Retrieves the visual tree that was generated for a particular row and column. /// /// The row that corresponds to the desired cell. /// The element if found, null otherwise. public FrameworkElement GetCellContent(EGC.DataGridRow dataGridRow) { if (dataGridRow == null) { throw new ArgumentNullException("dataGridRow"); } if (_dataGridOwner != null) { int columnIndex = _dataGridOwner.Columns.IndexOf(this); if (columnIndex >= 0) { EGC.DataGridCell cell = dataGridRow.TryGetCell(columnIndex); if (cell != null) { return cell.Content as FrameworkElement; } } } return null; } /// /// Creates the visual tree that will become the content of a cell. /// /// Whether the editing version is being requested. /// The data item for the cell. /// The cell container that will receive the tree. internal FrameworkElement BuildVisualTree(bool isEditing, object dataItem, EGC.DataGridCell cell) { if (isEditing) { return GenerateEditingElement(cell, dataItem); } else { return GenerateElement(cell, dataItem); } } /// /// Creates the visual tree that will become the content of a cell. /// protected abstract FrameworkElement GenerateElement(EGC.DataGridCell cell, object dataItem); /// /// Creates the visual tree that will become the content of a cell. /// protected abstract FrameworkElement GenerateEditingElement(EGC.DataGridCell cell, object dataItem); #endregion #region Editing /// /// Called when a cell has just switched to edit mode. /// /// A reference to element returned by GenerateEditingElement. /// The unedited value of the cell. protected virtual object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs) { return null; } /// /// Called when a cell's value is to be restored to its original value, /// just before it exits edit mode. /// /// A reference to element returned by GenerateEditingElement. /// The original, unedited value of the cell. protected virtual void CancelCellEdit(FrameworkElement editingElement, object uneditedValue) { } /// /// Called when a cell's value is to be committed, just before it exits edit mode. /// /// A reference to element returned by GenerateEditingElement. /// false if there is a validation error. true otherwise. protected virtual bool CommitCellEdit(FrameworkElement editingElement) { return true; } internal void BeginEdit(FrameworkElement editingElement, RoutedEventArgs e) { // This call is to ensure that the tree and its bindings have resolved // before we proceed to code that relies on the tree being ready. if (editingElement != null) { editingElement.UpdateLayout(); object originalValue = PrepareCellForEdit(editingElement, e); SetOriginalValue(editingElement, originalValue); } } internal void CancelEdit(FrameworkElement editingElement) { if (editingElement != null) { CancelCellEdit(editingElement, GetOriginalValue(editingElement)); ClearOriginalValue(editingElement); } } internal bool CommitEdit(FrameworkElement editingElement) { if (editingElement != null) { if (CommitCellEdit(editingElement)) { // Validation passed ClearOriginalValue(editingElement); return true; } else { // Validation failed. This cell will remain in edit mode. return false; } } return true; } private static object GetOriginalValue(DependencyObject obj) { return (object)obj.GetValue(OriginalValueProperty); } private static void SetOriginalValue(DependencyObject obj, object value) { obj.SetValue(OriginalValueProperty, value); } private static void ClearOriginalValue(DependencyObject obj) { obj.ClearValue(OriginalValueProperty); } private static readonly DependencyProperty OriginalValueProperty = DependencyProperty.RegisterAttached("OriginalValue", typeof(object), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null)); #endregion #region Owner Communication /// /// Notifies the DataGrid and the Cells about property changes. /// internal static void OnNotifyCellPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((EGC.DataGridColumn)d).NotifyPropertyChanged(d, e, NotificationTarget.Columns | NotificationTarget.Cells); } /// /// Notifies the DataGrid and the Column Headers about property changes. /// private static void OnNotifyColumnHeaderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((EGC.DataGridColumn)d).NotifyPropertyChanged(d, e, NotificationTarget.Columns | NotificationTarget.ColumnHeaders); } /// /// Notifies parts that respond to changes in the column. /// private static void OnNotifyColumnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((EGC.DataGridColumn)d).NotifyPropertyChanged(d, e, NotificationTarget.Columns); } /// /// General notification for DependencyProperty changes from the grid and/or column. /// internal void NotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e, NotificationTarget target) { if (EGC.DataGridHelper.ShouldNotifyColumns(target)) { // Remove columns target since we're handling it. If we're targeting multiple targets it may also need to get // sent to the DataGrid. target &= ~NotificationTarget.Columns; if (e.Property == EGC.DataGrid.MaxColumnWidthProperty || e.Property == MaxWidthProperty) { EGC.DataGridHelper.TransferProperty(this, MaxWidthProperty); } else if (e.Property == EGC.DataGrid.MinColumnWidthProperty || e.Property == MinWidthProperty) { EGC.DataGridHelper.TransferProperty(this, MinWidthProperty); } else if (e.Property == EGC.DataGrid.ColumnWidthProperty || e.Property == WidthProperty) { EGC.DataGridHelper.TransferProperty(this, WidthProperty); } else if (e.Property == EGC.DataGrid.ColumnHeaderStyleProperty || e.Property == HeaderStyleProperty) { EGC.DataGridHelper.TransferProperty(this, HeaderStyleProperty); } else if (e.Property == EGC.DataGrid.CellStyleProperty || e.Property == CellStyleProperty) { EGC.DataGridHelper.TransferProperty(this, CellStyleProperty); } else if (e.Property == EGC.DataGrid.IsReadOnlyProperty || e.Property == IsReadOnlyProperty) { EGC.DataGridHelper.TransferProperty(this, IsReadOnlyProperty); } else if (e.Property == EGC.DataGrid.DragIndicatorStyleProperty || e.Property == DragIndicatorStyleProperty) { EGC.DataGridHelper.TransferProperty(this, DragIndicatorStyleProperty); } else if (e.Property == DisplayIndexProperty) { CoerceValue(IsFrozenProperty); } if (e.Property == WidthProperty || e.Property == MinWidthProperty || e.Property == MaxWidthProperty) { CoerceValue(ActualWidthProperty); } } if (target != NotificationTarget.None) { // Everything else gets sent to the DataGrid so it can propogate back down // to the targets that need notification. EGC.DataGridColumn column = (EGC.DataGridColumn)d; EGC.DataGrid dataGridOwner = column.DataGridOwner; if (dataGridOwner != null) { dataGridOwner.NotifyPropertyChanged(d, e, target); } } } /// /// Method which propogates the property changed notification to datagrid /// /// protected void NotifyPropertyChanged(string propertyName) { if (DataGridOwner != null) { DataGridOwner.NotifyPropertyChanged(this, propertyName, new DependencyPropertyChangedEventArgs(), NotificationTarget.RefreshCellContent); } } /// /// Method used as property changed callback for properties which need RefreshCellContent to be called /// /// /// internal static void NotifyPropertyChangeForRefreshContent(DependencyObject d, DependencyPropertyChangedEventArgs e) { Debug.Assert(d is EGC.DataGridColumn, "d should be a DataGridColumn"); ((EGC.DataGridColumn)d).NotifyPropertyChanged(e.Property.Name); } /// /// Method which updates the cell for property changes /// /// /// protected internal virtual void RefreshCellContent(FrameworkElement element, string propertyName) { } /// /// Ensures that any properties that may be influenced by a change to the DataGrid are syncronized. /// internal void SyncProperties() { EGC.DataGridHelper.TransferProperty(this, MinWidthProperty); EGC.DataGridHelper.TransferProperty(this, MaxWidthProperty); EGC.DataGridHelper.TransferProperty(this, WidthProperty); EGC.DataGridHelper.TransferProperty(this, HeaderStyleProperty); EGC.DataGridHelper.TransferProperty(this, CellStyleProperty); EGC.DataGridHelper.TransferProperty(this, IsReadOnlyProperty); EGC.DataGridHelper.TransferProperty(this, DragIndicatorStyleProperty); } /// /// The owning DataGrid control. /// protected internal EGC.DataGrid DataGridOwner { get { return _dataGridOwner; } internal set { _dataGridOwner = value; } } #endregion #region Display Index /// /// Specifies the display index of this column. /// /// /// A lower display index means a column will appear first (to the left) of columns with a higher display index. /// Allowable values are from 0 to num columns - 1. (-1 is legal only as the default value and is modified to something else /// when the column is added to a DataGrid's column collection). DataGrid enforces that no two columns have the same display index; /// changing the display index of a column will cause the index of other columns to adjust as well. /// public int DisplayIndex { get { return (int)GetValue(DisplayIndexProperty); } set { SetValue(DisplayIndexProperty, value); } } /// /// The DependencyProperty that represents the Width property. /// public static readonly DependencyProperty DisplayIndexProperty = DependencyProperty.Register( "DisplayIndex", typeof(int), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(-1, new PropertyChangedCallback(DisplayIndexChanged), new CoerceValueCallback(OnCoerceDisplayIndex))); /// /// We use the coersion callback to validate that the DisplayIndex of a column is between 0 and DataGrid.Columns.Count /// The default value is -1; this value is only legal as the default or when the Column is not attached to a DataGrid. /// private static object OnCoerceDisplayIndex(DependencyObject d, object baseValue) { EGC.DataGridColumn column = (EGC.DataGridColumn)d; if (column.DataGridOwner != null) { column.DataGridOwner.ValidateDisplayIndex(column, (int)baseValue); } return baseValue; } /// /// Notifies the DataGrid that the display index for this column changed. /// private static void DisplayIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // Cells and ColumnHeaders invalidate Arrange; ColumnCollection handles modifying the DisplayIndex of other columns. ((EGC.DataGridColumn)d).NotifyPropertyChanged( d, e, NotificationTarget.Columns | NotificationTarget.ColumnCollection | NotificationTarget.Cells | NotificationTarget.ColumnHeaders | NotificationTarget.CellsPresenter | NotificationTarget.ColumnHeadersPresenter); } #endregion #region Auto Sorting /// /// Dependency property for SortMemberPath /// public static readonly DependencyProperty SortMemberPathProperty = DependencyProperty.Register( "SortMemberPath", typeof(string), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(String.Empty)); /// /// The property which the determines the member to be sorted upon when sorted on this column /// public string SortMemberPath { get { return (string)GetValue(SortMemberPathProperty); } set { SetValue(SortMemberPathProperty, value); } } /// /// Dependecy property for CanUserSort /// public static readonly DependencyProperty CanUserSortProperty = DependencyProperty.Register( "CanUserSort", typeof(bool), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnNotifySortPropertyChanged), new CoerceValueCallback(OnCoerceCanUserSort))); /// /// The property which determines whether the datagrid can be sorted upon this column or not /// public bool CanUserSort { get { return (bool)GetValue(CanUserSortProperty); } set { SetValue(CanUserSortProperty, value); } } /// /// Dependency property for SrtDirection /// public static readonly DependencyProperty SortDirectionProperty = DependencyProperty.Register( "SortDirection", typeof(Nullable), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnNotifySortPropertyChanged))); /// /// The property for current sort direction of the column /// public Nullable SortDirection { get { return (Nullable)GetValue(SortDirectionProperty); } set { SetValue(SortDirectionProperty, value); } } /// /// The Coercion callback for CanUserSort property. Checks if datagrid.Items can sort and /// returns the value accordingly. /// /// /// /// internal static object OnCoerceCanUserSort(DependencyObject d, object baseValue) { EGC.DataGridColumn column = (EGC.DataGridColumn)d; EGC.DataGrid dataGrid = column.DataGridOwner; if (dataGrid != null) { if (!dataGrid.Items.CanSort) { return false; } } return baseValue; } /// /// Property changed callback for CanUserSort, SortMemberPath and SortDirection properties /// /// /// private static void OnNotifySortPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((EGC.DataGridColumn)d).NotifyPropertyChanged(d, e, NotificationTarget.ColumnHeaders); } #endregion #region Auto Generation private static readonly DependencyPropertyKey IsAutoGeneratedPropertyKey = DependencyProperty.RegisterReadOnly( "IsAutoGenerated", typeof(bool), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(false)); /// /// The DependencyProperty for the IsAutoGenerated Property /// public static readonly DependencyProperty IsAutoGeneratedProperty = IsAutoGeneratedPropertyKey.DependencyProperty; /// /// This property determines whether the column is autogenerate or not. /// public bool IsAutoGenerated { get { return (bool)GetValue(IsAutoGeneratedProperty); } internal set { SetValue(IsAutoGeneratedPropertyKey, value); } } /// /// Helper Method which creates a default DataGridColumn object for the specified property type. /// /// /// internal static EGC.DataGridColumn CreateDefaultColumn(ItemPropertyInfo itemProperty) { Debug.Assert(itemProperty != null && itemProperty.PropertyType != null, "itemProperty and/or its PropertyType member cannot be null"); EGC.DataGridColumn dataGridColumn = null; EGC.DataGridComboBoxColumn comboBoxColumn = null; Type propertyType = itemProperty.PropertyType; // determine the type of column to be created and create one if (propertyType.IsEnum) { comboBoxColumn = new EGC.DataGridComboBoxColumn(); comboBoxColumn.ItemsSource = Enum.GetValues(propertyType); dataGridColumn = comboBoxColumn; } else if (typeof(string).IsAssignableFrom(propertyType)) { dataGridColumn = new EGC.DataGridTextColumn(); } else if (typeof(bool).IsAssignableFrom(propertyType)) { dataGridColumn = new EGC.DataGridCheckBoxColumn(); } else if (typeof(Uri).IsAssignableFrom(propertyType)) { dataGridColumn = new EGC.DataGridHyperlinkColumn(); } else { dataGridColumn = new EGC.DataGridTextColumn(); } // determine if the datagrid can sort on the column or not if (!typeof(IComparable).IsAssignableFrom(propertyType)) { dataGridColumn.CanUserSort = false; } dataGridColumn.Header = itemProperty.Name; // Set the data field binding for such created columns and // choose the BindingMode based on editability of the property. EGC.DataGridBoundColumn boundColumn = dataGridColumn as EGC.DataGridBoundColumn; if (boundColumn != null || comboBoxColumn != null) { Binding binding = new Binding(itemProperty.Name); if (comboBoxColumn != null) { comboBoxColumn.SelectedItemBinding = binding; } else { boundColumn.Binding = binding; } PropertyDescriptor pd = itemProperty.Descriptor as PropertyDescriptor; if (pd != null) { if (pd.IsReadOnly) { binding.Mode = BindingMode.OneWay; dataGridColumn.IsReadOnly = true; } } else { PropertyInfo pi = itemProperty.Descriptor as PropertyInfo; if (pi != null) { if (!pi.CanWrite) { binding.Mode = BindingMode.OneWay; dataGridColumn.IsReadOnly = true; } } } } return dataGridColumn; } #endregion #region Frozen Columns /// /// Dependency Property Key for IsFrozen property /// private static readonly DependencyPropertyKey IsFrozenPropertyKey = DependencyProperty.RegisterReadOnly( "IsFrozen", typeof(bool), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnNotifyFrozenPropertyChanged), new CoerceValueCallback(OnCoerceIsFrozen))); /// /// The DependencyProperty for the IsFrozen Property /// public static readonly DependencyProperty IsFrozenProperty = IsFrozenPropertyKey.DependencyProperty; /// /// This property determines whether the column is frozen or not. /// public bool IsFrozen { get { return (bool)GetValue(IsFrozenProperty); } internal set { SetValue(IsFrozenPropertyKey, value); } } /// /// Property changed callback for IsFrozen property /// /// /// private static void OnNotifyFrozenPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((EGC.DataGridColumn)d).NotifyPropertyChanged(d, e, NotificationTarget.ColumnHeaders); } /// /// Coercion call back for IsFrozenProperty. Ensures that IsFrozen is set as per the /// DataGrid's FrozenColumnCount property. /// /// /// /// private static object OnCoerceIsFrozen(DependencyObject d, object baseValue) { EGC.DataGridColumn column = (EGC.DataGridColumn)d; EGC.DataGrid dataGrid = column.DataGridOwner; if (dataGrid != null) { if (column.DisplayIndex < dataGrid.FrozenColumnCount) { return true; } else { return false; } } return baseValue; } #endregion #region Column Reordering /// /// The DependencyProperty that represents the CanUserReorder property. /// public static readonly DependencyProperty CanUserReorderProperty = DependencyProperty.Register("CanUserReorder", typeof(bool), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(true)); /// /// The property which determines if column header can be dragged or not /// public bool CanUserReorder { get { return (bool)GetValue(CanUserReorderProperty); } set { SetValue(CanUserReorderProperty, value); } } /// /// The DependencyProperty that represents the DragIndicatorStyle property. /// public static readonly DependencyProperty DragIndicatorStyleProperty = DependencyProperty.Register("DragIndicatorStyle", typeof(Style), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null, OnNotifyColumnPropertyChanged, OnCoerceDragIndicatorStyle)); /// /// The style property which would be applied on the column header drag indicator. /// public Style DragIndicatorStyle { get { return (Style)GetValue(DragIndicatorStyleProperty); } set { SetValue(DragIndicatorStyleProperty, value); } } private static object OnCoerceDragIndicatorStyle(DependencyObject d, object baseValue) { var column = d as EGC.DataGridColumn; return EGC.DataGridHelper.GetCoercedTransferPropertyValue( column, baseValue, DragIndicatorStyleProperty, column.DataGridOwner, EGC.DataGrid.DragIndicatorStyleProperty); } #endregion #region Clipboard Copy/Paste /// /// The binding that will be used to get or set cell content for the clipboard /// public virtual BindingBase ClipboardContentBinding { get { return _clipboardContentBinding; } set { _clipboardContentBinding = value; } } // This property is used together with ClipboardContentBinding to get/set the value from the item private static readonly DependencyProperty CellValueProperty = DependencyProperty.RegisterAttached("CellValue", typeof(object), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(null)); /// /// This method is called for each selected cell in each selected cell to retrieve the default cell content. /// Default cell content is calculated using ClipboardContentBinding. /// /// /// public virtual object OnCopyingCellClipboardContent(object item) { object cellValue = null; BindingBase binding = ClipboardContentBinding; if (binding != null) { FrameworkElement fe = new FrameworkElement(); fe.DataContext = item; fe.SetBinding(CellValueProperty, binding); cellValue = fe.GetValue(CellValueProperty); } // Raise the event to give a chance for external listeners to modify the cell content if (CopyingCellClipboardContent != null) { EGC.DataGridCellClipboardEventArgs args = new EGC.DataGridCellClipboardEventArgs(item, this, cellValue); CopyingCellClipboardContent(this, args); cellValue = args.Content; } return cellValue; } /// We don't provide default Paste but this public method is exposed to help custom implementation of Paste /// /// This method stores the cellContent into the item object using ClipboardContentBinding. /// /// /// public virtual void OnPastingCellClipboardContent(object item, object cellContent) { BindingBase binding = ClipboardContentBinding; if (binding != null) { // Raise the event to give a chance for external listeners to modify the cell content // before it gets stored into the cell if (PastingCellClipboardContent != null) { EGC.DataGridCellClipboardEventArgs args = new EGC.DataGridCellClipboardEventArgs(item, this, cellContent); PastingCellClipboardContent(this, args); cellContent = args.Content; } // Event handlers can cancel Paste of a cell by setting its content to null if (cellContent != null) { FrameworkElement fe = new FrameworkElement(); fe.DataContext = item; fe.SetBinding(CellValueProperty, binding); fe.SetValue(CellValueProperty, cellContent); BindingExpression be = fe.GetBindingExpression(CellValueProperty); be.UpdateSource(); } } } /// /// The event is raised for each selected cell after the cell clipboard content is prepared. /// Event handlers can modify the cell content before it gets stored into the clipboard. /// public event EventHandler CopyingCellClipboardContent; /// /// The event is raised for each selected cell before the clipboard content is transfered to the cell. /// Event handlers can modify the clipboard content before it gets stored into the cell content. /// public event EventHandler PastingCellClipboardContent; #endregion #region Special Input // TODO: Consider making a protected virtual. // If made public, look for PUBLIC_ONINPUT (in DataGridCell) and enable. internal virtual void OnInput(InputEventArgs e) { } internal void BeginEdit(InputEventArgs e) { var owner = DataGridOwner; if (owner != null) { if (owner.BeginEdit(e)) { e.Handled = true; } } } #endregion #region Column Resizing /// /// Dependency property for CanUserResize /// public static readonly DependencyProperty CanUserResizeProperty = DependencyProperty.Register("CanUserResize", typeof(bool), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnNotifyCanResizePropertyChanged))); /// /// Property which indicates if an end user can resize the column or not /// public bool CanUserResize { get { return (bool)GetValue(CanUserResizeProperty); } set { SetValue(CanUserResizeProperty, value); } } /// /// Property changed callback for CanUserResize property /// /// /// private static void OnNotifyCanResizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((EGC.DataGridColumn)d).NotifyPropertyChanged(d, e, NotificationTarget.ColumnHeaders); } #endregion #region Hidden Columns /// /// Dependency property for Visibility /// public static readonly DependencyProperty VisibilityProperty = DependencyProperty.Register( "Visibility", typeof(Visibility), typeof(EGC.DataGridColumn), new FrameworkPropertyMetadata(Visibility.Visible, new PropertyChangedCallback(OnVisibilityPropertyChanged))); /// /// The property which determines if the column is visible or not. /// public Visibility Visibility { get { return (Visibility)GetValue(VisibilityProperty); } set { SetValue(VisibilityProperty, value); } } /// /// Property changed callback for Visibility property /// private static void OnVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs eventArgs) { Visibility oldVisibility = (Visibility)eventArgs.OldValue; Visibility newVisibility = (Visibility)eventArgs.NewValue; if (oldVisibility != Visibility.Visible && newVisibility != Visibility.Visible) { return; } ((EGC.DataGridColumn)d).NotifyPropertyChanged( d, eventArgs, NotificationTarget.CellsPresenter | NotificationTarget.ColumnHeadersPresenter | NotificationTarget.ColumnCollection); } /// /// Helper IsVisible property /// internal bool IsVisible { get { return Visibility == Visibility.Visible; } } #endregion #region Data private EGC.DataGrid _dataGridOwner = null; // This property is updated by DataGrid when the column is added to the DataGrid.Columns collection private BindingBase _clipboardContentBinding; // Storage for ClipboardContentBinding private bool _ignoreRedistributionOnWidthChange = false; // Flag which indicates to ignore recomputation of column widths on width change of column private bool _processingWidthChange = false; // Flag which indicates that execution of width change callback to avoid recursions. #endregion } }