WPFヘルパー:GridOverlay.cs – Canvasに格子状のガイドをオーバーレイ表示

コンピュータ

GridOverlay.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace Maywork.WPF.Helpers;

public static class GridOverlay
{
    // =========================
    // Enable
    // =========================
    public static readonly DependencyProperty EnableProperty =
        DependencyProperty.RegisterAttached(
            "Enable",
            typeof(bool),
            typeof(GridOverlay),
            new PropertyMetadata(false, OnEnableChanged));

    public static void SetEnable(DependencyObject obj, bool value)
        => obj.SetValue(EnableProperty, value);

    public static bool GetEnable(DependencyObject obj)
        => (bool)obj.GetValue(EnableProperty);

    // =========================
    // Step
    // =========================
    public static readonly DependencyProperty StepProperty =
        DependencyProperty.RegisterAttached(
            "Step",
            typeof(double),
            typeof(GridOverlay),
            new PropertyMetadata(50.0, OnPropertyChanged));

    public static void SetStep(DependencyObject obj, double value)
        => obj.SetValue(StepProperty, value);

    public static double GetStep(DependencyObject obj)
        => (double)obj.GetValue(StepProperty);

    // =========================
    // Stroke
    // =========================
    public static readonly DependencyProperty StrokeProperty =
        DependencyProperty.RegisterAttached(
            "Stroke",
            typeof(Brush),
            typeof(GridOverlay),
            new PropertyMetadata(Brushes.White, OnPropertyChanged));

    public static void SetStroke(DependencyObject obj, Brush value)
        => obj.SetValue(StrokeProperty, value);

    public static Brush GetStroke(DependencyObject obj)
        => (Brush)obj.GetValue(StrokeProperty);

    // =========================
    // StrokeThickness
    // =========================
    public static readonly DependencyProperty StrokeThicknessProperty =
        DependencyProperty.RegisterAttached(
            "StrokeThickness",
            typeof(double),
            typeof(GridOverlay),
            new PropertyMetadata(1.0, OnPropertyChanged));

    public static void SetStrokeThickness(DependencyObject obj, double value)
        => obj.SetValue(StrokeThicknessProperty, value);

    public static double GetStrokeThickness(DependencyObject obj)
        => (double)obj.GetValue(StrokeThicknessProperty);

    // =========================
    // 内部管理
    // =========================
    private static readonly DependencyProperty PathProperty =
        DependencyProperty.RegisterAttached(
            "PathInternal",
            typeof(Path),
            typeof(GridOverlay));

    private static void SetPath(DependencyObject obj, Path? value)
        => obj.SetValue(PathProperty, value);

    private static Path? GetPath(DependencyObject obj)
        => (Path?)obj.GetValue(PathProperty);

    // =========================
    // Enable変更
    // =========================
    private static void OnEnableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is not Canvas canvas) return;

        if ((bool)e.NewValue)
        {
            var path = new Path
            {
                IsHitTestVisible = false
            };

            Panel.SetZIndex(path, int.MaxValue); 
            SetPath(canvas, path);
            canvas.Children.Add(path);

            canvas.SizeChanged += OnSizeChanged;

            Update(canvas);
        }
        else
        {
            var path = GetPath(canvas);
            if (path != null)
            {
                canvas.Children.Remove(path);
                SetPath(canvas, null);
            }

            canvas.SizeChanged -= OnSizeChanged;
        }
    }

    // =========================
    // プロパティ変更
    // =========================
    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Canvas canvas && GetEnable(canvas))
        {
            Update(canvas);
        }
    }

    private static void OnSizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (sender is Canvas canvas)
        {
            Update(canvas);
        }
    }

    // =========================
    // 更新処理
    // =========================
    private static void Update(Canvas canvas)
    {
        var path = GetPath(canvas);
        if (path == null) return;

        double width = canvas.ActualWidth;
        double height = canvas.ActualHeight;
        double step = GetStep(canvas);

        if (width <= 0 || height <= 0 || step <= 0) return;

        path.Data = CreateGridGeometry(width, height, step);
        path.Stroke = GetStroke(canvas);
        path.StrokeThickness = GetStrokeThickness(canvas);
    }

    // =========================
    // Geometry生成
    // =========================
    private static Geometry CreateGridGeometry(double width, double height, double step)
    {
        var geo = new StreamGeometry();

        using (var ctx = geo.Open())
        {
            // 縦線
            for (double x = 0; x <= width; x += step)
            {
                ctx.BeginFigure(new Point(x, 0), false, false);
                ctx.LineTo(new Point(x, height), true, false);
            }

            // 横線
            for (double y = 0; y <= height; y += step)
            {
                ctx.BeginFigure(new Point(0, y), false, false);
                ctx.LineTo(new Point(width, y), true, false);
            }
        }

        geo.Freeze();
        return geo;
    }
}
/*
// 使用例
<Canvas
    h:GridOverlay.Enable="True"
    h:GridOverlay.Step="50"
    h:GridOverlay.Stroke="#FF0000"
    h:GridOverlay.StrokeThickness="1">
        <Image x:Name="MyImage" />
</Canvas>
*/
Download

コメント