走进WPF之MVVM完整案例

学习WPF如果不学MVVM,仿佛缺少了灵魂。那什么是MVVM呢?为什么要学MVVM呢,本以一个简单的增删改查的小例子,简述MVVM的基本知识及如何通过进行MVVM架构的程序开发,仅供学习分享使用,如有不足之处,还请指正。

什么是MVVM?

MVVM是Model-View-ViewModel的简写。它本质上就是MVC (Model-View- Controller)的改进版。即模型-视图-视图模型。分别定义如下:

  • 【模型】指的是后端传递的数据。
  • 【视图】指的是所看到的页面。
  • 【视图模型】mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:
    • 一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
    • 二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。

MVVM示意图如下所示:

安装MvvmLight插件

项目名称右键-->管理NuGet程序包-->搜索MvvmLight-->安装。如下所示:

 弹出接受许可证窗口,点击【接受】如下所示:

 MvvmLight安装成功后,自动引用需要的第三方库,并默认生成示例内容,有些不需要的需要删除,如下所示:

MVVM示例截图

主要通过MVVM实现数据的CRUD【增删改查】基础操作,如下所示:

 MVVM开发步骤

1. 创建Model层

本例主要是对学生信息的增删改查,所以创建Student模型类,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApp3.Models
{
    /// <summary>
    /// 学生类
    /// </summary>
    public class Student
    {
        /// <summary>
        /// 唯一标识
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// 学生姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 年龄
        /// </summary>
        public int Age { get; set; }

        /// <summary>
        /// 班级
        /// </summary>
        public string Classes { get; set; }
    }
}

2. 创建DAL层

为了简化示例,模拟数据库操作,构建基础数据,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfApp3.Models;

namespace WpfApp3.DAL
{
    public class LocalDb
    {
        private List<Student> students;

        public LocalDb() {
            init();
        }

        /// <summary>
        /// 初始化数据
        /// </summary>
        private void init() {
            students = new List<Student>();
            for (int i = 0; i < 30; i++)
            {
                students.Add(new Student()
                {
                    Id=i,
                    Name=string.Format("学生{0}",i),
                    Age=new Random(i).Next(0,100),
                    Classes=i%2==0?"一班":"二班"
                });
            }
        }

        /// <summary>
        /// 查询数据
        /// </summary>
        /// <returns></returns>
        public List<Student> Query()
        {
            return students;
        }

        /// <summary>
        /// 按名字查询
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public List<Student> QueryByName(string name)
        {
            return students.Where((t) => t.Name.Contains(name)).ToList();//FindAll((t) => t.Name.Contains(name));
        }

        public Student QueryById(int Id)
        {
            var student = students.FirstOrDefault((t) => t.Id == Id);
            if (student != null)
            {
                return new Student() {
                    Id=student.Id,
                    Name=student.Name,
                    Age=student.Age,
                    Classes=student.Classes
                };
            }
            return null;
        }


        /// <summary>
        /// 新增学生
        /// </summary>
        /// <param name="student"></param>
        public void AddStudent(Student student)
        {
            if (student != null)
            {
                students.Add(student);
            }
        }

        /// <summary>
        /// 删除学生
        /// </summary>
        /// <param name="Id"></param>
        public void DelStudent(int Id)
        {
            var student = students.FirstOrDefault((t) => t.Id == Id); //students.Find((t) => t.Id == Id);
            if (student != null)
            {
                students.Remove(student);
            }

        }
    }


}

3. 创建View层

View层与用户进行交互,用户数据的展示,及事件的响应。在本例中,View层主要有数据查询展示,新增及编辑页面。

在View层,主要是命令的绑定,及数据的绑定。

  1. 在DataGridTextColumn中通过Binding="{Binding Id}"的形式绑定要展示的列属性名。
  2. 在Button按钮上通过Command="{Binding AddCommand}"的形式绑定要响应的命令。
  3. 在TextBox文本框中通过Text="{Binding Search}"的形式绑定查询条件属性。

数据展示窗口,如下所示:

