|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Automation;
namespace System.Windows.Controls
{
/// <summary>
/// A control for displaying a cell of the DataGrid.
/// </summary>
public class DataGridCell : ContentControl, IProvideDataGridColumn
{
private static readonly bool IsDataGridKeyboardSortDisabled;
private static readonly bool OptOutOfGridColumnResizeUsingKeyboard;
#region Constructors
/// <summary>
/// Instantiates global information.
/// </summary>
static DataGridCell()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DataGridCell), new FrameworkPropertyMetadata(typeof(DataGridCell)));
StyleProperty.OverrideMetadata(typeof(DataGridCell), new FrameworkPropertyMetadata(null, OnNotifyPropertyChanged, OnCoerceStyle));
ClipProperty.OverrideMetadata(typeof(DataGridCell), new FrameworkPropertyMetadata(null, new CoerceValueCallback(OnCoerceClip)));
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(DataGridCell), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local));
AutomationProperties.IsOffscreenBehaviorProperty.OverrideMetadata(typeof(DataGridCell), new FrameworkPropertyMetadata(IsOffscreenBehavior.FromClip));
// 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(DataGridCell), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange));
EventManager.RegisterClassHandler(typeof(DataGridCell), MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnAnyMouseLeftButtonDownThunk), true);
IsMouseOverPropertyKey.OverrideMetadata(typeof(DataGridCell), new UIPropertyMetadata(new PropertyChangedCallback(OnVisualStatePropertyChanged)));
EventManager.RegisterClassHandler(typeof(DataGridCell), LostFocusEvent, new RoutedEventHandler(OnAnyLostFocus), true);
EventManager.RegisterClassHandler(typeof(DataGridCell), GotFocusEvent, new RoutedEventHandler(OnAnyGotFocus), true);
AppContext.TryGetSwitch("System.Windows.Controls.DisableDataGridKeyboardSort", out IsDataGridKeyboardSortDisabled);
AppContext.TryGetSwitch("System.Windows.Controls.OptOutOfGridColumnResizeUsingKeyboard", out OptOutOfGridColumnResizeUsingKeyboard);
}
/// <summary>
/// Instantiates a new instance of this class.
/// </summary>
public DataGridCell()
{
_tracker = new ContainerTracking<DataGridCell>(this);
}
#endregion
#region Automation
protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
{
return new System.Windows.Automation.Peers.DataGridCellAutomationPeer(this);
}
#endregion
#region Cell Generation
/// <summary>
/// Prepares a cell for use.
/// </summary>
/// <remarks>
/// Updates the column reference.
/// This overload computes the column index from the ItemContainerGenerator.
/// </remarks>
internal void PrepareCell(object item, ItemsControl cellsPresenter, DataGridRow ownerRow)
{
PrepareCell(item, ownerRow, cellsPresenter.ItemContainerGenerator.IndexFromContainer(this));
}
/// <summary>
/// Prepares a cell for use.
/// </summary>
/// <remarks>
/// Updates the column reference.
/// </remarks>
internal void PrepareCell(object item, DataGridRow ownerRow, int index)
{
Debug.Assert(_owner == null || _owner == ownerRow, "_owner should be null before PrepareCell is called or the same value as the ownerRow.");
_owner = ownerRow;
DataGrid dataGrid = _owner.DataGridOwner;
if (dataGrid != null)
{
// The index of the container should correspond to the index of the column
if ((index >= 0) && (index < dataGrid.Columns.Count))
{
// Retrieve the column definition and pass it to the cell container
DataGridColumn column = dataGrid.Columns[index];
Column = column;
TabIndex = column.DisplayIndex;
}
if (IsEditing)
{
// If IsEditing was left on and this container was recycled, reset it here.
// Setting this property will result in BuildVisualTree being called.
IsEditing = false;
}
else if ((Content as FrameworkElement) == null)
{
// If there isn't already a visual tree, then create one.
BuildVisualTree();
if (!NeedsVisualTree)
{
Content = item;
}
}
// Update cell Selection
bool isSelected = dataGrid.SelectedCellsInternal.Contains(this);
SyncIsSelected(isSelected);
}
DataGridHelper.TransferProperty(this, StyleProperty);
DataGridHelper.TransferProperty(this, IsReadOnlyProperty);
CoerceValue(ClipProperty);
}
/// <summary>
/// Clears the cell of references.
/// </summary>
internal void ClearCell(DataGridRow ownerRow)
{
Debug.Assert(_owner == ownerRow, "_owner should be the same as the DataGridRow that is clearing the cell.");
_owner = null;
}
/// <summary>
/// Used by the DataGridRowGenerator owner to send notifications to the cell container.
/// </summary>
internal ContainerTracking<DataGridCell> Tracker
{
get { return _tracker; }
}
#endregion
#region Column Information
/// <summary>
/// The column that defines how this cell should appear.
/// </summary>
public DataGridColumn Column
{
get { return (DataGridColumn)GetValue(ColumnProperty); }
internal set { SetValue(ColumnPropertyKey, value); }
}
/// <summary>
/// The DependencyPropertyKey that allows writing the Column property value.
/// </summary>
private static readonly DependencyPropertyKey ColumnPropertyKey =
DependencyProperty.RegisterReadOnly("Column", typeof(DataGridColumn), typeof(DataGridCell), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnColumnChanged)));
/// <summary>
/// The DependencyProperty for the Columns property.
/// </summary>
public static readonly DependencyProperty ColumnProperty = ColumnPropertyKey.DependencyProperty;
/// <summary>
/// Called when the Column property changes.
/// Calls the protected virtual OnColumnChanged.
/// </summary>
private static void OnColumnChanged(object sender, DependencyPropertyChangedEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell != null)
{
cell.OnColumnChanged((DataGridColumn)e.OldValue, (DataGridColumn)e.NewValue);
}
}
/// <summary>
/// Called due to the cell's column definition changing.
/// Not called due to changes within the current column definition.
/// </summary>
/// <remarks>
/// Coerces ContentTemplate and ContentTemplateSelector.
/// </remarks>
/// <param name="oldColumn">The old column definition.</param>
/// <param name="newColumn">The new column definition.</param>
protected virtual void OnColumnChanged(DataGridColumn oldColumn, DataGridColumn newColumn)
{
// We need to call BuildVisualTree after changing the column (PrepareCell does this).
Content = null;
DataGridHelper.TransferProperty(this, StyleProperty);
DataGridHelper.TransferProperty(this, IsReadOnlyProperty);
}
#endregion
#region Notification Propagation
/// <summary>
/// Notifies the Cell of a property change.
/// </summary>
private static void OnNotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DataGridCell)d).NotifyPropertyChanged(d, string.Empty, e, DataGridNotificationTarget.Cells);
}
/// <summary>
/// Cancels editing the current cell & notifies the cell of a change to IsReadOnly.
/// </summary>
private static void OnNotifyIsReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var cell = (DataGridCell)d;
var dataGrid = cell.DataGridOwner;
if ((bool)e.NewValue && dataGrid != null)
{
dataGrid.CancelEdit(cell);
}
// re-evalutate the BeginEdit command's CanExecute.
CommandManager.InvalidateRequerySuggested();
cell.NotifyPropertyChanged(d, string.Empty, e, DataGridNotificationTarget.Cells);
}
/// <summary>
/// General notification for DependencyProperty changes from the grid or from columns.
/// </summary>
internal void NotifyPropertyChanged(DependencyObject d, string propertyName, DependencyPropertyChangedEventArgs e, DataGridNotificationTarget target)
{
DataGridColumn column = d as DataGridColumn;
if ((column != null) && (column != Column))
{
// This notification does not apply to this cell
return;
}
// All the notifications which are to be handled by the cell
if (DataGridHelper.ShouldNotifyCells(target))
{
if (e.Property == DataGridColumn.WidthProperty)
{
DataGridHelper.OnColumnWidthChanged(this, e);
}
else if (e.Property == DataGrid.CellStyleProperty || e.Property == DataGridColumn.CellStyleProperty || e.Property == StyleProperty)
{
DataGridHelper.TransferProperty(this, StyleProperty);
}
else if (e.Property == DataGrid.IsReadOnlyProperty || e.Property == DataGridColumn.IsReadOnlyProperty || e.Property == IsReadOnlyProperty)
{
DataGridHelper.TransferProperty(this, IsReadOnlyProperty);
}
else if (e.Property == DataGridColumn.DisplayIndexProperty)
{
TabIndex = column.DisplayIndex;
}
else if (e.Property == DataGrid.IsKeyboardFocusWithinProperty)
{
UpdateVisualState();
}
}
// All the notifications which needs forward to columns
if (DataGridHelper.ShouldRefreshCellContent(target))
{
if (column != null && NeedsVisualTree)
{
if (!string.IsNullOrEmpty(propertyName))
{
column.RefreshCellContent(this, propertyName);
}
else if (e.Property != null)
{
column.RefreshCellContent(this, e.Property.Name);
}
}
}
}
#endregion
#region Style
private static object OnCoerceStyle(DependencyObject d, object baseValue)
{
var cell = d as DataGridCell;
return DataGridHelper.GetCoercedTransferPropertyValue(
cell,
baseValue,
StyleProperty,
cell.Column,
DataGridColumn.CellStyleProperty,
cell.DataGridOwner,
DataGrid.CellStyleProperty);
}
#endregion
#region Template
internal override void ChangeVisualState(bool useTransitions)
{
if (DataGridOwner == null)
{
return;
}
// CommonStates
if (IsMouseOver)
{
VisualStates.GoToState(this, useTransitions, VisualStates.StateMouseOver, VisualStates.StateNormal);
}
else
{
VisualStateManager.GoToState(this, VisualStates.StateNormal, useTransitions);
}
// SelectionStates
if (IsSelected)
{
VisualStates.GoToState(this, useTransitions, VisualStates.StateSelected, VisualStates.StateUnselected);
}
else
{
VisualStates.GoToState(this, useTransitions, VisualStates.StateUnselected);
}
// FocusStates
if (DataGridOwner.IsKeyboardFocusWithin)
{
VisualStates.GoToState(this, useTransitions, VisualStates.StateFocused, VisualStates.StateUnfocused);
}
else
{
VisualStateManager.GoToState(this, VisualStates.StateUnfocused, useTransitions);
}
// CurrentStates
if (IsCurrent)
{
VisualStates.GoToState(this, useTransitions, VisualStates.StateCurrent, VisualStates.StateRegular);
}
else
{
VisualStateManager.GoToState(this, VisualStates.StateRegular, useTransitions);
}
// Interaction states
if (IsEditing)
{
VisualStates.GoToState(this, useTransitions, VisualStates.StateEditing, VisualStates.StateDisplay);
}
else
{
VisualStateManager.GoToState(this, VisualStates.StateDisplay, useTransitions);
}
base.ChangeVisualState(useTransitions);
}
/// <summary>
/// Builds a column's visual tree if not using templates.
/// </summary>
internal void BuildVisualTree()
{
if (NeedsVisualTree)
{
var column = Column;
if (column != null)
{
// Work around a problem with BindingGroup not removing BindingExpressions.
var row = RowOwner;
if (row != null)
{
var bindingGroup = row.BindingGroup;
if (bindingGroup != null)
{
RemoveBindingExpressions(bindingGroup, Content as DependencyObject);
}
}
// Ask the column to build a visual tree
FrameworkElement newContent = column.BuildVisualTree(IsEditing, RowDataItem, this);
// Before discarding the old visual tree, disconnect all its
// bindings, as in ItemContainerGenerator.UnlinkContainerFromItem.
// This prevents aliasing that can arise in recycling mode
FrameworkElement oldContent = Content as FrameworkElement;
if (oldContent != null && oldContent != newContent)
{
ContentPresenter cp = oldContent as ContentPresenter;
if (cp == null)
{
oldContent.SetValue(FrameworkElement.DataContextProperty, BindingExpressionBase.DisconnectedItem);
}
else
{
// for a template column, disconnect by setting the
// Content, to override the binding set up in
// DataGridTemplateColumn.LoadTemplateContent.
cp.Content = BindingExpressionBase.DisconnectedItem;
}
}
// hook the visual tree up through the Content property.
Content = newContent;
}
}
}
private void RemoveBindingExpressions(BindingGroup bindingGroup, DependencyObject element)
{
if (element == null)
return; // no content, hence no bindings to remove
var bindingExpressions = bindingGroup.BindingExpressions;
BindingExpressionBase[] bindingExpressionsCopy = new BindingExpressionBase[bindingExpressions.Count];
bindingExpressions.CopyTo(bindingExpressionsCopy, 0);
for (int i = 0; i < bindingExpressionsCopy.Length; i++)
{
// Check the binding's target element - it might have been GC'd
// (this can happen when column-virtualization is enabled).
// If so, fetching TargetElement will detach the binding and remove it
// from the binding group's collection. This side-effect is why we
// loop through a copy of the original collection, and don't rely
// on i to be a valid index into the original collection.
if (DataGridHelper.BindingExpressionBelongsToElement<DataGridCell>(bindingExpressionsCopy[i], this))
{
bindingExpressions.Remove(bindingExpressionsCopy[i]);
}
}
}
#endregion
#region Editing
/// <summary>
/// Whether the cell is in editing mode.
/// </summary>
public bool IsEditing
{
get { return (bool)GetValue(IsEditingProperty); }
set { SetValue(IsEditingProperty, value); }
}
/// <summary>
/// Represents the IsEditing property.
/// </summary>
public static readonly DependencyProperty IsEditingProperty = DependencyProperty.Register("IsEditing", typeof(bool), typeof(DataGridCell), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsEditingChanged)));
private static void OnIsEditingChanged(object sender, DependencyPropertyChangedEventArgs e)
{
((DataGridCell)sender).OnIsEditingChanged((bool)e.NewValue);
}
/// <summary>
/// Called when the value of IsEditing changes.
/// </summary>
/// <remarks>
/// Coerces the value of ContentTemplate.
/// </remarks>
/// <param name="isEditing">The new value of IsEditing.</param>
protected virtual void OnIsEditingChanged(bool isEditing)
{
if (IsKeyboardFocusWithin && !IsKeyboardFocused)
{
// Keep focus on the cell when flipping modes
Focus();
}
// If templates aren't being used, then a new visual tree needs to be built.
BuildVisualTree();
UpdateVisualState();
}
internal void NotifyCurrentCellContainerChanged()
{
UpdateVisualState();
}
/// <summary>
/// Whether the cell is the current cell.
/// </summary>
private bool IsCurrent
{
get
{
var row = RowOwner;
var column = Column;
if (row != null && column != null)
{
var dataGrid = row.DataGridOwner;
if (dataGrid != null)
{
return dataGrid.IsCurrent(row, column);
}
}
return false;
}
}
/// <summary>
/// Whether the cell can be placed in edit mode.
/// </summary>
public bool IsReadOnly
{
get { return (bool)GetValue(IsReadOnlyProperty); }
}
private static readonly DependencyPropertyKey IsReadOnlyPropertyKey =
DependencyProperty.RegisterReadOnly("IsReadOnly", typeof(bool), typeof(DataGridCell), new FrameworkPropertyMetadata(false, OnNotifyIsReadOnlyChanged, OnCoerceIsReadOnly));
/// <summary>
/// The DependencyProperty for IsReadOnly.
/// </summary>
public static readonly DependencyProperty IsReadOnlyProperty = IsReadOnlyPropertyKey.DependencyProperty;
private static object OnCoerceIsReadOnly(DependencyObject d, object baseValue)
{
var cell = d as DataGridCell;
var column = cell.Column;
var dataGrid = cell.DataGridOwner;
// We dont use the cell & 'baseValue' here because this property is read only on cell.
// the column may coerce a default value to 'true', so we'll use it's effective value for IsReadOnly
// as the baseValue.
return DataGridHelper.GetCoercedTransferPropertyValue(
column,
column.IsReadOnly,
DataGridColumn.IsReadOnlyProperty,
dataGrid,
DataGrid.IsReadOnlyProperty);
}
private static void OnAnyLostFocus(object sender, RoutedEventArgs e)
{
// Get the ancestor cell of old focused element.
// Set DataGrid.FocusedCell to null, if the cell doesn't
// have keyboard focus.
DataGridCell cell = DataGridHelper.FindVisualParent<DataGridCell>(e.OriginalSource as UIElement);
if (cell != null && cell == sender)
{
DataGrid owner = cell.DataGridOwner;
if (owner != null && !cell.IsKeyboardFocusWithin && owner.FocusedCell == cell)
{
owner.FocusedCell = null;
}
}
}
private static void OnAnyGotFocus(object sender, RoutedEventArgs e)
{
DataGridCell cell = DataGridHelper.FindVisualParent<DataGridCell>(e.OriginalSource as UIElement);
if (cell != null && cell == sender)
{
DataGrid owner = cell.DataGridOwner;
if (owner != null)
{
owner.FocusedCell = cell;
}
}
}
internal void BeginEdit(RoutedEventArgs e)
{
Debug.Assert(!IsEditing, "Should not call BeginEdit when IsEditing is true.");
IsEditing = true;
DataGridColumn column = Column;
if (column != null)
{
// Ask the column to store the original value
column.BeginEdit(Content as FrameworkElement, e);
}
RaisePreparingCellForEdit(e);
}
internal void CancelEdit()
{
Debug.Assert(IsEditing, "Should not call CancelEdit when IsEditing is false.");
DataGridColumn column = Column;
if (column != null)
{
// Ask the column to restore the original value
column.CancelEdit(Content as FrameworkElement);
}
IsEditing = false;
}
internal bool CommitEdit()
{
Debug.Assert(IsEditing, "Should not call CommitEdit when IsEditing is false.");
bool validationPassed = true;
DataGridColumn column = Column;
if (column != null)
{
// Ask the column to access the binding and update the data source
// If validation fails, then remain in editing mode
validationPassed = column.CommitEdit(Content as FrameworkElement);
}
if (validationPassed)
{
IsEditing = false;
}
return validationPassed;
}
private void RaisePreparingCellForEdit(RoutedEventArgs editingEventArgs)
{
DataGrid dataGridOwner = DataGridOwner;
if (dataGridOwner != null)
{
FrameworkElement currentEditingElement = EditingElement;
DataGridPreparingCellForEditEventArgs preparingCellForEditEventArgs = new DataGridPreparingCellForEditEventArgs(Column, RowOwner, editingEventArgs, currentEditingElement);
dataGridOwner.OnPreparingCellForEdit(preparingCellForEditEventArgs);
}
}
internal FrameworkElement EditingElement
{
get
{
// The editing element was stored in the Content property.
return Content as FrameworkElement;
}
}
#endregion
#region Selection
/// <summary>
/// Whether the cell is selected or not.
/// </summary>
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
/// <summary>
/// Represents the IsSelected property.
/// </summary>
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register("IsSelected", typeof(bool), typeof(DataGridCell), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsSelectedChanged)));
private static void OnIsSelectedChanged(object sender, DependencyPropertyChangedEventArgs e)
{
DataGridCell cell = (DataGridCell)sender;
bool isSelected = (bool)e.NewValue;
// There is no reason to notify the DataGrid if IsSelected's value came
// from the DataGrid.
if (!cell._syncingIsSelected)
{
DataGrid dataGrid = cell.DataGridOwner;
if (dataGrid != null)
{
// Notify the DataGrid that a cell's IsSelected property changed
// in case it was done programmatically instead of by the
// DataGrid itself.
dataGrid.CellIsSelectedChanged(cell, isSelected);
}
}
cell.RaiseSelectionChangedEvent(isSelected);
cell.UpdateVisualState();
}
/// <summary>
/// Used to synchronize IsSelected with the DataGrid.
/// Prevents unncessary notification back to the DataGrid.
/// </summary>
internal void SyncIsSelected(bool isSelected)
{
bool originalValue = _syncingIsSelected;
_syncingIsSelected = true;
try
{
IsSelected = isSelected;
}
finally
{
_syncingIsSelected = originalValue;
}
}
private void RaiseSelectionChangedEvent(bool isSelected)
{
if (isSelected)
{
OnSelected(new RoutedEventArgs(SelectedEvent, this));
}
else
{
OnUnselected(new RoutedEventArgs(UnselectedEvent, this));
}
}
/// <summary>
/// Raised when the item's IsSelected property becomes true.
/// </summary>
public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent("Selected", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DataGridCell));
/// <summary>
/// Raised when the item's IsSelected property becomes true.
/// </summary>
public event RoutedEventHandler Selected
{
add
{
AddHandler(SelectedEvent, value);
}
remove
{
RemoveHandler(SelectedEvent, value);
}
}
/// <summary>
/// Called when IsSelected becomes true. Raises the Selected event.
/// </summary>
/// <param name="e">Empty event arguments.</param>
protected virtual void OnSelected(RoutedEventArgs e)
{
RaiseEvent(e);
}
/// <summary>
/// Raised when the item's IsSelected property becomes false.
/// </summary>
public static readonly RoutedEvent UnselectedEvent = EventManager.RegisterRoutedEvent("Unselected", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DataGridCell));
/// <summary>
/// Raised when the item's IsSelected property becomes false.
/// </summary>
public event RoutedEventHandler Unselected
{
add
{
AddHandler(UnselectedEvent, value);
}
remove
{
RemoveHandler(UnselectedEvent, value);
}
}
/// <summary>
/// Called when IsSelected becomes false. Raises the Unselected event.
/// </summary>
/// <param name="e">Empty event arguments.</param>
protected virtual void OnUnselected(RoutedEventArgs e)
{
RaiseEvent(e);
}
#endregion
#region GridLines
// Different parts of the DataGrid draw different pieces of the GridLines.
// Cells draw a single line on their right side.
/// <summary>
/// Measure. This is overridden so that the cell can extend its size to account for a grid line on the right.
/// </summary>
protected override Size MeasureOverride(Size constraint)
{
// Make space for the GridLine on the right and 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.
DataGrid dataGridOwner = DataGridOwner;
bool horizontalLinesVisible = DataGridHelper.IsGridLineVisible(dataGridOwner, /*isHorizontal = */ true);
bool verticalLinesVisible = DataGridHelper.IsGridLineVisible(dataGridOwner, /*isHorizontal = */ false);
double horizontalLineThickness = 0;
double verticalLineThickness = 0;
if (horizontalLinesVisible)
{
horizontalLineThickness = dataGridOwner.HorizontalGridLineThickness;
constraint = DataGridHelper.SubtractFromSize(constraint, horizontalLineThickness, /*height = */ true);
}
if (verticalLinesVisible)
{
verticalLineThickness = dataGridOwner.VerticalGridLineThickness;
constraint = DataGridHelper.SubtractFromSize(constraint, verticalLineThickness, /*height = */ false);
}
Size desiredSize = base.MeasureOverride(constraint);
if (horizontalLinesVisible)
{
desiredSize.Height += horizontalLineThickness;
}
if (verticalLinesVisible)
{
desiredSize.Width += verticalLineThickness;
}
return desiredSize;
}
/// <summary>
/// Arrange. This is overriden so that the cell can position its content to account for a grid line on the right.
/// </summary>
/// <param name="arrangeSize">Arrange size</param>
protected override Size ArrangeOverride(Size arrangeSize)
{
// 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 right and bottom. All we have to do is compress and extend the size, just like Measure.
DataGrid dataGridOwner = DataGridOwner;
bool horizontalLinesVisible = DataGridHelper.IsGridLineVisible(dataGridOwner, /*isHorizontal = */ true);
bool verticalLinesVisible = DataGridHelper.IsGridLineVisible(dataGridOwner, /*isHorizontal = */ false);
double horizontalLineThickness = 0;
double verticalLineThickness = 0;
if (horizontalLinesVisible)
{
horizontalLineThickness = dataGridOwner.HorizontalGridLineThickness;
arrangeSize = DataGridHelper.SubtractFromSize(arrangeSize, horizontalLineThickness, /*height = */ true);
}
if (verticalLinesVisible)
{
verticalLineThickness = dataGridOwner.VerticalGridLineThickness;
arrangeSize = DataGridHelper.SubtractFromSize(arrangeSize, verticalLineThickness, /*height = */ false);
}
Size returnSize = base.ArrangeOverride(arrangeSize);
if (horizontalLinesVisible)
{
returnSize.Height += horizontalLineThickness;
}
if (verticalLinesVisible)
{
returnSize.Width += verticalLineThickness;
}
return returnSize;
}
/// <summary>
/// OnRender. Overriden to draw a vertical line on the right.
/// </summary>
/// <param name="drawingContext"></param>
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
DataGrid dataGrid = DataGridOwner;
if (DataGridHelper.IsGridLineVisible(dataGrid, /*isHorizontal = */ false))
{
double thickness = DataGridOwner.VerticalGridLineThickness;
Rect rect = new Rect(new Size(thickness, RenderSize.Height));
rect.X = RenderSize.Width - thickness;
drawingContext.DrawRectangle(DataGridOwner.VerticalGridLinesBrush, null, rect);
}
if (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 Input
private static void OnAnyMouseLeftButtonDownThunk(object sender, MouseButtonEventArgs e)
{
((DataGridCell)sender).OnAnyMouseLeftButtonDown(e);
}
/// <summary>
/// The left mouse button was pressed
/// </summary>
/// Consider making a protected virtual.
private void OnAnyMouseLeftButtonDown(MouseButtonEventArgs e)
{
bool focusWithin = IsKeyboardFocusWithin;
bool isCtrlKeyPressed = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
if (focusWithin && !isCtrlKeyPressed && !e.Handled && IsSelected)
{
// The cell is focused and there are no other special selection gestures,
// enter edit mode.
DataGrid dataGridOwner = DataGridOwner;
if (dataGridOwner != null)
{
// The cell was clicked, which means that other cells may
// need to be de-selected, let the DataGrid handle that.
dataGridOwner.HandleSelectionForCellInput(this, /* startDragging = */ false, /* allowsExtendSelect = */ true, /* allowsMinimalSelect = */ false);
if (!IsEditing && !IsReadOnly)
{
// Enter edit mode
dataGridOwner.BeginEdit(e);
e.Handled = true;
}
// Please note that unselecting rows is not really considered a
// handlable operation.
//e.Handled = true;
}
}
else if (!focusWithin || !IsSelected || isCtrlKeyPressed)
{
if (!focusWithin)
{
// The cell should receive focus on click
Focus();
}
DataGrid dataGridOwner = DataGridOwner;
if (dataGridOwner != null)
{
// Let the DataGrid process selection
dataGridOwner.HandleSelectionForCellInput(this, /* startDragging = */ Mouse.Captured == null, /* allowsExtendSelect = */ true, /* allowsMinimalSelect = */ true);
}
e.Handled = true;
}
#if PUBLIC_ONINPUT
else
{
SendInputToColumn(e);
}
#endif
}
/// <summary>
/// Reporting text composition.
/// </summary>
protected override void OnTextInput(TextCompositionEventArgs e)
{
SendInputToColumn(e);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
SendInputToColumn(e);
}
/// <summary>
/// Reporting a key was pressed.
/// </summary>
protected override void OnKeyDown(KeyEventArgs e)
{
if (!e.Handled)
{
if (!OptOutOfGridColumnResizeUsingKeyboard)
{
ModifierKeys modifierKeys = Keyboard.Modifiers & ModifierMask;
if (((e.SystemKey == Key.Right) || (e.SystemKey == Key.Left)) && (modifierKeys == ModifierKeys.Alt))
{
DataGridLength updatedWidth;
if (e.SystemKey == Key.Right)
{
updatedWidth = new DataGridLength(Column.ActualWidth + ColumnWidthStepSize);
}
else
{
updatedWidth = new DataGridLength(Column.ActualWidth - ColumnWidthStepSize);
}
if (Column != null)
{
if (Column.CanColumnResize(updatedWidth))
{
Column.SetCurrentValueInternal(DataGridColumn.WidthProperty, updatedWidth);
}
e.Handled = true;
}
return;
}
}
if (!IsDataGridKeyboardSortDisabled)
{
if (e.Key == Key.F3 && Column != null && Column.CanUserSort)
{
Column.DataGridOwner.PerformSort(Column);
e.Handled = true;
return;
}
}
}
SendInputToColumn(e);
}
#if PUBLIC_ONINPUT
// While DataGridColumn.OnInput is internal, these methods are not needed.
// If that method becomes exposed, then it would make sense to include a better
// range of input events.
/// <summary>
/// Reporting a key was released
/// </summary>
protected override void OnKeyUp(KeyEventArgs e)
{
SendInputToColumn(e);
}
/// <summary>
/// Reporting the mouse button was released
/// </summary>
protected override void OnMouseUp(MouseButtonEventArgs e)
{
SendInputToColumn(e);
}
#endif
private void SendInputToColumn(InputEventArgs e)
{
var column = Column;
if (column != null)
{
column.OnInput(e);
}
}
#endregion
#region Frozen Columns
/// <summary>
/// Coercion call back for clip property which ensures that the cell overlapping with frozen
/// column gets clipped appropriately.
/// </summary>
/// <param name="d"></param>
/// <param name="baseValue"></param>
/// <returns></returns>
private static object OnCoerceClip(DependencyObject d, object baseValue)
{
DataGridCell cell = (DataGridCell)d;
Geometry geometry = baseValue as Geometry;
Geometry frozenGeometry = DataGridHelper.GetFrozenClipForCell(cell);
if (frozenGeometry != null)
{
if (geometry == null)
{
return frozenGeometry;
}
geometry = new CombinedGeometry(GeometryCombineMode.Intersect, geometry, frozenGeometry);
}
return geometry;
}
#endregion
#region Helpers
internal DataGrid DataGridOwner
{
get
{
if (_owner != null)
{
DataGrid dataGridOwner = _owner.DataGridOwner;
if (dataGridOwner == null)
{
dataGridOwner = ItemsControl.ItemsControlFromItemContainer(_owner) as DataGrid;
}
return dataGridOwner;
}
return null;
}
}
private Panel ParentPanel
{
get
{
return VisualParent as Panel;
}
}
internal DataGridRow RowOwner
{
get { return _owner; }
}
internal object RowDataItem
{
get
{
DataGridRow row = RowOwner;
if (row != null)
{
return row.Item;
}
return DataContext;
}
}
private DataGridCellsPresenter CellsPresenter
{
get
{
return ItemsControl.ItemsControlFromItemContainer(this) as DataGridCellsPresenter;
}
}
private bool NeedsVisualTree
{
get
{
return (ContentTemplate == null) && (ContentTemplateSelector == null);
}
}
#endregion
#region Data
private DataGridRow _owner;
private ContainerTracking<DataGridCell> _tracker;
private bool _syncingIsSelected; // Used to prevent unnecessary notifications
private const double ColumnWidthStepSize = 10d;
private const ModifierKeys ModifierMask = ModifierKeys.Alt | ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Windows;
#endregion
}
}
|