A Simple MVVM Example[Forward]

In my opinion, if you are using WPF or Silverlight you should be using the MVVM design pattern. It is perfectly suited to the technology and allows you to keep your code clean and easy to maintain.

The problem is, there are a lot of online resources for MVVM, each with their own way of implementing the design pattern and it can be overwhelming. I would like to present MVVM in the simplest way possible using just the basics.

So lets start at the beginning.

MVVM

MVVM is short for Model-View-ViewModel.

Models are simple class objects that hold data. They should only contain properties and property validation. They are not responsible for getting data, saving data, click events, complex calculations, business rules, or any of that stuff.

Views are the UI used to display data. In most cases, they can be DataTemplates which is simply a template that tells the application how to display a class. It is OK to put code behind your view IF that code is related to the View only, such as setting focus or running animations.

ViewModels are where the magic happens. This is where the majority of your code-behind goes: data access, click events, complex calculations, business rules validation, etc. They are typically built to reflect a View. For example, if a View contains a ListBox of objects, a Selected object, and a Save button, the ViewModel will have an ObservableCollection ObectList, Model SelectedObject, and ICommand SaveCommand.

MVVM Example

I’ve put together a small sample showing these 3 layers and how they relate to each other. You’ll notice that other than property/method names, none of the objects need to know anything about the others. Once the interfaces have been designed, each layer can be built completely independent of the others.

Sample Model

For this example I’ve used a Product Model. You’ll notice that the only thing this class contains is properties and change notification code.

Usually I would also implement IDataErrorInfo here for property validation, however I have left this out for now.

    public class ProductModel : ObservableObject
    {
        #region Fields

        private int _productId;
        private string _productName;
        private decimal _unitPrice;

        #endregion // Fields

        #region Properties

        public int ProductId
        {
            get { return _productId; }
            set
            {
                if (value != _productId)
                {
                    _productId = value;
                    OnPropertyChanged("ProductId");
                }
            }
        }

        public string ProductName
        {
            get { return _productName; }
            set
            {
                if (value != _productName)
                {
                    _productName = value;
                    OnPropertyChanged("ProductName");
                }
            }
        }

        public decimal UnitPrice
        {
            get { return _unitPrice; }
            set
            {
                if (value != _unitPrice)
                {
                    _unitPrice = value;
                    OnPropertyChanged("UnitPrice");
                }
            }
        }

        #endregion // Properties
    }

The class inherits from ObservableObject, which is a custom class I use to avoid having to rewrite the property change notification code repeatedly. I would actually recommend looking into Microsoft PRISM’s NotificationObject  or MVVM Light’s ViewModelBase which does the same thing once you are comfortable with MVVM, but for now I wanted to keep 3rd party libraries out of this and to show the code.

    public abstract class ObservableObject : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Members

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object‘s PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);

            if (this.PropertyChanged != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                this.PropertyChanged(this, e);
            }
        }

        #endregion // INotifyPropertyChanged Members

        #region Debugging Aides

        /// <summary>
        /// Warns the developer if this object does not have
        /// a public property with the specified name. This
        /// method does not exist in a Release build.
        /// </summary>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public virtual void VerifyPropertyName(string propertyName)
        {
            // Verify that the property name matches a real,
            // public, instance property on this object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;

                if (this.ThrowOnInvalidPropertyName)
                    throw new Exception(msg);
                else
                    Debug.Fail(msg);
            }
        }

        /// <summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might
        /// override this property‘s getter to return true.
        /// </summary>
        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        #endregion // Debugging Aides
    }

In addition to the INotifyPropertyChanged methods, there is also a debug method to validate the PropertyName. This is because the PropertyChange notification gets passed in as a String, and I have caught myself forgetting to change this string when I change the name of a Property.

NoteThe PropertyChanged notification exists to alert the View that a value has changed so it knows to update. I have seen suggestions to drop it from the Model and to expose the Model’s properties to the View from the ViewModel instead of the Model, however I find in most cases this complicates things and requires extra coding. Exposing the Model to the View via the ViewModel is much simpler, although either method is valid.

Sample ViewModel

I am doing the ViewModel next because I need it before I can create the View. This should contain everything the User would need to interact with the page. Right now it contains 4 properties: a ProductModel, a GetProduct command, a SaveProduct command, an a ProductId used for looking up a product.

    public class ProductViewModel : ObservableObject
    {
        #region Fields

        private int _productId;
        private ProductModel _currentProduct;
        private ICommand _getProductCommand;
        private ICommand _saveProductCommand;

        #endregion

        #region Public Properties/Commands

        public ProductModel CurrentProduct
        {
            get { return _currentProduct; }
            set
            {
                if (value != _currentProduct)
                {
                    _currentProduct = value;
                    OnPropertyChanged("CurrentProduct");
                }
            }
        }

        public ICommand SaveProductCommand
        {
            get
            {
                if (_saveProductCommand == null)
                {
                    _saveProductCommand = new RelayCommand(
                        param => SaveProduct(),
                        param => (CurrentProduct != null)
                    );
                }
                return _saveProductCommand;
            }
        }

        public ICommand GetProductCommand
        {
            get
            {
                if (_getProductCommand == null)
                {
                    _getProductCommand = new RelayCommand(
                        param => GetProduct(),
                        param => ProductId > 0
                    );
                }
                return _getProductCommand;
            }
        }

        public int ProductId
        {
            get { return _productId; }
            set
            {
                if (value != _productId)
                {
                    _productId = value;
                    OnPropertyChanged("ProductId");
                }
            }
        }

        #endregion

        #region Private Helpers

        private void GetProduct()
        {
            // You should get the product from the database
            // but for now we‘ll just return a new object
            ProductModel p = new ProductModel();
            p.ProductId = ProductId;
            p.ProductName = "Test Product";
            p.UnitPrice = 10.00;
            CurrentProduct = p;
        }

        private void SaveProduct()
        {
            // You would implement your Product save here
        }

        #endregion
    }

