The trick to MVVM



  • MVVM is a well known pattern for building applications that separate their UI and logic layers. It ultimately comes down to: Keep the business logic out of the view, and keep the UI out of everything else.

    It's all really simple. Yet it doesn't take much work to find ridiculously huge frameworks designed to help you do it. In the spirit of a well known XKCD comic, I created my own. It has three files.

    First off, PRISM did some things right, so I implemented my own versions of two of their classes. (Technically three, but one is just the other but generic)

    The first is DelegateCommand:

    public class DelegateCommand : ICommand
    {
    	private readonly Action execute;
    	private readonly Func<bool> canExecute;
    
    	public DelegateCommand(Action execute, Func<bool> canExecute = null)
    	{
    		this.execute = execute;
    		this.canExecute = canExecute;
    	}
    
    	public bool CanExecute(object parameter)
    	{
    		return canExecute == null || canExecute();
    	}
    
    	public void Execute(object parameter)
    	{
    		execute?.Invoke();
    	}
    
    	public event EventHandler CanExecuteChanged;
    
    	public void RaiseCanExecuteChanged()
    	{
    		var changed = CanExecuteChanged;
    		changed?.Invoke(this, EventArgs.Empty);
    	}
    }
    
    public class DelegateCommand<T> : ICommand
    {
    	private readonly Action<T> execute;
    	private readonly Func<T, bool> canExecute;
    
    	public DelegateCommand(Action<T> execute, Func<T, bool> canExecute = null)
    	{
    		this.execute = execute;
    		this.canExecute = canExecute;
    	}
    
    	public bool CanExecute(object parameter)
    	{
    		return canExecute == null || canExecute((T)parameter);
    	}
    
    	public void Execute(object parameter)
    	{
    		execute?.Invoke((T) parameter);
    	}
    
    	public event EventHandler CanExecuteChanged;
    
    	public void RaiseCanExecuteChanged()
    	{
    		var changed = CanExecuteChanged;
    		changed?.Invoke(this, EventArgs.Empty);
    	}
    }
    

    The generic one lets you have command parameters, and either allows you to just pass in methods from the constructor rather than proliferating a billion ICommand subclasses.

    Second is the dumb base class I use for anything I need to bind to:

    public abstract class NotificationObject : INotifyPropertyChanged
    {
    	public event PropertyChangedEventHandler PropertyChanged;
    
    	public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    	{
    		if (propertyName != null)
    			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    	}
    }
    

    Since this is C#6, if I want to raise this for a property other than the caller, I can use nameof(). I used to have a second override that took an expression and got the name from it. This is better.

    So, that's basically everything in PRISM that's directly helpful. What you really need, though, is a good navigation system. Mine is weird, and perhaps not perfectly MVVM, but it's MVVM enough:

    [Export]
    public class InnerNavigation
    {
    	[ImportMany]
    	public Lazy<IView, IInnerNavigationMetadata>[] InnerNavigationViews { get; set; }
    
    	private readonly Dictionary<Type, Action<UserControl>> subscriptionDictionary = new Dictionary<Type, Action<UserControl>>();
    
    	public void Subscribe<TContext>(Action<UserControl> navigationAction)
    	{
    		subscriptionDictionary[typeof(TContext)] = navigationAction;
    	}
    
    	public TView NavigateTo<TView>() where TView : UserControl, IView
    	{
    		var result = InnerNavigationViews.FirstOrDefault(element => element.Value is TView);
    		var selectedView =  result?.Value as TView;
    		if(result != null)
    			subscriptionDictionary[result.Metadata.Owner](selectedView);
    
    		return selectedView;
    	}
    }
    

    There's some MEF going on here, obviously, and I haven't shown you the view and viewmetadata interfaces, but they're simple enough. IView basically just forces you to have a ViewModel in your view. Basically, though, this keeps a cache of all views and what context they navigate within, so you only ever have to call navigation.NavigateTo<SomeView>(), and it will update whatever subscribers are listening.

    Typically, this means that your mainwindow only has <ContentControl Content="{Binding CurrentView}"/> inside.

    MEF is more than happy to set up your main window for you:

    [Export]
    public partial class MainWindow
    {
    	[ImportingConstructor]
    	public MainWindow([Import] MainWindowViewModel viewModel)
    	{
    		InitializeComponent();
    		DataContext = viewModel;
    	}
    }
    

    And what does the ViewModel look like?

    [Export]
    public class MainWindowViewModel : NotificationObject
    {
    	private UserControl currentView;
    	public UserControl CurrentView
    	{
    		get { return currentView; }
    		set
    		{
    			if (currentView == value)
    				return;
    			currentView = value;
    			RaisePropertyChanged();
    		}
    	}
    
    	[ImportingConstructor]
    	public MainWindowViewModel([Import] InnerNavigation navigation)
    	{
    		navigation.Subscribe<MainWindow>(view => CurrentView = view);
    		navigation.NavigateTo<Page1View>();
    	}
    }
    

    So, when this thing sees a navigation called using a view that's set to navigate within MainWindow, it updates the binding with that new UserControl. You can pretty much do the same thing anywhere.

    Last of all comes the page view, so you can see the last bits.

    [InnerNavigation(typeof(MainWindow))]
    public partial class Page1View : IView
    {
    	public ViewModel ViewModel
    	{
    		get { return DataContext as Page1ViewModel; }
    		set { DataContext = value; }
    	}
    
    	[ImportingConstructor]
    	public Page1View([Import] Page1ViewModel viewModel)
    	{
    		InitializeComponent();
    		ViewModel = viewModel;
    	}
    }
    

    That InnerNavigationAttribute assigns the navigation parent for the view, but that's essentially it. All your views with that attribute are exported and composed automatically, so you never have to think about that stuff.

    The part that does the composition is in your App.xaml.cs:

    public partial class App
    {
    	protected override void OnStartup(StartupEventArgs e)
    	{
    		base.OnStartup(e);
    
    		var catalog = new AssemblyCatalog(typeof(App).Assembly);
    		var container = new CompositionContainer(catalog);
    		container.ComposeParts();
    		container.GetExportedValue<MainWindow>().Show();
    	}
    }
    

    Anyway, I made a project template that just has all this stuff set up, but essentially: No dependencies that aren't already part of .NET, and you don't have to think about much more than getting attributes right.

    Hooray for frameworks!



  • Another important point is that in some viewmodel, lets say our Page1ViewModel, if you want navigation, you'd end up with something like this:

    [Export]
    public class Page1ViewModel
    {
      private readonly InnerNavigation navigation;
    
      public DelegateCommand NavigateSomewhereElseCommand { get; }
    
      [ImportingConstructor]
      public Page1ViewModel([Import] InnerNavigation navigation)
      {
        this.navigation = navigation;
        NavigateSomewhereElseCommand = new DelegateCommand(NavigateSomewhereElse);
      }
    
      private void NavigateSomewhereElse()
      {
        var view = navigation.NavigateTo<SomewhereElseView>();
        // And because we know this has a viewmodel, we can do things with it! Like send it things!
      }
    }


  • So I redid the template on my machine at home, and simplified it some more. This one is for stock WPF and C#5.

    Anyway, if anyone wants to play around with it, there it is.

    Just goes in your Documents&lt;VS VERSION>\Templates\ProjectTemplates or somesuch



  • Interesting. I didn't understand the big picture from the code snippets, but I downloaded your template and will give it a go from there.

    I did some work in WPF a while back, but never used MVVM. Therefore, all my apps turned into a mess. I don't see myself going back to WPF, but I'd like to get into MVVM, since it seems many different platforms are using it to great effect.



  • The center of my design is MEF, a hidden jewel in .NET. Basically, the PRISM guys decided that Unity was a bit much, and wanted to build a system for plugins and composition at the same time, so they built MEF. They hooked it into PRISM, but later realized that with a little bit of code, you can do nearly everything it did, simpler. After reading about that, I tried it. It worked.



  • @Magus said:

    The center of my design is MEF, a hidden jewel in .NET. Basically, the PRISM guys decided that Unity was a bit much, and wanted to build a system for plugins and composition at the same time, so they built MEF. They hooked it into PRISM, but later realized that with a little bit of code, you can do nearly everything it did, simpler. After reading about that, I tried it. It worked.

    WTF! Useful information on THIS site?? :wink:

    Seriously, each of the frameworks had their advantages and disadvantages. I do like what you have done, but think there might be a hint of misrepresentation. MEF is fantastic, and using a sub-set (as you did) can be very "lean" in the amount of code you need to write - but it also has many capabilities that take a long time to learn to use properly [and can be used improperly] so while I agree it is a "hidden gem" I would discourage readers from thinking it was a "magic bullet"



  • @TheCPUWizard said:

    MEF is fantastic, and using a sub-set (as you did) can be very "lean" in the amount of code you need to write - but it also has many capabilities that take a long time to learn to use properly [and can be used improperly] so while I agree it is a "hidden gem" I would discourage readers from thinking it was a "magic bullet"

    ^ This.
    MEF is in many ways a loaded footgun the size of a cannon.



  • It certainly isn't a solution for every problem, but it takes most of the effort out of MVVM.

    There's definitely the problem of forgetting attributes though. The error messages are frequently nearly useless.

    @Ragnax said:

    MEF is in many ways a loaded footgun the size of a cannon.

    And while this is true, if you want to use, say, PRISM, you have around 5 DLLs plus your DI framework. I haven't tried the other popular frameworks really, but the amount of effort required to start doing something useful with my setup is really minimal, and that's all I'm going for. A simple starting point, which can, with no additional concepts, create a fairly powerful application.



  • I took a look at PRISM for StoreApps (a bit more lightweight than the WPF version) and thus downloaded their AdventureWorksSample.

    According to the docs, I was supposed to simply hit "Build Solution" and go from there.

    Instead I'm greeted by 23 build failures, 21 of those I can solve myself by including several missing using statements, the remaining two, however, are of the "method not implemented" kind.

    Good job, guys!



  • @Rhywden said:

    took a look at PRISM for StoreApps...23 build failures, 21 of those I can solve myself by including several missing using statements, the remaining two, however, are of the "method not implemented" kind.

    It has been a while since I played with the raw downloads of Prism (especially for store apps)...Please post the URL you downloaded from, I would like to take a look at this...



  • @Magus said:

    PRISM, you have around 5 DLLs plus your DI framework. I haven't tried the other popular frameworks really, but the amount of effort required to start doing something useful with my setup is really minimal, and that's all I'm going for

    I find it interesting that the number of DLL's is a consideration. With modern packaging and deployment tools, most do not find it to be an issue....can you provide more background?





  • Thanks for the link...I will check it out Monday trying to spend a little family time on a Sunday]



  • The number isn't really a problem, it just feels like overkill. Meanwhile, though MEF isn't part of the windows universal codebase, there's a nuget package for it, so with very little modification, you can set things up the same way there.



  • Thanks for this.

    I've got a small work project to finish up this week but as Visual Studio 2015 comes out tomorrow, and Windows 10 in ten days I feel it's time to get back into the C# groove :smile:



  • I'm currently a bit stumped and probably missing some bit of important automagic which I needed to set for PRISM / XAML / Blend / whatever. Specifically, I want to use design-time data to actually see something while I'm pushing things around in the designer / Blend.

    Caveat: This is a learning project.

    Everything is working when I build the project and actually run it. I'm running into some kind of namespace problem for the design time stuff, though. So, this is the project structure:

    App.xaml
      DesignViewModels/
        MainPageViewModel.cs
      ViewModels/
        MainPageViewModel.cs
      Views/
        MainPage.xaml
    

    ViewModels/MainPageViewModel.cs:

    using Microsoft.Practices.Prism.Mvvm;
    
    namespace App1.ViewModels
    {
        class MainPageViewModel : ViewModel
        {
            public MainPageViewModel()
            {
                Name = "Test2";
            }
            private string _name;
    
            public string Name
            {
                get { return _name; }
                set { SetProperty(ref _name, value); }
            }
            
        }
    }
    

    DesignViewModels/MainPageViewModels:

    using Microsoft.Practices.Prism.Mvvm;
    
    namespace App1.DesignViewModels
    {
        class MainPageViewModel : ViewModel
        {
            public string Name
            {
                get { return "DesignTest"; }
            }
        }
    }
    

    MainPage.xaml:

    <storeApps:VisualStateAwarePage
        x:Class="App1.Views.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App1.Views"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:mvvm="using:Microsoft.Practices.Prism.Mvvm"
        xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps"
        xmlns:designViewModels="using:App1.DesignViewModels"
        mc:Ignorable="d"
        mvvm:ViewModelLocator.AutoWireViewModel="True"
        >
        
        <d:VisualStateAwarePage.DataContext>
            <designViewModels:MainPageViewModel />
        </d:VisualStateAwarePage.DataContext>
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <TextBlock Text="{Binding Path=Name}" Style="{ThemeResource BodyTextBlockStyle}"/>
        </Grid>
    </storeApps:VisualStateAwarePage>
    

    The error I'm getting:

    The name "MainPageViewModel" does not exist in the namespace "using:App1.DesignViewModels".	C:\Users\___\documents\visual studio 2013\Projects\App1\App1\Views\MainPage.xaml
    

    So, what am I doing wrong?



  • I'm not really the one to ask but it could be to do with your classes not being public. I seem to remember that causing me grief before.



  • Yeah, that was it. Damn. Thanks!



  • Try using clr-namespace:Foo instead of using:Foo in the namespace declarations and recompile.

    Also :hanzo:ed.



  • \o/

    Now if that was StackOverflow and I had replied within 6.7 milliseconds I could have got 10 points :)



  • That works, too.

    Hrmh, turns out that the error was more likely related to not building the project and had nothing to do with the public keyword.

    Seems as if this stuff only works if you build the design-viewmodels before you try to access them in the designer.



  • One thing I really like about using MEF for composition is that you can very easily keep things constructor-injected, and you can ignore the (my keyboard suggests that the next word should be either Flintstones, Kardashians, or Kelly) attributes when testing. It doesn't dictate anything about the content of the class, only the metadata.



  • OK, I added your template and created a demo app. Nice job, everything worked fine in the first attempt.

    I never worked with MEF before. I like it. Kind of like dependency injection on steroids.

    My biggest complaint with your template/framework is that your InnerNavigation system is based on a fixed layout of View subclasses.

    This is the gist for the peanut gallery:

    a view
    [InnerNavigation(typeof (MainWindow))]
    public partial class Screen1View : IView {
    	//....
    }
    
    //....
    
    public class Screen1ViewModel : NotificationObject {
    	//...
    	private void NavigateToScreen2() {
    		navigation.NavigateTo<Screen2View>();
    	}
    }
    
    main window
    [ImportingConstructor]
    public MainWindowViewModel([Import] InnerNavigation innerNavigation) {
    	innerNavigation.Subscribe<MainWindow>(control => CurrentView = control);
    	innerNavigation.NavigateTo<Screen1View>();
    }
    

    Each "navigable" view you define has an unchangeable "parent" class that reacts to its navigation events. This parent is declared statically by each view you define. Navigation is done through generics.

    This feels... icky. Maybe I'm too used to the web, but IMO a child shouldn't even know what its parent is. Not to mention statically declare it in constructor.

    What if you wanted a view to move to a different parents? Or a widget to appear on multiple screens?

    Why use generics? That means the runtime will create a separate function for each View subclass you declare.

    Also, how would you test this? Can you inject a different view to handle navigation in your tests? I don't see how, since navigation is done through classes instead of instances. This also means you can only have a single navigable instance of each view, which might be what you want or might not.

    Am I misreading what your views are? If they are something like SPA routes, then some of these complaints would go away. Perhaps a rename from "View" to "Route" would make this clearer? But then you'd want something like "route arguments" or similar (eg. show me the "users list" filtered by name).



  • @cartman82 said:

    What if you wanted a view to move to a different parents? Or a widget to appear on multiple screens?

    For something like this, I wouldn't use my navigation at all, or would have multiple navigable 'regions'. But there's also a weakness in WPF here, because the same instance can't have multiple parents. I'd probably use an ExportFactory<T> or something for that.

    @cartman82 said:

    This feels... icky. Maybe I'm too used to the web, but IMO a child shouldn't even know what its parent is. Not to mention statically declare it in constructor.

    So, technically the class doesn't know it's parent. There's some metadata on it, but there isn't any reason you can't use it elsewhere. I could have made it use a string or something rather than a type.

    @cartman82 said:

    Also, how would you test this? Can you inject a different view to handle navigation in your tests? I don't see how, since navigation is done through classes instead of instances. This also means you can only have a single navigable instance of each view, which might be what you want or might not.

    So, yes, by default in MEF exports are shared, unless you ask for them to not be, or use an ExportFactory to request unique instances with the imports fulfilled. I definitely don't claim that this system is suitable for everything, but anywhere you have something wizardlike or with specific pages, it's rather nice. It's supposed to fill the same role as PRISM's RegionManager.

    But views can be pretty much anything. The only restriction I've given them is that they must have some kind of notifying viewmodel. Since navigation returns the view it navigates to, you can send things to the viewmodel, but perhaps it would have been better to use a ViewModel class that has some kind of arguments. I don't know. I'm not doing anything horribly complex with it all.



  • @Magus said:

    I definitely don't claim that this system is suitable for everything, but anywhere you have something wizardlike or with specific pages, it's rather nice. It's supposed to fill the same role as PRISM's RegionManager.

    Yup, I guess it wasn't fair treating your little template (which was explicitly designed to be simple) as some all-inclusive framework.

    I would still prefer it, though, if composition was done through templates or top-down, instead of bottom-up.



  • You can also do composition using strings in general, something like [Export("Details")] or even [Export(typrof(IThingsThatLookLikeThis), "WithThisName")].

    It's also worth looking into the catalogs (Currently I only mess with that in App.xaml.cs), because MEF is designed to enable plugin loading as well: you can use directory catalogs to scan assemblies for exports and such.



  • Necro time!

    So, I decided to start building a Win10 app with some simple card games, and wanted to use my navigation system in it. Sadly, MEF is a bit different in Windows Universal, so I had to scrap my navigation and build a new one. Not really an issue, since I can always [ImportMany] my views and do something similar... except that they no longer know about their parent.

    Which is probably fine. So, I've currently got it to work something like navigation.Navigate<HostPage>().To<SomeUserControl>(). This involves building an inner class and exposing it with Navigate. I like this and I dislike it; the class that Navigate creates has a public constructor, which is both nice and slightly odd, since I don't really want anyone constructing them manually. I could make this into a library and make the constructor internal, but I don't know if that's worth the frustration.

    As far as using it goes, you can technically navigate within just about any invalid thing, but at the same time, you can also display any view anywhere. I don't know if the design has improved or not at this point.



  • Necro time again!

    I've been messing with WPF, MVVM, MEF, etc. lately, so I decided to check out your code. Everything just works, and it definitely helped my understanding. So to say thanks, I thought I'd contribute back.

    I agree with @cartman82 about the "icky" factor of the navigation. I also think your solution to the "icky" factor seems too complex (I haven't actually seen the code, though).

    I thought, why not mimic the way Prism does navigation, but with none of the overhead. Here's what I came up with:

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class InnerNavigationAttribute : ExportAttribute
    {
    
        public InnerNavigationAttribute() : base(typeof(IView)) { }
    
    }
    
    [Export]
    public class InnerNavigation
    {
    
        [ImportMany]
        public Lazy<IView>[] InnerNavigationViews { get; set; }
    
        private readonly Dictionary<object, Action<IView>> navigationActions = new Dictionary<object, Action<IView>>();
    
        public void Register(object region, Action<IView> navigationAction) {
            navigationActions.Add(region, navigationAction);
        }
    
        public void Navigate(object region, Type TView) {
            var result = InnerNavigationViews.FirstOrDefault(element => element.Value.GetType() == TView);
            if (result != null) {
                navigationActions[region](result.Value);
            }
        }
    
    }
    
    

    You just decorate your IViews with [InnerNavigation] without having to specify the owner at design time. Instead of an owner, you define a "region" that will host the navigation view (as in Prism). For convenience, you can define a class to hold these:

    public static class NavigationRegions
    {
        public static readonly object MainWindow = "MainWindow";
    }
    

    Each region needs to register it's handler, for example in the ViewModel's constructor:

    [ImportingConstructor]
    public MainWindowViewModel([Import] InnerNavigation navigation) {
    
        navigation.Register(NavigationRegions.MainWindow, control => CurrentView = control);
        navigation.Navigate(NavigationRegions.MainWindow, typeof(SomeView));
    
    }
    
    

    Just like the regions, you can also declare a class to store the types of your views:

    public static class NavigationViews
    {
        public static readonly Type SomeView = typeof(MyProject.Views.SomeView);
    }
    

    Then the navigation becomes

    navigation.Navigate(NavigationRegions.MainWindow, NavigationViews.SomeView);
    

    Now your views can be hosted anywhere, and one view can have multiple navigation regions.



  • Also, I like to keep things in separate assemblies, which required this change to the composition stuff:

    var directoryCatalog = new DirectoryCatalog(System.AppDomain.CurrentDomain.BaseDirectory);
    var asmCatalog = new AssemblyCatalog(typeof(App).Assembly);
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(directoryCatalog);
    catalog.Catalogs.Add(asmCatalog);
    var container = new CompositionContainer(catalog);
    container.ComposeParts();
    
    


  • The other person working on my project with me quit recently, because he's really burned out (can't find a job in the backward country he's in), but I'll post my new version some time since it's a bit nicer than it was at this point.

    I've completely done away with children knowing about parents, and navigation is completely done with navigation.Navigate<MainWindow>().To<SomeView>() now, which ends up being quite clean. Your idea is probably a good one, though I don't like naming regions or keeping a list of view names. Either way, it's way simpler than what I had.



  • @Magus said:

    I don't like naming regions or keeping a list of view names

    Yeah, that's what I borrowed from Prism. Blame Microsoft, not me!



  • Oh I do! I hate prism. This whole thread is because of that.

    Also, words of wisdom for anyone trying to use MEF for a Windows app:

    The Microsoft.Composition package is awesome, but it has some changes, namely that the default setting is nonshared, so you have to add both SharedAttribute and ExportAttribute to get the same effect as a simple ExportAttribute in standard MEF, but I consider this a really good change.



  • So, time has passed, and I realized I hadn't posted the newer versions of things, so here they are.

    Obviously, neither DelegateCommand nor NotificationObject has not changed.

    Basically, I've added this interface:

    public interface INavigationContext
    {
      TView To<TView>() where TView : Control, IView;
    }
    

    My navigation class, (using the current MEF for windows store!) looks like this:

    [Export]
    [Shared]
    public class Navigation
    {
      private IEnumerable<Control> views;
      private readonly Dictionary<Type, Func<Control, Control>> navigationContexts = new Dictionary<Type, Func<Control, Control>>();
    
      public void Initialize(IEnumerable<Control> views)
      {
        this.views = views;
      }
    
      public void Subscribe<TContext>(Func<Control, Control> assigner)
        where TContext : Control
      {
        navigationContexts[typeof(TContext)] = assigner;
      }
    
      public INavigationContext Navigate<TContext>()
      {
        return new NavigationContext(navigationContexts[typeof(TContext)], this);
      }
    
      private sealed class NavigationContext : INavigationContext
      {
        private readonly Func<Control, Control> assigner;
        private readonly Navigation navigation;
    
        public NavigationContext(Func<Control, Control> assigner, Navigation navigation)
        {
          this.assigner = assigner;
          this.navigation = navigation;
        }
    
        public TView To<TView>()
          where TView : Control, IView
        {
          return assigner(navigation.views.FirstOrDefault(view => view is TView)) as TView;
        }
      }
    }
    

    The result is that now navigation is called as stated above, navigation.Navigate<HostThing>().To<SomePanel>()

    It has to be initialized at the composition root, but I don't mind that too much.

    EDIT: Decided to fix it with syntax highlighting. Discourse decided to 'help'.


Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.