一、导图解析
所以mvvm模式其实是以ViewModel为主的一种分层的开发模式
脱胎于mvc模式
所以其实 我们主要研究ViewModel就可以了
ViewModel 说白了,就是处理model的数据怎么在view里显示,view修改数据怎么回传到model
这样 你的界面和数据其实不存在严格的对应关系了
你的数据随便改
你的界面也可以随便改
因为怎么显示 由ViewModel决定,显示什么也是ViewModel决定的
所以一个软件可以两个人写 比如我写后台,你写界面 我们只要约定好接口,我们都可以随便改
而wpf 天生就是mvvm模式的
所以为什么我一开始就主张从mvvm入门学wpf。。事半功倍
二、实例解析:目文件的组织形式
请看下图
比如下图例子pipe类的界面和数据 命名就是 PipeModel PipeView PipeViewModel
所有的model都放在Model文件夹里 ,view放在View文件夹里,ViewModel放在ViewModel文件夹里
然后一个界面的m-v-v-m 采用约定命名
这样 看前缀就知道对应的关系了
1. Mode 模型
namespace 贱贱管线软件{/// <summary>/// 这里用于定义所有的数据的模型/// 比如界面里的管点坐标这个类别的定义如下/// </summary>public class PipePointModel{public double X { get; set; }public double Y { get; set; }public double Z { get; set; }}/// <summary>/// 立管信息/// </summary>public class TubeModel{/// <summary>/// 材质/// </summary>public string Material { get; set; }/// <summary>/// 直径/// </summary>public string Diameter { get; set; }/// <summary>/// 高度/// </summary>public string Height { get; set; }/// <summary>/// 备注/// </summary>public string Remarks { get; set; }}public class PipeModel{}}
两个属性类
- 管点坐标
- 立管材质
我看你的那个例子 这两个类别也就是操作这些数据是吧
这样的话 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>

开头那些先不看
直接看管点位置那个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其实代码量很少
那么开始看ViewModel
首先类的声明
继承了一个ViewModelBase类 
看一下 其实他实现了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的实例对不对?
按照一一对应的原则 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
