第5篇 WPF C# 數據綁定Model-View-ViewModel模式


第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);
       }
     }
  }
}

 
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM