mardi 26 mai 2015

Unlimited undo / redo custom Control

I'm trying to add unlimited undo/redo functionality to my application. I'm making use of the Command Pattern. When I add an Ellipsein Form1, it's added to commandList. But when the user resizes/moves a shape, I also want to add that command to commandList, how can I do this because adding happens in Form1 and resizing/moving in Ellipse. How to fill in the Execute and UnExecute method of Ellipse? The 'walking' trough the list with the Redo/Undo button already works, you can see it in the picture. In panel_MouseUp you see a Box, this is also a custom Control almost identical to Ellipse, you can comment the case out to get a runnable example.

ICommand.cs

public interface ICommand
{
    void Execute();
    void UnExecute();
}

Ellipse.cs

class Ellipse : Control, ICommand
{
    private Point mDown { get; set; }

    public Ellipse()
    {
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        this.BackColor = Color.Transparent;
        this.DoubleBuffered = true;
        this.ResizeRedraw = true;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        // Draw a black ellipse in the rectangle represented by the control.
        e.Graphics.FillEllipse(Brushes.Black, 0, 0, Width, Height);

    }  

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        mDown = e.Location;
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        // Call MyBase.OnMouseMove to activate the delegate. 
        base.OnMouseMove(e);


        if (e.Button == MouseButtons.Left)
        {
            Location = new Point(e.X + Left - mDown.X, e.Y + Top - mDown.Y);          
        }
    }

    /* Allow resizing at the bottom right corner */
    protected override void WndProc(ref Message m)
    {
        const int wmNcHitTest = 0x84;
        const int htBottomLeft = 16;
        const int htBottomRight = 17;
        if (m.Msg == wmNcHitTest)
        {
            int x = (int)(m.LParam.ToInt64() & 0xFFFF);
            int y = (int)((m.LParam.ToInt64() & 0xFFFF0000) >> 16);
            Point pt = PointToClient(new Point(x, y));
            Size clientSize = ClientSize;
            if (pt.X >= clientSize.Width - 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(IsMirrored ? htBottomLeft : htBottomRight);
                return;
            }
        }
        base.WndProc(ref m);
    }

    public void Execute()
    {
        Console.WriteLine("Execute command");
    }

    public void UnExecute()
    {
        Console.WriteLine("Unexecute command");
    }
}

Form1.cs

public partial class Form1 : Form
{
    private bool draw;
    private int x, y, xe, ye;

    private List<ICommand> commandList = new List<ICommand>();
    int current = 0;


    public Form1()
    {
        InitializeComponent();

        menuComboBoxShape.ComboBox.DataSource = Enum.GetValues(typeof(Item));
    }

    public enum Item
    {
        Pencil,
        Rectangle,
        Ellipse,
    }


    private void panel_MouseDown(object sender, MouseEventArgs e)
    {
        draw = true;
        x = e.X;
        y = e.Y;
    }

    private void panel_MouseUp(object sender, MouseEventArgs e)
    {
        draw = false;
        xe = e.X;
        ye = e.Y;

        Item item;
        Enum.TryParse<Item>(menuComboBoxShape.ComboBox.SelectedValue.ToString(), out item);

        switch (item)
        {

            case Item.Pencil:
                using (Graphics g = panel.CreateGraphics())
                using (var pen = new Pen(System.Drawing.Color.Black))     //Create the pen used to draw the line (using statement makes sure the pen is disposed)
                {
                    g.DrawLine(pen, new Point(x, y), new Point(xe, ye));
                }
                break;
            case Item.Rectangle:
                var box = new Box();
                panel.Controls.Add(box);
                box.Location = new Point(x, y);
                box.Width = (xe - x);
                box.Height = (ye - y);
                break;
            case Item.Ellipse:
                var el = new Ellipse();
                panel.Controls.Add(el);
                el.Location = new Point(x, y);
                el.Width = (xe - x);
                el.Height = (ye - y);
                //command execute
                el.Execute();
                commandList.Add(el);
                current++;
                break;
            default:
                break;
        }
    }

    private void undoButton_Click(object sender, EventArgs e)
    {

        if (current > 0)
        {
            ICommand command = commandList[--current];
            //Command unexecute
            command.UnExecute();
        }
        panel.Invalidate();
    }

    private void redoButton_Click(object sender, EventArgs e)
    {
        if (current < commandList.Count)
        {
            ICommand command = commandList[current++];
            //Command execute
            command.Execute();

        }
        panel.Invalidate();
    }

    private void clearAllButton_Click(object sender, EventArgs e)
    {
        commandList.Clear();
        current = 0;
        panel.Controls.Clear();
    }
}

output

Aucun commentaire:

Enregistrer un commentaire