435 lines
17 KiB
C#
435 lines
17 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Linq;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Controls.Primitives;
|
||
using System.Windows.Documents;
|
||
using System.Windows.Input;
|
||
using System.Windows.Media;
|
||
using MECF.Framework.UI.Client.ClientBase;
|
||
using MECF.Framework.UI.Client.DataGridTransform.DataGrid.Microsoft.Windows.Controls.Primitives;
|
||
using MECF.Framework.UI.Client.RecipeEditorLib.DGExtension.CustomColumn;
|
||
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel;
|
||
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params;
|
||
using EGC = ExtendedGrid.Microsoft.Windows.Controls;
|
||
using Point = System.Windows.Point;
|
||
using Size = System.Windows.Size;
|
||
|
||
namespace MECF.Framework.UI.Client.RecipeEditorLib.DGExtension
|
||
{
|
||
public sealed class XDataGrid : ExtendedGrid.Microsoft.Windows.Controls.DataGrid
|
||
{
|
||
#region Variables
|
||
|
||
public ScrollViewer ref_scrollviewer;
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
|
||
public XDataGrid()
|
||
{
|
||
SelectionChanged += OnSelectionChanged;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
|
||
public static readonly DependencyProperty SelectedItemsListProperty = DependencyProperty.Register(
|
||
nameof(SelectedItemsList), typeof(IList), typeof(XDataGrid), new PropertyMetadata(default(IList)));
|
||
|
||
public IList SelectedItemsList
|
||
{
|
||
get => (IList)GetValue(SelectedItemsListProperty);
|
||
set => SetValue(SelectedItemsListProperty, value);
|
||
}
|
||
|
||
|
||
public static readonly DependencyProperty UseHorizontalScrollingProperty = DependencyProperty.RegisterAttached("UseHorizontalScrolling", typeof(bool), typeof(XDataGrid),
|
||
new PropertyMetadata(default(bool), UseHorizontalScrollingChangedCallback));
|
||
|
||
public bool UseHorizontalScrolling
|
||
{
|
||
get => (bool)GetValue(UseHorizontalScrollingProperty);
|
||
set => SetValue(UseHorizontalScrollingProperty, value);
|
||
}
|
||
|
||
public static readonly DependencyProperty IsAccessibleWhitelistEditModeProperty = DependencyProperty.Register(
|
||
nameof(IsAccessibleWhitelistEditMode), typeof(bool), typeof(XDataGrid), new PropertyMetadata(default(bool), IsCellAccessPermissionEditModePropertyChangedCallback));
|
||
|
||
private static void IsCellAccessPermissionEditModePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||
{
|
||
if (d is XDataGrid grid && e.NewValue is bool isAweMode)
|
||
{
|
||
if (isAweMode)
|
||
{
|
||
// 如果进入单元格访问权限编辑模式,清除之前的拖拽框。
|
||
grid.ClearCellSelectionAdorner();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前DataGrid是否处于单元格访问权限编辑模式。
|
||
/// </summary>
|
||
public bool IsAccessibleWhitelistEditMode
|
||
{
|
||
get => (bool)GetValue(IsAccessibleWhitelistEditModeProperty);
|
||
set => SetValue(IsAccessibleWhitelistEditModeProperty, value);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region DPs
|
||
|
||
public static readonly DependencyProperty DragToFillAdornerThumbStyleProperty = DependencyProperty.Register(
|
||
nameof(DragToFillAdornerThumbStyle), typeof(Style), typeof(XDataGrid),
|
||
new PropertyMetadata(default(Style)));
|
||
|
||
public Style DragToFillAdornerThumbStyle
|
||
{
|
||
get => (Style)GetValue(DragToFillAdornerThumbStyleProperty);
|
||
set => SetValue(DragToFillAdornerThumbStyleProperty, value);
|
||
}
|
||
|
||
public static readonly DependencyProperty DragToFillAdornerColorBrushProperty = DependencyProperty.Register(
|
||
nameof(DragToFillAdornerColorBrush), typeof(Brush), typeof(XDataGrid), new PropertyMetadata(default(Brush)));
|
||
|
||
public Brush DragToFillAdornerColorBrush
|
||
{
|
||
get => (Brush)GetValue(DragToFillAdornerColorBrushProperty);
|
||
set => SetValue(DragToFillAdornerColorBrushProperty, value);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Excel-Like Cell Drag-To-Fill Adorner
|
||
|
||
private DataGridCellDragToFillAdorner _lastAdorner;
|
||
|
||
private void ClearCellSelectionAdorner()
|
||
{
|
||
// 清空之前的Adorner
|
||
if (_lastAdorner != null && _lastAdorner.AdornedElement is EGC.DataGridCell cell)
|
||
{
|
||
var al = AdornerLayer.GetAdornerLayer(cell);
|
||
var ads = al?.GetAdorners(cell)?.ToList();
|
||
if (ads != null && ads.Any())
|
||
foreach (var ad in ads)
|
||
al.Remove(ad);
|
||
}
|
||
|
||
if (_lastAdorner != null)
|
||
{
|
||
(_lastAdorner.AdornedElement as EGC.DataGridCell).KeyDown -= Cell_KeyDown;
|
||
|
||
_lastAdorner.OnDragStart -= LastAdornerOnOnDragStart;
|
||
_lastAdorner.OnDragDelta -= LastAdornerOnOnDragDelta;
|
||
_lastAdorner.OnDragCompleted -= LastAdornerOnOnDragCompleted;
|
||
}
|
||
|
||
_lastAdorner = null;
|
||
}
|
||
|
||
private void CreateCellSelectionAdorner(EGC.DataGridCellInfo? cellInfo)
|
||
{
|
||
ClearCellSelectionAdorner();
|
||
|
||
// 如果选中了单元格,并且没有在编辑模式,显示Adorner
|
||
if (!CurrentCellContainer.IsEditing && cellInfo.HasValue)
|
||
{
|
||
if (!CheckIsCellSupportDragToFill(TryFindCell(cellInfo.Value)))
|
||
return;
|
||
|
||
var cell = TryFindCell(cellInfo.Value);
|
||
|
||
cell.KeyDown += Cell_KeyDown;
|
||
|
||
if (cell != null && cell.DataContext is Param param)
|
||
{
|
||
// 根据Cell中Param的权限决定是否禁用拖拽填充功能
|
||
var isEnabled = param.Permission != MenuPermissionEnum.MP_NONE;
|
||
|
||
_lastAdorner =
|
||
new DataGridCellDragToFillAdorner(cell, new Size(cell.ActualWidth, cell.ActualHeight))
|
||
{
|
||
IsEnabled = isEnabled,
|
||
ThumbStyle = DragToFillAdornerThumbStyle,
|
||
Stroke = DragToFillAdornerColorBrush
|
||
};
|
||
_lastAdorner.OnDragStart += LastAdornerOnOnDragStart;
|
||
_lastAdorner.OnDragDelta += LastAdornerOnOnDragDelta;
|
||
_lastAdorner.OnDragCompleted += LastAdornerOnOnDragCompleted;
|
||
AdornerLayer.GetAdornerLayer(cell).Add(_lastAdorner);
|
||
}
|
||
}
|
||
}
|
||
|
||
private Param GetParamFromCell(EGC.DataGridCellInfo? cellInfo)
|
||
{
|
||
if (cellInfo.HasValue
|
||
&& cellInfo.Value.Item is RecipeStep rs
|
||
&& cellInfo.Value.Column is EditorDataGridTemplateColumnBase col)
|
||
{
|
||
return rs.FirstOrDefault(x => x.Name == col.ControlName);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private Param GetParamFromCell(EGC.DataGridCell cell)
|
||
{
|
||
return GetParamFromCell(new EGC.DataGridCellInfo(cell));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查指定的单元格是否支持拖动填充功能。
|
||
/// </summary>
|
||
/// <param name="cell"></param>
|
||
/// <returns></returns>
|
||
private bool CheckIsCellSupportDragToFill(EGC.DataGridCell cell)
|
||
{
|
||
// 仅当单元格绑定的参数为数值型参数、并且参数访问权限不为MP_NONE、并且所属列不为ReadOnly,可以开启拖动填充功能
|
||
if (cell.DataContext is Param param and IValueParam
|
||
&& cell.Column is EditorDataGridTemplateColumnBase { IsExpander: false, IsReadOnly: false })
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// If ESC key down when dragging, cancel the dragging.
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void Cell_KeyDown(object sender, KeyEventArgs e)
|
||
{
|
||
if (e.Key == Key.Escape)
|
||
{
|
||
_lastAdorner?.Cancel();
|
||
}
|
||
}
|
||
|
||
private void LastAdornerOnOnDragStart(object sender, DragStartedEventArgs e)
|
||
{
|
||
//throw new NotImplementedException();
|
||
}
|
||
|
||
private void LastAdornerOnOnDragCompleted(object sender, DragCompletedEventArgs e)
|
||
{
|
||
if (sender is DataGridCellDragToFillAdorner adorner
|
||
&& adorner.AdornedElement is EGC.DataGridCell cell
|
||
&& _selectionAnchor != null)
|
||
{
|
||
// 源单元格不支持拖放填充
|
||
if (!CheckIsCellSupportDragToFill(cell))
|
||
return;
|
||
|
||
if (adorner.StartCellIndex != adorner.EndCellIndex)
|
||
{
|
||
// 复制值
|
||
var srcParam = GetParamFromCell(_selectionAnchor);
|
||
|
||
if (srcParam is IValueParam param)
|
||
{
|
||
// 获取源数据
|
||
var value = param.GetValue();
|
||
|
||
var itemsHost = InternalItemsHost;
|
||
if (itemsHost != null)
|
||
{
|
||
|
||
var start = Math.Min(adorner.StartCellIndex, adorner.EndCellIndex);
|
||
var end = Math.Max(adorner.StartCellIndex, adorner.EndCellIndex);
|
||
for (var i = start; i <= end; i++)
|
||
{
|
||
if (itemsHost.Children[i] is EGC.DataGridRow row)
|
||
{
|
||
var targetCell = row.TryGetCell(_selectionAnchor.Value.Column.DisplayIndex);
|
||
|
||
// 如果当前行被设置为Visibility.Collapsed,则无法通过TryGetCell方法获取Cell对象
|
||
if(targetCell == null)
|
||
continue;
|
||
|
||
// 获取目标单元格绑定的Recipe参数对象
|
||
var targetParam = GetParamFromCell(targetCell);
|
||
|
||
// 如果目标参数没有写权限,则不要赋值
|
||
if(targetParam.Permission != MenuPermissionEnum.MP_READ_WRITE)
|
||
continue;
|
||
|
||
// 如果目标参数是值型参数,则写入源参数数值
|
||
if (targetParam is IValueParam p)
|
||
p.SetValue(value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 更新adorner尺寸
|
||
//adorner.Arrange(adorner.GetSelectionBound());
|
||
}
|
||
}
|
||
|
||
private void LastAdornerOnOnDragDelta(object sender, DragDeltaEventArgs e)
|
||
{
|
||
if (sender is DataGridCellDragToFillAdorner adorner && adorner.AdornedElement is EGC.DataGridCell cell)
|
||
{
|
||
// 检查当前Cell是否支持拖动填充
|
||
if (!CheckIsCellSupportDragToFill(cell))
|
||
return;
|
||
|
||
var itemsHost = InternalItemsHost;
|
||
if (itemsHost == null)
|
||
return;
|
||
|
||
// 获取当前鼠标附近的Cell
|
||
var closestCell = MouseOverCell ?? GetCellNearMouse();
|
||
if (closestCell != null)
|
||
{
|
||
var originCellInfo = new EGC.DataGridCellInfo(cell);
|
||
var closestCellInfo = new EGC.DataGridCellInfo(closestCell);
|
||
|
||
var startIndex = Items.IndexOf(originCellInfo.Item);
|
||
var endIndex = Items.IndexOf(closestCellInfo.Item);
|
||
|
||
// 判断当前鼠标的Y坐标是否超过了当前Row高度的一半。
|
||
// 如果超过一半,则选中当前行对应的单元格;否则不选择。
|
||
var closestRow = closestCell.RowOwner;
|
||
var pt = Mouse.GetPosition(closestRow);
|
||
var rowBounds = new Rect(new Point(), closestRow.RenderSize);
|
||
|
||
// 默认选择当前单元格
|
||
var recRender = new Rect(0, 0, cell.ActualWidth, cell.ActualHeight);
|
||
|
||
// 判断鼠标是否划过当前Cell一半的位置,超过一半选中,没超过则不选中。
|
||
if (endIndex > startIndex) // Select the cells below the origin cell.
|
||
{
|
||
if (pt.Y < (rowBounds.Height / 2.0))
|
||
endIndex -= 1; // don't select the cell nearby mouse
|
||
|
||
recRender.Height = 0;
|
||
for (var i = startIndex; i <= endIndex; i++)
|
||
{
|
||
if (itemsHost.Children[i] is EGC.DataGridRow row)
|
||
recRender.Height += row.RenderSize.Height;
|
||
}
|
||
}
|
||
else if (endIndex < startIndex) // Select the cells above the origin cell.
|
||
{
|
||
if (pt.Y > (rowBounds.Height / 2.0))
|
||
endIndex += 1; // don't select the cell nearby mouse
|
||
|
||
recRender.Height = 0;
|
||
for (var i = endIndex; i <= startIndex; i++)
|
||
{
|
||
if (itemsHost.Children[i] is EGC.DataGridRow row)
|
||
recRender.Height += row.RenderSize.Height;
|
||
}
|
||
|
||
/*Debug.WriteLine($"Start: {startIndex}, End: {endIndex}", "Adorner");
|
||
Debug.WriteLine($"Rendering Adorner: {recRender}", "Adorner");
|
||
Debug.WriteLine($"Cell Height: {cell.ActualHeight}, Render Height: {recRender.Height}", "Adorner");*/
|
||
|
||
recRender.Y = cell.ActualHeight - recRender.Height;
|
||
|
||
//Debug.WriteLine($"Render Y: {recRender.Y}", "Adorner");
|
||
}
|
||
|
||
// 重新计算Adorner尺寸
|
||
adorner.ArrangeSelectionBound(recRender, startIndex, endIndex);
|
||
}
|
||
}
|
||
}
|
||
|
||
protected override void EnsureInternalScrollControls()
|
||
{
|
||
base.EnsureInternalScrollControls();
|
||
|
||
if (_internalScrollHost != null)
|
||
_internalScrollHost.ScrollChanged += (sender, args) =>
|
||
{
|
||
// Clear the cell adorner while the view is scrolling.
|
||
ClearCellSelectionAdorner();
|
||
};
|
||
}
|
||
|
||
protected override void MakeFullRowSelection(object dataItem, bool allowsExtendSelect, bool allowsMinimalSelect)
|
||
{
|
||
base.MakeFullRowSelection(dataItem, allowsExtendSelect, allowsMinimalSelect);
|
||
/*if (CurrentCell != null)
|
||
CreateCellSelectionAdorner(CurrentCell);*/
|
||
}
|
||
|
||
protected override void MakeCellSelection(EGC.DataGridCellInfo cellInfo, bool allowsExtendSelect, bool allowsMinimalSelect)
|
||
{
|
||
base.MakeCellSelection(cellInfo, allowsExtendSelect, allowsMinimalSelect);
|
||
|
||
// 如果是单元格访问权限编辑模式,则不要启用Drag-To-Fill功能
|
||
if (IsAccessibleWhitelistEditMode)
|
||
return;
|
||
|
||
if (_selectionAnchor != null)
|
||
CreateCellSelectionAdorner(_selectionAnchor);
|
||
}
|
||
|
||
public override bool BeginEdit(RoutedEventArgs editingEventArgs)
|
||
{
|
||
if (IsAccessibleWhitelistEditMode)
|
||
return false;
|
||
|
||
return base.BeginEdit(editingEventArgs);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Methods
|
||
|
||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||
{
|
||
SelectedItemsList = SelectedItems;
|
||
}
|
||
|
||
|
||
private void _UseHorizontalScrollingChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
||
{
|
||
PreviewMouseWheel += delegate (object sender, MouseWheelEventArgs args)
|
||
{
|
||
ScrollViewer scrollViewer = GetTemplateChild("DG_ScrollViewer") as ScrollViewer;
|
||
if (scrollViewer != null)
|
||
{
|
||
if (args.Delta > 0)
|
||
scrollViewer.LineLeft();
|
||
else
|
||
scrollViewer.LineRight();
|
||
}
|
||
ref_scrollviewer = scrollViewer;
|
||
args.Handled = true;
|
||
};
|
||
}
|
||
|
||
private static void UseHorizontalScrollingChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
||
{
|
||
if (!(dependencyObject is XDataGrid dg))
|
||
throw new ArgumentException("Element is not an ItemsControl");
|
||
|
||
dg._UseHorizontalScrollingChangedCallback(dependencyObject, dependencyPropertyChangedEventArgs);
|
||
}
|
||
|
||
public static void SetUseHorizontalScrolling(ItemsControl element, bool value)
|
||
{
|
||
element.SetValue(UseHorizontalScrollingProperty, value);
|
||
}
|
||
|
||
public static bool GetUseHorizontalScrolling(ItemsControl element)
|
||
{
|
||
return (bool)element.GetValue(UseHorizontalScrollingProperty);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|