There is another new class here: the RelayCommand. This is essential for MVVM to work. It is a command that is meant to be executed by other classes to run code in this class by invoking delegates. Once again, I’d recommend checking out the MVVM Light Toolkit’s version of this command when you are more comfortable with MVVM, but I wanted to keep this simple so have included this code here.

    /// <summary>
    /// A command whose sole purpose is to relay its functionality to other
    /// objects by invoking delegates. The default return value for the
    /// CanExecute method is ‘true‘.
    /// </summary>
    public class RelayCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        #endregion // Fields

        #region Constructors

        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// Creates a new command.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        #endregion // Constructors

        #region ICommand Members

        [DebuggerStepThrough]
        public bool CanExecute(object parameters)
        {
            return _canExecute == null ? true : _canExecute(parameters);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameters)
        {
            _execute(parameters);
        }

        #endregion // ICommand Members
    }

Sample View

And now the Views. These are DataTemplates which define how a class should be displayed to the User. There are many ways to add these templates to your application, but the simplest way is to just add them to the startup window’s Resources.

<Window.Resources>
    <DataTemplate DataType="{x:Type local:ProductModel}">
        <Border BorderBrush="Black" BorderThickness="1" Padding="20">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>

                <TextBlock Grid.Column="0" Grid.Row="0"  Text="ID" VerticalAlignment="Center" />
                <TextBox Grid.Row="0" Grid.Column="1"  Text="{Binding ProductId}" />

                <TextBlock Grid.Column="0" Grid.Row="1"  Text="Name" VerticalAlignment="Center" />
                <TextBox Grid.Row="1" Grid.Column="1"  Text="{Binding ProductName}" />

                <TextBlock Grid.Column="0" Grid.Row="2"  Text="Unit Price" VerticalAlignment="Center" />
                <TextBox Grid.Row="2" Grid.Column="1"  Text="{Binding UnitPrice}" />

            </Grid>
        </Border>
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:ProductViewModel}">
        <DockPanel Margin="20">
            <DockPanel DockPanel.Dock="Top">
                <TextBlock Margin="10,2" DockPanel.Dock="Left" Text="Enter Product Id" VerticalAlignment="Center" />

                <TextBox Margin="10,2" Width="50" VerticalAlignment="Center" Text="{Binding Path=ProductId, UpdateSourceTrigger=PropertyChanged}" />

                <Button Content="Save Product" DockPanel.Dock="Right" Margin="10,2" VerticalAlignment="Center"
                        Command="{Binding Path=SaveProductCommand}" Width="100" />

                <Button Content="Get Product" DockPanel.Dock="Right" Margin="10,2" VerticalAlignment="Center"
                        Command="{Binding Path=GetProductCommand}" IsDefault="True" Width="100" />
            </DockPanel>

            <ContentControl Margin="20,10" Content="{Binding Path=CurrentProduct}" />
        </DockPanel>
    </DataTemplate>
</Window.Resources>

The View defines two DataTemplates: one for the ProductModel, and one for the ProductViewModel. You’ll need to add a namespace reference  to the Window definition pointing to your Views/ViewModels so you can define the DataTypes. Each DataTemplate only binds to properties belonging to the class it is made for.

In the ViewModel template, there is a ContentControl that is bound to ProductViewModel.CurrentProduct. When this control tries to display the CurrentProduct, it will use the ProductModel DataTemplate.

Starting the Sample

And finally, to start the application add the following on startup:

MainWindow app = new MainWindow();
ProductViewModel viewModel = new ProductViewModel();
app.DataContext = viewModel;
app.Show();

This is found in the code behind the startup file – usually App.xaml.cs.

This creates your Window (the one with the DataTemplates defined in Window.Resources), creates a ViewModel, and it sets the Window’s DataContext to the ViewModel.

And there you have it. A basic look at MVVM.

UPDATE
Sample code can be found here.

Notes

There are many other ways to do the things shown here, but I wanted to give you a good starting point before you start diving into the confusing world of MVVM.

The important thing to remember about using MVVM is your Forms, Pages, Buttons, TextBoxes, etc (the Views) are NOT your application. Your ViewModels are. The Views are merely a user-friendly way to interact with your ViewModels.

