3
\$\begingroup\$

Currently, I'm trying to learn every aspect of the MVVM design pattern. So basically I've written a little WPF App where I'm trying to understand Navigation in MVVM, the app has one MainView wich controls with a ContentControl the other Views. Even tho I've read so many things about MVVM I'm never comfortable with my Code, my code works fine but a little voice in my head keeps saying that I'm doing MVVM completely wrong. I spend so much time reading about MVVM because I really don't want to break the pattern.

Starting the Application and loading the ApplicationModel

 public partial class App {

        private void App_OnStartup(object sender, StartupEventArgs e) {

            ApplicationService.InitializeApplicationDirectorys();
            var applicationModel = ApplicationService.LoadApplicationModel() ?? 
                new ApplicationModel {
                    Settings = new SettingsModel {
                        WindowHeight = 350,
                        WindowLeft = 100,
                        WindowTop = 100,
                        WindowWidth = 500
                    }
                };
            var mainView = new MainView(applicationModel);
            mainView.Show();
        }
    } 

MainView.xaml

<Window x:Class="Test.Views.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        x:Name="Window"
        mc:Ignorable="d"
        Left="{Binding  ApplicationModel.Settings.WindowLeft, Mode=TwoWay}"
        Top="{Binding ApplicationModel.Settings.WindowTop, Mode=TwoWay}"
        Height="{Binding ApplicationModel.Settings.WindowHeight, Mode=TwoWay}" 
        Width="{Binding ApplicationModel.Settings.WindowWidth, Mode=TwoWay}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Closing">
            <i:InvokeCommandAction Command="{Binding ViewClosingCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>

    <Grid>
       <ContentControl Content="{Binding ContentWindow}"/>
    </Grid>

    <Button Text="Change View" Command="{Binding ChangeViewCommand}" CommandParameter="{Binding SelectedItem}"/>
</Window>

MainView.cs

public partial class MainView {  
            public MainView(ApplicationModel applicationModel) {
                InitializeComponent();
                DataContext = new MainViewModel(applicationModel);
            }
        }

MainViewModel.cs

public class MainViewModel : BaseViewModel {

        public MainViewModel(ApplicationModel applicationModel) {
            ApplicationModel = applicationModel;
            Initialize();
        }

        private ApplicationModel _applicationModel;
        private UserControl _contentWindow;
        private string _selectedItem;
        private DownloadService _downloadService;
        private DownloadView _downloadView;
        private SettingsView _settingsView;
        private HomeView _homeView;

        public ApplicationModel ApplicationModel {
            get { return _applicationModel; }
            set {
                _applicationModel = value;
                OnPropertyChanged(nameof(ApplicationModel));
            }
        }

        public UserControl ContentWindow {
            get { return _contentWindow; }
            set {
                _contentWindow = value;
                OnPropertyChanged(nameof(ContentWindow));
            }
        }

        public object SelectedItem {
            get { return _selectedItem; }
            set {
                _selectedItem = value;
                OnPropertyChanged(nameof(SelectedItem));
            }
        }

        public DownloadService DownloadService {
            get { return _downloadService; }
            set {
                _downloadService = value; 
                OnPropertyChanged(nameof(DownloadService));
            }
        }

        public DownloadView DownloadView {
            get { return _downloadView; }
            set {
                _downloadView = value;
                OnPropertyChanged(nameof(DownloadView));
            }
        }

        public SettingsView SettingsView {
            get { return _settingsView; }
            set {
                _settingsView = value;
                OnPropertyChanged(nameof(SettingsView));
            }
        }

        public HomeView HomeView {
            get { return _homeView; }
            set {
                _homeView = value; 
                OnPropertyChanged(nameof(HomeView));
            }
        }

        public CommandHandler<object> ViewClosingCommand {
            get {
                return new CommandHandler<object>(o => {
                    ApplicationService.SaveApplicationModel(ApplicationModel);
                });
            }
        }
        public CommandHandler<string> ChangeViewCommand {
            get {
                return new CommandHandler<string>(o => {
                    switch (SelectedItem) {
                        case nameof(Downloads):
                            ContentWindow = new DownloadView(ApplicationModel);                        
                            return;
                        case nameof(Settings):
                            ContentWindow = new SettingsView(ApplicationModel);
                            return;
                        case nameof(Home):
                            ContentWindow = new HomeView(ApplicationModel);
                            break;
                    }
                });
            }
        }

