アイコンエディタにアンドゥ機能を追加します。なんらかの編集が加えられたらパネル上の色情報をBitmapにして保存しておきます。アンドゥとリドゥボタンが押されたら保存しておいたBitmap情報から復元するという方法でやってみることにします。
まずはアンドゥバッファとこれを管理するためのクラスを示します。
Undobufクラスには編集前の情報と編集後の情報を保存しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Undobuf { public Undobuf(Bitmap oldBitmap, Bitmap newBitmap) { OldBitmap = oldBitmap; NewBitmap = newBitmap; } public Bitmap OldBitmap { get; private set; } public Bitmap NewBitmap { get; private set; } } |
Undobufを管理するUndoBufsクラスにはUndo、Redoのためのバッファ、実際にUndo、Redoが要求されたときの処理、そもそもUndoとRedoが可能かどうかを調べるメソッドを用意しています。
Undo、Redoが要求されたときはイベントを発生させて、イベントハンドラのなかで実際の処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
public class UndoBufs { List<Undobuf> undoBitmap = new List<Undobuf>(); List<Undobuf> redoBitmap = new List<Undobuf>(); public class UndobufArg : EventArgs { public UndobufArg(Undobuf undobuf) { Undobuf = undobuf; } public Undobuf Undobuf { get; private set; } public bool IsSucceeded { get; set; } = true; } public delegate void UndoHandler(object sender, UndobufArg e); public event UndoHandler Undo; public event UndoHandler Redo; public void DoUndo() { if(CanUndo()) { Undobuf undobuf = undoBitmap[0]; UndobufArg e = new UndobufArg(undobuf); Undo?.Invoke(this, e); if(e.IsSucceeded) { undoBitmap.RemoveAt(0); redoBitmap.Insert(0, undobuf); } } } public void DoRedo() { if(CanRedo()) { Undobuf undobuf = redoBitmap[0]; UndobufArg e = new UndobufArg(undobuf); Redo?.Invoke(this, e); if(e.IsSucceeded) { redoBitmap.RemoveAt(0); undoBitmap.Insert(0, undobuf); } } } public void InsertUndobuf(Bitmap oldBitmap, Bitmap newBitmap) { Undobuf undobuf = new Undobuf(oldBitmap, newBitmap); undoBitmap.Insert(0, undobuf); foreach(Undobuf buf in redoBitmap) { buf.OldBitmap.Dispose(); buf.NewBitmap.Dispose(); } redoBitmap.Clear(); } public void AllClear() { foreach(Undobuf buf in undoBitmap) { buf.OldBitmap.Dispose(); buf.NewBitmap.Dispose(); } foreach(Undobuf buf in redoBitmap) { buf.OldBitmap.Dispose(); buf.NewBitmap.Dispose(); } undoBitmap.Clear(); redoBitmap.Clear(); } public bool CanUndo() { return undoBitmap.Count != 0; } public bool CanRedo() { return redoBitmap.Count != 0; } } |
あとは簡単・・・と思ったら結構落とし穴がありました。
1 2 3 4 5 6 7 8 9 10 |
public class PanelEx : Panel { private void PanelEx_MouseDown(object sender, MouseEventArgs e) { Form1 f = (Form1)FindForm(); f.SaveOldBitmap(); // 以降は前回と同じ } } |
マウスがクリックされたら編集前のデータを保存しているのですが、範囲選択がされているときにこれをおこなうと範囲選択用の色まで保存されてしまうので、新しくつくったClearSelectionForSaveUndobufメソッドでいったん解除しています。
そのあとSaveOldBitmapで編集前のデータを保存しています。保存が終わったらRestoreSelectionAfterSaveUndobufで範囲選択の状態に戻しています。
SaveOldBitmapメソッドとClearSelectionForSaveUndobufメソッド、RestoreSelectionAfterSaveUndobufメソッドは以下のような内容になっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public partial class Form1 : Form { Bitmap oldBitmap = null; public void SaveOldBitmap() { ClearSelectionForSaveUndobuf(); // Undobufに格納するデータの作成 Bitmap bitmap = new Bitmap(32, 32); for(int i = 0; i < 32; i++) { for(int j = 0; j < 32; j++) { bitmap.SetPixel(j, i, panels[j, i].BackColor); } } oldBitmap = (Bitmap)bitmap.Clone(); bitmap.Dispose(); RestoreSelectionAfterSaveUndobuf() } // 一時的に選択解除 public void ClearSelectionForSaveUndobuf() { if(SelectionXYs.Count == 0) return; foreach(var o in SelectionXYs) { panels[o.X, o.Y].BackColor = o.oldColor; } } // 選択状態にもどす public void RestoreSelectionAfterSaveUndobuf() { if(SelectionXYs.Count == 0) return; foreach(var o in SelectionXYs) { panels[o.X, o.Y].BackColor = Color.Tan; } } } |
ドラッグがおわったらデータが変更されているか調べて、変更されていた場合はUndobufを追加しています。
1 2 3 4 5 6 7 8 9 |
public class PanelEx : Panel { private void PanelEx_DragDrop(object sender, DragEventArgs e) { // 前半部分は省略 f.InsertUndobuf(); } } |
InsertUndobufメソッドは以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public partial class Form1 : Form { UndoBufs undoBufs = new UndoBufs(); public void InsertUndobuf() { if(oldBitmap == null) return; ClearSelectionForSaveUndobuf(); // Undobufに格納するデータの作成 Bitmap afterBitmap = new Bitmap(32, 32); for(int i = 0; i < 32; i++) { for(int j = 0; j < 32; j++) { afterBitmap.SetPixel(j, i, panels[j, i].BackColor); } } if(!CompareBitmap(afterBitmap, oldBitmap)) { undoBufs.InsertUndobuf((Bitmap)oldBitmap.Clone(), (Bitmap)afterBitmap.Clone()); } oldBitmap.Dispose(); afterBitmap.Dispose(); oldBitmap = null; RestoreSelectionAfterSaveUndobuf(); } } |
データが変更されているかどうかはafterBitmapとoldBitmapを比較すればいいのですが、
1 2 |
if(afterBitmap != oldBitmap) // 変更時の処理 |
ではダメです。afterBitmapとoldBitmapのデータが同じでもオブジェクト的には違うからです。そこで以下のようにBitmap.GetPixelメソッドで調べています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { public bool CompareBitmap(Bitmap bitmap1, Bitmap bitmap2) { for(int a = 0; a < 32; a++) { for(int b = 0; b < 32; b++) { if(bitmap1.GetPixel(a, b) != bitmap2.GetPixel(a, b)) return false; } } return true; } } |
それからUndobufを追加するのはパネルがクリックされたときだけではありません。[文字を入力する]ボタンがクリックされたときも必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { private void buttonInsertString_Click(object sender, EventArgs e) { string str = textBox1.Text; if(str == "") { MessageBox.Show("文字が入力されていません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // Undobufに格納するデータの作成 SaveOldBitmap(); // 途中の処理は省略 // Undobufに格納するデータの作成 InsertUndobuf(); } } |
また新しいファイルが読み込まれたときはアンドゥバッファはクリアしなければなりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public partial class Form1 : Form { private void buttonOpenFile_Click(object sender, EventArgs e) { // 最後にアンドゥバッファをクリアする処理が追加されたため、if文を一部変更した。 OpenFileDialog dlg = new OpenFileDialog(); dlg.Filter = "アイコン|*.ico|ビットマップ|*.bmp|JPG|*.jpg|PNG|*.png|すべてのファイル|*.*"; if(dlg.ShowDialog() != DialogResult.OK) return; // *.ico if(dlg.FilterIndex == 1) { // 省略 } // *.bmp,*.jpg,*.png else if(dlg.FilterIndex == 2 || dlg.FilterIndex == 3 || dlg.FilterIndex == 4) { // 省略 } // すべてのファイル else if(dlg.FilterIndex == 5) { try { // 省略 } catch { MessageBox.Show("これは画像ファイルではありません", "", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } } undoBufs.AllClear(); } } |
あとはUndo、Redoボタンがおされたときの処理を書きます。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { private void buttonRedo_Click(object sender, EventArgs e) { undoBufs.DoRedo(); } private void buttonUndo_Click(object sender, EventArgs e) { undoBufs.DoUndo(); } } |
これでUndoBufsクラスのなかでイベントが発生するので、そのときの処理を書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public partial class Form1 : Form { public Form1() { // 他の処理は省略 undoBufs.Undo += UndoBufs_Undo; undoBufs.Redo += UndoBufs_Redo; } private void UndoBufs_Undo(object sender, UndoBufs.UndobufArg e) { oldBitmap = null; Bitmap bitmap = e.Undobuf.OldBitmap; for(int row = 0; row < 32; row++) { for(int colum = 0; colum < 32; colum++) { panels[colum, row].BackColor = bitmap.GetPixel(colum, row); } } } private void UndoBufs_Redo(object sender, UndoBufs.UndobufArg e) { Bitmap bitmap = e.Undobuf.NewBitmap; for(int row = 0; row < 32; row++) { for(int colum = 0; colum < 32; colum++) { panels[colum, row].BackColor = bitmap.GetPixel(colum, row); } } } } |
これでUndo、Redoの処理もできるようになりました。