Design question (long)

Topics: UI Architecture
May 13, 2011 at 11:22 AM

I apologise if this post appears confused, but if it does it is because its author is confused :-)
I also apologise for the length of the post.

I've been learning CM for awhile now and like so many others, I really enjoy using CM.
However, once in a while one runs into design problems often caused by ones old way of thinking/solving
a particular problem and I find myself in that spot right now.

<Background>

To learn WPF (and Silverlight) I have played around with a fairly non-trivial application which in its
first version didn't use MVVM at all. Then I came across Josh Smith and his book Advanced MVVM. I rewrote
my application to start using MVVM based on the ideas found in Josh' book and on various article found on
the web. And it all worked out quite nicely. :-)

Then I came across CM and was (more or less) immediately hooked. So I started the third rewrite
of my WPF-based application.

One aspect of the application is a schedule (or appointments) view for each registered
provider of a set of services (e.g. dentist, doctor, car mechanic, and so on). Each list of appointments is contained
in a XAML-based class called DockingPanel and each DockingPanel is contained in a POCO called DockingPanelContainer
which inherits Canvas.

DockingPanelContainer is responsible for arranging the DockingPanels in rows and columns, e.g. 2 x 4 and also knows
what to do if one DockingPanel is expanded or minimized or the DockingPanel's position is changed, e.g. the user dragging
and dropping it elsewhere in the DockingPanelContainer.

To complete the picture, I also have a class called AnimatedUserControl which inherits from UserControl. This class handles
animation of position, width and size based on an privately declared xml-structure describing a Canvas with corresponding storyboards.

(Whew!)

The xaml for the DockingPanel begins with <Controls:AnimatedUserControl x:class="DockingPanel"....> thereby setting up the
relationship between the two classes.

DockingPanelContainer is hosted in another xaml-based class called Admindashboard and the xaml for AdminDashboard is simple:
<Grid>
   <Controls:DockingPanelContainer x:Name="dockingPanelContainer"...../>
</Grid>

The code for adding a DockingPanel to its container is (from Admindashboard.xaml.cs):

void viewModel_DataLoaded (AdminDashboardViewModel sender)
{
   var idx = 0;
   if (sender == null) throw new NullReferenceException("Missing view model");
   dockingPanelContainer.MaxColumns = sender.PractitionerViewModels.Count;
   foreach (var practitionerViewModel in sender.PractitionerViewModels)
   {
      var patientSchedule = new GPPatientScheduleView{DataContext = new GPPatientScheduleViewModel(practitionerViewModel.Bookings) };
      dockingPanelContainer.Children.Add(new DockingPanel {
                                                            Name = "adminDockingPanel" + idx,
                                                            DataContext = practitionerViewModel,
                                                            Content = patientSchedule
                                                          });
      patientSchedule.Reset();
      idx++;
   }
   AddRegisterPatientDockingPanel();
   DragManager.Instance.CleanUp();
   dockingPanelContainer.Reset();
}

(Notice that we can have different kinds of content in our DockingPanels, e.g. appointments, register new customer, email-headers, news-feeds, etc.)

</Background>

Having provided you with a bit of background we can turn our attention to the CM version of the application. I am a bit more concrete in
my naming of the classes in this version as I have concentrated on the appointments-part of the app. To handle the appointments I have the
following ViewModels:

AppointmentsWorkspaceViewModel : Conductor<AppointmentViewModel>.Collection.AllActive, IWorkspace
AppointmentViewModel : Conductor<AppointmentItemViewModel>.Collection.AllActive, IHandle<DateChangedEvent>
AppointmentItemViewModel (will probably inherit Screen)

The idea being that AppointmentsWorkspaceViewModel acts as a "container" for each AppointmentViewModel which acts as a container (or list)
for its corresponding "items".

Now the problem I'm facing is how to take the existing trinity made out of AnimatedUserControl, DockingPanelContainer and DockingPanel and fit those
into the ViewModel-first approach "favoured" by CM. This is what I've come up with so far:

Four views:

AppointmentsWorkspaceView*,

AppointmentViewContainer,

AppointmentView*,

AppointmentItemView*

(the ones marked by * have a corresponding view model)

The XAML for AppointmentsWorkspaceView is:


<Grid>
   <Views:AppointmentViewContainer x:Name="AppointmentViewContainer"/>
</Grid>

AppointmentViewContainer is a POCO that inherits Canvas and basically implements the functionality found in the old DockingPanelContainer.
AppointmentView is a xaml-based class that implements the functionality found in the old DockingPanel.
AppointmentItemView is also a xaml-based class (currently empty).

My question is (and I know it is a broad one): How do I get this to play together? Is there a better or more appropriate approach? Or do I bite the
bullet and accept that this part of the application should use a view-first approach (if possible)? Should the logic found in the UI-classes be moved
to their corresponding view models? If yes, why? Doesn't this "pollute" the view models or is this fine to do?

TIA

--norgie

Coordinator
May 13, 2011 at 2:45 PM
Edited May 13, 2011 at 2:48 PM

