开发者

MVVM + Implementation of View specific functionalities called by the ViewModel

here is my "problem" I want to resolve:

I have got many "View only" specific functionalities for example:

  • Change the ResourcesDictionary of a View at runtime (for changing skins from black to blue or whatever)
  • Save and restore View specific settings like the view size, or grid properties set by a user
  • ...

All those functionalities have nothing to do with the ViewModel, since they are really view specific and might only fit to one client (View) of a ViewModel (in the case a ViewModel has got more than one client). The examples above are only two of a large amount of functionalities I want to implement, so I need a more generic solution instead of solutions that only fit those two examples.

When thinking of a solution I came two the following approaches

  • Create a ViewBase that inherits from DependancyObject. I dont like this solution because it somehow breaks the idea of the MVVM pattern where a View has no code behind. And to call this methods I somehow need to reference the View in my ViewModel which also negates the idea of seperation of concerns.
  • Create an IView interface. As dirty as the first approach. Each View needs to implement IView and therfor has code behind. Also the ViewModel needs to "somehow" know the IView implementation to call its methods
  • Bind Properties of the ViewModel to Triggers, Behaviours, Commands of the View. This approach seems to be the best, but I think I will run in a limitation of usage very fast because some functionalities might not work with this approach. For example just Binding a resourceDictionary to a View might not work because a merge is needed for correct display of new resources. Then again...I have view only specific functionalities / informations (like a resourcesdi开发者_运维技巧ctionary) in the ViewModel, but only a specific client of the ViewModel uses this property.

If anyone of you already had the same problem and got a smart/smooth (and mostly generic ;) ) solution for my problem, this would be great.

Thank you


The easiest way to do that without introducing coupling between the View and ViewModel is to use a Messenger (also called Mediator in some frameworks). The ViewModel simply broadcasts a "change theme" message, and the View subscribes to that message. Using the Messenger class from MVVM Light, you could do something along those lines:

Message definition

public class ThemeChangeMessage
{
    private readonly string _themeName;
    public ThemeChangeMessage(string themeName)
    {
        _themeName = themeName;
    }

    public string ThemeName { get { return _themeName; } }
}

ViewModel

Messenger.Default.Send(new ThemeChangeMessage("TheNewTheme");

View code-behind

public MyView()
{
    InitializeComponent();
    Messenger.Defaut.Register<ThemeChangeMessage>(ChangeTheme);
}

private void ChangeTheme(ThemeChangeMessage msg)
{
    ApplyNewTheme(msg.ThemeName);
}


I've long since adopted the way of thinking that Patterns were made for Man, not Man for patterns. Quite often you'll see a situation where MVVM doesn't fit and to resolve it, very smart people have come up with ways to get around it, while maintaining the pure MVVM look.

However, if you subscribe to my school of thought, or if you just like to keep it simple, another way is to allow the ViewModel to reference the view; via an interface of course, or that would just be terrible programming practice. Now the question becomes, how to get the view into the viewmodel?

the simplest way would be to do this in the view's dataContextChanged event. However if you want to try something different, how about using an attached property or dependency property to inject the view into the viewmodel?

I've successfully used this techniques on a number of WPF projects and don't feel dirty or somehow compromised. I call it MiVVM or Model Interface-to-View ViewModel.

The pattern is simple. Your Usercontrol should have an interface, call it IMyView. Then in the ViewModel you have a property with a setter of type IMyView, say

public IMyView InjectedView { set { _injectedView = value; } }

Then in the view you create a dependency property called This

public MyUserControl : IMyView
{
    public static readonly DependencyProperty ThisProperty = 
         DependencyProperty.Register("This", typeof(IMyView), typeof(MyUserControl)); 

    public MyUserControl() 
    {
       SetValue(ThisProperty, this);
    } 
    public IMyView This { get { return GetValue(ThisProperty); } set { /* do nothing */ } } 
}

finally in Xaml you can inject the view directly into the ViewModel using binding

<MyUserControl This="{Binding InjectedView, Mode=OneWayToSource}"/>

Try it out! I've used this pattern many times and you get an interface to the view injected once on startup. This means you maintain separation (Viewmodel can be tested as IView can be mocked), yet you get around the lack of binding support in many third party controls. Plus, its fast. Did you know binding uses reflection?

There's a demo project showcasing this pattern on this blog link. I'd advocate trying out the Attached Property implementation of MiVVM if you are using a third party control that you cannot modify.

Finally may I suggest to find the best tool for the job is almost always the best programming approach. If you set out to right "clean" or "correct" code you are often going to hit a wall where you need to change your approach throughout.


When you said

I have got many "View only" specific functionalities for example:

that makes me think that you are mixing the "What" and the "How". I'll explain what I mean by this.

The what is your app requirements:

  • Change skin color of app
  • Save & Restore
    • Size
    • Grid properties

I argue that the above has everything to do with your ViewModel, your VM should contain simple or complex properties that can tell your View what it wants to do e.g.

public class SettingsViewModel
{
  public Color Skin { get;set;}
  public Size ViewSize {get;set;}
  public GridProperties GridProperties {get;set;}

  public void Save() {//TODO:Add code}
  public void Restore() {//TODO:Add code}
}

your View would bind to that ViewModel and implement the "How".

If you're creating a web app then the how will take the ViewModel and create html. If you're using WPF you bind to those properties in XAML and create your UI(which might cause you to switch out ResourceDictionaries etc.)

Another thing that helped me out is to realize the asymmetrical relationship between the View and the ViewModel. In it's purest form the ViewModel should know nothing of the View, but the View should know everything it needs to know about the ViewModel.

That's the whole point behind separation of concerns.

Responses to your "solutions":

  • Your first option violates MVVM principles, have you read this article?
  • I believe this article will help you come to terms with view selection based on the ViewModel.
  • I don't know of what "limitations" you will come across, but WPF is quite robust and there will be solutions available.


I agree that View specific functionality should stay in the View (Save and Restore the window size, set focus to a specific control, etc.).

But I don’t agree that the introduction of an IView interface is ‘dirty’. That’s a common design pattern called Separated Interface which is described in Martin Fowler’s book Patterns of Enterprise Application Architecture. Furthermore, code-behind is not ‘evil’ as long the code relates to View specific functionalities. Unfortunately, that’s a common misunderstanding in the MVVM community.

If you give the introducing of an IView interface approach a change then you might find the WPF Application Framework (WAF) interesting. It solves our issues through this interface. You are going to see this in the sample applications.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