Sunday, April 22, 2007

Using Model View Presenter with VB6 ActiveX And .NET

I started working with a new team a couple weeks ago and so far things are going very well. The next phase of this project is to write some enhancements for an ActiveX Image Viewer component. Yes, you read that correctly...ActiveX in VB6. It's not polite to giggle.
I don't feel comfortable making changes to code that doesn't have test coverage, so I had to come up with a method to test VB6 ActiveX.
I figured that the ActiveX control was really just a view and I should be able to:
  1. Create the view Interface in .NET
  2. Create a Presenter class in .NET that works against the .NET view interface
  3. Expose the .NET view interface and presenter to COM and implement the view interface on the ActiveX control
Sounds simple.

Create the View Interface in .NET

The first  step was to create a simple view interface for this example. I wanted to have a simple text property and buttons for navigating the documents and the pages within the documents.
    [ComVisible(true)]
    [Guid("CC0FE3CD-BE47-46c4-81E9-08499EDF0633")]
    public interface IView
    {
        string Name { get; set;}

        ICommandButton PreviousPageButton { get; }
        ICommandButton NextPageButton { get; }
      
        ICommandButton PreviousDocumentButton { get; }
        ICommandButton NextDocumentButton { get; }
    }

I created another interface for the Command Button object. I did this so I wouldn't have to munge the view interface by including a "Visible", "Enabled" and  "Caption" property for each button on the view. The ICommandButton interface looks like this:
    [ComVisible(true)]
    [Guid("B5482029-1671-4cc1-8BB7-979E933CF81B")]
    public interface ICommandButton
    {
        string Caption { get; set;}
        bool Enabled { get; set;}
        bool Visible { get; set;}
    }
You'll notice the Command button properties are Read Only. The view will return the CommandButton to the Presenter so the Presenter can get/set the properties on the ICommandButton interface. The presenter should not create a Button Control and assign it to the view. That is the responsibility of the view.

Create the Presenter class

Next I create a simple presenter class. In order to work with VB 6 I had to do a couple things. First I had to have a default constructor. COM Interop needs a default constructor. Normally with the MVP pattern, you would create a constructor that takes in the IView interface, however VB6 doesn't support parameters in the constructor. Since constructor injection was a solution, I created SetView() method that takes in the view interface.
    [ComVisible(true)]
    [Guid("867E8A4E-6D08-4c06-9B42-F9C8C12CEBDF")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class Presenter
    {
        private IView view;

        /// <summary>
        /// Default constructor for needed for COM Interop
        /// </summary>
        public Presenter(){}

        /// <summary>
        /// Use Setter Injection since VB6
        /// doesn't allow parameterized constructors.
        /// </summary>
        /// <param name="view"></param>
        public void SetView(IView view)
        {
            this.view = view;
        }

        public void Initialize()
        {
            // Disable all the previous buttons
            view.PreviousDocumentButton.Enabled = false;
            view.PreviousPageButton.Enabled = false;
        }

        public void NextPage()
        {
            view.Name = "Next Page";
        }

        public void PreviousPage()
        {
            view.Name = "Previous Page";
        }

        public void NextDocument()
        {
            view.Name = "NextDocument";
        }

        public void PreviousDocument()
        {
            view.Name = "PreviousDocument";
        }

        private bool isEnabled = true;

        public void ToggleAllButtons()
        {
            view.NextDocumentButton.Enabled = isEnabled;
            view.NextPageButton.Enabled = isEnabled;
            view.PreviousDocumentButton.Enabled = isEnabled;
            view.PreviousPageButton.Enabled = isEnabled;

            //Toggle
            isEnabled = !isEnabled;
        }
    }

Implement the View in VB6

Go dust off your VB 6 we're going in!
I had to implement the view and a class that implemented the ICommandButton interface. The view is easy. I have a class that I can implement the IView  interface on, but how do I implement and interface on the class that already exists? Wrap it I guess. I created a CommandButtonWrapper Class that implemented the interface from the .NET Assembly. Once again I had to create a method to inject the CommandButton control.

In order to wire all the buttons and controls to the presenter I had to write this code on the Init of the ActiveX control.




The rest of the view is very basic and simple. All button click events are delegated to the presenter and the view properties return or set values on the controls of the view.
All in all this solution looks like it is going to work out. I'm concerned about a couple things, but hopeful that I can start test driving this ActiveX control. Wish me luck!

Outstanding Issues

State Management

Where should I be managing state? Typically I keep my presenters stateless, but in this instance I'm thinking that holding it in the Presenter isn't such a bad idea. I would love your thoughts on this.