【WPF】Excel 図形 みたいなのを作る

C#, プログラミングExcel, shape, WPF

また性懲りもなく変なことしている、のい太です。

今回は、WPF で Excel の図形モドキみないなのが出来ないかと思って挑戦してみました。

方眼紙を作る

まず初めに、Excel方眼紙を作ります。

とりあえず今回は 10px 毎に縦横の線を引きました。

MainWindow のコードビハインドに処理を書いても良かったのですが、図形を書く処理も入ってきて見にくくなるので、今回は Behavior で書きました。

Behavior に書くことで簡単に処理をカプセル化出来るのでオススメです。

Microsoft.Xaml.Behaviors.Wpf

まず、Behavior を作るために NuGetで「Microsoft.Xaml.Behaviors.Wpf」を入れます。

次に、プロジェクトに「Behaviors」フォルダを作って、その中に「CanvasGridBehavior.cs」クラスを作成します。

CanvasGridBehavior

キャンバスコントロールの読み込みが完了したら、グリッドを作成します。

線を引く方法は色々ありますが今回は、横専用のループと縦専用のループで画面いっぱいに線を引くようにしました。

using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace ExcelWpf.Behaviors
{
    class CanvasGridBehavior : Behavior<Canvas>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.Loaded += AssociatedObject_Loaded;
        }
        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.Loaded -= AssociatedObject_Loaded;
        }
        private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            CreateCanvasGrid();
        }
        private void CreateCanvasGrid()
        {
            for (var x = 0; x < AssociatedObject.ActualWidth / 10; x++)
            {
                var geometry = new StreamGeometry
                {
                    FillRule = FillRule.EvenOdd
                };
                using (var ctx = geometry.Open())
                {
                    ctx.BeginFigure(new Point(x * 10, 0), false, false);
                    ctx.LineTo(new Point(x * 10, AssociatedObject.ActualHeight), true, false);
                }
                geometry.Freeze();
                AssociatedObject.Children.Add(new Path
                {
                    Stroke = Brushes.Black,
                    StrokeThickness = 0.1,
                    Data = geometry
                });
            }
            for (var y = 0; y < AssociatedObject.ActualHeight / 10; y++)
            {
                var geometry = new StreamGeometry
                {
                    FillRule = FillRule.EvenOdd
                };
                using (var ctx = geometry.Open())
                {
                    ctx.BeginFigure(new Point(0, y * 10), false, false);
                    ctx.LineTo(new Point(AssociatedObject.ActualWidth, y * 10), true, false);
                }
                geometry.Freeze();
                AssociatedObject.Children.Add(new Path
                {
                    Stroke = Brushes.Black,
                    StrokeThickness = 0.1,
                    Data = geometry
                });
            }
        }
    }
}

MainWindow.xaml

XAML 側で、キャンバスに今作った CanvasGridBehavior を設定します。

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:b="http://schemas.microsoft.com/xaml/behaviors" 
    xmlns:Behaviors="clr-namespace:ExcelWpf.Behaviors" 
    x:Class="ExcelWpf.MainWindow"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Canvas x:Name="canvas" Background="Transparent">
            <b:Interaction.Behaviors>
                <Behaviors:CanvasGridBehavior/>
            </b:Interaction.Behaviors>
        </Canvas>
    </Grid>
</Window>

図形を作る

マウスクリックをすると、線の上から図形が作れるようにします。

MainWindow.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace ExcelWpf
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindow_Loaded;
        }
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            Loaded -= MainWindow_Loaded;
            canvas.MouseDown += Canvas_MouseDown;
            canvas.MouseMove += Canvas_MouseMove;
            canvas.MouseUp += Canvas_MouseUp;
        }
        bool isMouseDown = false;
        Point StartPoint;
        Point MovePoint;
        Rectangle rectangle;
        private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
        {
            var point = e.GetPosition(canvas);
            var leftOffset = point.X % 10;
            leftOffset = leftOffset < 5 ? -leftOffset : 10 - leftOffset;
            var topOffset = point.Y % 10;
            topOffset = topOffset < 5 ? -topOffset : 10 - topOffset;
            StartPoint = new Point(point.X + leftOffset, point.Y + topOffset);
            rectangle = new Rectangle
            {
                Stroke = Brushes.Black,
                Fill = Brushes.LightBlue,
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Top,
                Width = 0,
                Height = 0,
                Opacity = 0.3
            };
            Canvas.SetLeft(rectangle, StartPoint.X);
            Canvas.SetTop(rectangle, StartPoint.Y);
            canvas.Children.Add(rectangle);
            isMouseDown = true;
        }
        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (!isMouseDown) return;
            var point = e.GetPosition(canvas);
            var leftOffset = point.X % 10;
            leftOffset = leftOffset < 5 ? -leftOffset : 10 - leftOffset;
            var topOffset = point.Y % 10;
            topOffset = topOffset < 5 ? -topOffset : 10 - topOffset;
            MovePoint = new Point(point.X + leftOffset, point.Y + topOffset);
            rectangle.Width = Math.Abs(MovePoint.X - StartPoint.X);
            rectangle.Height = Math.Abs(MovePoint.Y - StartPoint.Y);
            if (MovePoint.X - StartPoint.X <= 0)
            {
                Canvas.SetLeft(rectangle, MovePoint.X);
            }
            if (MovePoint.Y - StartPoint.Y <= 0)
            {
                Canvas.SetTop(rectangle, MovePoint.Y);
            }
        }
        private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
        {
            isMouseDown = false;
            rectangle.Opacity = 1;
        }
    }
}

キャンバスに「MouseDown」「MouseMove」「MouseUp」のイベントを設定して、図形を作成しています。

また、マウスクリック位置から一番近い線に補正する処理を入れているので、ちゃんと線の上から図形が作成できるようになっています。

GitHub

今回のプロジェクトを GitHub で公開しておきますので、興味のある方はお好みでどうぞ。

おわりに

図形が描けるようになりましたが、まだまだ似ても似つかない感じです。

移動や線や文字を乗せたり、まだまだ先は長そうです。

スポンサーリンク