        private void Initialize() {
            ContentWindow = new HomeView(ApplicationModel);
        }
    }
\$\endgroup\$
2
\$\begingroup\$

One of the basic principals of MVVM is, that the view model doesn't know the view. My experience is, that this principal can be (and should be) applied for the most cases. However, it is possible that complex views or third party controls, where some functions are not conform to MVVM, require lots of boilerplate code to make it MVVM conform. IMHO that cases legitimize to break the pattern.


But now to your code...

Actually, it is not so far away from the MVVM way to implement such a navigation. You are working with a ContentWindow property that changes depending on the selected item. That mechanism is OK, but the view model exposes the views directly.

Normally, the MainViewModel would expose another (child) view model that will be represented by a view.

In your case, you can just create a DownloadViewModel, a SettingsViewModel and a HomeViewModel and return the instance of the view models instead of the views.

In a second step, you can define the visual representations of the view models by associating the view model's type with a view. That can be done in the ResourcesDictionary of the ContentControl via data templates.

For Example:

<ContentControl Content="{Binding ContentViewModel}">
    <ContentControl.Resources>
        <DataTemplate DataType="{x:Type viewModels:DownloadViewModel}">
            <DownloadView />
        </DataTemplate>

        <DataTemplate DataType="{x:Type viewModels:SettingsViewModel}">
            <SettingsView />
        </DataTemplate>

         ...

    </ContentControl.Resources>
</ContentControl>

Note that there is no need to pass the view model to the view's constructor because it is automatically bound to the view's data context.

\$\endgroup\$
  • \$\begingroup\$ Thanks for this great answer, I'm glad that what I learned recently is confirm with what you're describing. \$\endgroup\$ – iNCEPTiON_ Feb 19 '17 at 22:51
1
\$\begingroup\$

I've only really started looking at MVVM and WPF in the last couple of days, so I'm at the beginning of the learning curve. That said, your MainViewModel's ChangeViewCommand looks wrong to me.

My understanding is that the MVVM dependencies should essentially be:

View -> ViewModel -> Model.

What you've done is create a dependency going the other direction. So you have a view, creating a viewmodel, which in turn creates other views. This seems like it could easily turn into a circular dependency, which is bad.

I haven't yet found a solution I'm particularly happy with. One approach seems to be to use PRISM, but I haven't dived into that yet. At the moment, I'm imposing a project structure on myself which will force the issue. Essentially I'm creating a project for each element of the model (WPF project for Views, which references a class library for ViewModels, which in turn references a class library for Models). This prevents introducing unwanted dependencies going in the wrong direction.

\$\endgroup\$
  • \$\begingroup\$ I haven't yet found a solution I'm particularly happy with. becasue there is none. MVVM is like trying to applay a pattern just for the sake of applying a pattern. It never really works. No matter how you build it, it always feels wrong and has some drawbacks. \$\endgroup\$ – t3chb0t Jan 20 '17 at 21:19
  • \$\begingroup\$ Thanks for the reply! Oh I never thought it that way but I need the ApplicationModel in every View because it has Data that I need in order for the application to Work, so I thought passing it with the constructor would be the propitiate way. You changed my view of MVVM and left me with a question mark :) \$\endgroup\$ – iNCEPTiON_ Jan 20 '17 at 21:24
  • \$\begingroup\$ I too am just getting into MVVM. I'm focusing on is the code unit testable and design time building possible. Otherwise why worry about breaking the pattern. \$\endgroup\$ – flux Jan 21 '17 at 12:24
  • \$\begingroup\$ @t3chb0t MVVM and it precursors have been around for decades, I originally learned it as GOD Gui Objects Data, the concept that Isolating different elements of your application into layers with simplified interfaces has been around almost as long as programming and if used correctly always works, if it never really works for you then that suggests you haven't got the right idea about what they are trying to achieve \$\endgroup\$ – MikeT Apr 11 '17 at 11:59
  • \$\begingroup\$ @forsvarir if you have only just started looking at MVVM i suggest reading this msdn.microsoft.com/en-gb/library/hh848246.aspx Prism is basically a common set of Boiler plate classes that save you having to write your own, as always with boiler plate code its very useful but limited to what has been envisioned with it, and you may have to write your own classes that do the same thing in a different way to meet your own needs \$\endgroup\$ – MikeT Apr 11 '17 at 12:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.