Column > 【C#, VB】ペイントソフトの「進む」「戻る」機能の作成
2017/6/28 【C#, VB】ペイントソフトの「進む」「戻る」機能の作成

1年ほど前にC#とVBでペイントソフトみたいな線を描く記事を書きましたが、
ペイントソフトなら「進む」と「戻る」の機能も欲しいよなと思ったので作ってみました。

実際に動いている様子が下の通りです。


作り方は以下の通りです。


フォーム


フォームのデザインは下のような感じです。



前の記事ではフォームに線を直接描いていましたが、
よりペイントソフトっぽくするために描画用のpictureBoxを用意して、
上に戻ると進む用のメニューボタンを取り付けました。


プログラム概要


「進む」「戻る」機能の実装方法としては、マウスをクリックして線を描き始めてから
マウスが離れて描画が終了するごとに現在の画面を配列に保存し、
「進む」「戻る」のボタンが押されるとpictureBoxの画像をそれに対応したものに
差し替えるという感じの処理にしました。


プログラム


実際にC#で作ったプログラムVBで作ったプログラムは下のようになりました。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace paintSoftTest
{
    public partial class Form1 : Form
    {
        private const int LINE_WEIGHT = 5;
        private bool mouseDrug;
        private bool mouseDrawed;
        private int prevX;
        private int prevY;
        private List imgList;
        private int imgIndex;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            init();
        }

        private void init()
        {
            // 変数の初期化
            mouseDrug = false;
            mouseDrawed = false;
            imgList = new List();
            imgIndex = 0;
            
            // 白紙の状態をリストに追加
            Bitmap tmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
            pictureBox1.Image = tmp;
            imgList.Add(pictureBox1.Image);
        }

        private void undoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // 1つ前の状態に戻る
            if(imgIndex > 0)
            {
                imgIndex--;
            }
            pictureBox1.Image = imgList[imgIndex];
        }

        private void redoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // 1つ後の状態に進む
            if(imgIndex < imgList.Count - 1)
            {
                imgIndex++;
            }
            pictureBox1.Image = imgList[imgIndex];
        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            // 1つ前のマウスの位置を記録
            mouseDrug = true;
            prevX = e.Location.X;
            prevY = e.Location.Y;
            drawLine(e);
        }

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
        {
            // 後のリストを全て削除
            removeListAfterIndex();

            mouseDrug = false;
            // 今の状態をリストに追加
            if (mouseDrawed)
            {
                mouseDrawed = false;
                imgList.Add(pictureBox1.Image);
                imgIndex = imgList.Count - 1;
            }
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if (mouseDrug)
            {
                mouseDrawed = true;
                drawLine(e);
            }
        }

        private void removeListAfterIndex()
        {
            while(imgIndex + 1 < imgList.Count)
            {
                imgList.RemoveAt(imgIndex + 1);
            }
        }

        private void drawLine(MouseEventArgs e)
        {
            Bitmap canvas = new Bitmap(pictureBox1.Image);
            Pen objPen = new Pen(System.Drawing.Color.Black, LINE_WEIGHT);
            Graphics objGrp = Graphics.FromImage(canvas);
            objGrp.DrawLine(objPen, prevX, prevY, e.Location.X, e.Location.Y);
            objGrp.FillEllipse(Brushes.Black, prevX - objPen.Width / 2, prevY - objPen.Width / 2, objPen.Width, objPen.Width);
            prevX = e.Location.X;
            prevY = e.Location.Y;

            objPen.Dispose();
            objGrp.Dispose();

            pictureBox1.Image = canvas;
        }
    }
}
Public Class Form1
    Const LINE_WEIGHT As Integer = 5
    Private mouseDrug As Boolean
    Private mouseDrawed As Boolean
    Private prevX As Integer
    Private prevY As Integer
    Private imgList As List(Of Image)
    Private imgIndex As Integer

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        init()
    End Sub

    Private Sub init()
        ' 変数の初期化
        mouseDrug = False
        mouseDrawed = True
        imgList = New List(Of Image)
        imgIndex = 0

        ' 白紙の状態をリストに追加
        Dim tmp As Bitmap = New Bitmap(pictureBox1.Width, pictureBox1.Height)
        pictureBox1.Image = tmp
        imgList.Add(pictureBox1.Image)
    End Sub

    Private Sub undoToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles undoToolStripMenuItem.Click
        ' 1つ前の状態に戻る
        If imgIndex > 0 Then
            imgIndex -= 1
        End If
        pictureBox1.Image = imgList(imgIndex)
    End Sub

    Private Sub redoToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles redoToolStripMenuItem.Click
        ' 1つ後の状態に進む
        If imgIndex < imgList.Count - 1 Then
            imgIndex += 1
        End If
        pictureBox1.Image = imgList(imgIndex)
    End Sub

    Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles pictureBox1.MouseDown
        ' 1つ前のマウスの位置を記録
        mouseDrug = True
        prevX = e.Location.X
        prevY = e.Location.Y
        drawLine(e)
    End Sub

    Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles pictureBox1.MouseUp
        ' 後のリストを全て削除
        removeListAfterIndex()

        mouseDrug = False
        ' 今の状態をリストに追加
        If mouseDrawed Then
            mouseDrawed = False
            imgList.Add(pictureBox1.Image)
            imgIndex = imgList.Count - 1
        End If
    End Sub

    Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles pictureBox1.MouseMove
        If mouseDrug Then
            mouseDrawed = True
            drawLine(e)
        End If
    End Sub

    Private Sub removeListAfterIndex()
        While imgIndex + 1 < imgList.Count
            imgList.RemoveAt(imgIndex + 1)
        End While
    End Sub

    Private Sub drawLine(e As MouseEventArgs)
        Dim canvas = New Bitmap(pictureBox1.Image)
        Dim objPen = New Pen(System.Drawing.Color.Black, LINE_WEIGHT)
        Dim objGrp = Graphics.FromImage(canvas)
        objGrp.DrawLine(objPen, prevX, prevY, e.Location.X, e.Location.Y)
        objGrp.FillEllipse(Brushes.Black, prevX - objPen.Width / 2, prevY - objPen.Width / 2, objPen.Width, objPen.Width)
        prevX = e.Location.X
        prevY = e.Location.Y

        objPen.Dispose()
        objPen.Dispose()

        pictureBox1.Image = canvas
    End Sub
End Class

線を書き終わったとき(PictureBox1_MouseUp())のpictureBox1の状態を
imgListに保存し、「進む」と「戻る」ボタンをクリックすることで
画面に表示させる画像のインデックス(imgIndex)を操作しています。

また、描き終わったときにただただ画像をリストに追加するだけだと、
「戻る」ボタンを押してから新しい線を描いた場合に
昔の画像が残ったままになってしまうので
線を描き終わった後に、今のインデックスより後に残っている画像があれば
それらを全て削除するようにしています(removeListAfterIndex())





こんな感じで今回の記事は終了です。
「進む」「戻る」の機能を実装することはできましたが、
描画の際に裏画面での処理などを全くせずに
描画したものを直接表示させてしまっていることと、
リストに追加する画像の数に制限をかけていないので、大量に線を描かれると
メモリをかなり食いそうな気がするのでその2点には注意かなと思います。


inserted by FC2 system