您的位置:首页 > 编程语言 > C#

第5篇 WPF C# 数据绑定Model-View-ViewModel模式

2015-11-02 12:16 585 查看
第5篇 WPF C# 数据绑定Model-View-ViewModel模式

参考资料:

John Sharp:《Microsoft Visual C# 2013 Step by Step》

周 靖 译:John Sharp《Visual C# 2012从入门到精通》

前言

Model-View-ViewModel模式即MVVM模式编程涉及五个文件:

1、MainWindow.xaml文件      UI界面文件

2、MainWindow.xaml.cs文件     UI架构文件

3、DataLib.cs数据类库文件       数据元素类库文件

4、ViewModel.cs文件        视图模型文件

5、Command.cs文件         UI命令文件

上面的文件名斜体为自由定义。正体为参考资料资料定义。

1、界面文件

MainWindow.xaml

<Window x:Class="StudyDisplay.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Background="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}">
<!-- Grid定义 -->
<Grid.ColumnDefinitions>
……
</Grid.RowDefinitions>

<StackPanel Grid.Row="0" Grid.Column="0" Grid.RowSpan="5" Background="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}" />
<StackPanel Grid.Row="0" Grid.Column="1" Grid.RowSpan="5" Background="{DynamicResource {x:Static SystemColors.GradientInactiveCaptionBrushKey}}" />

<!--1-->
<StackPanel Grid.Row="0" Grid.Column="2" Style="{StaticResource TheStackPanelStyle}" >    ……    </StackPanel>

<!--2-->
<StackPanel Grid.Row="1" Grid.Column="2"  Style="{StaticResource TheStackPanelStyle}" >
<StackPanel>
<Label Style="{StaticResource TheLabeltyle}">采样周期</Label>
<TextBox Style="{StaticResource TheTextBoxStyle}"
Name="inputSC"
Text="{Binding Current.SampleCircle,Mode=TwoWay}"
GotFocus="inputSC_GotFocus"
PreviewMouseLeftButtonDown="inputSC_PreviewMouseLeftButtonDown"
/>
</StackPanel>
<StackPanel>
<Label Style="{StaticResource TheLabeltyle}">公制/英制</Label>
<ComboBox Style="{StaticResource TheComboBoxStyle}" Background="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<ComboBoxItem Style="{StaticResource ThComboBoxItemStyle}">公制</ComboBoxItem>
<ComboBoxItem Style="{StaticResource ThComboBoxItemStyle}">英制</ComboBoxItem>
<ComboBoxItem Style="{StaticResource ThComboBoxItemStyle}">华制</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel>
<Label Style="{StaticResource TheLabeltyle}">采样周期1</Label>
<TextBox Style="{StaticResource TheTextBoxStyle}"
Name="outputSC"
Text="{Binding Current.SampleCircle,Mode=TwoWay}" 
               GotFocus="outputSC_GotFocus"
/>
</StackPanel>
<StackPanel>
<Label Style="{StaticResource TheLabeltyle}">公制/英制1</Label>
<ComboBox Style="{StaticResource TheComboBoxStyle}" Background="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<ComboBoxItem Style="{StaticResource ThComboBoxItemStyle}">公制</ComboBoxItem>
<ComboBoxItem Style="{StaticResource ThComboBoxItemStyle}">英制</ComboBoxItem>
<ComboBoxItem Style="{StaticResource ThComboBoxItemStyle}">华制</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<!--3-->
<StackPanel Grid.Row="2" Grid.Column="2" Style="{StaticResource TheStackPanelStyle}">
<CheckBox Content="CheckBox" Checked="CheckBox_Checked_1" />
<CheckBox Content="CheckBox" Checked="CheckBox_Checked" />
<CheckBox Content="CheckBox" />
<CheckBox Content="CheckBox" Checked="CheckBox_Checked" />
<CheckBox Content="CheckBox" />
<CheckBox Content="CheckBox" />
<CheckBox Content="CheckBox" />
</StackPanel>
<!--4-->
<StackPanel Grid.Row="3" Grid.Column="2" Style="{StaticResource TheStackPanelStyle}">……    </StackPanel>

<!--5-->
<StackPanel Grid.Row="4" Grid.Column="2" Style="{StaticResource TheStackPanelStyle}">……    </StackPanel>

</Grid>
<!--
<Page.TopAppBar >
<AppBar IsSticky="True">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<AppBarButton x:Name="previousCustomer" Icon="Back" Command="{Binding Path=PreviousCustomer}"/>
<AppBarButton x:Name="nextCustomer" Icon="Forward" Command="{Binding Path=NextCustomer}"/>
</StackPanel>
</AppBar>
</Page.TopAppBar>
-->
</Window>


1.1 绑定方法

如上列代码中粗体所示。绑定语句为:

Text="{Binding Current.SampleCircle,Mode=TwoWay}"

与简单的数据绑定相比,增加了 Current.

1.2 视图调用ViewModel

