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点には注意かなと思います。