レイヤーをドラッグ&ドロップで移動できるようにします。レイヤーの上で右クリックするとコンテキストメニュー[レイヤー○○を移動させる]が表示されます。これをクリックすると左クリック ⇒ ドラッグ&ドロップでレイヤーの位置を変更できます。変更したあとであってもレイヤーの外側をクリックするまではドラッグで位置を変更することができます。
まずコンテキストメニューに[レイヤー○○を移動させる]を表示させます。
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 |
class ContextMenuEx1 { Form1 _form; ContextMenuStrip CreateContextMenuStrip(Layer layer) { ContextMenuStrip menuStrip = new ContextMenuStrip(); AddMenuEdit(menuStrip, layer); AddMenuBackOne(menuStrip, layer); AddMenuMove(menuStrip, layer); // 追加 return menuStrip; } // 移動されようとしているレイヤーと実際に移動させるためのオブジェクト public Layer MovingLayer = null; public LayerMove LayerMove = null; void AddMenuMove(ContextMenuStrip menuStrip, Layer layer) { ToolStripMenuItem item = new ToolStripMenuItem(); item.Text = String.Format("レイヤー「{0}」を移動させる", layer.Name); item.Tag = layer; item.Click += Item_Click3; menuStrip.Items.Add(item); } void Item_Click3(object sender, EventArgs e) { ToolStripMenuItem mi = (ToolStripMenuItem)sender; Layer layer = (Layer)mi.Tag; MovingLayer = layer; _form.ShowLayers(layer); } } |
コンテキストメニューをクリックしたらフィールド変数 Form1.contextMenuEx1のIsMovingが変化します。左クリックされたときにForm1.ContextMenuEx.IsMovingを調べることで移動のための処理を開始すべきかどうか判断できます。処理を開始しなければならない場合は LayerMoveStart(Layer layer, Point clickPoint)メソッドが実行されます。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); this.MouseUp += Form1_MouseUp; this.MouseMove += Form1_MouseMove; // 既存のその他のコードは省略 } ContextMenuEx1 contextMenuEx1 = null; private void ScroolPictureBox1_PictureBoxMouseDown(object sender, MouseEventArgs e) { ShowLayers(); if (e.Button == MouseButtons.Right) { // コンテキストメニューを表示させる Layer layer = GetLayerFromPoint(new Point(e.X, e.Y)); if (layer != null) contextMenuEx1 = new ContextMenuEx1(this, layer); else contextMenuEx1 = null; return; } if (e.Button == MouseButtons.Left) { if (contextMenuEx1 == null) return; if (contextMenuEx1.MovingLayer != null) { LayerMoveStart(contextMenuEx1.MovingLayer, new Point(e.X, e.Y)); return; } } } } |
LayerMoveStart(Layer layer, Point clickPoint)メソッドではクリックされた場所がレイヤーの内部かどうかを調べます。レイヤーの外部がクリックされたのであれば移動させる処理はしなくてよいということになります。
では移動処理が必要な場合はどうするか? マウスキャプチャをすることでForm1にMouseUpイベントとMouseMoveイベントを捕捉できるようにします。それと同時に移動するレイヤーを枠で囲って視覚的にもわかりやすくします。このときは新たに作成した ShowLayers(Layer selectedLayer)メソッドで編集中の画像を表示させます。
ただしこの処理をするときはBitmapの生成が高速でおこなわれます。そこで毎回Bitmapを生成してScroolPictureBox.Bitmapにセットするようなことはしません。これをやると画像がカクカクしてしまいます。処理が追いつかないのです。そこで別の方法(後述)をとります。
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 |
public partial class Form1 : Form { void LayerMoveStart(Layer layer, Point clickPoint) { if (layer != null) { // クリックされた場所がレイヤーの外側なら移動の処理はキャンセル if (clickPoint.X < layer.Left || layer.Right < clickPoint.X || clickPoint.Y < layer.Top || layer.Bottom < clickPoint.Y) { // 移動は行なわれないので、移動に関する情報をもつ contextMenuEx1 は nullにする contextMenuEx1 = null; Text = "レーヤー移動は中止"; ShowLayers(); return; } if (contextMenuEx1 != null) { Text = "レーヤーを移動します。"; Capture = true; // マウスキャプチャ contextMenuEx1.LayerMove = new LayerMove(this, layer, Control.MousePosition); ShowLayers(layer); // 移動するレイヤーを枠で囲って表示する } } } public void ShowLayers(Layer selectedLayer) { scroolPictureBox1.PictureBoxInvalidate(); } } |
それからLayerMoveクラスは以下のようになっています。どのレイヤーを動かすのか、レイヤーの元の位置、レイヤーを動かすためにクリックされた部分の座標(MouseUpのときの座標と比較すればどれだけ移動するのかがわかる)を格納し、実際にレイヤーを移動させるメソッドをもちます。
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 |
public class LayerMove { public Point MoveStartScreenPos = new Point(); public Layer Layer = null; public int LayerOldPosX = 0; public int LayerOldPosY = 0; public Form1 _form1 = null; public LayerMove(Form1 form1, Layer layer, Point moveStartScreenPos) { _form1 = form1; Layer = layer; MoveStartScreenPos = moveStartScreenPos; LayerOldPosX = Layer.X; LayerOldPosY = Layer.Y; } public void MoveLayer() { // クリックされた座標との差を利用して、元の座標から移動させる int dx = Control.MousePosition.X - MoveStartScreenPos.X; int dy = Control.MousePosition.Y - MoveStartScreenPos.Y; Layer.X = LayerOldPosX + dx; Layer.Y = LayerOldPosY + dy; _form1.ShowLayers(Layer); } } |
LayerMoveStartメソッド内でマウスキャプチャをしているのでマウスの移動やマウスボタンが離されたときの通知はForm1クラスに届きます。そこでマウスの移動やマウスボタンが離されたときの処理を書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { private void Form1_MouseUp(object sender, MouseEventArgs e) { if (contextMenuEx1 != null && contextMenuEx1.LayerMove != null) contextMenuEx1.LayerMove.MoveLayer(); } private void Form1_MouseMove(object sender, MouseEventArgs e) { if (contextMenuEx1 != null && contextMenuEx1.LayerMove != null) contextMenuEx1.LayerMove.MoveLayer(); } } |
ShowLayers(Layer selectedLayer)メソッドについて
まずScroolPictureBoxクラスを改良します。やっていることはこのクラスのなかにあるPictureBoxのPaintイベントに対して処理をすることができるようになったことと、PictureBoxのPaintイベントを引き起こすことができるようにPictureBoxInvalidate()メソッドを追加したことです。
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 |
public partial class ScroolPictureBox : UserControl { public ScroolPictureBox() { InitializeComponent(); pictureBox1.Location = new Point(0, 0); pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize; pictureBox1.MouseDown += PictureBox1_MouseDown; // ここより上は既存のコード pictureBox1.Paint += PictureBox1_Paint; // 追加した部分 } public void PictureBoxInvalidate() { pictureBox1.Invalidate(); } public delegate void PaintHandler(object sender, PaintEventArgs e); public event PaintHandler PicturePaint; private void PictureBox1_Paint(object sender, PaintEventArgs e) { PicturePaint?.Invoke(this, e); } } |
そしてForm1クラスでイベントハンドラ scroolPictureBox1.PicturePaintを追加。ShowLayers(Layer selectedLayer)メソッドが実行されたらフィールド変数に引数をコピーしてPictureBoxInvalidate()を呼び出します。するとBitmapがつくられるのは1回だけ。マウスがうごくたびにBitmapに複数のレイヤーのデータを書き込んで、これをscroolPictureBox1>Bitmap = bitmap;なんてする時間がかかる処理は行なわれなくなります。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); MenuItemLayers.DropDownOpening += MenuItemLayers_DropDownOpening; scroolPictureBox1.BorderStyle = BorderStyle.None; scroolPictureBox1.PictureBoxMouseDown += ScroolPictureBox1_PictureBoxMouseDown; this.MouseUp += Form1_MouseUp; this.MouseMove += Form1_MouseMove; // これより上は既存のコード // 追加された部分 scroolPictureBox1.PicturePaint += scroolPictureBox1_PicturePaint; } public void ShowLayers(Layer selectedLayer) { // 引数は使わない scroolPictureBox1.PictureBoxInvalidate(); } private void scroolPictureBox1_PicturePaint(object sender, PaintEventArgs e) { if (contextMenuEx1 != null && contextMenuEx1.MovingLayer != null) { ShowLayersOnMoving(e.Graphics); } } void ShowLayersOnMoving(Graphics g) { int newWidth = 0; int newHeight = 0; if (Layers.Count > 0) { newWidth = Layers.Max(x => x.Right); newHeight = Layers.Max(x => x.Bottom); } // なにもないBitmapをつくる Bitmap bitmap = new Bitmap(newWidth, newHeight); scroolPictureBox1.Bitmap = bitmap; foreach (Layer layer in Layers) { g.DrawImage(layer.Bitmap, new Rectangle(layer.X, layer.Y, layer.Width, layer.Height)); } g.DrawRectangle(new Pen(Color.Black), SelectedLayer.X, SelectedLayer.Y, SelectedLayer.Width, SelectedLayer.Height); } } |
これでレイヤーを移動させるときに動作がカクカクすることはなくなります。またコンテキストメニュー [レイヤーXXXを移動させる]を選択するとともに対象のレイヤーに枠が表示されます。そして移動が終わっても枠は消えません。右クリックをしたり、レイヤーのない部分をクリックすると枠は消えます。