代码末尾一段(<Page.TopAppBar ></Page.TopAppBar>)在Win7下无法运行,是Win8的AppBar控件。用"Back"和"Forward"按钮分别绑定命令上一个、下一个命令。该命令实现:数据引导ID的增减1,从而使上列绑定数据在视图模型中列出的各个数据表绑定。

2、架构文件

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace StudyDisplay
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
//架构绑定
public MainWindow()
{
InitializeComponent();

DataLib dataLib = new DataLib
{
SampleCircle = "John"
};
        //在MainPage构造器中删除创建Customer对象的代码,替换成创建ViewModel类实例 的一个语句。
            ViewModel viewModel = new ViewModel();
//修改设置MainPage对象的DataContext属性的语句,来引用新的ViewModel对象。
this.DataContext = viewModel;
     }

     //交互逻辑
private void inputSC_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// var control = sender as System.Windows.Controls.TextBox;
//if (control == null)
// return;
// Keyboard.Focus(control);
// e.Handled = true;
}
private void inputSC_GotFocus(object sender, RoutedEventArgs e)
{
// inputSC_GotFocus.SelectAll();
}
private void outputSC_GotFocus(object sender, RoutedEventArgs e)
{
//outputSC_GotFocus.SelectAll();
}
}
}


在MainPage构造器中删除创建Customer对象的代码,替换成创建ViewModel类实例 的一个语句。修改设置MainPage对象的DataContext属性的语句,来引用新的ViewModel对象。如加粗的语句所示。

3、数据元素类库文件

DataLib.cs

按照简单数据绑定所建立的数据类库文件无需改变。(建立方法见上1篇)

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

namespace StudyDisplay
{
// 第一步:声明一个类,准备必要的属性
public class DataLib : INotifyPropertyChanged
{
public int _customerID;
//准备必要的属性1:CustomerID
public int CustomerID
{
get { return this._customerID; }
set
{
this._customerID = value;
this.OnPropertyChanged("CustomerID");
}
}
//准备必要的属性2:CustomerID
public string _sampleCircle;
public string SampleCircle
{
get { return this._sampleCircle; }
set
{
this._sampleCircle = value;
this.OnPropertyChanged("SampleCircle");
}
}
// 第二步:完成“具有双向的功能”

     //OnPropertyChanged方法引发PropertyChanged事件。
public event PropertyChangedEventHandler PropertyChanged;
//PropertyChanged 事件的PropertyChangedEventArgs参数指定了发生改变的属性的名称。该值作为参
//数传给OnPropertyChanged方法。
protected virtual void OnPropertyChanged(string propertyName)

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


4、视图模型文件

项目资源管理器—>项目—>右键|添加—>类—>文件名:ViewModel.cs 新建ViewModel.cs文件。

ViewModel.cs

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

using System.ComponentModel;    //H、

namespace StudyDisplay
{
public class ViewModel : INotifyPropertyChanged    //H、

{ private List<DataLib> dataLib;
     //2、在ViewModel类中添加以下私有变量currentCustomer,在构造器中把它初始化为0;
private int currentDataLib;

