ひとまず完成イメージがこちら
画像中の点線表示機能のみこの記事で紹介し、それ以外は開発中アプリ固有のUIのため触れません
画像ベースのタイル描画はTileControlが適する
既製のものとして、TileControlも選択肢です。
ただしTileControl.ImageSourceはUriを要求し画像ファイルが必要なため、ランタイム中に画像を生成するような動的に点線を表示したい場合には適しません。
カスタムなタイル描画にCanvasControlを利用するコード例
ページ先頭の画像のXaml構成はざっくりこんな感じです
- Grid
- アプリ固有の描き込みエリア
- RenderTransform に LateUpdateCanvasTransform を保有
- 点線を表示するCanvasControl
- アプリ固有の描き込みエリア
アプリ固有の描き込みエリアは縦横ともにCenter表示、点線表示のCanvasControlは親GridにStretch表示しています。
private void SnapGridCanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
using var list = new CanvasCommandList(sender);
using var session = list.CreateDrawingSession();
var canvasSize = _vm.EpisodeContext!.DefaultCanvasSize.ToVector2();
var transform = LateUpdateCanvasTransform;
float invScale = 1f / (float)LateUpdateCanvasTransform.ScaleX;
Vector2 translate = new Vector2( MathF.Round((float)transform.TranslateX), MathF.Round((float)transform.TranslateY));
float strokeWidth = 1f * invScale;
float strokeWidthHalf = strokeWidth * 0.5f;
float strokeX = canvasSize.X * 0.25f;
float strokeY = canvasSize.Y * 0.25f;
DrawDashedLine(session, new Vector2(strokeWidthHalf, strokeWidthHalf), new Vector2(strokeX, strokeWidthHalf), Colors.Black, strokeWidth, CanvasDashStyle.Dash);
DrawDashedLine(session, new Vector2(strokeWidthHalf, strokeWidthHalf), new Vector2(strokeWidthHalf, strokeY), Colors.Black, strokeWidth, CanvasDashStyle.Dash);
using var tile = new TileEffect()
{
Source = list,
SourceRectangle = new Rect(0, 0, strokeX, strokeY),
};
using var crop = new CropEffect()
{
Source = tile,
SourceRectangle = new Rect(0, 0, sender.ActualSize.X * 1000, sender.ActualSize.Y * 1000),
};
// タイル用CanvasControlは左上が原点
// キャンバスは画面中央が原点
// キャンバスの変形に応じて表示しつつ、タイル用CanvasControlの原点がキャンバスの左上となるよう
// 中央への移動とキャンバス原点への移動で二段階している
args.DrawingSession.Transform =
Matrix3x2.CreateTranslation(-canvasSize * 0.5f)
* Matrix3x2.CreateScale((float)transform.ScaleX, (float)transform.ScaleY)
* Matrix3x2.CreateRotation((float)transform.Rotation)
* Matrix3x2.CreateTranslation(translate)
* Matrix3x2.CreateTranslation(sender.ActualSize * 0.5f);
// draw the effect (using bitmap as input)
args.DrawingSession.DrawImage(crop);
}
/// <summary>
/// CanvasDrawingSession に破線を描画します。
/// </summary>
private void DrawDashedLine(
CanvasDrawingSession ds,
Vector2 p1,
Vector2 p2,
Color color,
float strokeWidth = 1f,
CanvasDashStyle dashStyle = CanvasDashStyle.Dash,
float dashOffset = 0f,
float[]? customDash = null)
{
using var stroke = new CanvasStrokeStyle()
{
DashStyle = customDash is null ? dashStyle : CanvasDashStyle.DashDot,
DashOffset = dashOffset,
EndCap = CanvasCapStyle.Round,
DashCap = CanvasCapStyle.Round,
LineJoin = CanvasLineJoin.Round,
};
if (customDash != null)
{
stroke.CustomDashStyle = customDash;
}
ds.DrawLine(p1, p2, color, strokeWidth, stroke);
}
参考
CanvasControl
CanvasControlを利用すると配置したXamlの親要素にStretchされた状態で描写できて便利です。
CanvasControl.Drawイベントにフックすると描写範囲の指定されたCanvasDrawingSessionが渡されます。
TileEffect
Sourceに指定した描画処理を繰り返し表示できるエフェクトです。
CropEffect
SourceRectangleに指定した範囲で描画処理範囲をくり抜きます。サイズ変更可能な方向にのみ点線を表示したいので表示範囲を制限するために利用します。

