Composite UI, the designer and IoC
I am trying to think of a way to make this view work. It is a single view that contains more views, each of them responsible for a subset of the functionality in the view. This is the first time that I am dealing with smart client architecture for a long time.
It is surprising to see how much the web affect my thinking. I am having trouble dealing with the stateful nature of things.
Anyway, the problem that I have here is that I want to have a controller with the following constructor:
public class AccountController { public AccountController( ICalendarView calendarView, ISummaryView summaryView, IActionView actionView) { } }
The problem is that those views are embedded inside a parent view. And since those sub views are both views and controls inside the parent view, I can't think of a good way to deal with both of them at once.
I think that I am relying on the designer more than is healthy for me. I know how to solve the issue, break those into three views and build them at runtime, I just... hesitate before doing this. Although I am not really sure why.
Another option is to register the view instances when the parent view loads, but that means that the parent view has to know about the IoC container, and that makes me more uncomfortable.
It is a bit more of a problem when I consider that there are two associated calendar views, which compose a single view.
Interesting problem.
Comments
Would you need to make the ParentView aware of IoC for it to register the sub views?
Could you not have:
ParentView : IParentView
{
public ICalendarView CalendarView { get; set; }
public ISummaryView CalendarView { get; set; }
}
IoC would inject them at instantiation. Then you would simple need to add them to the proper place holder control during Load.
Yes, that is what I wanted to avoid.
No real way around that, and I ended up doing just that.
It isn't as nice as with the designer, but it works
In those cases I generally have:
ParentView : ICalendarView, ISummaryView, IActionView
{
}
Keep your AccountController contructor as is. It thinks it is getting three different views but in reality is getting the same object three different times. IoC will hide this from the Controller. I find this to be the most designer friendly.
Hm, explicitly hooking together views and controllers (when this could be avoided) does not feel good.
Why not do the other way around?
public sealed class BigCalendarView(ICalendarController controller) {}
public sealed class SmallCalendarView(ICalendarController controller) {}
and let the IoC handle the rest?
You could even close/open views on the fly and let user customize the layout.
Blogged about it here http://rabdullin.com/how-to-implement-complex-ui-composition-in-xlim/
Who request the views from the container?
This means that the UI is in control of the application, and I don't want that.
Oren,
How does the execution flow affect the control responsibility?
If your computer is woken up by LAN card, do you say the OS is controlled by the LAN?))
Ayende, I am in complete sync with you about going to winforms after so much web based development. I look forward to hearing what you have to say on the situation.
It has to do with who has control over the execution. For example, this means that the controller has harder time switching view if it need to.
I tend to prefer a method that puts the control in the hand of the controller, which then direct the view.
Oren,
What kind of switching are you talking about?
If the controller needs to switch from one view instance to another at run-time, then that's a problem (although I cannot come up with scenario that requires exactly this scenario).
If you are talking about decoupling of controllers from views then that's not the problem at all. Controllers encapsulate logic, views know only about the specific controller interfaces (and a single view can rely on multiple controller interfaces) which tell them what exactly they are supposed to do.
Thus the coupling is low, and additionally a lot of controllers are shared between Web and desktop views.
I agree with Oren. The Controller should depend on the View not the other way around. If testing is a high priority, then you want as little code as possible in the View as UI code is difficult to test. Put all you can in the Controller, then have the Controller depend on the View, and have the View be unaware of the Controller, lends itself well to testing.
This is known as Passive View:
http://martinfowler.com/eaaDev/PassiveScreen.html
"It is surprising to see how much the web affect my thinking. I am having trouble dealing with the stateful nature of things."
Have you tried Webforms? It maintains state quite nicely!
This is always a challenging scenario, since the presenter (or controller) only knows about IView, and not the implementation (winforms, wpf, console, etc...). This is compounded by the idea of having multiple views within a shell form like you are showing in your example.
What we ended up doing was build our own MVP framework to handle the creation at runtime, just like how you said in your post that you know how to do it, but hesitate. The framework is in charge of creating the instances of the classes (view/presenter) then activating them (either showing the form, or embedding it in a panel or tab control or whatever).
The heart of the framework is the MVPManager that we wrote. The presenters/views get registered into on startup, along with some parameters so that it knows how to activate the view (i.e show the form), or what target to embed the view controls into.
The shell form has x number of winforms control (panels) that act as 'targets' that host the view panes, like you have in your example. We then expose these controls as an extra interface in the view with a target name so that the mvp manager can access it. When we registered the child controls, we tell it what parent control, and its embedded target so then the mvpManager activator knows which view belongs with which target panel. We instantiate the views and presenters with a factory (that just calls windsor to resolve the dependencies). Then add the views to their parent control, set their dock property to full, set any other properties, and we're done.
Each view is paired with its own presenter, so in your example, we'd have 3 or 4 views and as many presenters. Like Oren and Graham Nash have said, I agree the presenter/controller should depened on IView, and IView is passed into the constructor of the presenter. Makes it way easier to test that way, and also makes it much easier to swap views (winforms to wpf, or whatever).
To navigate between views (i.e. show another dialog form or whatever), the presenter only needs to know the registered target name of the view it wants to activate. (This has allowed us to totally decouple our presentation architecture.) The presenter calls the Activate method on the mvp manager (the presenter has a dependency reference to the mvp manager), and passes in the navigation target name, and some state object it wants to pass to the target presenter. The mvp manager then instantiates the presenter and view and shows the view. Our views never talk to anything except the presenter.
To communicate between presenters, we opted to use a shared state object, and raise events on that object to notify the other presenters that something changed. This state object would be passed into the presenter by the mvp manager just after the presenter was resolved from the container.
We can reuse any view or presenter in different forms by simply changing the parameters that we registered the targets with in the mvp manager. In one of our applications, we half the forms are wpf and half are winforms. As we migrate to wpf, all we really need to do is build a new window that implements IView, and change one line of registration code. Its incredibly flexible, and the calling presenter has no idea what kind of view the target is -- it just knows its name, and what type of state object it is expecting.
Our framework was loosely based on microsoft's UIP 2.0, (not to be confused with CAB since we didn't really like it). We chose the best ideas from UIP, ignored what we didn't really like (like shared objects) and added our own flavours to it to suit our environment. It was a bit of work to get right, but I think we've managed to harness our UI problems, in the same way that you are trying to figure out a good design for your environment. And believe me, I had the same questions last summer as you do now. Ayende, I hope you can come up with a great solution, and let us know on your blog what you end up doing.
My solution was to have a generic method on the base IView interface as such: T GetChildView<T>()
Then I simply use a generic factory interface to couple the child view to the child controller and create an instance through that.
It was the best way I could think of to get both the designer to work and the view to be decoupled (as well as the controller not needing a reference to the container), it adds a bit of extra setup to testing, but just a couple of lines at most.
hmmm,
the views should need to know about other views should they? The actions come from a controller and go to a view. Therefore each sub controller need only know about its view. The parent controller knows about all other controllers.
Jeremy Miller has a similar scenario in his StoryTeller project. A main ApplicationShell form which contains a treeview style explorer and a tabcontrol panel. The explorer and tabcontrol both have their own controllers, but the ApplicationController isnt aware of the views I believe.
The classic "pattern" solution to this is the mediator. It is actually quite hard to do a mediator while still maintaining separation of view from controller. What I have successfully done in the past is to code the controller (mediator) against an interface of the view(s). This allows quite a clean separation of concerns.
How does web differ in this case from winforms? I ran into similar problems with monorail MVC on the first day I tried it.
Use a layout manager / docking-thingy library for laying out where things go in the main shell.
Other than that, views are independent, supervising controllers manage them via specific I_XXX_View interfaces.
Controllers do NOT speak to each other directly. Use commands.
Menus (ribbons, and context menus too) also activate commands.
Put entities in a wrapped up hashtable (I call it a client-repository). When things get added there, have it raise an event so that controllers can act.
Be aware of threading issues if you're doing background communications stuff.
I should be publishing a software factory in the next few weeks that wraps up all the relevant frameworks and infrastructure to make this all work easily.
Keep fighting the good fight.
Comment preview