【问题标题】:Proper way of implementing MVVM Pattern in WPF在 WPF 中实现 MVVM 模式的正确方法
【发布时间】:2017-10-25 11:27:25
【问题描述】:

我刚开始学习来自 Java Swing 和 WinForms 的 WPF。我决定尝试一些新的东西来学习其他开发程序的概念和技术。上次介绍了 MVC Pattern 的概念。据我所知,这是一种分离 UI 逻辑、业务逻辑和数据的方法。我发现 WPF 的关键概念之一是绑定和 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 中操作它们。我不确定这是否是实现 MVVM 的正确方法,或者它甚至是 MVVM,因为我将其视为 MVC 模式。

在维基百科上,它说:

组件是 Model、View、ViewModel 和 Binder。

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

【问题讨论】:

  • 模型应该只是一个具有数据属性的类(如果需要,可以使用方法)。视图模型应该是实现 INotifyPropertyChanged 的​​模型。视图后面的代码最多只能说 this.DataContext = ;
  • 这意味着我每次在 ViewModel 中更新数据时都必须调用 PropertyChanged?我需要将模型的声明移动到视图?
  • @Mr.Leeds 没有实际理由(除了个人喜好)不在模型类中实现 INotifyPropertyChanged。 Svcutil 之类的工具甚至可以通过这种方式生成您的模型。
  • 我有点困惑在哪里实现这些接口以及模型视图视图模型应该是什么样子。在我观看的一个教程中,程序员在每个属性的 set{} 上调用了 PropertyChanged。
  • 请注意,在现实生活中通常没有模型(我的意思是作为一个单独的类)。 Viewmodel 结合了自身和模型(即同时具有命令、数据等)。旁注:pce = new PropertyChangedEventArgs(""); 然后到处使用它是很奇怪的。您应该传递更改属性的名称,而不是空字符串。

标签: c# .net wpf xaml mvvm


【解决方案1】:

为了 MVVM,视图模型不应包含对视图的引用(被认为是不好的做法)

是知道 ViewModel 的 View 而不是相反的 View。 View 知道 ViewModel,而 ViewModel 又知道 Model(或 Models)

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

请记住,ViewModel 可以被视为适应视图需要的模型,因此考虑到这一点,我更愿意将模型类保留为简单的 POCO 对象并在 ViewModel 上编写 INotifyPropertyChanged 实现

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

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

您应该决定是需要 View-first 方法还是 ViewModel-first 方法。

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

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

根据我告诉你的,这里有一个例子:

View(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();
        }
    }
}

模型(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 模式,而不是重新发明轮子(另外,您将获得一个导航系统,在视图之间导航)。

【讨论】:

    【解决方案2】:

    您的问题非常广泛,但这里有一些想法。

    视图模型不应该对视图有任何了解。您应该简单地将MainWindowDataContext 设置为视图模型的实例,而不是注入MainWindowViewModel 引用MainWindow

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

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

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

    【讨论】:

    • 您能否提供一些来自MainWindowViewModel 及其xaml 的代码,其中包含名字和姓氏等两个属性?
    • 因为这样看起来干净利落!
    【解决方案3】:

    经过几个晚上的学习,我终于“吸收”了MVVM的概念以及它与MVC的区别。

    模型 - 大多数时候它们只是具有属性的类。比如UserProduct等等……

    视图 - 用户界面。 RegisterUserView, LoginView, AddProductView.

    ViewModel - 这是动作发生的地方。 ViewModel 根据需求/规则操作模型并将其公开给View。但是ViewModel不知道View的存在。

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

    与 MVC 相比(只是我的看法),

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

    WPF 很痛苦,但它真的很强大。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-06
      • 2018-04-13
      • 1970-01-01
      • 1970-01-01
      • 2013-07-26
      • 1970-01-01
      • 2016-04-26
      • 2012-11-11
      相关资源
      最近更新 更多