在WPF中实现MVVM模式的正确方法

时间:2017-10-25 11:27:25

标签: c# .net wpf xaml mvvm

我刚刚开始学习来自Java Swing和WinForms的WPF。我决定尝试新的东西来学习开发程序的其他概念和技术。上次,我介绍了MVC Pattern的概念。根据我所学到的,它是一种分离UI逻辑,业务逻辑和数据的方法。我发现WPF的一个关键概念是Binding和MVVM模式。

这是我尝试实现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>

我在ViewModel中创建了一个Model和View并对其进行了操作。我不确定这是否是实现MVVM的正确方法,或者它甚至是MVVM,因为我将其视为MVC模式。

在维基百科上,它说:

组件包括Model,View,ViewModel和Binder。

enter image description here

我的代码的这一部分显示了一个带横幅的窗口,横幅的最右边部分是显示日期和时间的标签。它可以工作,但我担心的是,我实现它的方式实际上是遵循MVVM模式。

3 个答案:

答案 0 :(得分:4)

为了MVVM,View Model不应该包含对View的引用(被认为是不好的做法)

View是否知道ViewModel而不是相反。 View知道ViewModel,而ViewModel又知道模型(或模型)

应该在ViewModel中实现INotifyPropertyChanged接口,以允许视图通过绑定更新自身(在某些情况下,实现模型上的接口也是完全合法的。)

请记住,ViewModel可以看作是一个适合View需要的Model,所以考虑到这一点,我更喜欢将Model类保留为简单的POCO对象,并在其上编写INotifyPropertyChanged实现。视图模型

ViewModel成为View的DataContext(您可以在View后面或xaml中的代码中的View构造函数中分配DataContext。)

要浏览视图,您可以使用(至少)2种方法

您应该决定是使用View-first方法还是ViewModel优先方法。

在View-First方法中,当您想要导航到新页面时,您创建一个View,并且某个机制(binder)将创建相应的ViewModel(进而创建或获取模型)

在ViewModel第一种方法中,您创建一个新的ViewModel(然后创建或获取模型),绑定器将创建相应的视图。

根据我所说的,这是一个例子:

查看(MainWindowView.cs),我们分配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) => { };
 }

另外,我认为你应该看看像prismcaliburn micro这样的框架(我更喜欢第一个)来帮助你正确实现MVVM模式而不是重新发明轮子(如加上你还会得到一个导航系统,在视图之间导航。

答案 1 :(得分:3)

你的问题很广泛,但这里有一些想法。

视图模型不应该知道有关视图的任何信息。您应该只将MainWindowViewModel的{​​{1}}设置为视图模型的实例,而不是将MainWindow注入DataContext,而不是MainWindow

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

当视图绑定到视图模型时,MainWindowViewModel可以初始化和/或与模型通信。

此外,视图模型不应公开任何UIElements,例如UserControlUIElements在视图中定义。

答案 2 :(得分:0)

经过几天的研究,我终于“吸收”了MVVM的概念及其与MVC的区别。

模型-大多数情况下,它们只是具有属性的类。像UserProduct等...

视图-用户界面。 RegisterUserViewLoginViewAddProductView

ViewModel-这是执行操作的地方。 ViewModel根据需求/规则操作模型,并为View公开模型。但是ViewModel不知道View的存在。

绑定-这是ViewViewModel之间的粘合剂。

与MVC相比(我认为),

    View中的
  • MVC是被动的,而View中的MVVM是主动的。 Controller中的MVC决定应在View中显示哪些内容,而在MVVM中,View执行绑定,使其对应该显示的内容负责。

WPF非常痛苦,但确实功能强大。