Proper way of implementing MVVM Pattern in WPF

2019-10-04 06:23发布

I just started learning WPF coming from Java Swing and WinForms. I decided to try something new to learn other concepts and technologies for developing programs. Last time, I was introduced on the concept of MVC Pattern. For what I have learned, it is a way of separating the UI logic, business logic, and data. I found out that one of the key concepts of WPF is Binding and the MVVM Pattern.

Here is a part of my code where i tried implementing MVVM.

MainWindowModel.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Controls;

namespace DocNanzDCMS.Model
{
    public class MainWindowModel : INotifyPropertyChanged
    {
        private PropertyChangedEventArgs pce;

        public MainWindowModel()
        {
            pce = new PropertyChangedEventArgs("");
        }

        private UserControl userControl;
        #region ControlProperty
        public UserControl ContentProperty {
            get
            {
                return userControl;
            }

            set
            {
                userControl = value;
                PropertyChanged(this, pce);
            }
        }
        #endregion

        private DateTime dateTime;
        #region DateProperty
        public String DateProperty
        {
            get
            {
                return dateTime.ToLongDateString();
            }
            set
            {
                dateTime = DateTime.Parse(value);
                PropertyChanged(this, pce);
            }
        }
        #endregion

        public String TimeProperty
        #region TimeProperty
        {
            get
            {
                return dateTime.ToLongTimeString();
            }
            set
            {
                dateTime = DateTime.Parse(value);
                PropertyChanged(this, pce);
            }
        }
        #endregion

        private String title;
        public String TitleProperty
        #region TitleProperty
        {
            get
            {
                return title;
            }
            set
            {
                title = value;
                PropertyChanged(this, pce);
            }
        }
        #endregion

        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
    }
}

MainWindowViewModel.cs

using DocNanzDCMS.Model;
using DocNanzDCMS.View;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace DocNanzDCMS.ViewModel
{
    public class MainWindowViewModel
    {
        private MainWindow mainWindow;
        private MainWindowModel mainWindowModel;
        private Thread mainWindowThread;

        private LoginModel loginModel;
        private LoginViewModel loginViewModel;
        private LoginView loginView;

        private String title;

        public MainWindowViewModel(MainWindowModel mainWindowModel, MainWindow mainWindow)
        {
            this.mainWindowModel = mainWindowModel;
            this.mainWindow = mainWindow;
            initialize();
        }

        private void initialize()
        {
            loginModel = new LoginModel();
            loginView = new LoginView();
            loginViewModel = new LoginViewModel(loginModel, loginView);

            mainWindow.DataContext = mainWindowModel;
            mainWindowThread = new Thread(BackgroundProcess);
            mainWindowThread.IsBackground = true;
            mainWindowThread.Start();

            gotoLogin();
        }

        private void BackgroundProcess()
        {
            while(true)
            {
                updateTitle();
                updateTime();
                try
                {
                    Thread.Sleep(100);
                }
                catch(ThreadInterruptedException e)
                {
                }
            }
        }

        public void gotoLogin()
        {
            mainWindowModel.ContentProperty = loginView;
            title = "Login";
        }

        private void updateTime()
        {
            mainWindowModel.DateProperty = DateTime.Now.ToString();
            mainWindowModel.TimeProperty = DateTime.Now.ToString();
        }

        public void updateTitle()
        {
            mainWindowModel.TitleProperty = "Doc Nanz Dental | "+title;
        }
    }
}

MainWindow.cs

using DocNanzDCMS.Model;
using DocNanzDCMS.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace DocNanzDCMS
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MainWindowModel mainWindowModel;
        private MainWindowViewModel mainWindowViewModel;

        public MainWindow()
        {
            InitializeComponent();
            initializeApp();
        }

        private void initializeApp()
        {
            mainWindowModel = new MainWindowModel();
            mainWindowViewModel = new MainWindowViewModel(mainWindowModel, this);
        }
    }
}

MainWindow.xaml

<Window x:Class="DocNanzDCMS.MainWindow"
        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:local="clr-namespace:DocNanzDCMS"
        mc:Ignorable="d"
        Title="{Binding TitleProperty}" Height="600" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="75"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <!--Banner-->
        <Grid Grid.Row="0" Background="AliceBlue">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="225"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="200"/>
            </Grid.ColumnDefinitions>
            <!--Date and Time Panel-->
            <Grid Grid.Column="2" Background="Aquamarine">
                <Grid.RowDefinitions>
                    <RowDefinition Height="1.5*"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                <!--Date Background-->
                <StackPanel Grid.Row="0" Background="BurlyWood"/>
                <!--Date-->
                <Label Grid.Row="0" Content="{Binding DateProperty}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                <!--Time Background-->
                <StackPanel Grid.Row="1" Background="BlanchedAlmond"/>
                <!--Time-->
                <Label Grid.Row="1" Content="{Binding TimeProperty}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Grid>
        </Grid>
        <!--Content-->
        <ScrollViewer Grid.Row="1" Content="{Binding ContentProperty}"/>
        <!--Status Bar-->
        <Grid Grid.Row="2">

        </Grid>
    </Grid>
