views:

126

answers:

2

My WPF project will be organised like this :

Screens
   Group1
      Screen1
         View.xaml
         ViewModel.cs
   Group2
      Screen2
         View.xaml
         ViewModel.cs

To show the Screen1 from the Screen2 I'll use something like this: ScreenManager.Show("Group1.Screen1") This looks (using reflection) in the Screens.Group1.Screen1 namespace for a View and a ViewModel and instantiates them.

How can I eliminate the magic string without coupling Screen1 and Screen2 (I don't want the classes in Screen2 to use the Screen1 namespace). Also I would like some kind of screen discovery (autocompletion/intellisense)

Or maybe some way (automate test) to verify that all calls to ScreenManager.Show are valid.

Update : I came up with this:

public class ScreenNames
{
    public Group1Screens Group1;

    public class Group1Screens
    {
        public ScreenName Screen1;
    }
}

public sealed class ScreenName
{
    private ScreenName() { }
}

public class ScreenManager : IScreenManager
{
    public void Show(Expression<Func<ScreenNames, ScreenName>> x) {}
}

Usage:

screenManager.Show(x=>x.Group1.Screen1);

Not ideal but I suppose violating DRY is still better than magic strings. And I can automatically test (with reflection) that all calls are valid.

+3  A: 

You don't need all that ScreenManager stuff in WPF, because the DataTemplate engine can take care of this for you with pure markup.

You can simply databind a particular area of your application with a ContentPresenter and a bunch of DataTemplates. Bind the area to a property of a 'root' ViewModel, and let the 'root' ViewModel implement INotifyPropertyChanged so that WPF knows if you change the ViewModel in that area.

public class RootViewModel : INotifyPropertyChanged
{
    public object Screen1ViewModel { get; }

    public object Screen2ViewModel { get; }
}

Databind one ContentPresenter control to the Screen1ViewModel property using

<ContentControl Content="{Binding Path=Screen1ViewModel}" />

and similarly for the next one. When you need to change the content of Screen1, you simply re-assign Screen1ViewModel from code, and because of the raised PropertyChanged event, WPF will pick it up and bind the new ViewModel to a new View.

The DataTemplates may be as simple as this:

<Window.Resources>
    <DataTemplate DataType="{x:Type foo:MyViewModel}">
        <self:MyControl />
    </DataTemplate>
    <DataTemplate DataType="{x:Type foo:MyOtherViewModel}">
        <self:MyOtherControl />
    </DataTemplate>
</Window.Resources>

In case you are not familiar with it, this article on MVVM in WPF is an excellent introduction.

Mark Seemann
Thank you for your answer, I'm not sure I completely understood but it stil doesn't answer to my question: how to keep the ViewModel's decoupled ? I don't want the Screen2 ViewModel to directly use the Screen1
Catalin DICU
@Mark: I like this approach a lot, but I'm not sure if this is what Catalin is after. It almost sounds to me like he wants to implement navigation of sorts and be able to go from one page to another. If I understand your approach, you suggest switching the viewmodel via databinding, which is really cool, but I don't know if that's what he's after. BTW, can you recommend any good online reading that goes through examples where you want to change ViewModels? I always have a 1:1:1 relationship between View:ViewModel:Model and never change the ViewModel (because I don't know its advantages).
Dave
@Catalin DICU: In this model, Screen1ViewModel knows nothing about Screen2ViewModel and vice versa. RootViewModel obviously know about both, but notice that both properties are declared as the type System.Object, signifying that they can really be anything. If you don't want to hardwire knowledge about the VM types into RootViewModel, you can use DI to inject the VMs into RootViewModel. This would give it a constructor like `RootViewModel(object vm1, object vm2)`.
Mark Seemann
@Dave: I'm not aware of any other articles than the MVVM article I already linked to, but armed with the knowledge in that article (and what I just wrote), it's really not very difficult.
Mark Seemann
@Mark: I agree that it's not difficult. I just can't figure out examples where I'd actually want to change a View's ViewModel. :) Probably just lack of breadth of experience on my part. If you can give me a simple example, I'd appreciate it!
Dave
@Dave: Wizards are the most common example. You basically just have one window with a ContentPresenter and some buttons, and then you change the ViewModel as the user presses Next or Previous. Another canonical example is the type of application called a Composite Application (think dashboard), but I must admit that I've never written one of those, let alone liked the concept much.
Mark Seemann
@Mark: ah, thanks. Wizards make sense. Guess I won't be using the pattern in this way any time soon. :)
Dave
A: 

Finally I used T4 code generation to generate my ScreenNames class. I did that by adapting this code : Auto generate strong typed navigation class for all user controls in ASP.NET web application

Catalin DICU