一、导图解析

Cad.Net之Wpf_MVVM - 图1 所以mvvm模式其实是以ViewModel为主的一种分层的开发模式
脱胎于mvc模式
所以其实 我们主要研究ViewModel就可以了
ViewModel 说白了,就是处理model的数据怎么在view里显示,view修改数据怎么回传到model
这样 你的界面和数据其实不存在严格的对应关系了
你的数据随便改
你的界面也可以随便改
因为怎么显示 由ViewModel决定,显示什么也是ViewModel决定的
所以一个软件可以两个人写 比如我写后台,你写界面 我们只要约定好接口,我们都可以随便改
而wpf 天生就是mvvm模式的
所以为什么我一开始就主张从mvvm入门学wpf。。事半功倍

二、实例解析:目文件的组织形式

请看下图

比如下图例子pipe类的界面和数据 命名就是 PipeModel PipeView PipeViewModel
图片.png
所有的model都放在Model文件夹里 ,view放在View文件夹里,ViewModel放在ViewModel文件夹里
然后一个界面的m-v-v-m 采用约定命名
这样 看前缀就知道对应的关系了

1. Mode 模型

  1. namespace 贱贱管线软件
  2. {
  3. /// <summary>
  4. /// 这里用于定义所有的数据的模型
  5. /// 比如界面里的管点坐标这个类别的定义如下
  6. /// </summary>
  7. public class PipePointModel
  8. {
  9. public double X { get; set; }
  10. public double Y { get; set; }
  11. public double Z { get; set; }
  12. }
  13. /// <summary>
  14. /// 立管信息
  15. /// </summary>
  16. public class TubeModel
  17. {
  18. /// <summary>
  19. /// 材质
  20. /// </summary>
  21. public string Material { get; set; }
  22. /// <summary>
  23. /// 直径
  24. /// </summary>
  25. public string Diameter { get; set; }
  26. /// <summary>
  27. /// 高度
  28. /// </summary>
  29. public string Height { get; set; }
  30. /// <summary>
  31. /// 备注
  32. /// </summary>
  33. public string Remarks { get; set; }
  34. }
  35. public class PipeModel
  36. {
  37. }
  38. }

两个属性类

  • 管点坐标
  • 立管材质

我看你的那个例子 这两个类别也就是操作这些数据是吧
这样的话 model其实就写完了
当然 后续你可以在这里写和界面无关的数据处理函数
mode的就这么简单??几句话就解释完啦??
其实Mode就是
Wpf的容器就用一个类、类里面的属性就是容器里面的控件
下图界面使用了**expander**就是对应图一的Mode

2. View 界面

<UserControl x:Class="贱贱管线软件.PipeView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:eb="clr-namespace:NFox.WPF;assembly=NFox"
             xmlns:local="clr-namespace:贱贱管线软件"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <eb:IntToStringConverter x:Key="IntToStringConverter"/>
    </UserControl.Resources>
    <StackPanel>
        <Expander Header="管点位置" IsExpanded="True" DataContext="{Binding PipePoint}">
            <StackPanel >
                <DockPanel Margin="0,1,0,1">
                    <Button Name="GetPointX" Content="X 坐标" Width="60"></Button>
                    <TextBox x:Name="X" IsEnabled="False" Text="{Binding X, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToStringConverter}}"></TextBox>
                </DockPanel>
                <DockPanel  Margin="0,1,0,1">
                    <Button Name="GetPointY" Content="Y 坐标" Width="60"></Button>
                    <TextBox  x:Name="Y"  IsEnabled="False" Text="{Binding Y, UpdateSourceTrigger=PropertyChanged,Converter={StaticResource IntToStringConverter}}"></TextBox>
                </DockPanel>
                <DockPanel  Margin="0,1,0,1">
                    <Button Name="GetPointH" Content="地面高程" Width="60"></Button>
                    <TextBox  x:Name="SurH" Text="{Binding Z,UpdateSourceTrigger=PropertyChanged,Converter={StaticResource IntToStringConverter}}"></TextBox>
                </DockPanel>
            </StackPanel>
        </Expander>
        <Expander Header="立管信息" IsExpanded="True" DataContext="{Binding TubeModel}">
            <StackPanel>
                <DockPanel  Margin="0,1,0,1">
                    <Button Content="立管材质" Width="60" IsEnabled="False"></Button>
                    <TextBox  x:Name="LgMaterial" Text="{Binding Material, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
                </DockPanel>
                <DockPanel  Margin="0,1,0,1">
                    <Button Content="立管管径" Width="60" IsEnabled="False"></Button>
                    <TextBox  x:Name="LgDS" Text="{Binding Diameter, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
                </DockPanel>
                <DockPanel  Margin="0,1,0,1">
                    <Button Content="立管高度" Width="60" IsEnabled="False"></Button>
                    <TextBox  x:Name="LgHeight" Text="{Binding Height, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
                </DockPanel>
                <DockPanel  Margin="0,1,0,1">
                    <Button Content="立管备注" Width="60" IsEnabled="False"></Button>
                    <TextBox  x:Name="LgRemark" Text="{Binding Remarks, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
                </DockPanel>
            </StackPanel>
        </Expander>
    </StackPanel>
</UserControl>

