Navigation:  Application Architecture > Putting it All Together > Designing an Application Component >

Implementing a view extension

Previous pageReturn to chapter overviewNext page

With the view extension point in place, we can now proceed to implement a view.   Typically, the view will reside in a separate plugin assembly, named according to the windowing toolkit in use.

Let’s assume we are going to create a Windows Forms view.  If our component resides in an assembly called TextEditor.dll, then the view would typically be placed in TextEditor.View.WinForms.dll.

We’ll name our view class TextEditorComponentView.  Again, this is the preferred naming convention. XComponent implies a view class named XComponentView.

[ExtensionOf(typeof(TextEditorComponentViewExtensionPoint))]

public class TextEditorComponentView : WinFormsView,  

                                       IApplicationComponentView

{

    public TextEditorComponentView()

    {

    }

}

 

 

Note that TextEditorComponentView implements the IApplicationComponentView interface, and extends the WinFormsView class.  The former is necessary to act as an application component view.  The latter simply provides some base functionality that any Windows Forms based view should have.

Now in order to implement this class, we need to provide the following method implementations:

public class TextEditorComponentView : WinFormsView, 

                                       IApplicationComponentView

{

    public void SetComponent(IApplicationComponent component)

    {

    }

 

    public override object GuiElement 

    {

        get {}

    }

}

 

 

The SetComponent method is called by the framework when it first creates the view, in order to associate it with an instance of the component.  The GuiElement property is invoked by the framework to obtain a reference to a GUI object that provides the user-interface for the view.  In the case of a WinForms view, this will be an object of type System.Windows.Forms.Control (typically a UserControl).

Let’s create a UserControl named TextEditorControl.  The view class implementation can then be filled in as follows:

public class TextEditorComponentView : WinFormsView, 

                                       IApplicationComponentView

{

    private TextEditorComponent _component;

    private TextEditorControl _control;

 

    public void SetComponent(IApplicationComponent component)

    {

        _component = (TextEditorComponent)component;

    }

 

    public override object GuiElement

    {

        get

        {

            if (_control == null)

            {

                _control = new TextEditorControl(_component);

            }

            return _control;

        }

    }

}

 

 

Note that the view class is really just glue: the real work of providing the UI will be done by the TextEditorControl.  In the SetComponent method, the view simply stores a reference to the component (which we can cast to TextEditorComponent, since we know the component will be of this type).  In the GuiElement property, the view creates an instance of the TextEditorControl and passes it a reference to the component.

That is all that is required for the implementation of the view class itself, but we still need to create TextEditorControl.  Typically, this will be done in the Visual Studio Forms Designer.  However, we will need to add some code manually in order to bind the view to the presentation model.

Let’s assume that we have used the designer to create TextEditorControl with at least three TextBox controls (named _txtText, _txtFilename and _txtWordCount) and two Button controls (named _btnSave and _btnCancel) as follows 1:

namespace MyPlugin.TextEditor

{

   partial class TextEditorControl

   {

      /// <summary> 

      /// Required designer variable.

      /// </summary>

      private System.ComponentModel.IContainer components = null;

 

      /// <summary> 

      /// Clean up any resources being used.

      /// </summary>

      /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>

      protected override void Dispose(bool disposing)

      {

         if (disposing && (components != null))

         {

            components.Dispose();

         }

         base.Dispose(disposing);

      }

 

      #region Component Designer generated code

 

      // Omitted from example for brevity

 

      #endregion

 

      private System.Windows.Forms.TextBox _txtText;
      private System.Windows.Forms.TextBox _txtFilename;
      private System.Windows.Forms.TextBox _txtWordCount;
      private System.Windows.Forms.Button _btnCancel;
      private System.Windows.Forms.Button _btnSave;

   }

}

 

using System.Windows.Forms;

 

namespace MyPlugin.TextEditor

{

   public partial class TextEditorControl : UserControl

   {

      public TextEditorControl()

      {

         InitializeComponent();

      }

   }

}

 

The first thing we need to do is change the constructor to accept an instance of the TextEditorComponent, since that is how we have set things up in the view class:

using System.Windows.Forms;

 

namespace MyPlugin.TextEditor

{

   public partial class TextEditorControl : UserControl

   {

      private TextEditorComponent _component;

 

      public TextEditorControl(TextEditorComponent component)

      {

         InitializeComponent();

 

         _component = component;

      }

   }

}

 

Now we can use .NET data-binding in order to bind the individual controls to properties on the component.  The class now looks like this:

public partial class TextEditorControl : UserControl
{
   private TextEditorComponent _component;
 
   public TextEditorControl(TextEditorComponent component)
   {
      InitializeComponent();
 
      _component = component;
 
      _txtText.DataBindings.Add("Text", _component,
                                "Text", true,
                                DataSourceUpdateMode.OnPropertyChanged);
 
      _txtFilename.DataBindings.Add("Text", _component,
                                    "Filename", true,
                                    DataSourceUpdateMode.OnPropertyChanged);
 
      _txtWordCount.DataBindings.Add("Text", _component,
                                     "WordCount", true,
                                     DataSourceUpdateMode.OnPropertyChanged);
   }
}

 

The data bindings automatically transfer data between controls on the form and the corresponding presentation model properties on the component.  For an in-depth discussion of .NET data-binding, see the .NET framework documentation.

In addition to data-binding, we need to implement event-handlers for the save and cancel buttons.  This is shown here using anonymous delegates:

_btnSave.Click += delegate(object sender, EventArgs args)

{

     _component.Save();

};

 

_btnCancel.Click += delegate(object sender, EventArgs args)

{

     _component.Cancel();

};

 

Our control is missing a display for the results of the validation that we performed in the component.  To get this working, we can make our control inherit from ApplicationComponentUserControl, which will detect input errors and show an error icon beside the related control.

namespace MyPlugin.TextEditor

{

   public partial class TextEditorControl : ApplicationComponentUserControl

   {

   }

}

 

That takes care of binding the view to the presentation model.  Note how thin the view is: it contains no business logic whatsoever, other than the simple logic of shuffling data back and forth between the component and the controls, and responding appropriately to button clicks.  There's only one last thing we need to fix: call the correct overload of the constructor in ApplicationComponentUserControl such that it knows which component provides the validation logic.

The final constructor looks like this:

public TextEditorControl(TextEditorComponent component) : base(component)
{
   InitializeComponent();
 
   _component = component;
 
   _txtText.DataBindings.Add("Text", _component,
                             "Text", true,
                             DataSourceUpdateMode.OnPropertyChanged);
 
   _txtFilename.DataBindings.Add("Text", _component,
                                 "Filename", true,
                                 DataSourceUpdateMode.OnPropertyChanged);
 
   _txtWordCount.DataBindings.Add("Text", _component,
                                  "WordCount", true,
                                  DataSourceUpdateMode.OnPropertyChanged);
 
   _btnSave.Click += delegate(object sender, EventArgs args) { _component.Save(); };
 
   _btnCancel.Click += delegate(object sender, EventArgs args) { _component.Cancel(); };
}

 


1  For brevity, we've excluded most of the designer-generated code.  This example will still work as long as you've included the minimum controls with the same names.