Posted on
March 1, 2008 06:22
by
JP
Categories:
Actions:
E-mail |
Permalink |
Comments (2) |
Trackback
I am working on a large WinForms project utilizing MVP. Some of our views are really, really big with lots of tabs (hey, the users wanted this, not me). This makes things difficult for several reasons.
For one thing, the WinForms designer just can't keep up when it comes to large forms with all kinds of nested controls on it (maybe 2008 is better, I don't know). Last year we decided to make the contents of every tab a user control. It sounds ridiculous, because we don't reuse any of the stuff from screen to screen, but it really speeds things up in Visual Studio and we get far fewer designer crashes...for whatever reason. Also, even if we don't need reuse, the UserControls still provide a nice separation of concerns.
Things get interesting because we need to pass a lot of data around in our views. For instance, we have tons of lookup information that needs to get to various parts of the view in order to populate dropdown lists and other things. The fact that the view is broken up across x number of user controls makes things interesting.
If my presenter fetches the data and I pass it back to the view, it will have to be re-routed to the appropriate user control on a tab some place:
// from the presenter
_view.SetCountryCodes(codes);
// in the implementation somewhere
public void SetCountryCodes(IList<ILookupValue> codes)
{
this.ClientTab.SetCountryCodes(codes);
}
My views and presenters were starting to get tons of GetXXX and SetXXX methods on them. We thought about using events declared on the presenter because each user control (on every tab) has a reference to the presenter, so it would be relatively easy for each part of the view to subscribe to events they need in order to get data back from the presenter. The main reason I didn't want to use events like this is because I thought I would have an EventArgs Explosion (CountryCodeEventArgs, StateCodeEventArgs, EmployeeEventArgs, etc).
It's funny, but my mind still does not go to generics for solutions, even though I use them all the time.
But it hit me. I can just use generics and create an all-purpose event. Something that says "Hey, I have data that you need":
public class GenericEventArgs<T> : EventArgs
{
private readonly T _data;
public GenericEventArgs(T data)
{
_data = data;
}
public T Data
{
get { return _data; }
}
}
This allows me to declare type-safe albeit scary-looking events in my presenter like this:
public event EventHandler<GenericEventArgs<IList<ILookupValue>>> SetCountryCodes;
Each part of my view can subscribe to the events on the presenter that are pertinent. My view interface doesn't have a million methods defined on it. And finally, this stuff is very easy to test.
Another pattern I have utilized is Introduce Parameter Object [Fowler Refactoring] (well, kind of). Instead of having an event for each type of data that I want to send to various parts of my view, I create a single class that contains all of my lookup data:
public class LookupData
{
public IList<ILookupValue> _countryCodes;
public IList<ILookupValue> _stateCodes;
public IList<ILookupValue> _projectTypeCodes;
public IList<ILookupValue> _uomCodes;
public IList<ILookupValue> CountryCodes
{
get { return _countryCodes; }
set { _countryCodes = value; }
}
public IList<ILookupValue> StateCodes
{
get { return _stateCodes; }
set { _stateCodes = value; }
}
public IList<ILookupValue> ProjectTypeCodes
{
get { return _projectTypeCodes; }
set { _projectTypeCodes = value; }
}
public IList<ILookupValue> UomCodes
{
get { return _uomCodes; }
set { _uomCodes = value; }
}
}
Now, I can pass everything via a single event:
public event EventHandler<GenericEventArgs<LookupData>> SetLookupData;
Tab A might need country codes. Tab B might need project type code. Every part of the view gets the same object, but just takes what it needs out of it.