<Window x:Class="WpfApp3.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:WpfApp3"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="80"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0" Margin="5" VerticalAlignment="Center">
            <TextBlock Text="姓名:" Margin="10" Padding="5"></TextBlock>
            <TextBox x:Name="sname" Text="{Binding Search}" Width="120" Margin="10" Padding="5"></TextBox>
            <Button x:Name="btnQuery" Content="查询" Margin="10" Padding="5" Width="80" Command="{Binding QueryCommand}"></Button>
            <Button x:Name="btnReset" Content="重置" Margin="10" Padding="5" Width="80" Command="{Binding ResetCommand}"></Button>
            <Button x:Name="btnAdd" Content="创建" Margin="10" Padding="5" Width="80"  Command="{Binding AddCommand}"></Button>
        </StackPanel>
        <DataGrid x:Name="dgInfo" Grid.Row="1" AutoGenerateColumns="False" CanUserAddRows="False" CanUserSortColumns="False" Margin="10" ItemsSource="{Binding GridModelList}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Id" Width="100"  Binding="{Binding Id}"></DataGridTextColumn>
                <DataGridTextColumn Header="姓名" Width="100" Binding="{Binding Name}"></DataGridTextColumn>
                <DataGridTextColumn Header="年龄" Width="100" Binding="{Binding Age}"></DataGridTextColumn>
                <DataGridTextColumn Header="班级" Width="100" Binding="{Binding Classes}"></DataGridTextColumn>
                <DataGridTemplateColumn Header="操作" Width="*">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
                                <Button x:Name="edit" Content="编辑" Width="60" Margin="3" Height="25" CommandParameter="{Binding Id}" Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"></Button>
                                <Button x:Name="delete" Content="删除" Width="60" Margin="3" Height="25"  CommandParameter="{Binding Id}" Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"></Button>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

新增及编辑页面,如下所示:

<Window x:Class="WpfApp3.Views.StudentWindow"
        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:WpfApp3.Views"
        mc:Ignorable="d"
        Title="StudentWindow" Height="440" Width="500" AllowsTransparency="False" WindowStartupLocation="CenterScreen" WindowStyle="None">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="60"></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="60"></RowDefinition>
        </Grid.RowDefinitions>
        <TextBlock FontSize="30" Margin="10">修改学生信息</TextBlock>
        <StackPanel Grid.Row="1" Orientation="Vertical">
            <TextBlock FontSize="20" Margin="10" Padding="5">姓名</TextBlock>
            <TextBox x:Name="txtName" FontSize="20"  Padding="5" Text="{Binding Model.Name}"></TextBox>
            <TextBlock FontSize="20" Margin="10"  Padding="5">年龄</TextBlock>
            <TextBox x:Name="txtAge" FontSize="20"  Padding="5" Text="{Binding Model.Age}"></TextBox>
            <TextBlock FontSize="20" Margin="10"  Padding="5">班级</TextBlock>
            <TextBox x:Name="txtClasses" FontSize="20"  Padding="5" Text="{Binding Model.Classes}"></TextBox>
        </StackPanel>
        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Name="btnSave" Content="保存" Margin="10" FontSize="20" Width="100" Click="btnSave_Click" ></Button>
            <Button x:Name="btnCancel" Content="取消" Margin="10" FontSize="20" Width="100" Click="btnCancel_Click" ></Button>
        </StackPanel>
    </Grid>
</Window>

3. 创建ViewModel层

ViewModel层是MVVM的核心所在,起到承上启下的作用。ViewModel需要继承GalaSoft.MvvmLight.ViewModelBase基类。

ViewModel中属性实现数据的绑定,命令实现用户交互的响应。如下所示:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using WpfApp3.DAL;
using WpfApp3.Models;
using WpfApp3.Views;

namespace WpfApp3.ViewModel
{
    /// <summary>
    ///
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        #region 属性及构造函数

        private LocalDb localDb;

        private ObservableCollection<Student> gridModelList;

        public ObservableCollection<Student> GridModelList
        {
            get { return gridModelList; }
            set
            {
                gridModelList = value;
                RaisePropertyChanged();
            }
        }

        /// <summary>
        /// 查询条件
        /// </summary>
        private string search;

        public string Search
        {
            get { return search; }
            set
            {
                search = value;
                RaisePropertyChanged();
            }
        }


        /// <summary>
        ///
        /// </summary>
        public MainViewModel()
        {
            localDb = new LocalDb();
            QueryCommand = new RelayCommand(this.Query);
            ResetCommand = new RelayCommand(this.Reset);
            EditCommand = new RelayCommand<int>(this.Edit);
            DeleteCommand = new RelayCommand<int>(this.Delete);
            AddCommand = new RelayCommand(this.Add);
        }

        #endregion

