【问题标题】:Render large and clickable grid wpf渲染大且可点击的网格wpf
【发布时间】:2018-01-26 15:31:17
【问题描述】:

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

我使用 Canvas 作为父级并向其子级列表中添加省略号,但是当我尝试渲染 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);
    }
}

有什么想法吗?

【问题讨论】:

  • 您是否需要能够以不同的方式渲染椭圆,例如,如果光标在一个或用户点击一个,外观是否应该改变?
  • 我正在搜索用户可以点击每个人,然后点击的那个会改变它的颜色,这就是我一一渲染它们的原因,但我不在乎它们是如何渲染的或它们是如何绘制
  • 您能更详细地描述一下您的用例吗?有很多问题浮现在脑海中,但告诉我们你打算用它做什么可能会更容易。

标签: c# wpf canvas optimization drawing


【解决方案1】:

就效率而言,最“轻量级”的解决方案可能是将单个椭圆渲染到RenderTargetBitmap,然后让自定义控件绘制一个带有平铺ImageBrush 的矩形,以一遍又一遍地重复相同的椭圆.使用RenderTargetBitmap 的好处在于您可以让开发人员插入 any 视觉元素以平铺;它不一定是椭圆。

您需要一种在屏幕坐标和(column, row) 对之间进行转换的方法。你会知道椭圆位图的大小,所以这部分应该很容易。

要在鼠标悬停或按下椭圆时处理细微的变化,您应该将一些剪辑几何图形推入您的DrawingContext 以在所有位置绘制除了光标下包含椭圆的区域.然后弹出遮罩并用 alternate 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);
    }
}

【讨论】:

  • 好的,我已经为它完成了自定义控件和鼠标控制,所以当我在一个元素中单击时,我可以检索元素位置(列,行)但不知道如何重新绘制 那个元素和它。
  • 在两个矩形上绘制所有内容(悬停的单元格较小,其余的则较大)。对于大矩形,通过从大矩形中减去小矩形的几何来创建一些剪切几何,然后在绘制大矩形之前将该剪辑推入 DC。这应该在所有除了悬停的单元格处绘制“常规”椭圆。然后绘制悬停的单元格。
  • 很抱歉,我想我不明白。我正在创建一个矩形(xaml);然后我使用基本椭圆创建一个 DrawingVisual,并使用 TileMode = TileMode.Tile 将此视觉效果渲染到 ImageBrush 中,最后将此 Brush 应用于 xaml 矩形。但是现在,您说我应该创建另一个包含替代椭圆的矩形(xaml?)并将其绘制在先前的上下文中?请问可以写代码吗?我想我不会得到它。
  • 啊,我明白了。误解:我不是要画Rectangle 元素,而是要使用DrawingContext 来绘制矩形几何图形。查看我的答案的最新补充。
【解决方案2】:

您遇到了 WPF 框架可以呈现的限制。椭圆,或所有的绘图对象,是具有相当多的属性和事件的对象。渲染 10,000 会导致您滞后。

您最好的选择是使用 DrawingContext 并创建自己的更简单的方法,并创建自己的方法来跟踪单击某物的时间。

【讨论】:

  • 使用 DrawingContext,我可以只“重绘”一个椭圆,还是需要重绘所有元素?
  • 你应该可以使用 DirtyRect 只重绘一个区域,但它变得更加复杂
  • 您能添加一个指向 DirtyRect 的链接吗?我在 google xd 上找不到它
  • 我成功了。我可以毫无延迟地渲染大图像,但无法使其可点击。我正在使用列表来存储所有“省略号”(位置和颜色),当我单击时,我会检查位置,并用正确的颜色重新绘制所有内容,但检查位置会持续几分钟。
猜你喜欢
  • 1970-01-01
  • 2012-06-09
  • 1970-01-01
  • 1970-01-01
  • 2013-02-24
  • 1970-01-01
  • 2012-09-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多