Despite your good effort, my brain is still having a hard time understanding the scenario correctly :) However, it sounds very similar to the types of issues people run into when working with window docking managers. In these situations, MVVM is often not the best pattern to use. Remember in all of this our general goal is "separated presentation" which enables the solution to be more easily maintained and extended. It also improves testability and helps to manage complexity. MVVM is one of the patterns you can use to achieve this, but there are others. As a UI programmer, try to approach each scenario with a fresh mind, not making any assumptions about how something should be built until you uncover the specifics of the situation. It turns out, that many UI issues can be solved quite elegantly with MVVM (in a Xaml world). The type of scenario you are describing sounds like it will lend itself better to a Supervising Controller or Passive View design because you need more explicit control over the manipulation of the view. Do a bit of research on those patterns. They are a very powerful tool to have in your tool belt. You can probably build the outer pieces of this component using one of these, but build the individual components that reside inside the docks with standard MVVM. Often "real world" applications use quite a few different patterns throughout. It's nice to have consistency in your design, but sometimes "forcing" that consistency actually causes greater complexity. In this case, you have to step back and consider the simplest thing that could work which will meet the overarching design goal of "separated presentation."

Coordinator
May 13, 2011 at 2:47 PM

Some resources for you:

http://codebetter.com/jeremymiller/2007/07/26/the-build-your-own-cab-series-table-of-contents/

May 13, 2011 at 3:04 PM
Edited May 13, 2011 at 3:11 PM

I often find quite difficult to get a clear overall vision of a non trivial scenario composed of multiple views and VMs without having it in VS and jump back and forth between classes.
The problem you have, as I understand it, is having a set of UI element implementing a complex visual behavior (docking, etc).
Those UI element seem to be not particularly binding-friendly; in other words, it seems you have to "load" them using methods instead of populating an ItemSource property (like we usually deal with ItemsControl in WPF).
Am I correct?

This issue is also very frequent with third-party docking manager or complex controls. You can find quite a lot discussion about it in the forum.
There are many options to solve the problem in a model-first approach:

1) define a service to abstract the UI  operations and use it in the VM; BladeWise made a useful IDockingManager service along this path: http://caliburnmicro.codeplex.com/discussions/231809.
I believe it can be adapted to your scenario, too

2) define an interface (to be implemented in the view) aimed to expose the view behavior in an "abstract" fashion to the VM, which can obtain a reference to the view using GetView(...) method.
This approach resembles the MVP pattern [EDIT: actually, the PassiveView "nuance" of MVP, as Rob suggested].

3) espose some observable properties and/or collection in the VM, so that the view can observe VM changes and react to them accrodingly.
This is basically a replacement for WPF binding: you have to obtain a reference of VM in the code behind (usually through DataContext property), then explicitly subscribe to INotifyPropertyChanged and/or INotifyCollectionChanges in order to get informed when something changes in the VM. In the handlers you have directly manipulate the view to represent the detected changes.

There is also an involving IResult, but is somewhat similar to 2), yet more elegant sometimes.

A final comment about moving logic into VM: it is usually the PREFERRED choice, unless it is a pure visual/graphical concern.
Navigation logic, for example, should live in VMs in my opinion. 
The VM is mainly intended to capture application logic (i.e.: HOW application works and drive user around) thus allowing you to test it without involving WPF infrastructure at all.

Please, let me know if that helps or if I've misunderstood your questions.

May 13, 2011 at 3:07 PM
Edited May 13, 2011 at 3:09 PM

Cannot believe it, Rob! I checked the thread no more than 10 minutes ago! (OK, 20 perhaps)
I have to contact Codeplex Team and ask them for a "New Reply just Posted!" feature :-)

Coordinator
May 13, 2011 at 3:42 PM

Well, at least we are thinking along the same lines :) Your resources are much more practical than the ones I gave.

May 13, 2011 at 4:24 PM

"Despite your good effort, my brain is still having a hard time understanding the scenario correctly". Sorry. I warned you though :-)

Rob, regarding your reply, I couldn't agree with you more. However, sometimes I fail to take the necessary step back to get an overview of the situation and end up more confused than necessary. The important bit then is not to forge ahead with a design that reflects that confusion. I believe I managed that this time by seeking advice here at the CM forum :-)

Thanks for taking time to try to understand my problem.

norgie

May 13, 2011 at 4:25 PM

Rob, thanks for the link. Will definitively have a look.

norgie

May 13, 2011 at 4:39 PM
Edited May 13, 2011 at 4:44 PM

Marco, some of that code was "inherited" and not written by me (haven't had time to do something about the code either) so I can't really say anything about whether it is binding friendly or not or if it was me who at that particular time due to lack of understanding didn't utilize any binding mechanisms that maybe was already in place..

I totally agree with you that it is difficult to understand a non-trivial scenario, e.g. due to lack of context, etc. However, you got the gist of what I was trying to explain/achieve and I will look at your examples and try to understand how they can be applied to what I'm trying to achieve.I will probably learn a lot in the process of doing so and that is a Good Thing (tm).

I am a big fan of DTSTTCPW (do the simplest thing that could possibly work), but sometimes my understanding of a "technology" isn't good enough to know what the simplest thing that could possibly work is. All I knew before posting was that I was heading in what seemed to be the wrong direction and that I was not doing the STTCPW.

I'm also a big fan of (impromptu) white board discussions, but since I'm the sole developer on this application, that isn't an option. I could probably try some rubber ducking, though. :-)

So, Rob and Marco, thank you for your replies and thanks for taking time to try to understand the problem. Be warned though, I may come back with more questions. ;-)

--norgie

May 13, 2011 at 5:50 PM

@norgie: no problem. Also, design related discussions are my favourite ones :-)

@rob: funny enough, I just got a Codeplex Survey dialog :-O so I seized the opportunity to report the proposal