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(); } } } /// /// 设置或返回当前DataGrid是否处于单元格访问权限编辑模式。 /// public bool IsAccessibleWhitelistEditMode { get => (bool)GetValue(IsAccessibleWhitelistEditModeProperty); set => SetValue(IsAccessibleWhitelistEditModeProperty, value); } #endregion #region DPs public static readonly DependencyProperty AllowDragToFillProperty = DependencyProperty.Register( nameof(AllowDragToFill), typeof(bool), typeof(XDataGrid), new PropertyMetadata(true)); public bool AllowDragToFill { get => (bool)GetValue(AllowDragToFillProperty); set => SetValue(AllowDragToFillProperty, value); } 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(); if (!AllowDragToFill) return; // 如果选中了单元格,并且没有在编辑模式,显示Adorner if (CurrentCellContainer.IsEditing || !cellInfo.HasValue) return; 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)); } /// /// 检查指定的单元格是否支持拖动填充功能。 /// /// /// 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; } /// /// If ESC key down when dragging, cancel the dragging. /// /// /// 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; } protected override void OnArrowKeyDown(KeyEventArgs e) { //TODO 重映射方向键后,拖拽填充控件的位置不对,此功能还需要调试。 var remapKey = e.Key; remapKey = remapKey switch { Key.Left => Key.Down, Key.Down => Key.Right, Key.Right => Key.Up, Key.Up => Key.Left, _ => remapKey }; var args = new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, remapKey); args.RoutedEvent = e.RoutedEvent; base.OnArrowKeyDown(e); } 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 } }