图片.png
开头那些先不看
直接看管点位置那个expander
datacontext就是上下文数据
下面的就是textbox绑定x y z了
UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToStringConverter}} 这些先不看
不影响理解
xaml其实就这么写就行了
**这里绑定的pipepoint不是model里的 而是下面我们要讲的ViewModel里的 **

3. ViewMode 桥梁

*重点 重点 重点 重点 重点 重点 重点 重点 重点*

using NFox.WPF;
namespace 贱贱管线软件
{
    public class PipeViewModel : ViewModelBase
    {
        /// <summary>
        /// 定义管点位置的属性
        /// </summary>
        private PipePointModel pipePoint;
        public PipePointModel PipePoint
        {
            get
            {
                return pipePoint;
            }
            set
            {
                SetProperty(ref pipePoint, value);
            }
        }
        /// <summary>
        /// 定义立管信息属性
        /// </summary>
        private TubeModel tubeModel;
        public TubeModel TubeModel
        {
            get { return tubeModel; }
            set
            {
                SetProperty(ref tubeModel, value);
            }
        }
        public PipeViewModel()
        {
            TubeModel = new TubeModel
            {
                Material = "nihao",
                Diameter = "1.2",
                Height = "3.2",
                Remarks = "help"
            };
            PipePoint = new PipePointModel
            {
                X = 1.1,
                Y = 2.2,
                Z = 3.3
            };
        }
    }
}

其实你现在应该有疑问就是 wpf怎么知道去哪里找我绑定的那个属性呢
毕竟你虽然写了model 写了view 可以并没有写他们连接的代码
wpf就这么智能?
这应该是你的疑问
那么其实奥秘就在ViewModel里
为什么mvvm模式的ViewModel这么重要
你发现model和view其实代码量很少
image.png
那么开始看ViewModel
首先类的声明
继承了一个ViewModelBase类
图片.png

看一下 其实他实现了INotifyPropertyChanged接口
现在我们不关心这个base类到底干了什么
我们要先知道mvvm怎么用,然后再去研究怎么来的是吧
ViewModel 的数据是用属性表达的
你可以看到代码里我定义了两个属性
只不过属性的set写法和普通的不太一样 是吧
代码其实很好懂
记住了 ViewModel的套路就是一个私有的字段,类型就是你model里定义的数据模型
写法呢 是固定的 ref这个千万不能忘

/// <summary>
/// 定义管点位置的属性
/// </summary>
private PipePointModel pipePoint;
public PipePointModel PipePoint
{
    get
    {
        return pipePoint;
    }
    set
    {
        SetProperty(ref pipePoint, value);
    }
}

然后一个公开的属性
set里为什么这么写 其实说白了 就是为了双向绑定的
view里改的数据 能给这个属性赋值而已
大部分的mvvm框架大同小异 都是这么做 不过 就是函数名不一样而已
然后就是构造函数 把属性初始化

///View界面就已经绑定了 ViewMode里的属性
<Expander Header="管点位置" IsExpanded="True" DataContext="{Binding PipePoint}">

public PipePointModel PipePoint
{
    get
    {
        return pipePoint;
    }
    set
    {
        SetProperty(ref pipePoint, value);
    }
}

到目前为止 你能发现ViewModel和model有联系了

public PipePointModel PipePoint ///在Mode里定义的数据类

但是ViewModel和view也没啥联系啊
貌似也没啥多余的代码了
怎么就能工作呢

首先我在model里定义了一个类 叫pipepointmodel
然后我在ViewModel里创建了一个实例叫 pipepoit
然后我通过datacontext将这个实例绑定到了expander上
然后expander里的控件就知道了我的上级那里有个实例,我们下级可以直接用这个实例的属性
所以expander里的textbox可以直接绑定x属性

这里面有个地方是断的
就是 expander怎么知道实例的
实例是在ViewModel里创建的
也不是在view里创建的
所以根据前面讲的 你可以猜猜 textbox知道他的上级expander有个实例,
那么expander是不是也知道他的上级有个实例呢
那么如果他的上级有个实例 那么这个实例是谁,在哪里实例化的?

expander能绑定pipepoint,那么他的上级绑定的示例应该是pipeviewmodel的实例对不对?
图片.png
按照一一对应的原则 expander对应pipepoint,那么expander的上级应该对应pipeviewmodel对吧
我们现在一直在猜测是这样的。。。。是吧 按道理来说应该是这样的是吧
那么 你看下xaml的后台代码

/// <summary>
/// PipeView.xaml 的交互逻辑
/// </summary>
public partial class PipeView : UserControl
{
    public PipeView()
    {
        InitializeComponent();
        DataContext = new PipeViewModel();
    }
}

奇迹在这里 pipeviewmodel的实例化在这里完成的
那么这里的datacontext是哪个级别的呢?
主体窗口的data
那么主窗体的下级 孙级 都可以知道祖宗绑定了个实例
我们都可以用这个实例的属性了
所以 你的界面有几级的数据,你的ViewModel就要提供几级的数据
这里 的例子里 界面绑定了两个级别的实例
分别是主窗体的datacontext和expander的context

然后ViewModel里还要写一些跟界面有关的数据计算的函数啥的
现在 model view ViewModel 已经全部穿起来了
层次分明 逻辑清晰 各司其职

课件代码
链接:https://pan.baidu.com/s/1Vl34PrkNNIp3zJnAGdN5Xg
提取码:ss2t