渲染大而可点击的网格wpf

时间:2018-01-26 15:31:17

标签: c# wpf canvas optimization drawing

我正在尝试创建一个大网格,单元格已经绘制了一个可点击(点击事件)省略号。

我正在使用Canvas作为父级并将Ellipses添加到Children List但是当我尝试渲染100x100省略号时,它会滞后。

我尝试使用DrawingVisual和DrawingContext渲染它们但是,它们不可点击,我将无法存储省略号属性(颜色,笔触......)。

呈现省略号的当前代码

for (int i = 0; i < this.grid.Cols; i++) {
    for (int j = 0; j < this.grid.Rows; j++) {
        SolidColorBrush fillBrush = Brushes.Red;
        SolidColorBrush strokeBrush = Brushes.Blue;

        Ellipse ellipse = new Ellipse() {
            Width = this.grid.Radius,
            Height = this.grid.Radius,
            Fill = fillBrush,
            Stroke = strokeBrush,
            StrokeThickness = 2
        };

        Canvas.SetTop(ellipse, (this.grid.Radius * j) + (j * this.grid.Margin));
        Canvas.SetLeft(ellipse, (this.grid.Radius * i) + (i * this.grid.Margin));

        children.Add(ellipse);
        parent.Children.Add(ellipse);
    }
}

enter image description here

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

在效率方面,最轻盈的&#39;解决方案可能是将单个椭圆渲染到RenderTargetBitmap,然后让自定义控件绘制一个带有平铺ImageBrush的矩形,一遍又一遍地重复相同的椭圆。使用RenderTargetBitmap的好处在于,您可以让开发人员将任何可视元素插入到tile中;它不需要是椭圆形。

您需要一种在屏幕坐标和(column, row)对之间进行转换的方法。您将知道椭圆位图的大小,因此该部分应该非常简单。

要在鼠标悬停或按下椭圆时处理细微变化,您应该将一些剪辑几何图形推入DrawingContext以绘制到处除了包含椭圆的区域光标。然后弹出蒙版并绘制一个矩形,其中替换 ImageBrush包含处于悬停/按下状态的椭圆。

要处理点击,您需要编写一些自定义命中测试逻辑。基本上,你会想要这样的东西:

private bool TryHitTest(Point p, out int column, out int row)
{
    column = -1;
    row = -1;

    if (p.X < 0 || p.X > ActualWidth || p.Y < 0 || p.Y > ActualHeight)
        return false;

    var image = /* your ellipse bitmap */;
    if (image == null)
        return false;

    var tileWidth = image.Width;
    var tileHeight = image.Height;

    var x = (int)(p.X % tileWidth);
    var y = (int)(p.Y % tileHeight);

    // If you want pixel-perfect hit testing, check the alpha channel.
    // Otherwise, skip this check.
    if (image.GetPixel(x, y).A == 0)
        return false;

    column = (int)Math.Floor(p.X / tileWidth);
    row = (int)Math.Floor(p.Y / tileHeight);

    return true;
}

如果您想要像素完美的点击测试,则应将RenderTargetBitmap复制到WriteableBitmap,然后使用 WriteableBitmapEx 库来获取经过测试的颜色坐标并检查alpha是否为非零。

要提供点击事件通知,您需要处理常见的鼠标事件:

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
    e.Handled = CaptureMouse();
}

protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
    if (IsMouseCaptured &&
        TryHitTest(e.GetPosition(this), out var column, out var row))
    {
        ReleaseMouseCapture();
        e.Handled = true;

        //
        // Raise 'TileClick' event with 'column' and 'row'. 
        //
    }
}

private (int x, int y) _lastHover;

protected override void OnMouseMove(MouseEventArgs e)
{
    TryHitTest(e.GetPosition(this), out var x, out var y);

    if (_lastHover.x != x || _lastHover.y != y)
        InvalidateVisual();

    _lastHover = (x, y);
}

附录:渲染

由于我对如何描述渲染到矩形存在一些困惑,让我澄清一下:我不是在谈论绘制Rectangle元素。我们的想法是创建一个执行自己渲染的自定义控件,并绘制使用平铺图像画笔绘制的矩形几何。您的OnRender方法看起来像这样:

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

    var regularBrush  = /* regular cell tiled brush */;
    var hoverBrush =  /* hovered cell brush */;

    var fullBounds = new Rect(/* full bounds */);
    var hoverBounds = new Rect(/* bounds of hovered cell */);

    var hasHoveredCell = /* is there a hovered cell? */;
    if (hasHoveredCell)
    {
        // Draw everywhere *except* the hovered cell.
        dc.PushClip(
            Geometry.Combine(
                new RectangleGeometry(fullBounds),
                new RectangleGeometry(hoverBounds),
                GeometryCombineMode.Exclude,
                Transform.Identity));
    }

    dc.DrawRectangle(regularBrush , null, fullBounds);

    if (hasHoveredCell)
    {
        // Pop the clip and draw the hovered cell.
        dc.Pop();
        dc.DrawRectangle(hoverBrush, null, hoverBounds);
    }
}

答案 1 :(得分:1)

您正在限制WPF可以使用它的框架呈现的内容。省略号或所有绘图对象是具有相当多属性和事件的对象。渲染10,000将导致您滞后。

最好的办法是使用DrawingContext并创建更简单的方法,并创建自己的方法来跟踪点击的内容。