ようこそ。睡眠不足なプログラマのチラ裏です。

移動とリサイズ可能なコントロール

ネタ元:VB.NETで作る!移動、リサイズの出来るコントロールを作る
http://shinshu.fm/MHz/88.44/archives/0000043418.html


単純におもろいなぁと思いまして、C#でMix-inっぽくシンプルにコントロールに実装できるように
それ用のインターフェイスおよび、その拡張メソッドをこさえてみた。
深くは煮詰めてはいないけど、とりあえずこんな感じでよいでしょう。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace ClassLibrary1
{
    public interface IResizeAndMove 
    {
        int MinWidth { get; }
        int MinHeight { get; }

        int MaxWidth { get; }
        int MaxHeight { get; }

        int ResizeWidth { get; set; }
        int ResizeHeight { get; set; }
    }

    public static class IResizeAndMoveExtentions
    {
        #region メンバ
        private static readonly ContextMenu _cmResize = new ContextMenu();
        private static readonly MenuItem _mnuResize = new MenuItem("Edit",mnuResize_Click);
        private static readonly MenuItem _mnuBringToFront = new MenuItem("BringToFront", mnuBringToFront_Click);
        private static readonly MenuItem _mnuSendToBack = new MenuItem("SendToBack", mnuSendToBack_Click);

        #endregion

        #region 拡張メソッド
        public static void InitializeResizeAndMove<T>(this T self)
        where T : System.Windows.Forms.Control, IResizeAndMove
        {
            lock (_mnuResize)
            {
                self.ContextMenu = _cmResize;
                if (_cmResize.MenuItems.Count > 0) return;
                _cmResize.MenuItems.Add(_mnuResize);
                _cmResize.MenuItems.Add(_mnuBringToFront);
                _cmResize.MenuItems.Add(_mnuSendToBack);
            }
        }
        #endregion

        #region Static Method
        private static void mnuResize_Click(object sender, EventArgs e)
        {
            var m = (MenuItem)sender;
            var c = m.GetContextMenu().SourceControl;
            using (var f = new IResizeAndMoveExtentions.ResizeBorderForm(c))
            {
                f.ShowDialog(c.FindForm());
            }
        }

        private static void mnuBringToFront_Click(object sender, EventArgs e)
        {
            var m = (MenuItem)sender;
            var c = m.GetContextMenu().SourceControl; 
            c.BringToFront();
        }

        private static void mnuSendToBack_Click(object sender, EventArgs e)
        {
            var m = (MenuItem)sender;
            var c = m.GetContextMenu().SourceControl; 
            c.SendToBack();
        }
        #endregion

        private sealed class ResizeBorderForm : System.Windows.Forms.Form 
        {
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// 使用中のリソースをすべてクリーンアップします。
            /// </summary>
            /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows フォーム デザイナで生成されたコード

            /// <summary>
            /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
            /// コード エディタで変更しないでください。
            /// </summary>
            private void InitializeComponent()
            {
                this.SuspendLayout();
                this.AutoScaleBaseSize = new System.Drawing.Size(5, 12); 
                this.BackColor = System.Drawing.Color.SkyBlue;
                this.ClientSize = new System.Drawing.Size(100,20); 
                this.ControlBox = false; 
                this.Cursor = System.Windows.Forms.Cursors.Default; 
                this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
                this.Name = "ResizeBorderForm"; 
                this.Opacity = 0.5; 
                this.ShowInTaskbar = false; 
                this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
                this.ResumeLayout(false);
            }

            #endregion

            #region メンバ
            private Control _targetControl;
            private Point _moveAdjustPoint;
            private Point _resizeAdjustPoint;
            private bool _hasResizeEvent = false;
            #endregion

            #region コンストラクタ

            public ResizeBorderForm(Control target)
            {
                _targetControl = target;

                InitializeComponent();

                var rect = new Rectangle(target.Location ,target.Size);
                var srect = target.Parent.RectangleToScreen(rect);

                this.Top = srect.Y;
                this.Left = srect.X;

                this.Width = srect.Width;
                this.Height = srect.Height;

                var tc = (IResizeAndMove)target;
                this.MinimumSize = new Size(tc.MinWidth, tc.MinHeight);
                this.MaximumSize = new Size(tc.MaxWidth, tc.MaxHeight);

                //リサイズ用調整値の取得
                _resizeAdjustPoint.Y = this.Top;
                _resizeAdjustPoint.X = this.Left;

                this._hasResizeEvent = true;
            }
            #endregion

            #region オーバーライド
            protected override void OnMouseDown(MouseEventArgs e)
            {
                if (e.Button != MouseButtons.Left) return;

                _moveAdjustPoint = new Point(e.X, e.Y);

                this.Cursor = Cursors.SizeAll;
                this._hasResizeEvent = true;

                base.OnMouseDown(e);
            }

            protected override void  OnMouseDoubleClick(MouseEventArgs e)
            {
                if (e.Button == MouseButtons.Left)
                {
                    this.Close();
                    this._hasResizeEvent = false;
                    return;
                }
                base.OnMouseDoubleClick(e);
            }

            protected override void OnMouseUp(MouseEventArgs e)
            {
                if (!this._hasResizeEvent) return;

                this.Cursor = Cursors.Default;
                _resizeAdjustPoint.Y = this.Top;
                _resizeAdjustPoint.X = this.Left;

                base.OnMouseUp(e);
            }

            protected override void OnMouseMove(MouseEventArgs e)
            {
                if (e.Button != MouseButtons.Left) return;

                var moveX = (e.X - _moveAdjustPoint.X);
                var moveY = (e.Y - _moveAdjustPoint.Y);
                this.Left += moveX;
                this.Top += moveY;
                this._targetControl.Left += moveX;
                this._targetControl.Top += moveY;

                base.OnMouseMove(e);
            }

            protected override void OnResize(EventArgs e)
            {
                var tc = (IResizeAndMove)_targetControl;
               
                base.OnResize(e);
                if (!this._hasResizeEvent) return;

                if (this.Top != _resizeAdjustPoint.Y) _targetControl.Top += (this.Top - _resizeAdjustPoint.Y);
                if (this.Left != _resizeAdjustPoint.X) _targetControl.Left += (this.Left - _resizeAdjustPoint.X);
                tc.ResizeWidth = this.Width;
                tc.ResizeHeight = this.Height;

                _resizeAdjustPoint.Y = this.Top;
                _resizeAdjustPoint.X = this.Left;
            }
            #endregion
        }
    }
}