</Window>

I created a Model and View and manipulated those in ViewModel. I am not sure if this is a proper way of implementing MVVM or is it even an MVVM, because I am seeing it as MVC pattern.

On Wikipedia, it says:

The components are, Model, View, ViewModel, and Binder.

enter image description here

This part of my code displays a window with a banner, and on rightmost part of the banner are labels that displays date and time. It works, but my concern is if the way I made it is actually following MVVM pattern.

标签: c# .net wpf xaml mvvm
3条回答
甜甜的少女心
2楼-- · 2019-10-04 06:34

For the sake of MVVM the View Model should not contain a reference to the View (is considered bad practice)

Is the View that know the ViewModel and not the opposite. The View knows the ViewModel which in turn know the Model (or Models)

The INotifyPropertyChanged interface should be implemented in the ViewModel to permits the view to update itself through binding (in some circustances is perfectly legit to implement the interface on the Model also).

Keep in mind that the ViewModel can be seen as a Model adapted to the need of the View, so with this in mind i prefer to leave the Model classes as simple POCO objects and write the INotifyPropertyChanged implementation on the ViewModel

The ViewModel become the DataContext of the View (you can assign the DataContext in the View's constructor in code behind or in the xaml).

For navigating through views you could use (at minimum) 2 approaches

You should decide if you want a View-first approach or a ViewModel-first approach.

In the View-First approach when you want to navigate to a new page you create a View and some mechanism (the binder) will create the respective ViewModel (which in turn create or obtain the Model)

In the ViewModel first approach you create a new ViewModel (which in turn create or obtain the Model) and the binder will create the respective View.

Based on what i told you, here is an example:

View (MainWindowView.cs), we assign the DataContext:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainWindowViewModel()
    }
}

ViewModel (MainWindowViewModel.cs):

namespace DocNanzDCMS.ViewModel
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        private MainWindowModel mainWindowModel;
        public Model {get {return mainWindowModel;}}

        public MainWindowViewModel()
        {
            this.mainWindowModel = new mainWindowModel();
        }
    }
}

Model (MainWindowModel.cs) :

public class MainWindowModel
{
        private PropertyChangedEventArgs pce;

        public MainWindowModel()
        {
            pce = new PropertyChangedEventArgs("");
        }

        private UserControl userControl;
        #region ControlProperty
        public UserControl ContentProperty {
            get
            {
                return userControl;
            }

            set
            {
                userControl = value;
                PropertyChanged(this, pce);
            }
        }
        #endregion

        private DateTime dateTime;
        #region DateProperty
        public String DateProperty
        {
            get
            {
                return dateTime.ToLongDateString();
            }
            set
            {
                dateTime = DateTime.Parse(value);
                PropertyChanged(this, pce);
            }
        }
        #endregion

        public String TimeProperty
        #region TimeProperty
        {
            get
            {
                return dateTime.ToLongTimeString();
            }
            set
            {
                dateTime = DateTime.Parse(value);
                PropertyChanged(this, pce);
            }
        }
        #endregion

        private String title;
        public String TitleProperty
        #region TitleProperty
        {
            get
            {
                return title;
            }
            set
            {
                title = value;
                PropertyChanged(this, pce);
            }
        }
        #endregion

        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
 }

Also, i think you should look at some framework like prism or caliburn micro (i prefer the first one) to assist you in the correct implementation on the MVVM pattern and not reinventing the wheel (as a plus you will get also a navigation system, to navigate between views).

查看更多
Emotional °昔
3楼-- · 2019-10-04 06:46

Your question is very broad but here are some thoughts.

A view model shouldn't know anything about the view. Instead of injecting the MainWindowViewModel with a reference to the MainWindow, you should simply set the DataContext of the MainWindow to an instance of the view model:

public MainWindow()
{
        InitializeComponent();
        DataContext = new MainWindowViewModel();
}

The MainWindowViewModel can then initialize and/or communicate with the model while the view binds to the view model.

Also, a view model shouldn't expose any UIElements such as for example a UserControl. UIElements are defined in the view.

查看更多
兄弟一词,经得起流年.
4楼-- · 2019-10-04 06:50

After several nights of studying, I finally "absorbed" the concept of MVVM and its difference with MVC.

Model - Most of the time they are just classes with properties. Like User, Product etc...

View - The user interface. RegisterUserView, LoginView, AddProductView.

ViewModel - This is where the action happens. ViewModel manipulates the model based on the requirements/rules and exposes it for View. But ViewModel does not know the existence of View.

Binding - This is the glue between the View and the ViewModel.

In comparison with MVC (Just my opinion),

  • View in MVC is passive while View in MVVM is active. The Controller in MVC decides what contents should be displayed in View, while in MVVM, View performs the binding making it responsible on what it should display.

WPF is so much pain, but it is really powerful.

查看更多
登录 后发表回答