     //添加NextCustomer和PreviousCustomer自动属性。
//视图将绑定到这些Command对象,允许在客户之间导航。
public Command NextDataLib { get; private set; }
public Command PreviousDataLib { get; private set; }
//
public ViewModel()                      //ViewModel构造器
{
this.currentDataLib = 0;            //2、把currentCustomer初始化为0
this.IsAtStart = true;              //设置IsAtStart和IsAtEnd属性
this.IsAtEnd = false;               //设置IsAtStart和IsAtEnd属性
//设置NextCustomer和PreviousCustomer属性来引用新的Command对象,
this.NextDataLib = new Command(this.Next, () =>
{ return this.dataLib.Count > 0 && !this.IsAtEnd; });   //Lambda表达式
this.PreviousDataLib = new Command(this.Previous, () =>
{ return this.dataLib.Count > 0 && !this.IsAtStart; }); //Lambda表达式
//1、ViewModel类将一个List<Customer>对象作为它的模型,构造器用示例数据填充该列表。
this.dataLib = new List<DataLib>
{
new DataLib
{
CustomerID = 1,
SampleCircle = "John",
},
new DataLib
{
CustomerID = 2,
SampleCircle = "Diana",
},
new DataLib
{
CustomerID = 3,
SampleCircle = "Francesca",
}
};
//
        }
//添加以下字段和属性。将用这两个属性跟踪ViewModel的状态。如果ViewModel的
//currentCustomer字段定位在customers集合起始处,IsAtStart属性将设为true,
//如果定位在customers集合末尾, IsAtEnd属性将设为true。
private bool _isAtStart;
     public bool IsAtStart
{
get { return this._isAtStart; }
set
{
this._isAtStart = value;
this.OnPropertyChanged("IsAtStart");
}
}
private bool _isAtEnd;
public bool IsAtEnd
{
get { return this._isAtEnd; }
set
{
this._isAtEnd = value;
this.OnPropertyChanged("IsAtEnd");
}
}
//3、在 ViewModel类中添加Current属性,放到构造器之后。
public DataLib Current
{
get { return this.dataLib[currentDataLib]; }
}
//将加粗的私有方法Next和Previous添加到ViewModel类,放到Current属性之后。
//注意 Count属性返回集合中的数据项的数量,但记住集合项的编号是从0到Count – 1。
//这些方法更新currentCustomer变量来引用客户列表中的下一个(或上一个)客户。
//注意,方法负责维护IsAtStart和IsAtEnd属性的值,并通过为Current属性引发
//PropertyChanged事件来指出当前客户已发生改变。两个方法都是私有方法,它
//们不应从ViewModel类的外部访问。外部类通过命令来运行这些方法。命令将在下面的步骤中添加。
//!+++!此处涉及自定义的变量
private void Next()
{
if (this.dataLib.Count - 1 > this.currentDataLib)
{
this.currentDataLib++;
this.OnPropertyChanged("Current");
this.IsAtStart = false;
this.IsAtEnd =
(this.dataLib.Count - 1 == this.currentDataLib);
}
}
private void Previous()
{
if (this.currentDataLib > 0)
{
this.currentDataLib--;
this.OnPropertyChanged("Current");
this.IsAtEnd = false;
this.IsAtStart = (this.currentDataLib == 0);
}
}
//I、在ViewModel类末尾添加PropertyChanged事件和OnPropertyChanged方法。其实就是在
//Customer类中添加的代码。记住,视图在控件的数据绑定表达式中通过Current属性来引用数据。
//ViewModel类移动至不同的客户时,必须引发PropertyChanged事件通知视图所显示的数据发生改变。

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
}


4.1 建立ViewModel

见上列代码中1、2、3、三步。

第一步://1、ViewModel类将一个List<Customer>对象作为它的模型,构造器用示例数据填充该列表。

第二步://2、在ViewModel类中添加以下私有变量currentCustomer,在构造器中把它初始化为0;

第三步://3、在 ViewModel类中添加Current属性,放到构造器之后。

到此,ViewModel已经建立。ViewModel通过 Current属性提供对Customer信息的访问,但它没有提供在不同Customer之间导航方式。

可实现方法来递增和递减currentCustomer变量,使Current属性能获取不同的Customer。 但在这样做的时候,又不能使视图对 ViewModel产生依赖。

最常见的解决方案是Command模式。在这个模式中,ViewModel用方法来实现可由视图调用的命令。这里关键在于不能在视图的代码中显式引用这些方法名。所以,需要将命令绑定到由UI控件触发的操作。这正是下一节的练习要做事情。

4.2 向ViewModel添加命令

(续5、命令文件)向ViewModel类添加NextCustomer和PreviousCustomer命令

H、在文件顶部添加using指令,修改ViewModel类的定义来实现INotifyPropertyChanged接口。

I、在ViewModel类末尾添加PropertyChanged事件和OnPropertyChanged方法。其实就是在Customer类中添加的代码。

5、命令文件

项目资源管理器—>项目—>右键|添加—>类—>文件名:Command.cs 新建VCommand.cs文件。

Command.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;  //增加;ICommand接口在该命名空间中。
//using Windows.UI.Xaml;      //Win8控件的引用

namespace StudyDisplay
{
public class Command : ICommand
{
     //A、在Command类中添加以下私有字段。
private Action methodToExecute = null;
private Func<bool> methodToDetectCanExecute = null;
//private DispatcherTimer canExecuteChangedEventTimer = null;
 //B、为Command类添加构造器。获取两个参数:一个Action对象和一个Func<T> 对象,参数值赋
       //给methodToExecute和methodToExecute字段。
    //Command构造器:
        public Command(Action methodToExecute,Func<bool> methodToDetectCanExecute)
       {

this.methodToExecute = methodToExecute;
this.methodToDetectCanExecute = methodToDetectCanExecute;
       //G:添加这些代码:
//this.canExecuteChangedEventTimer = new DispatcherTimer();
//this.canExecuteChangedEventTimer.Tick += canExecuteChangedEventTimer_Tick;
//this.canExecuteChangedEventTimer.Interval = new TimeSpan(0, 0, 1);
//this.canExecuteChangedEventTimer.Start();
//这些代码初始化DispatcherTimer对象,将计时器周期设为 1秒并启动计时器。 秒并启动计时器。
        }
     //C、使用methodToExecute和methodToDetectCanExecute字段引用的方法来实
     //现Command类的Execute和CanExecute方法。
public void Execute(object parameter)
{
this.methodToExecute();
}
public bool CanExecute(object parameter)
{
if (this.methodToDetectCanExecute == null)
{
return true;
}
else
{
return this.methodToDetectCanExecute();
}
}
     //D、为Command类添加公共CanExecuteChanged事件。
public event EventHandler CanExecuteChanged;

    //F、在Command类末尾添加以下canExecuteChangedEventTimer__Tick方法。
    void canExecuteChangedEventTimer_Tick(object sender, object e)
    {
      if (this.CanExecuteChanged != null)

       {
          this.CanExecuteChanged(this, EventArgs.Empty);
       }
     }
  }
}


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: