Subscribe to andrewsw's Blog        RSS Feed
***** 1 Votes

MVC and C# WinForms Mismatch

Icon 5 Comments
I found this brief MVC example for Java which used four direction buttons (North, South, East, West) and a speed slider, and provided a message to indicate whether the direction had changed and whether any speed was specified. I decided well, it should be possible to do something similar in C# WinForms, even though I was well aware that WinForms isn't considered a suitable vehicle for this pattern. (I'm obstinate.)

I also thought it would be nice to actually draw lines, like that old "game boy" Etch-A-Sketch.

Model-View-Controller :wikipedia

Pre-Warning: This isn't an MVC tutorial, so don't expect to conquer this subject here!
(I do have a tutorial for MVP though, which is a more suitable pattern.)

Posted Image

The drawing happens in a Panel (pnlDraw):

Posted Image

I anchored the Controls so that the form can be resized. (I didn't include any code to prevent drawing spiling outside of the Panel.)

The Model (MoveModel.cs) is simple enough:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace MoveMVC {

    public enum DIRECTION { UP, DOWN, LEFT, RIGHT };

    public class MoveModel : INotifyPropertyChanged {
        // INotifyPropertyChanged Interface
        // https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx

        public event PropertyChangedEventHandler PropertyChanged;

        private DIRECTION _direction;
        private int _pixels;

        public MoveModel() {
            this._direction = DIRECTION.UP;
            this._pixels = 10;
        }

        public DIRECTION Direction {
            get {
                return this._direction;
            }
            set {
                this._direction = value;
                NotifyPropertyChanged();
            }
        }

        public int Pixels {
            get {
                return this._pixels;
            }
            set {
                this._pixels = value;
                NotifyPropertyChanged();
            }
        }

        // This method is called by the Set accessor of each property.
        // The CallerMemberName attribute that is applied to the optional propertyName
        // parameter causes the property name of the caller to be substituted as an argument.
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") {
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

It is just storing the state (Direction and Pixels) and notifying of changes to this state.

What about the View? It is possible to create a separate View class but, as the View should be fairly passive, the Form class itself can provide/be the View. This is different to Java where we write code to construct the GUI. (We could, of course, do the same in C#, which I know is many people's preference.) In this case, the construction of the GUI and its consideration as "the View" doesn't really cause us any pause for thought. With C#, and the Form Designer, it seems unnecessary, overkill, to go to the lengths of creating a distinct View class. That is, in my opinion.

Anyway, the View (our Form) stores a reference to the Model, listens for changes in the Model, and updates the UI, by providing information in a Label's Text and drawing lines.

This is where MVC starts to unravel a little. Shouldn't the Controller tell the View to draw lines? Should the Controller call a method of the View to draw lines? The inbuilt ability of a WinForm to draw on itself makes it a little awkward for the Controller to commandeer this feature.

Now the Controller is supposed to respond to UI events, to Button clicks (in the Form/View). How do we transfer this event-processing to the Controller? Using INotifyPropertyChanged (again)? With Custom Events? It just doesn't seem "right" that, for example, click-events in the Form class would raise an event that the Controller would listen for, and the Controller would then call a method of the Form to draw on itself.

[I suppose the Buttons and NumericUpDown themselves could be exposed as public properties of the Form so that the Controller can take total charge or responding to Click/Change events. This is a step too far, though, an anti-pattern, as the Controller would then be too tightly integrated with the specific UI/WinForm.]

Anyway, I'm over-thinking this. Besides, it is just a mini-project which I knew was likely to end in tears (sad face).

Let's continue. I decided to use Custom Events, Directionchanged and PixelsChanged. When a Button is clicked, or the pixel-value is increased (using a NumericUpDown Control) it raises either of these events. The Controller listens for these events and updates the Model. The Form/View is listening for these changes to the Model, and responds by providing feedback in the Label and re-drawing the lines.

Her is the View/Form (frmMove.cs):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MoveMVC {

    public partial class frmMove : Form {
        // the view needs a reference to the model
        private MoveModel _model;
        // for feedback
        private DIRECTION _direction;
        private int _pixels;
        private Point _origPoint, _prevPoint;
        Boolean notMoved = true;
        private List<Point> _pts = new List<Point>();

        // custom events
        // http://www.dreamincode.net/forums/topic/176796-quick-and-easy-custom-events/
        public event EventHandler<DirectionArgs> Directionchanged;
        public event EventHandler<PixelArgs> PixelsChanged;

        private void RaiseDirectionchanged(DIRECTION direction) {
            EventHandler<DirectionArgs> handler = Directionchanged;
            if (handler != null) {
                handler(null, new DirectionArgs(direction));
            }
        }
        private void RaisePixelsChanged(int pixels) {
            EventHandler<PixelArgs> handler = PixelsChanged;
            if (handler != null) {
                handler(null, new PixelArgs(pixels));
            }
        }

        public frmMove(MoveModel model) {
            InitializeComponent();
            this._model = model;
            this._model.PropertyChanged += _model_PropertyChanged;
            this._origPoint = new Point(this.pnlDraw.Width / 2, this.pnlDraw.Height / 2);
        }

        private void frmMove_Load(object sender, EventArgs e) {
            // determine initial state
            this._direction = _model.Direction;
            this._pixels = _model.Pixels;
            this.nudPixels.Value = this._pixels;
            lblFeedback.Text = "Direction: " + this._direction + "\nPixels:" + this._pixels;
        }

        private void btnDirection_Click(object sender, EventArgs e) {
            Button btn = (Button)sender;
            RaiseDirectionchanged((DIRECTION)Enum.Parse(typeof(DIRECTION), btn.Text));
        }
        private void nudPixels_ValueChanged(object sender, EventArgs e) {
            NumericUpDown nud = (NumericUpDown)sender;
            RaisePixelsChanged((int)nud.Value);
        }

        private void _model_PropertyChanged(object sender, PropertyChangedEventArgs e) {
            // update values from model
            this._direction = _model.Direction;
            this._pixels = _model.Pixels;
            lblFeedback.Text = "Direction: " + this._direction + "\nPixels:" + this._pixels;
            if (e.PropertyName == "Direction") {
                this.pnlDraw.Invalidate();
            }
        }

        private void pnlDraw_Paint(object sender, PaintEventArgs e) {
            int newX = 0, newY = 0;
            if (notMoved) {
                newX = this._origPoint.X;
                newY = this._origPoint.Y;
                notMoved = false;
            } else {
                newX = this._prevPoint.X;
                newY = this._prevPoint.Y;
            }

            switch (this._direction) {
                case DIRECTION.UP:
                    newY -= this._pixels;
                    break;
                case DIRECTION.DOWN:
                    newY += this._pixels;
                    break;
                case DIRECTION.LEFT:
                    newX -= this._pixels;
                    break;
                case DIRECTION.RIGHT:
                    newX += this._pixels;
                    break;
                default:
                    break;
            }
            this._prevPoint = new Point(newX, newY);
            this._pts.Add(this._prevPoint);

            using (Pen pen = new Pen(Color.Black, 3)) {
                Point tempPt = this._origPoint;
                foreach (Point pt in this._pts) {
                    e.Graphics.DrawLine(pen, tempPt, pt);
                    tempPt = pt;
                }
            }
        }
    }

    public class DirectionArgs : EventArgs {
        #region Fields
        private DIRECTION _direction;
        #endregion Fields

        #region Constructors
        public DirectionArgs(DIRECTION direction) {
            _direction = direction;
        }
        #endregion Constructors

        #region Properties
        public DIRECTION Direction {
            get { return _direction; }
            set { _direction = value; }
        }
        #endregion Properties
    }

    public class PixelArgs : EventArgs {
        #region Fields
        private int _pixels;
        #endregion Fields

        #region Constructors
        public PixelArgs(int pixels) {
            _pixels = pixels;
        }
        #endregion Constructors

        #region Properties
        public int Pixels {
            get { return _pixels; }
            set { _pixels = value; }
        }
        #endregion Properties
    }
}


The Controller itself (MoveController.cs) is straightforward, listening for activity in the form (responding to UI events) and updating the Model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MoveMVC {
    class MoveController {
        // the controller needs references for both the model and view
        private MoveModel _model;
        private frmMove _view;

        public MoveController(MoveModel model, frmMove view) {
            this._model = model;
            this._view = view;

            // the controller needs to listen to the view
            view.Directionchanged += view_Directionchanged;
            view.PixelsChanged += view_PixelsChanged;
        }

        private void view_Directionchanged(object sender, DirectionArgs e) {
            _model.Direction = e.Direction;
        }

        private void view_PixelsChanged(object sender, PixelArgs e) {
            _model.Pixels = e.Pixels;
        }
    }
}

However, at the end of the day I've skipped a bit. From Wikipedia:

Quote

A controller can send commands to the model to update the model's state (e.g. editing a document). It can also send commands to its associated view to change the view's presentation of the model (e.g. by scrolling through a document).

I am not sending any commands to the View, the View updates itself by responding to the Model. Earlier though, it states "The third part, the controller, accepts input and converts it to commands for the model or view". The "or" is significant and means that I am still following, essentially, this pattern:

Posted Image



This hints at why MVC is typically more suited as a Web framework. In a website the View, the web page, is stateless. The Controller receives responses and will then send commands back to the View (or with the View).



Here is Program.cs that ties it all together:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MoveMVC {
    static class Program {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            MoveModel model = new MoveModel();
            frmMove view = new frmMove(model);
            MoveController controller = new MoveController(model, view);
            Application.Run(view);
        }
    }
}

Was this a useful exercise? Have I learnt much about MVC? Maybe. I suppose I've managed to confirm that WinForms and MVC are not a good match, and I've had a little more practice with Custom Events. The application itself is reasonably interesting.

5 Comments On This Entry

Page 1 of 1

Recoil Icon

11 June 2016 - 08:20 AM
I found it mildly humorous that you would use an Etch-A-Sketch-style approach, and still not implement a way to undo anything...

Throw on a button and name it "Flip-n-Shake" and have it reset the drawing ;)
0

andrewsw Icon

11 June 2016 - 08:28 AM
It would only need to clear the list (and repaint) but I leave it as an exercise for you ;)
1

andrewsw Icon

12 June 2016 - 10:21 AM
A good article:

Interactive Application Architecture Patterns

detailing the history and evolution of MVC and MVP to help understand their differences.

Quote

MVC Misconceptions

One common misconception about the relationship between the MVC components is that the purpose of the Controller is to separate the View from the Model. While the MVC pattern does decouple the applicationís domain layer from presentation concerns, this is achieved through the Observer Pattern, not through the Controller. The Controller was conceived as a mediator between the end user and the application, not between the View and the Model.
0

CasiOo Icon

15 June 2016 - 10:18 AM
I prefer having thick models and thin views and controllers, which is why I don't like the view's paint method doing so much work
Ideally it should be simple
private void pnlDraw_Paint(object sender, PaintEventArgs e) {
    using (Pen pen = new Pen(model.Color, 3)) {
        foreach (LineSegment lineSegment in model.LineSegments) {
            e.Graphics.DrawLine(pen, lineSegment.P1, lineSegment.P2);
        }
    }
}

1

andrewsw Icon

15 June 2016 - 12:47 PM
Yes, I was encouraged by the earlier example I mentioned which didn't draw anything. I added the drawing as an additional presentational feature, I didn't consider adding the lines to the model. That makes sense ;) and provides a less supine model.
0
Page 1 of 1

Trackbacks for this entry [ Trackback URL ]

There are no Trackbacks for this entry

October 2017

S M T W T F S
1234567
891011121314
15161718192021
22 232425262728
293031    

Tags

    Recent Entries

    Recent Comments

    Search My Blog

    2 user(s) viewing

    2 Guests
    0 member(s)
    0 anonymous member(s)

    Categories