WPF DataGrid中如何实现类似Excel的选择矩形?

3
这个问题类似于在WPF ItemsControl中实现类似Excel的拖动选择,但我想在DataGrid中实现,而不是在ListBox中。
我正在尝试使WPF DataGrid中的选择矩形看起来像Excel中的选择矩形,包括右下角的小黑色方块,并且不会在每个选定单元格周围有边框。 是否有人有在WPF DataGrid中实现这一点的示例?

enter image description here

1个回答

2

首先,您需要使用此答案中描述的方法隐藏焦点单元格上的边框:

<DataGrid.CellStyle>
   <Style TargetType="DataGridCell">
      <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
   </Style>
</DataGrid.CellStyle>

接下来,您需要使用 DataGridOnSelectedCellsChanged 事件添加/删除一个装饰器,该装饰器将绘制选择矩形。对于填充手柄,您可以使用一个 Thumb,它会为您处理拖放操作:

    void DataGrid_OnSelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
    {
        var datagrid = (System.Windows.Controls.DataGrid)sender;
        AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(datagrid);
        if (datagrid.SelectedCells.Any())
        {
            DataGridCellInfo firstCellInfo = datagrid.SelectedCells.First();
            FrameworkElement firstElt = firstCellInfo.Column.GetCellContent(firstCellInfo.Item);

            DataGridCellInfo lastCellInfo = datagrid.SelectedCells.Last();
            FrameworkElement lastElt = lastCellInfo.Column.GetCellContent(lastCellInfo.Item);

            if (firstElt != null && lastElt != null)
            {
                var firstcell = (DataGridCell)firstElt.Parent;
                var lastCell = (DataGridCell) lastElt.Parent;
                Point topLeft = datagrid.PointFromScreen(firstcell.PointToScreen(new Point(0, 0)));
                Point bottomRight = datagrid.PointFromScreen(lastCell.PointToScreen(new Point(lastCell.ActualWidth, lastCell.ActualHeight)));
                var rect = new Rect(topLeft, bottomRight);
                if (fillHandleAdorner == null)
                {
                    fillHandleAdorner = new FillHandleAdorner(datagrid, rect);
                    adornerLayer.Add(fillHandleAdorner);
                }
                else
                    fillHandleAdorner.Rect = rect;
            }
        }
        else
        {
            adornerLayer.Remove(fillHandleAdorner);
            fillHandleAdorner = null;
        }
    }

填充手柄修饰器类的代码:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace DataGrid
{
    internal class FillHandleAdorner : Adorner
    {
        const int SIZE = 5;

        Rect rect;

        readonly Thumb thumb;

        readonly VisualCollection visualChildren;

        Point mousePosition;

        System.Windows.Controls.DataGrid dataGrid;

        public Rect Rect
        {
            get { return rect; }
            set
            {
                rect = value;
                UpdateThumbPos(rect);
                mousePosition = rect.BottomRight;
                InvalidateVisual();
            }
        }

        public FillHandleAdorner(UIElement adornedElement, Rect rect)
            : base(adornedElement)
        {
            dataGrid = (System.Windows.Controls.DataGrid)adornedElement;
            Rect = rect;

            visualChildren = new VisualCollection(this);
            var border = new FrameworkElementFactory(typeof(Border));
            border.SetValue(Border.BackgroundProperty, Brushes.Black);
            thumb = new Thumb
            {
                Cursor = Cursors.Cross,
                Background = new SolidColorBrush(Colors.Black),
                Height = SIZE,
                Template = new ControlTemplate(typeof(Thumb)) { VisualTree = border },
                Width = SIZE
            };
            visualChildren.Add(thumb);

            UpdateThumbPos(rect);

            thumb.DragDelta += thumb_DragDelta;
        }

        void UpdateThumbPos(Rect rect)
        {
            if (thumb == null) return;
            mousePosition = rect.BottomRight;
            mousePosition.Offset(-SIZE/2 - 1, -SIZE/2 - 1);
            thumb.Arrange(new Rect(mousePosition, new Size(SIZE, SIZE)));
        }

        void thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            mousePosition.Offset(e.HorizontalChange, e.VerticalChange);
            IInputElement inputElt = dataGrid.InputHitTest(mousePosition);
            var tb = inputElt as TextBlock;
            if (tb == null) return;
            Point bottomRight = dataGrid.PointFromScreen(tb.PointToScreen(new Point(tb.ActualWidth + 1, tb.ActualHeight + 1)));
            Rect = new Rect(rect.TopLeft, bottomRight);
        }

        protected override int VisualChildrenCount
        {
            get { return visualChildren.Count; }
        }

        protected override Visual GetVisualChild(int index)
        {
            return visualChildren[index];
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            var blackSolidBrush = new SolidColorBrush(Colors.Black);
            var pen = new Pen(blackSolidBrush, 3);
            pen.Freeze();
            double halfPenWidth = pen.Thickness / 2;

            Rect rangeBorderRect = rect;
            rangeBorderRect.Offset(-1, -1);

            GuidelineSet guidelines = new GuidelineSet();
            guidelines.GuidelinesX.Add(rangeBorderRect.Left + halfPenWidth);
            guidelines.GuidelinesX.Add(rangeBorderRect.Right + halfPenWidth);
            guidelines.GuidelinesY.Add(rangeBorderRect.Top + halfPenWidth);
            guidelines.GuidelinesY.Add(rangeBorderRect.Bottom + halfPenWidth);

            Point p1 = rangeBorderRect.BottomRight;
            p1.Offset(0, -4);
            guidelines.GuidelinesY.Add(p1.Y + halfPenWidth);

            Point p2 = rangeBorderRect.BottomRight;
            p2.Offset(-4, 0);
            guidelines.GuidelinesX.Add(p2.X + halfPenWidth);

            drawingContext.PushGuidelineSet(guidelines);

            var geometry = new StreamGeometry();
            using (StreamGeometryContext ctx = geometry.Open())
            {
                ctx.BeginFigure(p1, true, false);
                ctx.LineTo(rangeBorderRect.TopRight, true, false);
                ctx.LineTo(rangeBorderRect.TopLeft, true, false);
                ctx.LineTo(rangeBorderRect.BottomLeft, true, false);
                ctx.LineTo(p2, true, false);
            }
            geometry.Freeze();
            drawingContext.DrawGeometry(null, pen, geometry);

            drawingContext.Pop();
        }
    }
}

代码并不完整,因为它是为一个尚未开始的项目而准备的,但它应该帮助您开始。

感谢您的晚(但好!)回答@Maxence。+1 的努力。 - edtheprogrammerguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接