The MVP pattern
The Model-View-Presenter (MVP) pattern is designed to split the UI (View) from the business logic (Model) via a class that sends messages between the two (Presenter). In the implementation I use (Passive View) the view has little or, if possible, no logic. When an event occurs (e.g. a button press), the view raises an event which is handled by the presenter. The presenter then invokes a void method on the model. If required, the model will raise an event to signal that the work is completed. The presenter handles that event too, and calls a method on the view to cause a UI update to occur.
The Classes
The solution consists of three projects: the Gadgeteer project, the class library and the test project.
The class library defines the classes and interfaces required to implement the MVP pattern - the model, the presenter and the view interface.
The IView interface declares an event ButtonPressed, to be handled by the presenter, and a method ShowColour that will be called by the presenter:
using Microsoft.SPOT; namespace ButtonAndLight.Lib { public delegate void ButtonPressedEventHandler(object sender, EventArgs args); public interface IView { // The event that the UI will raise when the button is pressed event ButtonPressedEventHandler ButtonPressed; //The method the presenter will call when the colour needs to change void ShowColour(byte r, byte g, byte b); } }The Model class declares an event ColourChanged, to be handled by the presenter, and a method CalculateNewColour, to be called by the presenter:
using Microsoft.SPOT; namespace ButtonAndLight.Lib { public class Model { public Model() : this(new RandomGenerator()) { } public Model(IRandomGenerator randomGenerator) { _randomGenerator = randomGenerator; } private IRandomGenerator _randomGenerator; // Old school event handler for the Micro Framework public delegate void ColourChangedEventHandler(object sender, ColourChangedEventArgs args); // The event that will be raised when the colour has changed public event ColourChangedEventHandler ColourChanged; // This will be called by the presenter public void CalculateNewColour() { var r = _randomGenerator.GetNextColourPart(); var g = _randomGenerator.GetNextColourPart(); var b = _randomGenerator.GetNextColourPart(); OnColourChanged(r, g, b); } private void OnColourChanged(byte r, byte g, byte b) { if (ColourChanged != null) { ColourChanged(this, new ColourChangedEventArgs(r, g, b)); } } } public class ColourChangedEventArgs : EventArgs { public byte R { get; private set; } public byte G { get; private set; } public byte B { get; private set; } public ColourChangedEventArgs(byte r, byte g, byte b) { R = r; G = g; B = b; } } }The Presenter class stitches the model and view together, by calling the relevant method when an event occurs:
using Microsoft.SPOT; namespace ButtonAndLight.Lib { public class Presenter { IView _view; Model _model; public Presenter(IView view, Model model) { _view = view; _model = model; _view.ButtonPressed += new ButtonPressedEventHandler(view_ButtonPressed); _model.ColourChanged += new Model.ColourChangedEventHandler(model_ColourChanged); } void model_ColourChanged(object sender, ColourChangedEventArgs args) { _view.ShowColour(args.R, args.G, args.B); } void view_ButtonPressed(object sender, EventArgs args) { _model.CalculateNewColour(); } } }These are the Gadgeteer components:
using ButtonAndLight.Lib; using Microsoft.SPOT.Presentation.Media; using Microsoft.SPOT; using Gadgeteer.Modules.GHIElectronics; namespace ButtonAndLight { public partial class Program : IView { private Presenter _presenter; void ProgramStarted() { _presenter = new Presenter(this, new Model()); // Wire up handler for the the physical button press button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed); } void button_ButtonPressed(Button sender, Button.ButtonState state) { if (ButtonPressed != null) { ButtonPressed(this, EventArgs.Empty); } } public event ButtonPressedEventHandler ButtonPressed; public void ShowColour(byte r, byte g, byte b) { var colour = ColorUtility.ColorFromRGB(r, g, b); multicolorLed.TurnColor(colour); } } }All that's left now is the test class. As this is the Micro Framework, I've had to do by hand what I'd normally have done with Rhino and NUnit
using System; using Microsoft.SPOT; namespace ButtonAndLight.Lib.Test { public class Program { public static void Main() { ButtonPressed_ExpectRandomColourGenerated(); } private static void ButtonPressed_ExpectRandomColourGenerated() { // Create objects var view = new FakeView(); var model = new Model(new FakeRandomGenerator()); var presenter = new Presenter(view, model); // Raise event view.RaiseButtonPressEvent(); // Check result if (view.R == 10 && view.G == 20 && view.B == 30) { Debug.Print("Success"); return; } throw new InvalidOperationException("Failure"); } } public class FakeView : IView { // Sensing variables public byte R { get; private set; } public byte G { get; private set; } public byte B { get; private set; } // Pretend a button's been pushed public void RaiseButtonPressEvent() { if (ButtonPressed != null) { ButtonPressed(this, EventArgs.Empty); } } #region IView interface public event ButtonPressedEventHandler ButtonPressed; public void ShowColour(byte r, byte g, byte b) { R = r; G = g; B = b; } #endregion } public class FakeRandomGenerator : IRandomGenerator { byte[] _values = new byte[] { 10, 20, 30 }; int _valuePointer = 0; public byte GetNextColourPart() { return _values[_valuePointer++]; } } }Here's a video of the hardware in action:
No comments:
Post a Comment