1053 lines
41 KiB
C#
1053 lines
41 KiB
C#
|
//---------------------------------------------------------------------------
|
||
|
//
|
||
|
// 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.Data;
|
||
|
using System.Windows.Input;
|
||
|
using System.Windows.Media;
|
||
|
using MS.Internal;
|
||
|
using EGC = ExtendedGrid.Microsoft.Windows.Controls;
|
||
|
|
||
|
namespace ExtendedGrid.Microsoft.Windows.Controls
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// A control that will be responsible for generating column headers.
|
||
|
/// This control is meant to be specified within the template of the DataGrid.
|
||
|
///
|
||
|
/// It typically isn't in the subtree of the main ScrollViewer for the DataGrid.
|
||
|
/// It thus handles scrolling the column headers horizontally. For this to work
|
||
|
/// it needs to be able to find the ScrollViewer -- this is done by setting the
|
||
|
/// SourceScrollViewerName property.
|
||
|
/// </summary>
|
||
|
public class DataGridColumnHeadersPresenter : ItemsControl
|
||
|
{
|
||
|
static DataGridColumnHeadersPresenter()
|
||
|
{
|
||
|
Type ownerType = typeof(EGC.DataGridColumnHeadersPresenter);
|
||
|
|
||
|
DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ownerType));
|
||
|
FocusableProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(false));
|
||
|
|
||
|
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(EGC.DataGridCellsPanel));
|
||
|
ItemsPanelProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(new ItemsPanelTemplate(factory)));
|
||
|
|
||
|
VirtualizingStackPanel.IsVirtualizingProperty.OverrideMetadata(
|
||
|
ownerType,
|
||
|
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsVirtualizingPropertyChanged), new CoerceValueCallback(OnCoerceIsVirtualizingProperty)));
|
||
|
|
||
|
VirtualizingStackPanel.VirtualizationModeProperty.OverrideMetadata(
|
||
|
ownerType,
|
||
|
new FrameworkPropertyMetadata(VirtualizationMode.Recycling));
|
||
|
}
|
||
|
|
||
|
#region Initialization
|
||
|
|
||
|
/// <summary>
|
||
|
/// Tells the row owner about this element.
|
||
|
/// </summary>
|
||
|
public override void OnApplyTemplate()
|
||
|
{
|
||
|
base.OnApplyTemplate();
|
||
|
|
||
|
// Find the columns collection and set the ItemsSource.
|
||
|
DataGrid grid = ParentDataGrid;
|
||
|
|
||
|
if (grid != null)
|
||
|
{
|
||
|
ItemsSource = new EGC.ColumnHeaderCollection(grid.Columns);
|
||
|
grid.ColumnHeadersPresenter = this;
|
||
|
EGC.DataGridHelper.TransferProperty(this, VirtualizingStackPanel.IsVirtualizingProperty);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ItemsSource = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Automation
|
||
|
|
||
|
protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
|
||
|
{
|
||
|
return new EGC.DataGridColumnHeadersPresenterAutomationPeer(this);
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Layout
|
||
|
|
||
|
/// <summary>
|
||
|
/// Measure
|
||
|
/// </summary>
|
||
|
protected override Size MeasureOverride(Size availableSize)
|
||
|
{
|
||
|
Size desiredSize;
|
||
|
Size childConstraint = availableSize;
|
||
|
childConstraint.Width = Double.PositiveInfinity;
|
||
|
|
||
|
desiredSize = base.MeasureOverride(childConstraint);
|
||
|
|
||
|
Size indicatorSize;
|
||
|
if (_columnHeaderDragIndicator != null && _isColumnHeaderDragging)
|
||
|
{
|
||
|
_columnHeaderDragIndicator.Measure(childConstraint);
|
||
|
indicatorSize = _columnHeaderDragIndicator.DesiredSize;
|
||
|
desiredSize.Width = Math.Max(desiredSize.Width, indicatorSize.Width);
|
||
|
desiredSize.Height = Math.Max(desiredSize.Height, indicatorSize.Height);
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDropLocationIndicator != null && _isColumnHeaderDragging)
|
||
|
{
|
||
|
_columnHeaderDropLocationIndicator.Measure(availableSize);
|
||
|
indicatorSize = _columnHeaderDropLocationIndicator.DesiredSize;
|
||
|
desiredSize.Width = Math.Max(desiredSize.Width, indicatorSize.Width);
|
||
|
desiredSize.Height = Math.Max(desiredSize.Height, indicatorSize.Height);
|
||
|
}
|
||
|
|
||
|
desiredSize.Width = Math.Min(availableSize.Width, desiredSize.Width);
|
||
|
|
||
|
return desiredSize;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Arrange
|
||
|
/// </summary>
|
||
|
/// <param name="finalSize">Arrange size</param>
|
||
|
protected override Size ArrangeOverride(Size finalSize)
|
||
|
{
|
||
|
UIElement child = (VisualTreeHelper.GetChildrenCount(this) > 0) ? VisualTreeHelper.GetChild(this, 0) as UIElement : null;
|
||
|
|
||
|
if (child != null)
|
||
|
{
|
||
|
Rect childRect = new Rect(finalSize);
|
||
|
EGC.DataGrid dataGrid = ParentDataGrid;
|
||
|
if (dataGrid != null)
|
||
|
{
|
||
|
childRect.X = -dataGrid.HorizontalScrollOffset;
|
||
|
childRect.Width = Math.Max(finalSize.Width, dataGrid.CellsPanelActualWidth);
|
||
|
}
|
||
|
|
||
|
child.Arrange(childRect);
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDragIndicator != null && _isColumnHeaderDragging)
|
||
|
{
|
||
|
_columnHeaderDragIndicator.Arrange(new Rect(
|
||
|
new Point(_columnHeaderDragCurrentPosition.X - _columnHeaderDragStartRelativePosition.X, 0),
|
||
|
new Size(_columnHeaderDragIndicator.Width, _columnHeaderDragIndicator.Height)));
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDropLocationIndicator != null && _isColumnHeaderDragging)
|
||
|
{
|
||
|
Point point = FindColumnHeaderPositionByCurrentPosition(_columnHeaderDragCurrentPosition, true);
|
||
|
double dropIndicatorWidth = _columnHeaderDropLocationIndicator.Width;
|
||
|
point.X -= dropIndicatorWidth * 0.5;
|
||
|
_columnHeaderDropLocationIndicator.Arrange(new Rect(point, new Size(dropIndicatorWidth, _columnHeaderDropLocationIndicator.Height)));
|
||
|
}
|
||
|
|
||
|
return finalSize;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Override of UIElement.GetLayoutClip(). This is a tricky way to ensure we always clip regardless of the value of ClipToBounds.
|
||
|
/// </summary>
|
||
|
protected override Geometry GetLayoutClip(Size layoutSlotSize)
|
||
|
{
|
||
|
RectangleGeometry clip = new RectangleGeometry(new Rect(RenderSize));
|
||
|
clip.Freeze();
|
||
|
return clip;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Column Header Generation
|
||
|
|
||
|
/// <summary>
|
||
|
/// Instantiates an instance of a container.
|
||
|
/// </summary>
|
||
|
/// <returns>A new DataGridColumnHeader.</returns>
|
||
|
protected override DependencyObject GetContainerForItemOverride()
|
||
|
{
|
||
|
return new EGC.DataGridColumnHeader();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Determines if an item is its own container.
|
||
|
/// </summary>
|
||
|
/// <param name="item">The item to test.</param>
|
||
|
/// <returns>true if the item is a DataGridColumnHeader, false otherwise.</returns>
|
||
|
protected override bool IsItemItsOwnContainerOverride(object item)
|
||
|
{
|
||
|
return item is EGC.DataGridColumnHeader;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which returns the result of IsItemItsOwnContainerOverride to be used internally
|
||
|
/// </summary>
|
||
|
internal bool IsItemItsOwnContainerInternal(object item)
|
||
|
{
|
||
|
return IsItemItsOwnContainerOverride(item);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Prepares a new container for a given item.
|
||
|
/// </summary>
|
||
|
/// <remarks>We do not want to call base.PrepareContainerForItemOverride in this override because it will set local values on the header</remarks>
|
||
|
/// <param name="element">The new container.</param>
|
||
|
/// <param name="item">The item that the container represents.</param>
|
||
|
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
|
||
|
{
|
||
|
EGC.DataGridColumnHeader header = element as EGC.DataGridColumnHeader;
|
||
|
|
||
|
if (header != null)
|
||
|
{
|
||
|
EGC.DataGridColumn column = ColumnFromContainer(header);
|
||
|
Debug.Assert(column != null, "We shouldn't have generated this column header if we don't have a column.");
|
||
|
|
||
|
if (header.Column == null)
|
||
|
{
|
||
|
// A null column means this is a fresh container. PrepareContainer will also be called simply if the column's
|
||
|
// Header property has changed and this container needs to be given a new item. In that case it'll already be tracked.
|
||
|
header.Tracker.Debug_AssertNotInList(_headerTrackingRoot);
|
||
|
header.Tracker.StartTracking(ref _headerTrackingRoot);
|
||
|
}
|
||
|
|
||
|
header.Tracker.Debug_AssertIsInList(_headerTrackingRoot);
|
||
|
|
||
|
header.PrepareColumnHeader(item, column);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Clears a container of references.
|
||
|
/// </summary>
|
||
|
/// <param name="element">The container being cleared.</param>
|
||
|
/// <param name="item">The data item that the container represented.</param>
|
||
|
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
|
||
|
{
|
||
|
EGC.DataGridColumnHeader header = element as EGC.DataGridColumnHeader;
|
||
|
|
||
|
base.ClearContainerForItemOverride(element, item);
|
||
|
|
||
|
if (header != null)
|
||
|
{
|
||
|
header.Tracker.StopTracking(ref _headerTrackingRoot);
|
||
|
header.ClearHeader();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private EGC.DataGridColumn ColumnFromContainer(EGC.DataGridColumnHeader container)
|
||
|
{
|
||
|
Debug.Assert(HeaderCollection != null, "This is a helper method for preparing and clearing a container; if it's called we must have a valid ItemSource");
|
||
|
|
||
|
int index = ItemContainerGenerator.IndexFromContainer(container);
|
||
|
return HeaderCollection.ColumnFromIndex(index);
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Notification Propagation
|
||
|
|
||
|
/// <summary>
|
||
|
/// General notification for DependencyProperty changes from the grid.
|
||
|
/// </summary>
|
||
|
internal void NotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e, NotificationTarget target)
|
||
|
{
|
||
|
NotifyPropertyChanged(d, string.Empty, e, target);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Notification for column header-related DependencyProperty changes from the grid or from columns.
|
||
|
/// </summary>
|
||
|
internal void NotifyPropertyChanged(DependencyObject d, string propertyName, DependencyPropertyChangedEventArgs e, NotificationTarget target)
|
||
|
{
|
||
|
EGC.DataGridColumn column = d as EGC.DataGridColumn;
|
||
|
if (EGC.DataGridHelper.ShouldNotifyColumnHeadersPresenter(target))
|
||
|
{
|
||
|
if (e.Property == EGC.DataGridColumn.WidthProperty ||
|
||
|
e.Property == EGC.DataGridColumn.DisplayIndexProperty)
|
||
|
{
|
||
|
if (column.IsVisible)
|
||
|
{
|
||
|
InvalidateDataGridCellsPanelMeasureAndArrange();
|
||
|
}
|
||
|
}
|
||
|
else if (e.Property == EGC.DataGrid.FrozenColumnCountProperty ||
|
||
|
e.Property == EGC.DataGridColumn.VisibilityProperty ||
|
||
|
e.Property == EGC.DataGrid.CellsPanelHorizontalOffsetProperty ||
|
||
|
string.Compare(propertyName, "ViewportWidth", StringComparison.Ordinal) == 0 ||
|
||
|
string.Compare(propertyName, "DelayedColumnWidthComputation", StringComparison.Ordinal) == 0)
|
||
|
{
|
||
|
InvalidateDataGridCellsPanelMeasureAndArrange();
|
||
|
}
|
||
|
else if (e.Property == EGC.DataGrid.HorizontalScrollOffsetProperty)
|
||
|
{
|
||
|
InvalidateArrange();
|
||
|
InvalidateDataGridCellsPanelMeasureAndArrange();
|
||
|
}
|
||
|
else if (string.Compare(propertyName, "RealizedColumnsBlockListForNonVirtualizedRows", StringComparison.Ordinal) == 0)
|
||
|
{
|
||
|
InvalidateDataGridCellsPanelMeasureAndArrange(/* withColumnVirtualization */ false);
|
||
|
}
|
||
|
else if (string.Compare(propertyName, "RealizedColumnsBlockListForVirtualizedRows", StringComparison.Ordinal) == 0)
|
||
|
{
|
||
|
InvalidateDataGridCellsPanelMeasureAndArrange(/* withColumnVirtualization */ true);
|
||
|
}
|
||
|
else if (e.Property == EGC.DataGrid.CellsPanelActualWidthProperty)
|
||
|
{
|
||
|
InvalidateArrange();
|
||
|
}
|
||
|
else if (e.Property == EGC.DataGrid.EnableColumnVirtualizationProperty)
|
||
|
{
|
||
|
EGC.DataGridHelper.TransferProperty(this, VirtualizingStackPanel.IsVirtualizingProperty);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (EGC.DataGridHelper.ShouldNotifyColumnHeaders(target))
|
||
|
{
|
||
|
if (e.Property == EGC.DataGridColumn.HeaderProperty)
|
||
|
{
|
||
|
if (HeaderCollection != null)
|
||
|
{
|
||
|
HeaderCollection.NotifyHeaderPropertyChanged(column, e);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Notify the DataGridColumnHeader objects about property changes
|
||
|
EGC.ContainerTracking<EGC.DataGridColumnHeader> tracker = _headerTrackingRoot;
|
||
|
|
||
|
while (tracker != null)
|
||
|
{
|
||
|
tracker.Container.NotifyPropertyChanged(d, e);
|
||
|
tracker = tracker.Next;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Column Virtualization
|
||
|
|
||
|
/// <summary>
|
||
|
/// Property changed callback for VirtualizingStackPanel.IsVirtualizing property
|
||
|
/// </summary>
|
||
|
private static void OnIsVirtualizingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||
|
{
|
||
|
EGC.DataGridColumnHeadersPresenter headersPresenter = (EGC.DataGridColumnHeadersPresenter)d;
|
||
|
EGC.DataGridHelper.TransferProperty(headersPresenter, VirtualizingStackPanel.IsVirtualizingProperty);
|
||
|
if (e.OldValue != headersPresenter.GetValue(VirtualizingStackPanel.IsVirtualizingProperty))
|
||
|
{
|
||
|
headersPresenter.InvalidateDataGridCellsPanelMeasureAndArrange();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Coercion callback for VirtualizingStackPanel.IsVirtualizing property
|
||
|
/// </summary>
|
||
|
private static object OnCoerceIsVirtualizingProperty(DependencyObject d, object baseValue)
|
||
|
{
|
||
|
var headersPresenter = d as EGC.DataGridColumnHeadersPresenter;
|
||
|
return EGC.DataGridHelper.GetCoercedTransferPropertyValue(
|
||
|
headersPresenter,
|
||
|
baseValue,
|
||
|
VirtualizingStackPanel.IsVirtualizingProperty,
|
||
|
headersPresenter.ParentDataGrid,
|
||
|
EGC.DataGrid.EnableColumnVirtualizationProperty);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method which invalidate the underlying itemshost's measure and arrange
|
||
|
/// </summary>
|
||
|
private void InvalidateDataGridCellsPanelMeasureAndArrange()
|
||
|
{
|
||
|
if (_internalItemsHost != null)
|
||
|
{
|
||
|
_internalItemsHost.InvalidateMeasure();
|
||
|
_internalItemsHost.InvalidateArrange();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method which invalidate the underlying itemshost's measure and arrange
|
||
|
/// </summary>
|
||
|
/// <param name="withColumnVirtualization">
|
||
|
/// True to invalidate only when virtualization is on.
|
||
|
/// False to invalidate only when virtualization is off.
|
||
|
/// </param>
|
||
|
private void InvalidateDataGridCellsPanelMeasureAndArrange(bool withColumnVirtualization)
|
||
|
{
|
||
|
// Invalidates measure and arrange if the flag and the virtualization
|
||
|
// are either both true or both false.
|
||
|
if (withColumnVirtualization == VirtualizingStackPanel.GetIsVirtualizing(this))
|
||
|
{
|
||
|
InvalidateDataGridCellsPanelMeasureAndArrange();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Workaround for not being able to access the panel instance of
|
||
|
/// itemscontrol directly
|
||
|
/// </summary>
|
||
|
internal Panel InternalItemsHost
|
||
|
{
|
||
|
get { return _internalItemsHost; }
|
||
|
set { _internalItemsHost = value; }
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Column Reordering
|
||
|
|
||
|
/// <summary>
|
||
|
/// Override of VisualChildrenCount which accomodates the indicators as visual children
|
||
|
/// </summary>
|
||
|
protected override int VisualChildrenCount
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
int visualChildrenCount = base.VisualChildrenCount;
|
||
|
if (_columnHeaderDragIndicator != null)
|
||
|
{
|
||
|
visualChildrenCount++;
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDropLocationIndicator != null)
|
||
|
{
|
||
|
visualChildrenCount++;
|
||
|
}
|
||
|
|
||
|
return visualChildrenCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Override of GetVisualChild which accomodates the indicators as visual children
|
||
|
/// </summary>
|
||
|
protected override Visual GetVisualChild(int index)
|
||
|
{
|
||
|
int visualChildrenCount = base.VisualChildrenCount;
|
||
|
if (index == visualChildrenCount)
|
||
|
{
|
||
|
if (_columnHeaderDragIndicator != null)
|
||
|
{
|
||
|
return _columnHeaderDragIndicator;
|
||
|
}
|
||
|
else if (_columnHeaderDropLocationIndicator != null)
|
||
|
{
|
||
|
return _columnHeaderDropLocationIndicator;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (index == visualChildrenCount + 1)
|
||
|
{
|
||
|
if (_columnHeaderDragIndicator != null && _columnHeaderDropLocationIndicator != null)
|
||
|
{
|
||
|
return _columnHeaderDropLocationIndicator;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return base.GetVisualChild(index);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets called on mouse left button down of child header, and ensures preparation for column header drag
|
||
|
/// </summary>
|
||
|
internal void OnHeaderMouseLeftButtonDown(MouseButtonEventArgs e)
|
||
|
{
|
||
|
if (ParentDataGrid == null)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDragIndicator != null)
|
||
|
{
|
||
|
RemoveVisualChild(_columnHeaderDragIndicator);
|
||
|
_columnHeaderDragIndicator = null;
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDropLocationIndicator != null)
|
||
|
{
|
||
|
RemoveVisualChild(_columnHeaderDropLocationIndicator);
|
||
|
_columnHeaderDropLocationIndicator = null;
|
||
|
}
|
||
|
|
||
|
Point mousePosition = e.GetPosition(this);
|
||
|
EGC.DataGridColumnHeader header = FindColumnHeaderByPosition(mousePosition);
|
||
|
|
||
|
if (header != null)
|
||
|
{
|
||
|
EGC.DataGridColumn column = header.Column;
|
||
|
|
||
|
if (ParentDataGrid.CanUserReorderColumns && column.CanUserReorder)
|
||
|
{
|
||
|
PrepareColumnHeaderDrag(header, e.GetPosition(this), e.GetPosition(header));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_isColumnHeaderDragging = false;
|
||
|
_prepareColumnHeaderDragging = false;
|
||
|
_draggingSrcColumnHeader = null;
|
||
|
InvalidateArrange();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets called on mouse move of child header, and ensures column header drag
|
||
|
/// </summary>
|
||
|
internal void OnHeaderMouseMove(MouseEventArgs e)
|
||
|
{
|
||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||
|
{
|
||
|
if (_prepareColumnHeaderDragging)
|
||
|
{
|
||
|
_columnHeaderDragCurrentPosition = e.GetPosition(this);
|
||
|
|
||
|
if (!_isColumnHeaderDragging)
|
||
|
{
|
||
|
if (CheckStartColumnHeaderDrag(_columnHeaderDragCurrentPosition, _columnHeaderDragStartPosition))
|
||
|
{
|
||
|
StartColumnHeaderDrag();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bool shouldDisplayDragIndicator = IsMousePositionValidForColumnDrag(2.0);
|
||
|
Visibility dragIndicatorVisibility = shouldDisplayDragIndicator ? Visibility.Visible : Visibility.Collapsed;
|
||
|
|
||
|
if (_columnHeaderDragIndicator != null)
|
||
|
{
|
||
|
_columnHeaderDragIndicator.Visibility = dragIndicatorVisibility;
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDropLocationIndicator != null)
|
||
|
{
|
||
|
_columnHeaderDropLocationIndicator.Visibility = dragIndicatorVisibility;
|
||
|
}
|
||
|
|
||
|
InvalidateArrange();
|
||
|
|
||
|
DragDeltaEventArgs dragDeltaEventArgs = new DragDeltaEventArgs(
|
||
|
_columnHeaderDragCurrentPosition.X - _columnHeaderDragStartPosition.X,
|
||
|
_columnHeaderDragCurrentPosition.Y - _columnHeaderDragStartPosition.Y);
|
||
|
|
||
|
_columnHeaderDragStartPosition = _columnHeaderDragCurrentPosition;
|
||
|
ParentDataGrid.OnColumnHeaderDragDelta(dragDeltaEventArgs);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets called on mouse left button up of child header, and ensures reordering of columns on successful completion of drag
|
||
|
/// </summary>
|
||
|
internal void OnHeaderMouseLeftButtonUp(MouseButtonEventArgs e)
|
||
|
{
|
||
|
if (_isColumnHeaderDragging)
|
||
|
{
|
||
|
_columnHeaderDragCurrentPosition = e.GetPosition(this);
|
||
|
FinishColumnHeaderDrag(false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ClearColumnHeaderDragInfo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets called on mouse lost capture of child header and ensures that when capture gets lost
|
||
|
/// the drag ends in appropriate state. In this case it restore the drag state to
|
||
|
/// the start of the operation by finishing the drag with cancel flag
|
||
|
/// </summary>
|
||
|
internal void OnHeaderLostMouseCapture(MouseEventArgs e)
|
||
|
{
|
||
|
if (_isColumnHeaderDragging &&
|
||
|
Mouse.LeftButton == MouseButtonState.Pressed)
|
||
|
{
|
||
|
FinishColumnHeaderDrag(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method which clears the header drag state
|
||
|
/// </summary>
|
||
|
private void ClearColumnHeaderDragInfo()
|
||
|
{
|
||
|
_isColumnHeaderDragging = false;
|
||
|
_prepareColumnHeaderDragging = false;
|
||
|
_draggingSrcColumnHeader = null;
|
||
|
if (_columnHeaderDragIndicator != null)
|
||
|
{
|
||
|
RemoveVisualChild(_columnHeaderDragIndicator);
|
||
|
_columnHeaderDragIndicator = null;
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDropLocationIndicator != null)
|
||
|
{
|
||
|
RemoveVisualChild(_columnHeaderDropLocationIndicator);
|
||
|
_columnHeaderDropLocationIndicator = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which prepares the state for the start of column header drag
|
||
|
/// </summary>
|
||
|
private void PrepareColumnHeaderDrag(EGC.DataGridColumnHeader header, Point pos, Point relativePos)
|
||
|
{
|
||
|
_prepareColumnHeaderDragging = true;
|
||
|
_isColumnHeaderDragging = false;
|
||
|
_draggingSrcColumnHeader = header;
|
||
|
_columnHeaderDragStartPosition = pos;
|
||
|
_columnHeaderDragStartRelativePosition = relativePos;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which checks if mouse move is sufficient to start the drag
|
||
|
/// </summary>
|
||
|
private static bool CheckStartColumnHeaderDrag(Point currentPos, Point originalPos)
|
||
|
{
|
||
|
return DoubleUtil.GreaterThan(Math.Abs(currentPos.X - originalPos.X), SystemParameters.MinimumHorizontalDragDistance);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which checks during and after the drag if the position is valid for the drop
|
||
|
/// </summary>
|
||
|
private bool IsMousePositionValidForColumnDrag(double dragFactor)
|
||
|
{
|
||
|
int nearestDisplayIndex = -1;
|
||
|
return IsMousePositionValidForColumnDrag(dragFactor, out nearestDisplayIndex);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which checks during and after the drag if the position is valid for the drop and returns the drop display index
|
||
|
/// </summary>
|
||
|
private bool IsMousePositionValidForColumnDrag(double dragFactor, out int nearestDisplayIndex)
|
||
|
{
|
||
|
nearestDisplayIndex = -1;
|
||
|
bool isDraggingColumnFrozen = false;
|
||
|
if (_draggingSrcColumnHeader.Column != null)
|
||
|
{
|
||
|
isDraggingColumnFrozen = _draggingSrcColumnHeader.Column.IsFrozen;
|
||
|
}
|
||
|
|
||
|
int frozenCount = 0;
|
||
|
if (ParentDataGrid != null)
|
||
|
{
|
||
|
frozenCount = ParentDataGrid.FrozenColumnCount;
|
||
|
}
|
||
|
|
||
|
nearestDisplayIndex = FindDisplayIndexByPosition(_columnHeaderDragCurrentPosition, true);
|
||
|
if (isDraggingColumnFrozen && nearestDisplayIndex >= frozenCount)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!isDraggingColumnFrozen && nearestDisplayIndex < frozenCount)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
double height = 0.0;
|
||
|
|
||
|
if (_columnHeaderDragIndicator == null)
|
||
|
{
|
||
|
height = _draggingSrcColumnHeader.RenderSize.Height;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
height = Math.Max(_draggingSrcColumnHeader.RenderSize.Height, _columnHeaderDragIndicator.Height);
|
||
|
}
|
||
|
|
||
|
return DoubleUtil.LessThanOrClose(-height * dragFactor, _columnHeaderDragCurrentPosition.Y) &&
|
||
|
DoubleUtil.LessThanOrClose(_columnHeaderDragCurrentPosition.Y, height * (dragFactor + 1));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which start the column header drag. Includes raising events and creating default ghosts
|
||
|
/// </summary>
|
||
|
private void StartColumnHeaderDrag()
|
||
|
{
|
||
|
Debug.Assert(ParentDataGrid != null, "ParentDataGrid is null");
|
||
|
|
||
|
_columnHeaderDragStartPosition = _columnHeaderDragCurrentPosition;
|
||
|
DragStartedEventArgs dragStartedEventArgs = new DragStartedEventArgs(_columnHeaderDragStartPosition.X, _columnHeaderDragStartPosition.Y);
|
||
|
ParentDataGrid.OnColumnHeaderDragStarted(dragStartedEventArgs);
|
||
|
|
||
|
EGC.DataGridColumnReorderingEventArgs reorderingEventArgs = new EGC.DataGridColumnReorderingEventArgs(_draggingSrcColumnHeader.Column);
|
||
|
|
||
|
_columnHeaderDragIndicator = CreateColumnHeaderDragIndicator();
|
||
|
_columnHeaderDropLocationIndicator = CreateColumnHeaderDropIndicator();
|
||
|
|
||
|
reorderingEventArgs.DragIndicator = _columnHeaderDragIndicator;
|
||
|
reorderingEventArgs.DropLocationIndicator = _columnHeaderDropLocationIndicator;
|
||
|
ParentDataGrid.OnColumnReordering(reorderingEventArgs);
|
||
|
|
||
|
if (!reorderingEventArgs.Cancel)
|
||
|
{
|
||
|
_isColumnHeaderDragging = true;
|
||
|
_columnHeaderDragIndicator = reorderingEventArgs.DragIndicator;
|
||
|
_columnHeaderDropLocationIndicator = reorderingEventArgs.DropLocationIndicator;
|
||
|
|
||
|
if (_columnHeaderDragIndicator != null)
|
||
|
{
|
||
|
SetDefaultsOnDragIndicator();
|
||
|
AddVisualChild(_columnHeaderDragIndicator);
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDropLocationIndicator != null)
|
||
|
{
|
||
|
SetDefaultsOnDropIndicator();
|
||
|
AddVisualChild(_columnHeaderDropLocationIndicator);
|
||
|
}
|
||
|
|
||
|
_draggingSrcColumnHeader.SuppressClickEvent = true;
|
||
|
InvalidateMeasure();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FinishColumnHeaderDrag(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which returns a default control for column header drag indicator
|
||
|
/// </summary>
|
||
|
private Control CreateColumnHeaderDragIndicator()
|
||
|
{
|
||
|
Debug.Assert(_draggingSrcColumnHeader != null, "Dragging header is null");
|
||
|
|
||
|
EGC.DataGridColumnFloatingHeader floatingHeader = new EGC.DataGridColumnFloatingHeader();
|
||
|
floatingHeader.ReferenceHeader = _draggingSrcColumnHeader;
|
||
|
return floatingHeader;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which set the default values on drag indicator
|
||
|
/// </summary>
|
||
|
private void SetDefaultsOnDragIndicator()
|
||
|
{
|
||
|
Debug.Assert(_columnHeaderDragIndicator != null, "Drag indicator is null");
|
||
|
Debug.Assert(_draggingSrcColumnHeader != null, "Dragging header is null");
|
||
|
EGC.DataGridColumn column = _draggingSrcColumnHeader.Column;
|
||
|
Style style = null;
|
||
|
if (column != null)
|
||
|
{
|
||
|
style = column.DragIndicatorStyle;
|
||
|
}
|
||
|
|
||
|
_columnHeaderDragIndicator.Style = style;
|
||
|
_columnHeaderDragIndicator.CoerceValue(WidthProperty);
|
||
|
_columnHeaderDragIndicator.CoerceValue(HeightProperty);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which returns the default control for the column header drop indicator
|
||
|
/// </summary>
|
||
|
private Control CreateColumnHeaderDropIndicator()
|
||
|
{
|
||
|
Debug.Assert(_draggingSrcColumnHeader != null, "Dragging header is null");
|
||
|
|
||
|
EGC.DataGridColumnDropSeparator indicator = new EGC.DataGridColumnDropSeparator();
|
||
|
indicator.ReferenceHeader = _draggingSrcColumnHeader;
|
||
|
return indicator;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which sets the default values on drop indicator
|
||
|
/// </summary>
|
||
|
private void SetDefaultsOnDropIndicator()
|
||
|
{
|
||
|
Debug.Assert(_columnHeaderDropLocationIndicator != null, "Drag indicator is null");
|
||
|
Debug.Assert(_draggingSrcColumnHeader != null, "Dragging header is null");
|
||
|
Style style = null;
|
||
|
if (ParentDataGrid != null)
|
||
|
{
|
||
|
style = ParentDataGrid.DropLocationIndicatorStyle;
|
||
|
}
|
||
|
|
||
|
_columnHeaderDropLocationIndicator.Style = style;
|
||
|
_columnHeaderDropLocationIndicator.CoerceValue(WidthProperty);
|
||
|
_columnHeaderDropLocationIndicator.CoerceValue(HeightProperty);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Method which completes the column header drag. Includes raising of events and changing column display index if needed.
|
||
|
/// </summary>
|
||
|
private void FinishColumnHeaderDrag(bool isCancel)
|
||
|
{
|
||
|
Debug.Assert(ParentDataGrid != null, "ParentDataGrid is null");
|
||
|
_prepareColumnHeaderDragging = false;
|
||
|
_isColumnHeaderDragging = false;
|
||
|
|
||
|
_draggingSrcColumnHeader.SuppressClickEvent = false;
|
||
|
|
||
|
if (_columnHeaderDragIndicator != null)
|
||
|
{
|
||
|
_columnHeaderDragIndicator.Visibility = Visibility.Collapsed;
|
||
|
EGC.DataGridColumnFloatingHeader floatingHeader = _columnHeaderDragIndicator as EGC.DataGridColumnFloatingHeader;
|
||
|
if (floatingHeader != null)
|
||
|
{
|
||
|
floatingHeader.ClearHeader();
|
||
|
}
|
||
|
|
||
|
RemoveVisualChild(_columnHeaderDragIndicator);
|
||
|
}
|
||
|
|
||
|
if (_columnHeaderDropLocationIndicator != null)
|
||
|
{
|
||
|
_columnHeaderDropLocationIndicator.Visibility = Visibility.Collapsed;
|
||
|
EGC.DataGridColumnDropSeparator separator = _columnHeaderDropLocationIndicator as EGC.DataGridColumnDropSeparator;
|
||
|
if (separator != null)
|
||
|
{
|
||
|
separator.ReferenceHeader = null;
|
||
|
}
|
||
|
|
||
|
RemoveVisualChild(_columnHeaderDropLocationIndicator);
|
||
|
}
|
||
|
|
||
|
DragCompletedEventArgs dragCompletedEventArgs = new DragCompletedEventArgs(
|
||
|
_columnHeaderDragCurrentPosition.X - _columnHeaderDragStartPosition.X,
|
||
|
_columnHeaderDragCurrentPosition.Y - _columnHeaderDragStartPosition.Y,
|
||
|
isCancel);
|
||
|
|
||
|
ParentDataGrid.OnColumnHeaderDragCompleted(dragCompletedEventArgs);
|
||
|
_draggingSrcColumnHeader.InvalidateArrange();
|
||
|
|
||
|
if (!isCancel)
|
||
|
{
|
||
|
int newDisplayIndex = -1;
|
||
|
bool dragEndPositionValid = IsMousePositionValidForColumnDrag(
|
||
|
2.0,
|
||
|
out newDisplayIndex);
|
||
|
|
||
|
EGC.DataGridColumn column = _draggingSrcColumnHeader.Column;
|
||
|
if (column != null && dragEndPositionValid && newDisplayIndex != column.DisplayIndex)
|
||
|
{
|
||
|
column.DisplayIndex = newDisplayIndex;
|
||
|
|
||
|
EGC.DataGridColumnEventArgs columnEventArgs = new EGC.DataGridColumnEventArgs(_draggingSrcColumnHeader.Column);
|
||
|
ParentDataGrid.OnColumnReordered(columnEventArgs);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_draggingSrcColumnHeader = null;
|
||
|
_columnHeaderDragIndicator = null;
|
||
|
_columnHeaderDropLocationIndicator = null;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method to determine the display index based on the given position
|
||
|
/// </summary>
|
||
|
private int FindDisplayIndexByPosition(Point startPos, bool findNearestColumn)
|
||
|
{
|
||
|
Point headerPos;
|
||
|
int displayIndex;
|
||
|
EGC.DataGridColumnHeader header;
|
||
|
FindDisplayIndexAndHeaderPosition(startPos, findNearestColumn, out displayIndex, out headerPos, out header);
|
||
|
return displayIndex;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method to determine the column header based on the given position
|
||
|
/// </summary>
|
||
|
private EGC.DataGridColumnHeader FindColumnHeaderByPosition(Point startPos)
|
||
|
{
|
||
|
Point headerPos;
|
||
|
int displayIndex;
|
||
|
EGC.DataGridColumnHeader header;
|
||
|
FindDisplayIndexAndHeaderPosition(startPos, false, out displayIndex, out headerPos, out header);
|
||
|
return header;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method to determine the position of drop indicator based on the given mouse position
|
||
|
/// </summary>
|
||
|
private Point FindColumnHeaderPositionByCurrentPosition(Point startPos, bool findNearestColumn)
|
||
|
{
|
||
|
Point headerPos;
|
||
|
int displayIndex;
|
||
|
EGC.DataGridColumnHeader header;
|
||
|
FindDisplayIndexAndHeaderPosition(startPos, findNearestColumn, out displayIndex, out headerPos, out header);
|
||
|
return headerPos;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method which estimates the column width
|
||
|
/// </summary>
|
||
|
private static double GetColumnEstimatedWidth(EGC.DataGridColumn column, double averageColumnWidth)
|
||
|
{
|
||
|
double columnEstimatedWidth = column.Width.DisplayValue;
|
||
|
if (DoubleUtil.IsNaN(columnEstimatedWidth))
|
||
|
{
|
||
|
columnEstimatedWidth = Math.Max(averageColumnWidth, column.MinWidth);
|
||
|
columnEstimatedWidth = Math.Min(columnEstimatedWidth, column.MaxWidth);
|
||
|
}
|
||
|
|
||
|
return columnEstimatedWidth;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Helper method to find display index, header and header start position based on given mouse position
|
||
|
/// </summary>
|
||
|
private void FindDisplayIndexAndHeaderPosition(Point startPos, bool findNearestColumn, out int displayIndex, out Point headerPos, out EGC.DataGridColumnHeader header)
|
||
|
{
|
||
|
Debug.Assert(ParentDataGrid != null, "ParentDataGrid is null");
|
||
|
|
||
|
Point originPoint = new Point(0, 0);
|
||
|
headerPos = originPoint;
|
||
|
displayIndex = -1;
|
||
|
header = null;
|
||
|
|
||
|
if (startPos.X < 0.0)
|
||
|
{
|
||
|
if (findNearestColumn)
|
||
|
{
|
||
|
displayIndex = 0;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
double headerStartX = 0.0;
|
||
|
double headerEndX = 0.0;
|
||
|
int i = 0;
|
||
|
EGC.DataGrid dataGrid = ParentDataGrid;
|
||
|
double averageColumnWidth = dataGrid.InternalColumns.AverageColumnWidth;
|
||
|
bool firstVisibleNonFrozenColumnHandled = false;
|
||
|
for (i = 0; i < dataGrid.Columns.Count; i++)
|
||
|
{
|
||
|
displayIndex++;
|
||
|
EGC.DataGridColumnHeader currentHeader = dataGrid.ColumnHeaderFromDisplayIndex(i);
|
||
|
if (currentHeader == null)
|
||
|
{
|
||
|
EGC.DataGridColumn column = dataGrid.ColumnFromDisplayIndex(i);
|
||
|
if (!column.IsVisible)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
headerStartX = headerEndX;
|
||
|
if (i >= dataGrid.FrozenColumnCount &&
|
||
|
!firstVisibleNonFrozenColumnHandled)
|
||
|
{
|
||
|
headerStartX -= dataGrid.HorizontalScrollOffset;
|
||
|
firstVisibleNonFrozenColumnHandled = true;
|
||
|
}
|
||
|
|
||
|
headerEndX = headerStartX + GetColumnEstimatedWidth(column, averageColumnWidth);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GeneralTransform transform = currentHeader.TransformToAncestor(this);
|
||
|
headerStartX = transform.Transform(originPoint).X;
|
||
|
headerEndX = headerStartX + currentHeader.RenderSize.Width;
|
||
|
}
|
||
|
|
||
|
if (DoubleUtil.LessThanOrClose(startPos.X, headerStartX))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (DoubleUtil.GreaterThanOrClose(startPos.X, headerStartX) &&
|
||
|
DoubleUtil.LessThanOrClose(startPos.X, headerEndX))
|
||
|
{
|
||
|
if (findNearestColumn)
|
||
|
{
|
||
|
double headerMidX = (headerStartX + headerEndX) * 0.5;
|
||
|
if (DoubleUtil.GreaterThanOrClose(startPos.X, headerMidX))
|
||
|
{
|
||
|
headerStartX = headerEndX;
|
||
|
displayIndex++;
|
||
|
}
|
||
|
|
||
|
if (_draggingSrcColumnHeader != null && _draggingSrcColumnHeader.Column != null && _draggingSrcColumnHeader.Column.DisplayIndex < displayIndex)
|
||
|
{
|
||
|
displayIndex--;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
header = currentHeader;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (i == dataGrid.Columns.Count)
|
||
|
{
|
||
|
displayIndex = dataGrid.Columns.Count - 1;
|
||
|
headerStartX = headerEndX;
|
||
|
}
|
||
|
|
||
|
headerPos.X = headerStartX;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Helpers
|
||
|
|
||
|
private EGC.ColumnHeaderCollection HeaderCollection
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return ItemsSource as EGC.ColumnHeaderCollection;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal EGC.DataGrid ParentDataGrid
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (_parentDataGrid == null)
|
||
|
{
|
||
|
_parentDataGrid = EGC.DataGridHelper.FindParent<EGC.DataGrid>(this);
|
||
|
}
|
||
|
|
||
|
return _parentDataGrid;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal EGC.ContainerTracking<EGC.DataGridColumnHeader> HeaderTrackingRoot
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return _headerTrackingRoot;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Data
|
||
|
|
||
|
private EGC.ContainerTracking<EGC.DataGridColumnHeader> _headerTrackingRoot;
|
||
|
|
||
|
private EGC.DataGrid _parentDataGrid = null;
|
||
|
|
||
|
private bool _prepareColumnHeaderDragging;
|
||
|
private bool _isColumnHeaderDragging;
|
||
|
private EGC.DataGridColumnHeader _draggingSrcColumnHeader;
|
||
|
private Point _columnHeaderDragStartPosition;
|
||
|
private Point _columnHeaderDragStartRelativePosition;
|
||
|
private Point _columnHeaderDragCurrentPosition;
|
||
|
private Control _columnHeaderDropLocationIndicator;
|
||
|
private Control _columnHeaderDragIndicator;
|
||
|
|
||
|
private Panel _internalItemsHost;
|
||
|
|
||
|
#endregion
|
||
|
}
|
||
|
}
|