        #region command

        /// <summary>
        /// 查询命令
        /// </summary>
        public RelayCommand QueryCommand { get; set; }

        /// <summary>
        /// 重置命令
        /// </summary>
        public RelayCommand ResetCommand { get; set; }

        /// <summary>
        /// 编辑
        /// </summary>
        public RelayCommand<int> EditCommand { get; set; }

        /// <summary>
        /// 删除
        /// </summary>
        public RelayCommand<int> DeleteCommand { get; set; }

        /// <summary>
        /// 新增
        /// </summary>
        public RelayCommand AddCommand { get; set; }

        #endregion

        public void Query()
        {
            List<Student> students;
            if (string.IsNullOrEmpty(search))
            {
                students = localDb.Query();
            }
            else
            {
                students = localDb.QueryByName(search);
            }

            GridModelList = new ObservableCollection<Student>();
            if (students != null)
            {
                students.ForEach((t) =>
                {
                    GridModelList.Add(t);
                });
            }
        }

        /// <summary>
        /// 重置
        /// </summary>
        public void Reset()
        {
            this.Search = string.Empty;
            this.Query();
        }

        /// <summary>
        /// 编辑
        /// </summary>
        /// <param name="Id"></param>
        public void Edit(int Id)
        {
            var model = localDb.QueryById(Id);
            if (model != null)
            {
                StudentWindow view = new StudentWindow(model);
                var r = view.ShowDialog();
                if (r.Value)
                {
                    var newModel = GridModelList.FirstOrDefault(t => t.Id == model.Id);
                    if (newModel != null)
                    {
                        newModel.Name = model.Name;
                        newModel.Age = model.Age;
                        newModel.Classes = model.Classes;
                    }
                    this.Query();
                }
            }
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="Id"></param>
        public void Delete(int Id)
        {
            var model = localDb.QueryById(Id);
            if (model != null)
            {
                var r = MessageBox.Show($"确定要删除吗【{model.Name}】?","提示",MessageBoxButton.YesNo);
                if (r == MessageBoxResult.Yes)
                {
                    localDb.DelStudent(Id);
                    this.Query();
                }
            }
        }

        /// <summary>
        /// 新增
        /// </summary>
        public void Add()
        {
            Student model = new Student();
            StudentWindow view = new StudentWindow(model);
            var r = view.ShowDialog();
            if (r.Value)
            {
                model.Id = GridModelList.Max(t => t.Id) + 1;
                localDb.AddStudent(model);
                this.Query();
            }
        }
    }
}

4. 数据上下文

当各个层分别创建好后,那如何关联起来呢?答案就是DataContext【数据上下文】。

查询页面上下文,如下所示:

namespace WpfApp3
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            MainViewModel viewModel = new MainViewModel();
            viewModel.Query();
            this.DataContext = viewModel;
        }
    }
}

新增页面上下文,如下所示:

namespace WpfApp3.Views
{
    /// <summary>
    /// StudentWindow.xaml 的交互逻辑
    /// </summary>
    public partial class StudentWindow : Window
    {
        public StudentWindow(Student student)
        {
            InitializeComponent();
            this.DataContext = new
            {
                Model = student
            };
        }

        private void btnSave_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = true;
        }

        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }
    }
}

总结

MVVM具有低耦合,可重用,可测试,独立开发的优点,核心要素就两个:

  • 属性发生变化时的通知,即可达到数据的实时更新。
  • 命令是实现用户与程序之间数据和算法的桥梁。

备注

本文作为MVVM的简单入门示例,旨在抛砖引玉,一起学习,共同进步。如果对WPF的其他入门知识,不是很了解,可以参考其他博文。

玉楼春·别后不知君远近

欧阳修 〔宋代〕

