就效率而言,最“轻量级”的解决方案可能是将单个椭圆渲染到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);
}
}