A friend recently requested that I document how I've recently been doing smart client development. I decided to put together a small application to show the basics.
The first step to creating a smart client application is setting up the solution. The UI will need to be separated into a Class Library project and a Windows Applicaiton project. The Windows Application project will usually contain only the entry point to the application. All other UI components will live in the Class Library.
Testability is the reason we separate the UI components into their own library. It's possible to be tricky and combine the projects; however, separating this way is easy and allows us to test the UI components without any additional steps.
The EntryPoint class has the responsibility of creating an instance of the MainPresenter and the MainForm. (I'm also going to pass in a UserData instance and a ViewFactory instance to the MainPresenter's constructor. Perhaps in the future I'll document how I'd likely handle this using
PicoContainer for a real system.)
public class EntryPoint
{
[STAThread]
static void Main()
{
MainForm mainForm = new MainForm();
UserData userData = new UserData();
new MainPresenter(mainForm, userData, createViewFactory());
Application.Run(mainForm);
}
private static ViewFactory createViewFactory()
{
return new ViewFactory(getViews());
}
private static UserControl[] getViews()
{
return new UserControl[] { getReadView(), getUpdateView() };
}
private static UserControl getReadView()
{
return new ReadView();
}
private static UserControl getUpdateView()
{
return new UpdateView();
}
}
The MainForm contains a button for viewing user details, a button for
editing user details, and a panel where the user controls will be added
in response to user actions.
Next we'll add the views and handle navigating between them. For now I'll use view place holders that have a label that says "Read" or "Update" depending on which view is being displayed. The ReadView also navigates to the UpdateView when the label is clicked. This allows us to verify that the navigation is wired and working. The User Controls are normal User Controls that implement the IView interface allowing them to be used for navigation.
public class ReadView : UserControl, IView
{
private Label label1;
private Container components = null;
public event NavigateHandler Navigate;
public ReadView()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
... snipped generated code ...
private void label1_Click(object sender, System.EventArgs e)
{
Navigate(typeof(UpdateView));
}
}
The IView interface only contains the Navigate event used for specifying which User Control to navigate to.
public interface IView
{
event NavigateHandler Navigate;
}
The NavigationHandler specifies a parameter of Type allowing you to specify which view to navigate to.
public delegate void NavigateHandler(Type viewType);
The ViewFactory holds each View in a Hashtable and returns them based on the type requested.
public class ViewFactory
{
public Hashtable viewHash = new Hashtable();
public ViewFactory(UserControl[] views)
{
for (int i = 0; i < views.Length; i++)
{
this.viewHash[views[i].GetType()] = views[i];
}
}
public IView Find(Type type)
{
return (IView) viewHash[type];
}
}
The MainForm also implements IView. Implementing IView allows MainForm to raise Navigation events. The user triggers this navigate by clicking one of the buttons. The button click event then raises Navigate with the appropriate view specified.
public class MainForm
{
...
private void viewButton_Click(object sender, EventArgs e)
{
Navigate(typeof(ReadView));
}
}
The MainFormPresenter ties everything together by subscribing to the Navigate events of both the MainForm and each View. When the Navigate event is raised the Presenter requests a view from the ViewFactory.
public class MainPresenter
{
private readonly MainForm form;
private readonly UserData user;
private ViewFactory viewFactory;
public MainPresenter(MainForm form, UserData user, ViewFactory viewFactory)
{
this.form = form;
form.Navigate+=new NavigateHandler(Navigate);
this.user = user;
this.viewFactory = viewFactory;
}
private void Navigate(System.Type viewType)
{
form.ContentPanel.Controls.Clear();
IView view = viewFactory.Find(viewType);
view.Navigate+=new NavigateHandler(Navigate);
form.ContentPanel.Controls.Add((Control) view);
}
}
This example is simple, but will be the basis for showing how to implement a easily testable and well separated UI layer.