别后不知君远近,触目凄凉多少闷。渐行渐远渐无书,水阔鱼沉何处问。
夜深风竹敲秋韵,万叶千声皆是恨。故攲单枕梦中寻,梦又不成灯又烬。注:攲(yǐ)

  • 16
    点赞
  • 121
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
### 回答1: WPF MVVM 是一种先进的开发模式,它通过数据绑定和命令绑定将视图与逻辑分离,以达到可重用性、可维护性、可扩展性和可测试性的目的。在一个完整WPF MVVM 项目中,通常会包含以下几个方面的内容: 1. 视图层(View): 视图层负责呈现数据和用户交互,它是 WPF 中的 XAML 文件。在 MVVM 中,视图层只负责呈现数据和相关的事件响应,把控制逻辑和业务逻辑交给ViewModel层。 2. 布局管理器(Layout Manager): 布局管理器负责管理视图的布局,根据不同的分辨率和屏幕大小,自动调整布局。这样可以使应用程序更具自适应性和兼容性。 3. ViewModel层: ViewModel层是MVVM模式的核心,它是一个中介层,负责逻辑处理和与视图层和数据层之间的数据交换。ViewModel层会包括各种命令(Command)、属性(Propetry)和数据(Data) 三大类数据模型,提供数据绑定和命令绑定等实现,将视图和模型解耦,将业务逻辑和控制逻辑分离。 4. 数据层(Data Access Layer): 数据层是负责数据读写的部分,包括数据访问对象(DAO)、数据模型(Model)和数据操作(Data Access Object),从而实现数据的存储、读取和更新。 5. 服务层(Service Layer): 服务层包括一些系统服务和第三方服务,例如网络服务、邮件服务、文件服务等。通过服务层,应用程序可以实现与其它系统或者接口的交互,提高系统的扩展性和可维护性。 总之,WPF MVVM项目是将各个层面保持独立的整个项目结构和流程化之后的一种方案的实现。它极大的提高了开发效率和代码的可重用性和维护性,适用于各种规模的项目或者团队,将其应用在项目中更有助于提升软件开发的质量和效果。 ### 回答2: WPF MVVM是一种在WPF项目中使用的最佳架构模式。一份完整WPF MVVM项目应当包括以下几个方面: 1. 项目架构与设计 项目架构与设计应当有良好的规划与设计,应该包括一个正式的软件需求文档,以及包含系统模块和代码库的详细项目文档、代码注释和设计文档。 2. 数据库设计 项目应该定义数据库结构,并包含实现该结构的示例数据。 3. 代码实现框架 WPF MVVM 项目应该包含一个完整的框架,使得团队可以轻松地编写项目所需的代码。该框架应该包括一个基础架构的模块,以及UI界面模块、数据访问模块、数据模型模块等模块。 4. 文件组织 项目文件应该进行良好的组织和管理。所有的代码应该按照模块进行分类,并将其存放在相应的文件夹下。例如,可以将“ViewModels”文件夹放置于“Views”文件夹下面,将“Services”和“Repository”文件夹放置于根目录下。 5. 测试 在WPF MVVM项目中,测试是至关重要的。项目需包括一份详细的测试计划,以及一些工具来编写单元测试。 总之,一个完整WPF MVVM项目包括架构和设计、数据库设计、代码实现框架、文件组织和测试,可以提高项目的质量、效率与开发速度。 ### 回答3: WPF MVVM 是一种现代化的软件开发模式,它借助于WPF(Windows Presentation Foundation)技术,实现了界面与业务逻辑的分离,大大提高了程序的可维护性和可扩展性,并且使我们可以更好的实现测试驱动开发和重用代码。 一个完整WPF MVVM项目通常包含有以下几个方面的内容: 1. 数据源:数据源即应用程序需要使用的数据,可以是本地数据,也可以是远程服务器提供的数据。在开发过程中,我们需要通过合适的方式将数据导入我们的应用程序中,并对其进行处理。 2. ViewModel:ViewModel 是连接视图和模型的桥梁,它包含有从模型中获取数据的方法,并将数据转换成视图可以理解并显示的格式。同时ViewModel还提供了应用程序内部交互的命令和事件。 3. 视图:视图即我们的UI(用户界面),这里我们可以使用XAML定义我们的UI,当然也可以选择在代码中手动创建UI。视图可以通过数据绑定和命令绑定与ViewModel 进行交互。 4. 业务逻辑:业务逻辑是指应用程序中的数据操作和处理规则,例如验证用户输入、计算数据、存储数据等。 5. 单元测试:完成以上步骤之后,我们需要编写单元测试来验证每个组件是否都在正确运行,并且不会影响其他组件的正常操作。 在实现一个完整WPF MVVM项目的时候,我们需要注意代码的可维护性和可扩展性,并且我们需要遵循MVVM模式的规则,将视图和业务逻辑分离,在视图和ViewModel之间建立良好的绑定关系。这样我们才能够完成高效顺畅的开发,并且在维护项目时也会更加方便快捷。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老码识途呀

写作不易,多谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值