So if you want to change pages, you should not be changing pages in the View, but instead you should be setting something like the AppViewModel.CurrentPage = YourPageViewModel. If you want to run a Save method, you don’t put that behind a button’s Click event, but rather bind the Button.Command to a ViewModel’s ICommand property.

I started with  Josh Smith’s article on MVVM, which was a good read but for a beginner like me, some of these concepts flew right over my head.

I’ve never done a blog or tutorial before, but I noticed there is a lot of confusion about what MVVM is and how to use it. Since I struggled through the maze of material online to figure out what MVVM is and how its used, I thought I’d try and write a simpler explanation. I hope this clarifies things a bit and doesn’t make it worse :)

>> Next – Navigation with MVVM

时间: 2024-12-30 00:07:37

A Simple MVVM Example[Forward]的相关文章

【转载MVVM模式的简介】

原文链接:http://www.cnblogs.com/sirkevin/archive/2012/11/28/2793471.html 使用WPF+Mvvm开发一年多,期间由于对Mvvm模式的理解不足,遇到了很多问题,也绕了很多弯子:网上提供的Mvvm的示例比较简单,实际项目中的需求也各种各样. 不过经过几个项目,也有了一些对Mvvm模式的理解:1. Mvvm是什么,Mvvm是怎么来的? Mvvm模式广泛应用在WPF项目开发中,使用此模式可以把UI和业务逻辑分离开,使UI设计人员和业务逻辑人员

MVVM开发模式简单实例MVVM Demo

本文主要是翻译Rachel Lim的一篇有关MVVM模式介绍的博文 A Simple MVVM Example 并具体给出了一个简单的Demo(原文是以WPF开发的,对于我自己添加的一部分会用红色标注) 现在开始: 在我看来,如果你使用的是WPF或Sliverlight来开发程序就应该使用MVVM设计模式.它是你的代码清晰明了并易于维护. 可问题是网上有很多有关MVVM模式的资源都有自己强大的实现方式.这里我将介绍最基础的MVVM设计模式的实现方法. MVVM  (是Model-View-Vie

WPF MVVM模式的一些理解

/*本文转自 http://www.cnblogs.com/sirkevin/archive/2012/11/28/2793471.html */ 使用WPF+Mvvm开发一年多,期间由于对Mvvm模式的理解不足,遇到了很多问题,也绕了很多弯子:网上提供的Mvvm的示例比较简单,实际项目中的需求也各种各样.不过经过几个项目,也有了一些对Mvvm模式的理解: 1. Mvvm是什么,Mvvm是怎么来的?Mvvm模式广泛应用在WPF项目开发中,使用此模式可以把UI和业务逻辑分离开,使UI设计人员和业务

mvvm的理解

1. Mvvm是什么,Mvvm是怎么来的?Mvvm模式广泛应用在WPF项目开发中,使用此模式可以把UI和业务逻辑分离开,使UI设计人员和业务逻辑人员能够分工明确. Mvvm模式是根据MVP模式来的,可以简单的说,Mvvm模式就是WPF版的MVP模式.MVP模式,MVC模式,这几个模式都是为了抽离出UI逻辑和业务逻辑. 2. 使用Mvvm模式可以参考的主流框架及简单介绍.主流开源框架:Simple Mvvm,Mvvm Light和Prism.Simple Mvvm和Mvvm Light基本一致,都

非常漂亮的一个验证实例

Attributes-based Validation in a WPF MVVM Application Jeremy Alles, 28 Jul 2010 CPOL otes of 3 or less require a comment Description of a method which uses attribute to perform user entry validation in a WPF MVVM application Download demo - 30.81 KB

php单元测试到底是什么东西呢?

前言: 真正写php代码也有3年时间了,勉强算是一个php程序员, 但是,心底却一直没有底气. 都说测试驱动开发,可我连程序开发中什么是单元测试?这种基本的程序员的素养都 还不是很清楚,痛定思痛,决定这些基本的知识技能还是要有所了解和掌握.要不然,一直用着别人现成的框架,写着一些简单的业务逻辑代码,宝宝心里其实是慌的 :). 这篇文章不错:https://www.sitepoint.com/tutorial-introduction-to-unit-testing-in-php-with-php

.NET Open Source Developer Projects

There are many .NET open source developer projects. This list is intended to provide links to projects on CodePlex or it GitHub.  The recent .NET Core Open Source is on GitHub .NET Implementations .NET Core - Core .NET Framework Mono Project - Cross-

.NET 开源开发项目

本文列出了 .NET 开源开发项目(open source developer projects).意在包括对开发过程的所有方面有所帮组的项目.对于消费项目(consumerprojects),请参阅.NET开源消费项目清单. 下面按字母排序,并提供一行文字说明.GitHub/CodePlex(或其他)链接优先. .NET 实现 .NET Core - Core .NET 框架 C# Native – 把 C# 编译成本地代码. Cosmos - C# 开源的管理操作系统,一个操作系统是"con

A Complete List of .NET Open Source Developer Projects

http://scottge.net/2015/07/08/a-complete-list-of-net-open-source-developer-projects/?utm_source=tuicool NET Implementations .NET Core – Core .NET Framework C# Native – Compiles C# to native. Cosmos – C# Open Source Managed Operating System, an operat