코드 조각 : 다이어그램 에디터 샘플 - escaco95/Charcoal GitHub Wiki

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

public class TranspCtrl : Control
{
    public TranspCtrl()
    {
        Items = new ItemCollection(this);
        ResizeRedraw = true;
        DoubleBuffered = true;
    }
    public ItemCollection Items { get; private set; }
    //
    static readonly Brush BrushShadow = new SolidBrush(Color.FromArgb(32, 0, 0, 0));
    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;

        g.TranslateTransform(-VIEWPOINT_X, -VIEWPOINT_Y);

        foreach (var item in Items.Items)
        {
            if (item.Buffer == null)
            {
                item.Render();
            }

            if (item.CastShadow)
                g.FillRectangle(BrushShadow, item.X + 5, item.Y + 5, item.Width, item.Height);

            g.DrawImage(item.Buffer, item.X, item.Y, item.Width, item.Height);

            if (item.Selected)
                g.DrawRectangle(Pens.Lime, item.X - 1, item.Y - 1, item.Width + 1, item.Height + 1);
        }

        if (MouseJobParam == null)
            return;

        MouseJobParam.Paint(this,g);
    }
    volatile int Updating = 0;
    public void BeginUpdate()
    {
        Updating++;
    }
    public void EndUpdate()
    {
        Updating--;
        if (Updating == 0)
            Invalidate();
    }
    internal void TryUpdate()
    {
        if (Updating == 0)
            Invalidate();
    }
    //
    double GetDistance(float x, float y, float x2, float y2)
    {
        double dx = x2 - x, dy = y2 - y;
        return Math.Sqrt(dx * dx + dy * dy);
    }
    public void DeselectAll()
    {
        foreach (var item in Items.Items)
        {
            if (!item.Selected)
                continue;
            item.Deselect();
        }
    }
    Item MouseHitTest(int x, int y)
    {
        var worldMouse = GetWorldPos(x, y);
        var nx = (int)worldMouse.X;
        var ny = (int)worldMouse.Y;

        Item hitTarget = null;

        foreach (var item in Items.Items)
        {
            if (!item.Bound.Contains(nx, ny))
                continue;
            hitTarget = item;
        }

        return hitTarget;
    }
    //
    volatile float VIEWPOINT_X = 0;
    volatile float VIEWPOINT_Y = 0;
    void ViewTo(float x, float y)
    {
        VIEWPOINT_X = x;
        VIEWPOINT_Y = y;
        TryUpdate();
    }
    void ViewToward(float x, float y)
    {
        VIEWPOINT_X += x;
        VIEWPOINT_Y += y;
        TryUpdate();
    }
    PointF GetWorldPos(Point p) => GetWorldPos(p.X, p.Y);
    PointF GetWorldPos(int x, int y)
    {
        return new PointF(
            VIEWPOINT_X + x,
            VIEWPOINT_Y + y
            );
    }
    Point GetControlPos(PointF p) => GetControlPos(p.X, p.Y);
    Point GetControlPos(float x, float y)
    {
        return new Point(
            (int)(x),
            (int)(y)
            );
    }
    //
    enum MouseJobType
    {
        Idle,
        Drag,
    }
    enum MouseDragSelectType
    {
        None,
        Additive,
        Toggle,
    }
    class MouseDragJobTypeParam : MouseJobTypeParam
    {
        public MouseDragSelectType SelectType;
        public bool IsDrag;
        public int OriginX;
        public int OriginY;
        public PointF OriginWorldLocation;
        public PointF PrevWorldLocation;
        public PointF OffsetWorldLocation;
        public Rectangle Bound
        {
            get
            {
                var ox = (OriginWorldLocation.X < OffsetWorldLocation.X)
                    ? OriginWorldLocation.X
                    : OffsetWorldLocation.X;

                var oy = (OriginWorldLocation.Y < OffsetWorldLocation.Y)
                    ? OriginWorldLocation.Y
                    : OffsetWorldLocation.Y;
                var w = Math.Abs(OriginWorldLocation.X - OffsetWorldLocation.X);
                var h = Math.Abs(OriginWorldLocation.Y - OffsetWorldLocation.Y);

                return new Rectangle((int)ox, (int)oy, (int)w, (int)h);
            }
        }
        public override void Paint(TranspCtrl sender, Graphics g)
        {
            if (!IsDrag)
                return;
            switch (SelectType)
            {
                case MouseDragSelectType.Additive:
                    {
                        var bound = Bound;
                        g.FillRectangle(new SolidBrush(Color.FromArgb(32, Color.Aqua)), bound);
                        g.DrawRectangle(new Pen(Color.FromArgb(192, Color.Aqua)), bound);
                    }
                    break;
                case MouseDragSelectType.Toggle:
                    {
                        var bound = Bound;
                        g.FillRectangle(new SolidBrush(Color.FromArgb(32, Color.Maroon)), bound);
                        g.DrawRectangle(new Pen(Color.FromArgb(192, Color.Maroon)), bound);
                    }
                    break;
                default:
                    var dx = (int)(OffsetWorldLocation.X - OriginWorldLocation.X);
                    var dy = (int)(OffsetWorldLocation.Y - OriginWorldLocation.Y);
                    foreach (var item in sender.Items.Items)
                    {
                        if (!item.Selected)
                            continue;
                        if (item.Buffer == null)
                            item.Render();
                        var itembound = item.Bound;
                        itembound.Offset(dx, dy);
                        g.FillRectangle(new SolidBrush(Color.FromArgb(32, Color.Aqua)), itembound);
                        g.DrawRectangle(new Pen(Color.FromArgb(192, Color.Aqua)), itembound);
                    }
                    break;
            }
        }
    }
    abstract class MouseJobTypeParam { public abstract void Paint(TranspCtrl sender,Graphics g); }
    MouseJobType MouseJob = MouseJobType.Idle;
    MouseJobTypeParam MouseJobParam = null;
    void MouseJobClear()
    {
        MouseJobParam = null;
        MouseJob = MouseJobType.Idle;
    }
    protected override void OnMouseMove(MouseEventArgs e)
    {
        BeginUpdate();
        switch (MouseJob)
        {
            case MouseJobType.Idle:
                var hitItem = MouseHitTest(e.X, e.Y);
                if (hitItem != null)
                    Cursor = Cursors.Hand;
                else
                    Cursor = Cursors.Default;
                break;
            case MouseJobType.Drag:
                var dragJob = MouseJobParam as MouseDragJobTypeParam;
                dragJob.OffsetWorldLocation = GetWorldPos(e.Location);
                if (GetDistance(dragJob.OffsetWorldLocation.X, dragJob.OffsetWorldLocation.Y, dragJob.OriginWorldLocation.X, dragJob.OriginWorldLocation.Y) < 3)
                    break;
                dragJob.IsDrag = true;
                /*
                var dx = dragJob.OffsetWorldLocation.X - dragJob.PrevWorldLocation.X;
                var dy = dragJob.OffsetWorldLocation.Y - dragJob.PrevWorldLocation.Y;
                dragJob.PrevWorldLocation = dragJob.OffsetWorldLocation;
                foreach(var item in Items.Items)
                {
                    if (!item.Selected)
                        continue;
                    item.MoveToward((int)dx, (int)dy);
                }
                */
                break;
        }
        EndUpdate();
        base.OnMouseMove(e);
    }
    protected override void OnMouseDown(MouseEventArgs e)
    {
        BeginUpdate();
        switch (MouseJob)
        {
            case MouseJobType.Idle:
                switch (e.Button)
                {
                    case MouseButtons.Left:
                        {
                            if (e.Clicks == 1)
                            {
                                var hitItem = MouseHitTest(e.X, e.Y);
                                MouseJob = MouseJobType.Drag;
                                MouseJobParam = new MouseDragJobTypeParam()
                                {
                                    SelectType = (hitItem == null)
                                    ? (ModifierKeys == Keys.Shift)
                                        ? MouseDragSelectType.Additive
                                        : (ModifierKeys == Keys.Control)
                                            ? MouseDragSelectType.Toggle
                                            : MouseDragSelectType.Additive
                                    : (ModifierKeys == Keys.Control)
                                        ? MouseDragSelectType.Toggle
                                        : MouseDragSelectType.None,
                                    IsDrag = false,
                                    OriginX = e.X,
                                    OriginY = e.Y,
                                    OriginWorldLocation = GetWorldPos(e.Location),
                                    OffsetWorldLocation = GetWorldPos(e.Location),
                                    PrevWorldLocation = GetWorldPos(e.Location),
                                };
                                if (hitItem != null)
                                {
                                    if (!hitItem.Selected)
                                    {
                                        if ((ModifierKeys != Keys.Control) && (ModifierKeys != Keys.Shift))
                                        {
                                            DeselectAll();
                                        }
                                    }
                                    Cursor = Cursors.Hand;
                                    hitItem.Select();
                                }
                                else
                                {
                                    if ((ModifierKeys != Keys.Control) && (ModifierKeys != Keys.Shift))
                                    {
                                        DeselectAll();
                                    }
                                }
                            }
                            else
                            {
                                Console.WriteLine("Double Click!");
                            }
                        }
                        break;
                    case MouseButtons.Right:
                        {
                            var hitItem = MouseHitTest(e.X, e.Y);
                            if (hitItem == null)
                            {
                                DeselectAll();
                            }
                            else
                                Items.Remove(hitItem);
                        }
                        break;
                }
                break;
        }
        EndUpdate();
        base.OnMouseDown(e);
    }
    protected override void OnMouseUp(MouseEventArgs e)
    {
        BeginUpdate();
        switch (MouseJob)
        {
            case MouseJobType.Drag:
                var dragJob = MouseJobParam as MouseDragJobTypeParam;
                dragJob.OffsetWorldLocation = GetWorldPos(e.Location);
                if (dragJob.IsDrag)
                {
                    switch (dragJob.SelectType)
                    {
                        case MouseDragSelectType.Additive:
                            {
                                var dragSelectBound = dragJob.Bound;
                                foreach (var item in Items.Items)
                                {
                                    if (item.Selected)
                                        continue;
                                    if (!dragSelectBound.IntersectsWith(item.Bound))
                                        continue;
                                    item.Select();
                                }
                            }
                            break;
                        case MouseDragSelectType.Toggle:
                            {
                                var dragSelectBound = dragJob.Bound;
                                foreach (var item in Items.Items)
                                {
                                    if (!dragSelectBound.IntersectsWith(item.Bound))
                                        continue;
                                    if (item.Selected)
                                        item.Deselect();
                                    else
                                        item.Select();
                                }
                            }
                            break;
                        default:
                            var dx = dragJob.OffsetWorldLocation.X - dragJob.OriginWorldLocation.X;
                            var dy = dragJob.OffsetWorldLocation.Y - dragJob.OriginWorldLocation.Y;
                            foreach (var item in Items.Items)
                            {
                                if (!item.Selected)
                                    continue;
                                item.MoveToward((int)dx, (int)dy);
                            }
                            break;
                    }
                }
                Cursor = Cursors.Default;
                MouseJobClear();
                break;
        }
        EndUpdate();
        base.OnMouseUp(e);
    }
    //
    protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
    {
        base.OnPreviewKeyDown(e);
        switch (e.KeyCode)
        {
            case Keys.Up:
                ViewToward(0, -100);
                break;
            case Keys.Down:
                ViewToward(0, 100);
                break;
            case Keys.Left:
                ViewToward(-100, 0);
                break;
            case Keys.Right:
                ViewToward(100, 0);
                break;
        }
    }
    //
    public class ItemCollection
    {
        internal List<Item> Items;
        public TranspCtrl Parent { get; private set; }
        internal ItemCollection(TranspCtrl parent)
        {
            Parent = parent;
            Items = new List<Item>();
        }
        public Item Add(string text, int x, int y)
        {
            var item = new Item()
            {
                Parent = Parent,
                Text = text,
            };
            item.MoveTo(x, y);
            return Add(item);
        }
        public Item Add(Item item)
        {
            item.Parent = Parent;
            lock (Items)
            {
                Items.Add(item);
            }
            Parent.TryUpdate();
            return item;
        }
        public void Remove(Item item)
        {
            item.Parent = null;
            Items.Remove(item);
            Parent.TryUpdate();
        }
    }
    public class Item
    {
        public TranspCtrl Parent { get; internal set; }
        public Bitmap Buffer = null;
        public Rectangle Bound { get; private set; } = new Rectangle(0, 0, 100, 100);
        //
        public void MoveToward(int x, int y) => MoveTo(Bound.X + x, Bound.Y + y);
        public void MoveTo(int x, int y)
        {
            Bound = new Rectangle(x, y, Bound.Width, Bound.Height);
            Parent?.TryUpdate();
        }
        //
        public bool CastShadow { get; set; } = true;
        public int X { get => Bound.X; }
        public int Y { get => Bound.Y; }
        public int Width { get => Bound.Width; }
        public int Height { get => Bound.Width; }
        public string Text;
        SolidBrush BackBrush = new SolidBrush(Color.FromArgb(63, 63, 70));
        public Color BackColor { get => BackBrush.Color; set => BackBrush = new SolidBrush(value); }
        //
        public bool Selected { get; private set; }
        public void Deselect()
        {
            Selected = false;
            Parent.TryUpdate();
        }
        public void Select()
        {
            Selected = true;
            Parent.TryUpdate();
        }
        //
        public void Render()
        {
            var buf = new Bitmap(Width, Height);

            using (var g = Graphics.FromImage(buf))
            {
                g.Clear(Color.Transparent);

                g.DrawRectangle(Pens.Black, 0, 0, Width - 1, Height - 1);
                g.FillRectangle(BackBrush, 0, 0, Width, Height);

                g.DrawString(Text, Parent.Font, Brushes.White, 4, 4);
            }

            if (Buffer != null)
                Buffer.Dispose();
            Buffer = buf;
        }
    }
}
⚠️ **GitHub.com Fallback** ⚠️