//--------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using EGC = ExtendedGrid.Microsoft.Windows.Controls; using MS.Internal; namespace ExtendedGrid.Microsoft.Windows.Controls { /// /// Helper code for DataGrid. /// internal static class DataGridHelper { #region GridLines // Common code for drawing GridLines. Shared by DataGridDetailsPresenter, DataGridCellsPresenter, and Cell /// /// Returns a size based on the given one with the given double subtracted out from the Width or Height. /// Used to adjust for the thickness of grid lines. /// public static Size SubtractFromSize(Size size, double thickness, bool height) { if (height) { return new Size(size.Width, Math.Max(0.0, size.Height - thickness)); } else { return new Size(Math.Max(0.0, size.Width - thickness), size.Height); } } /// /// Test if either the vertical or horizontal gridlines are visible. /// public static bool IsGridLineVisible(EGC.DataGrid dataGrid, bool isHorizontal) { if (dataGrid != null) { EGC.DataGridGridLinesVisibility visibility = dataGrid.GridLinesVisibility; switch (visibility) { case EGC.DataGridGridLinesVisibility.All: return true; case EGC.DataGridGridLinesVisibility.Horizontal: return isHorizontal; case EGC.DataGridGridLinesVisibility.None: return false; case EGC.DataGridGridLinesVisibility.Vertical: return !isHorizontal; } } return false; } #endregion #region Notification Propagation public static bool ShouldNotifyCells(NotificationTarget target) { return TestTarget(target, NotificationTarget.Cells); } public static bool ShouldNotifyCellsPresenter(NotificationTarget target) { return TestTarget(target, NotificationTarget.CellsPresenter); } public static bool ShouldNotifyColumns(NotificationTarget target) { return TestTarget(target, NotificationTarget.Columns); } public static bool ShouldNotifyColumnHeaders(NotificationTarget target) { return TestTarget(target, NotificationTarget.ColumnHeaders); } public static bool ShouldNotifyColumnHeadersPresenter(NotificationTarget target) { return TestTarget(target, NotificationTarget.ColumnHeadersPresenter); } public static bool ShouldNotifyColumnCollection(NotificationTarget target) { return TestTarget(target, NotificationTarget.ColumnCollection); } public static bool ShouldNotifyDataGrid(NotificationTarget target) { return TestTarget(target, NotificationTarget.DataGrid); } public static bool ShouldNotifyDetailsPresenter(NotificationTarget target) { return TestTarget(target, NotificationTarget.DetailsPresenter); } public static bool ShouldRefreshCellContent(NotificationTarget target) { return TestTarget(target, NotificationTarget.RefreshCellContent); } public static bool ShouldNotifyRowHeaders(NotificationTarget target) { return TestTarget(target, NotificationTarget.RowHeaders); } public static bool ShouldNotifyRows(NotificationTarget target) { return TestTarget(target, NotificationTarget.Rows); } public static bool ShouldNotifyRowSubtree(NotificationTarget target) { NotificationTarget value = NotificationTarget.Rows | NotificationTarget.RowHeaders | NotificationTarget.CellsPresenter | NotificationTarget.Cells | NotificationTarget.RefreshCellContent | NotificationTarget.DetailsPresenter; return TestTarget(target, value); } private static bool TestTarget(NotificationTarget target, NotificationTarget value) { return (target & value) != 0; } #endregion #region Tree Helpers /// /// Walks up the templated parent tree looking for a parent type. /// public static T FindParent(FrameworkElement element) where T : FrameworkElement { FrameworkElement parent = element.TemplatedParent as FrameworkElement; while (parent != null) { T correctlyTyped = parent as T; if (correctlyTyped != null) { return correctlyTyped; } parent = parent.TemplatedParent as FrameworkElement; } return null; } public static T FindVisualParent(UIElement element) where T : UIElement { UIElement parent = element; while (parent != null) { T correctlyTyped = parent as T; if (correctlyTyped != null) { return correctlyTyped; } parent = VisualTreeHelper.GetParent(parent) as UIElement; } return null; } /// /// Helper method which determines if any of the elements of /// the tree is focusable and has tab stop /// public static bool TreeHasFocusAndTabStop(DependencyObject element) { if (element == null) { return false; } UIElement uielement = element as UIElement; if (uielement != null) { if (uielement.Focusable && KeyboardNavigation.GetIsTabStop(uielement)) { return true; } } else { ContentElement contentElement = element as ContentElement; if (contentElement != null && contentElement.Focusable && KeyboardNavigation.GetIsTabStop(contentElement)) { return true; } } int childCount = VisualTreeHelper.GetChildrenCount(element); for (int i = 0; i < childCount; i++) { DependencyObject child = VisualTreeHelper.GetChild(element, i) as DependencyObject; if (TreeHasFocusAndTabStop(child)) { return true; } } return false; } #endregion #region Cells Panel Helper /// /// Invalidates a cell's panel if its column's width changes sufficiently. /// /// The cell or header. /// public static void OnColumnWidthChanged(EGC.IProvideDataGridColumn cell, DependencyPropertyChangedEventArgs e) { Debug.Assert((cell is EGC.DataGridCell) || (cell is EGC.DataGridColumnHeader), "provideColumn should be one of the cell or header containers."); UIElement element = (UIElement)cell; EGC.DataGridColumn column = cell.Column; bool isColumnHeader = (cell is EGC.DataGridColumnHeader); if (column != null) { // determine the desired value of width for auto kind columns EGC.DataGridLength width = column.Width; if (width.IsAuto || (!isColumnHeader && width.IsSizeToCells) || (isColumnHeader && width.IsSizeToHeader)) { EGC.DataGridLength oldWidth = (EGC.DataGridLength)e.OldValue; double desiredWidth = 0.0; if (oldWidth.UnitType != width.UnitType) { double constraintWidth = column.GetConstraintWidth(isColumnHeader); if (!DoubleUtil.AreClose(element.DesiredSize.Width, constraintWidth)) { element.InvalidateMeasure(); element.Measure(new Size(constraintWidth, double.PositiveInfinity)); } desiredWidth = element.DesiredSize.Width; } else { desiredWidth = oldWidth.DesiredValue; } if (DoubleUtil.IsNaN(width.DesiredValue) || DoubleUtil.LessThan(width.DesiredValue, desiredWidth)) { column.SetWidthInternal(new EGC.DataGridLength(width.Value, width.UnitType, desiredWidth, width.DisplayValue)); } } } } /// /// Helper method which returns the clip for the cell based on whether it overlaps with frozen columns or not /// /// The cell or header. /// public static Geometry GetFrozenClipForCell(EGC.IProvideDataGridColumn cell) { EGC.DataGridCellsPanel panel = GetParentPanelForCell(cell); if (panel != null) { return panel.GetFrozenClipForChild((UIElement)cell); } return null; } /// /// Helper method which returns the parent DataGridCellsPanel for a cell /// /// The cell or header. /// Parent panel of the given cell or header public static EGC.DataGridCellsPanel GetParentPanelForCell(EGC.IProvideDataGridColumn cell) { Debug.Assert((cell is EGC.DataGridCell) || (cell is EGC.DataGridColumnHeader), "provideColumn should be one of the cell or header containers."); UIElement element = (UIElement)cell; return VisualTreeHelper.GetParent(element) as EGC.DataGridCellsPanel; } /// /// Helper method which returns the parent DataGridCellPanel's offset from the scroll viewer /// for a cell or Header /// /// The cell or header. /// Parent Panel's offset with respect to scroll viewer public static double GetParentCellsPanelHorizontalOffset(EGC.IProvideDataGridColumn cell) { EGC.DataGridCellsPanel panel = GetParentPanelForCell(cell); if (panel != null) { return panel.ComputeCellsPanelHorizontalOffset(); } return 0.0; } #endregion #region Property Helpers public static bool IsDefaultValue(DependencyObject d, DependencyProperty dp) { return DependencyPropertyHelper.GetValueSource(d, dp).BaseValueSource == BaseValueSource.Default; } public static object GetCoercedTransferPropertyValue( DependencyObject baseObject, object baseValue, DependencyProperty baseProperty, DependencyObject parentObject, DependencyProperty parentProperty) { return GetCoercedTransferPropertyValue( baseObject, baseValue, baseProperty, parentObject, parentProperty, null, null); } /// /// Computes the value of a given property based on the DataGrid property transfer rules. /// /// /// This is intended to be called from within the coercion of the baseProperty. /// /// The target object which recieves the transferred property /// The baseValue that was passed into the coercion delegate /// The property that is being coerced /// The object that contains the parentProperty /// A property who's value should be transfered (via coercion) to the baseObject if it has a higher precedence. /// Same as parentObject but evaluated at a lower presedece for a given BaseValueSource /// Same as parentProperty but evaluated at a lower presedece for a given BaseValueSource /// public static object GetCoercedTransferPropertyValue( DependencyObject baseObject, object baseValue, DependencyProperty baseProperty, DependencyObject parentObject, DependencyProperty parentProperty, DependencyObject grandParentObject, DependencyProperty grandParentProperty) { // Transfer Property Coercion rules: // // Determine if this is a 'Transfer Property Coercion'. If so: // We can safely get the BaseValueSource because the property change originated from another // property, and thus this BaseValueSource wont be stale. // Pick a value to use based on who has the greatest BaseValueSource // If not a 'Transfer Property Coercion', simply return baseValue. This will cause a property change if the value changes, which // will trigger a 'Transfer Property Coercion', and we will no longer have a stale BaseValueSource var coercedValue = baseValue; if (IsPropertyTransferEnabled(baseObject, baseProperty)) { var propertySource = DependencyPropertyHelper.GetValueSource(baseObject, baseProperty); var maxBaseValueSource = propertySource.BaseValueSource; if (parentObject != null) { var parentPropertySource = DependencyPropertyHelper.GetValueSource(parentObject, parentProperty); if (parentPropertySource.BaseValueSource > maxBaseValueSource) { coercedValue = parentObject.GetValue(parentProperty); maxBaseValueSource = parentPropertySource.BaseValueSource; } } if (grandParentObject != null) { var grandParentPropertySource = DependencyPropertyHelper.GetValueSource(grandParentObject, grandParentProperty); if (grandParentPropertySource.BaseValueSource > maxBaseValueSource) { coercedValue = grandParentObject.GetValue(grandParentProperty); maxBaseValueSource = grandParentPropertySource.BaseValueSource; } } } return coercedValue; } /// /// Causes the given DependencyProperty to be coerced in transfer mode. /// /// /// This should be called from within the target object's NotifyPropertyChanged. It MUST be called in /// response to a change in the target property. /// /// The DependencyObject which contains the property that needs to be transfered. /// The DependencyProperty that is the target of the property transfer. public static void TransferProperty(DependencyObject d, DependencyProperty p) { var transferEnabledMap = GetPropertyTransferEnabledMapForObject(d); transferEnabledMap[p] = true; d.CoerceValue(p); transferEnabledMap[p] = false; } private static Dictionary GetPropertyTransferEnabledMapForObject(DependencyObject d) { var propertyTransferEnabledForObject = _propertyTransferEnabledMap[d] as Dictionary; if (propertyTransferEnabledForObject == null) { propertyTransferEnabledForObject = new Dictionary(); _propertyTransferEnabledMap.SetWeak(d, propertyTransferEnabledForObject); } return propertyTransferEnabledForObject; } private static bool IsPropertyTransferEnabled(DependencyObject d, DependencyProperty p) { var propertyTransferEnabledForObject = _propertyTransferEnabledMap[d] as Dictionary; if (propertyTransferEnabledForObject != null) { bool isPropertyTransferEnabled; if (propertyTransferEnabledForObject.TryGetValue(p, out isPropertyTransferEnabled)) { return isPropertyTransferEnabled; } } return false; } /// /// Tracks which properties are currently being transfered. This information is needed when GetPropertyTransferEnabledMapForObject /// is called inside of Coersion. /// private static WeakHashtable _propertyTransferEnabledMap = new WeakHashtable(); #endregion #region Input Gestures // Taken from KeyGesture.CreateFromResourceStrings internal static KeyGesture CreateFromResourceStrings(string keyGestureToken, string keyDisplayString) { // combine the gesture and the display string, producing a string // that the type converter will recognize if (!String.IsNullOrEmpty(keyDisplayString)) { keyGestureToken += DISPLAYSTRING_SEPARATOR + keyDisplayString; } return _keyGestureConverter.ConvertFromInvariantString(keyGestureToken) as KeyGesture; } private const char DISPLAYSTRING_SEPARATOR = ','; private static TypeConverter _keyGestureConverter = new KeyGestureConverter(); #endregion #region Theme /// /// Will return the string version of the current theme name. /// Will apply a resource reference to the element passed in. /// public static string GetTheme(FrameworkElement element) { object o = element.ReadLocalValue(ThemeProperty); if (o == DependencyProperty.UnsetValue) { element.SetResourceReference(ThemeProperty, _themeKey); } return (string)element.GetValue(ThemeProperty); } /// /// Private property used to determine the theme name. /// private static readonly DependencyProperty ThemeProperty = DependencyProperty.RegisterAttached("Theme", typeof(string), typeof(EGC.DataGridHelper), new FrameworkPropertyMetadata(String.Empty)); /// /// The resource key used to fetch the theme name. /// private static ComponentResourceKey _themeKey = new ComponentResourceKey(typeof(EGC.DataGrid), "Theme"); /// /// Sets up a property change handler for the private theme property. /// Use this to receive a theme change notification. /// Requires calling GetTheme on an element of the given type at some point. /// public static void HookThemeChange(Type type, PropertyChangedCallback propertyChangedCallback) { ThemeProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(String.Empty, propertyChangedCallback)); } #endregion #region Binding internal static void EnsureTwoWay(BindingBase bindingBase) { if (bindingBase == null) { return; } // If it is a standard Binding, then set the mode to TwoWay Binding binding = bindingBase as Binding; if (binding != null) { if (binding.Mode != BindingMode.TwoWay) { binding.Mode = BindingMode.TwoWay; binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit; } return; } // A multi-binding can be set to TwoWay as well MultiBinding multiBinding = bindingBase as MultiBinding; if (multiBinding != null) { if (multiBinding.Mode != BindingMode.TwoWay) { multiBinding.Mode = BindingMode.TwoWay; multiBinding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit; } return; } // A priority binding is a list of bindings, each should be set to TwoWay PriorityBinding priBinding = bindingBase as PriorityBinding; if (priBinding != null) { Collection subBindings = priBinding.Bindings; int count = subBindings.Count; for (int i = 0; i < count; i++) { EnsureTwoWay(subBindings[i]); } } } internal static BindingExpression GetBindingExpression(FrameworkElement element, DependencyProperty dp) { if (element != null) { return element.GetBindingExpression(dp); } return null; } internal static void UpdateSource(FrameworkElement element, DependencyProperty dp) { BindingExpression binding = EGC.DataGridHelper.GetBindingExpression(element, dp); if (binding != null) { binding.UpdateSource(); } } internal static void UpdateTarget(FrameworkElement element, DependencyProperty dp) { BindingExpression binding = EGC.DataGridHelper.GetBindingExpression(element, dp); if (binding != null) { binding.UpdateTarget(); } } internal static void SyncColumnProperty(DependencyObject column, DependencyObject content, DependencyProperty contentProperty, DependencyProperty columnProperty) { if (IsDefaultValue(column, columnProperty)) { content.ClearValue(contentProperty); } else { content.SetValue(contentProperty, column.GetValue(columnProperty)); } } internal static string GetPathFromBinding(Binding binding) { if (binding != null) { if (!string.IsNullOrEmpty(binding.XPath)) { return binding.XPath; } else if (binding.Path != null) { return binding.Path.Path; } } return null; } #endregion #region Other Helpers /// /// Method which takes in DataGridHeadersVisibility parameter /// and determines if row headers are visible. /// public static bool AreRowHeadersVisible(EGC.DataGridHeadersVisibility headersVisibility) { return (headersVisibility & EGC.DataGridHeadersVisibility.Row) == EGC.DataGridHeadersVisibility.Row; } /// /// Helper method which coerces a value such that it satisfies min and max restrictions /// public static double CoerceToMinMax(double value, double minValue, double maxValue) { value = Math.Max(value, minValue); value = Math.Min(value, maxValue); return value; } #endregion } }