例えば、以下のような感じで、簡単に実装することができる。


ボタンコントロールでの実装

using System.Windows.Forms;

namespace ClassLibrary1
{
    public partial class ButtonEx : Button,IResizeAndMove
    {
        public ButtonEx()
        {
            this.InitializeResizeAndMove();
        }

        #region IResizeAndMove メンバ
        public int MinWidth { get { return 20; } }
        public int MinHeight { get { return 20; } }
        public int MaxWidth { get { return 200; } }
        public int MaxHeight { get { return 80; } }

        public int ResizeWidth
        {
            get{ return this.Width; }
            set { this.Width = value; }
        }

        public int ResizeHeight
        {
            get { return this.Height; }
            set { this.Height = value; }
        }
       #endregion
    }
}


オーナードローなコンボボックスでの実装

using System.Drawing;
using System.Windows.Forms;

namespace ClassLibrary1
{
    public partial class ComboBoxEx : ComboBox , IResizeAndMove 
    {
        public ComboBoxEx()
        {
            this.InitializeResizeAndMove();

            this.DoubleBuffered = true;
            this.DrawMode = DrawMode.OwnerDrawFixed;
        }

        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            e.DrawBackground();

            var cmb = this;
            //項目に表示する文字列
            string txt = e.Index > -1 ? cmb.Items[e.Index].ToString() : cmb.Text;

            Brush b = new SolidBrush(e.ForeColor);
            float ym = (e.Bounds.Height - e.Graphics.MeasureString(txt, cmb.Font).Height) / 2;
            e.Graphics.DrawString(txt, cmb.Font, b, e.Bounds.X, e.Bounds.Y + ym);
            b.Dispose();
            e.DrawFocusRectangle();

            base.OnDrawItem(e);
        }

        #region IResizeAndMove メンバ
        public int MinWidth {get { return 20; }}
        public int MinHeight{get { return 20; }}
        public int MaxWidth {get { return 300; }}
        public int MaxHeight {get { return 100; }}

        public int ResizeWidth
        {
            get { return this.Width; }
            set { this.Width = value; }
        }

        public int ResizeHeight
        {
            get { return this.ItemHeight; }
            set
            {
                value -= 7;
                if (value < this.MinHeight) value = this.MinHeight;
                this.ItemHeight = value; 
            }
        }
        #endregion
    }
}


チェックボックスでの実装

using System.Windows.Forms;

namespace ClassLibrary1
{
    public class CheckBoxEx : CheckBox, IResizeAndMove 
    {
        public CheckBoxEx()
        {
            this.InitializeResizeAndMove();
            this.AutoSize = false;
        }

        #region IResizeAndMove メンバ
        public int MinWidth { get { return 20; } }
        public int MinHeight { get { return 20; } }
        public int MaxWidth { get { return 200; } }
        public int MaxHeight { get { return 80; } }

        public int ResizeWidth
        {
            get { return this.Width; }
            set { this.Width = value; }
        }

        public int ResizeHeight
        {
            get { return this.Height; }
            set { this.Height = value; }
        }
        #endregion
    }
}


とりあえず、うごきますね。
実用に耐え得るようにするのであれば、もうちょい調整は必要っぽい。