日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

[译]WPF 应用程序和MVVM设计模式 ——Josh Smith

發布時間:2025/3/14 asp.net 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [译]WPF 应用程序和MVVM设计模式 ——Josh Smith 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這篇文章討論:

模式與WPF

MVP模式

為什么MVVM更加適用于WPF

用MVVM構建一個應用程序

譯文地址:

? ? ? ??http://www.cnblogs.com/lujiao_cs/archive/2011/10/30/2229419.html

內容:

  專業的軟件用戶界面開發不太簡單。它可能混雜了數據、交互設計、視覺設計、連接、多線程、安全性、國際化、驗證、單元測試以及可觸摸技術。考慮到用戶界面暴露了系統的底層并且必須滿足用戶的不可預知的需求,它可能是許多應用程序中最不穩定的部分。有一些常用的設計模式可以解決這個問題,但是隔離并且訪問這些關注點可能比較難。模式越是復雜,更多的捷徑可能會被用到,這些捷徑漸漸的破壞了以前所有正確方式做事情的努力。

  不總是設計模式的問題。有時候我們會用到復雜的設計模式,由于現有的UI平臺不能提供一個很好的設計模式,它需要寫很多的代碼。我們需要的是這樣一種平臺,它使得利用簡單的、能夠經受時間考驗的、并且能讓開發者接受的設計模式來構建UI變得簡單。幸運的是,WPF很好的提供了這些。

  由于軟件界采用WPF的比率不斷增長,WPF團隊已經開發了它自己的模式生態系統和實踐。在這篇文章中,我將回顧一些很好的設計實踐,并且用WPF實現一個應用程序客戶端。利用WPF的一些核心特性結合MVVM設計模式,我將介紹一個實例程序,它展示了利用正確的方式構建WPF程序是多么簡單。

  在這篇文章的結尾,我們將用數據模板、命令、數據綁定、資源系統和MVVM模式結合在一起創建一個簡單、可測試的、簡單的框架。利用這個框架,我們可以創建任何的WPF程序。此文章的示例程序可以當做一個真實的WPF項目的模板,它運用MVVM作為核心架構。程序中的單元測試項目會告訴你測試應用程序的UI功能是多么簡單,這些功能存在于ViewModel類中。在進入細節之前,讓我們首先回顧一下為什么要用MVVM這樣的模式。

有序與混亂

  在一個簡單的“hello?world”程序里面使用設計模式是多余的,并且會適得其反。任何一個合格的開發人員能一目了然的理解幾行代碼。?然而,當程序的功能增加時,代碼行和組件的數量相應地也會增加。最后,系統的復雜性和重復出現的問題增加,這促使開發者以一種易于理解、討論、擴展、故障排除的的方式去組織代碼。在源代碼中,我們通過對實體的良好命名來減少認知的混亂。我們通過考慮它在系統中的功能角色來確定適用于一段代碼的名稱。

  開發者經常通過設計模式特意的組織代碼,而不是自然的運用它們。這兩種方式都沒有錯,但是在這篇文章里,我考察了在一個WPF應用程序中明確地使用MVVM作為架構的好處。這些類的名字包含了MVVM設計模式中熟悉的條款。例如,以“ViewModel”結尾的類是View的抽象。這有助于減少避免前面提到的認知的混亂。你能很愉快的控制這種混亂,在許多專業的軟件開發項目中這是一個自然的狀態。

MVVM的演化

  自從人們開始創建軟件的用戶界面,就有一些常用的設計模式使它變得變得簡單。例如,MVP模式在各種UI編程平臺下大受歡迎。MVP?是MVC模式的變種,它已經存在了數十年了。如果你以前沒有用過MVP模式,這里做一個簡單的說明。你在屏幕上看到的就是VIew,它所展示的數據就是Model,Presenter連接這兩者。View依賴于Presenter?利用Model的數據去填充它、對用戶的輸入做出反應、提供輸入驗證以及一些其它的驗證。如果你想要學習更多的MVP,推薦你閱讀Jean-Paul?Boodhoo?的?August?2006?Design?Patterns?column。在2004年,Martin?Fowler?發表了關于Presentation?Model?(PM)模式的文章。PM?模式在將View和他的狀態和行為分離上面比較相似。PM模式有趣的地方是創造了名為Presentation?Model的View的抽象。View僅僅只是Presentation?Model的表現。按照Fowler的解釋,Presentation?Model頻繁更新的是它的View,以便這兩者之間保持一致。?同步的邏輯作為代碼存在于Presnetation?Model類里。

  在2005年,微軟的WPF和SL架構師John?Gossman在他的博客中公開了MVVM模式。?MVVM與?Fowler的PM模式完全相同,這兩種模式都專注于View的抽象,它包含了View的狀態和行為。Fowler引入Presentation?Model作為一種與UI平臺無關的View的抽象的創建,而Gossman引入MVVM作為一種利用WPF的核心特征去簡化用戶界面的創建的標準化的方式。?在這個以意義上看,MVVM相對于通用的PM更加專一化,是為WPF和Silverlight平臺而量身定做的。?

  Glenn?Block在2008年九月發表了這篇優秀的文章:Prism:?Patterns?for?Building?Composite?Applications?with?WPF。他解釋了針對于WPF的Microsoft?Composite?Application?Guidance。ViewModel?還未被用到。反而,Presentation?Model?被用來描述View的抽象。貫穿這篇文章,我將這種模式叫做MVVM、View的抽象叫做ViewModel。我發現這個術語在WPF和MVVM社區更加流行。

  不像MVP的提出,ViewModel?不需要View的引用。View綁定到ViewModel中的一個屬性。這個屬性展示了Model對象的數據以及View的其它狀態特性。由于ViewModel?對象被設置為View的DataContent,View和ViewModel之間的綁定易于構建。如果ViewModel?里面的屬性值改變了,那些新的值會通過數據綁定傳遞到View。當用戶點擊View的一個按鈕,ViewModel?里面的一個命令就會執行完成需要的動作。ViewModel而不是View執行了Model數據的修改。View類不知道Model類的存在,ViewModel?和Model也不知道View。實際上,Model完全不知道ViewModel和View存在的事實。?這是一種松耦合的設計,你將看到這種方式的好處。

為什么WPF開發者喜歡MVVM

  一旦開發人員喜歡上了MVVM和WPF,就很難區分他們兩者。MVVM?是WPF開發者的通用語,因為他非常適合WPF平臺,WPF被設計使它很容易使用MVVM模式去構建應用程序。實際上,微軟內部利用MVVM開發應用程序,例如Expression?Blend,而核心WPF平臺正在構建中。WPF的許多方面,例如look-less控件模型和數據模板,通過MVVM將顯示和狀態和行為分離。使得MVVM成為一種優秀的設計模式的最重要的一個方面就是數據綁定。通過將View的屬性綁定到ViewModel,可以在這兩者之間得到松耦合,完全地移除了在ViewModel里直接寫代碼更新一個view的需要。數據綁定系統同時提供了輸入驗證,提供了一個標準的方式將驗證錯誤傳到View。

  WPF的另外兩個使得MVVM模式有用的特性的數據模板和資源系統。View的數據模板將ViewModel的對象顯示到用戶界面。你可以在Xaml中定義模板,并且在運行時讓資源系統自動查找和應用這些模板。你可以在我的2008年7月的文章Data?and?WPF:?Customize?Data?Display?with?Data?Binding?and?WPF中學習更多的數據綁定和數據模板。如果WPF不支持Commands,MVVM將遠沒有如此強大。在這篇文章中,我將告訴你ViewModel如何將Commands公開到View,從而讓View使用它的功能。如果你對Command不熟悉,我推薦你閱讀Brian?Noyes在2008年9月的綜合性的文章Advanced?WPF:?Understanding?Routed?Events?and?Commands?in?WPF。

  除了WPF(以及Silverlight2)的特征使得MVVM自然地方式去構建應用程序,這個模式流行的也因為ViewModel類容易做單元測試。當一個程序的交互邏輯存在于一套ViewModel類中時,你可以很容易寫出代碼測試它。從某種意義上說看,Views?和單元測試是ViewModel?的兩種不同的消費者。應用程序的ViewModel的一套測試程序提供了一個自由快速的回歸測試,幫助減少程序維護的消耗。

  除了促進創造自動回歸測試,ViewModel?的可測試性有利于正確地設計易于換膚的用戶界面。當你設計一個應用程序的時候,想象你需要你需要寫一個單元測試來測試ViewModel,這樣你就明白一些功能是該寫在View里面還是ViewModel里面。如果你能為Viewmodel寫單元測試而沒有創建任何UI對象,你也就能完全地剝離ViewModel因為它不依賴于指定的可見元素。?

  最后,對于視覺設計的開發人員,使用MVVM使得創建平滑的設計者/開發者工作流變得更加簡單。由于View只是ViewModel的任意消費者,很容易剝離View而換一個新的View。這個簡單的步驟允許我們進行快速的原型設計以及設計人員進行UI的評估。

開發團隊可以將注意力集中于健壯的ViewModel類的創建。設計人員可以專注于創建用戶友好的Views。連接這兩個團隊的輸出除了確保Xaml里面的綁定存在可能設計更多。

演示程序

  此刻,我已經查閱了MVVM的歷史和理論操作。我調查了為什么它在WPF開發者中這么流行。現在,我們看看這個模式是如何運作的。這篇文章的演示程序用多種方式來實現MVVM。它提供了豐富的實例用以將概念融入實際的背景中。我在Visual?Studio?2008?SP1,.NET框架3.5?SP1中創建這個演示程序。單元測試運行在Visual?Studio單元測試系統。

  程序可能包含多個“工作區”,用戶可以點擊左邊導航區域將它們打開。所有的“工作區”在主要內容區域的TabControl?中展示。用戶可以點擊Tab項的關閉按鈕來關閉一個工作區。應用程序有兩個可用的工作區:"All?Customers"?和"New?Customer"??。

運行程序并且打開一些工作區之后,界面如下:

圖?1?工作區

  一次只能打開一個"All?Customers"?工作區,但是可以同時打開多個"New?Customer"?工作區。當用戶想創建一個新客戶的時候,他必須填寫圖2?的表格數據。

圖2?用戶填寫數據表格

  當用戶填寫有效的數據,并且點擊Save按鈕的后,新的用戶名字出現在標簽項中并且這個用戶被添加在?All?customers中。此程序不支持編輯和刪除已存在的用戶,但是此功能以及其它很多相似的功能在建立好應用程序框架之后是很容易實現的。現在你已經很好的理解了這個示例程序所作的,讓我們研究它是如何設計與實現的吧!

Relaying?Command?邏輯

  除了類構造函數里面的InitializeComponent?生成的標準模板代碼,程序的每一個View都有一個codebehind?文件。實際上,你可以從工程中移除掉viewcodebehind?文件,應用程序將仍然能正確地編譯和運行。?盡管View中沒有事件處理方法,當用戶點擊按鈕的時候,程序仍然能夠響應用戶的請求。這是因為綁定建立在UI控件,例如超鏈接,按鈕和菜單項控件上的Command屬性上。綁定使得用戶在控件上點擊的時候,ViewModel?的ICommand對象被執行。你可以把command對象作為一個適配器,它使得從XAML中聲明的視圖中調用ViewModel的功能變得簡單。當ViewModel展示了ICommand的一個實例時,Command對象通過ViewModel去完成它的工作。一種可行的實現模式是在ViewModel類中創建一個私有的嵌套類,一遍Command可以使用ViewModel類中的私有成員。并且不會污染到命名空間。這個嵌套類實現了ICommand接口,并且將ViewModel的一個應用注入到它的一個構造函數中。然而為每一個實現了ICommand接口的Command創建嵌套類可能使得ViewModel類變得膨脹,越多的代碼意味著越多的Bug存在的可能性。

  在本程序中,RelayCommand?類解決了這個問題。RelayCommand?允許你通過代理注入Command的邏輯到構造函數中。這種方法允許在ViewModel類中以簡潔的,簡明的命令實現。RelayCommand?是Microsoft?Composite?Application?Library種中DelegateCommand?的簡化。圖3?展示了RelayCommand?類:

?

public class RelayCommand : ICommand
{
  #region Fields

  readonly Action<object> _execute;
  readonly Predicate<object> _canExecute;

  #endregion // Fields

  #region Constructors

  public RelayCommand(Action<object> execute)
  :this(execute,null)
  {
  }

  public RelayCommand(Action<object> execute, Predicate<object> canExecute)
  {
    if (execute== null)
      throw new ArgumentNullException("execute");
    _execute= execute;
   _canExecute= canExecute;
  }
  #endregion // Constructors

  #region ICommand Members

  [DebuggerStepThrough]
  public bool CanExecute(object parameter)
  {
    return _canExecute== null ? true : _canExecute(parameter);
  }
  public event EventHandler CanExecuteChanged
  {
    add { CommandManager.RequerySuggested+= value; }
    remove { CommandManager.RequerySuggested-= value; }
  }
  public void Execute(object parameter)
  {
    _execute(parameter);
  }

  #endregion // ICommand Members
}復制代碼

圖?3

  ICommand?接口實現的一部分——CanExecuteChanged?事件有一個有趣的特征。它將事件的訂閱委托給CommandManager.RequerySuggested?事件。它使得無論什么時候請求內置的命令,如果可以執行的話,WPF的命令請求所有的RelayCommand?對象。下面的代碼來自CustomerViewModel?類,顯示了怎樣用lambda表達式配置RelayCommand,我將在之后更進一步解釋。

RelayCommand _saveCommand;
public ICommand SaveCommand
{
   get
  {
    if (_saveCommand== null)
    {
      _saveCommand= new RelayCommand(param=> this.Save(),
      param=> this.CanSave );
    }
     return _saveCommand;
  }
} ?復制代碼

ViewModel類層次結構

  大部分的ViewModel類需要相同的特性。它們經常要實現INotifyPropertyChanged?接口,它們經常需要一個用戶友好的顯示名稱并且它們需要能夠關閉實例中的工作區。這個問題使得很自然的需要去創建一兩個ViewModel的基類,以便所有的新的ViewModel類能夠繼承自實現了這些通用功能的基類。ViewModel?類的繼承層次如下圖4:

4?繼承層次

?

?

ViewModelBase類

  ViewModelBase?是繼承關系中的根類,這就是為什么它實現通用的INotifyPropertyChanged接口并且有一個DisplayName屬性。INotifyPropertyChanged?接口包含了一個叫PropertyChanged的事件。無論什么時候ViewModel?對象的一個屬性有一個新值,它會激活PropertyChanged?事件通知數據綁定系統這個新值。根據通知,數據綁定系統查詢到這個屬性,一些UI元素上的屬性也會接受到這個新值。為了讓WPF知道ViewModel對象的哪一個屬性發生改變,PropertyChangedEventArgs類公開一個String類型的PropertyName屬性。你必須小心的傳遞屬性名到事件參數中。否則,WPF將停止為一個新值查詢錯誤的屬性。

  ViewModelBase?的一個有趣的地方是它提供了驗證在ViewModel對象中實際存在的一個屬性名。這在重構中是非常有用的,因為通過VS2008?重構屬性改變一個屬性的名字將不會修改源代碼中包含了屬性名的字符串。在事件參數中用錯誤的屬性名觸發PropertyChanged?事件將會導致難以捕獲的Bug,因此這個小的特性將節省很多時間。在圖?5?ViewModelBase?的代碼中增加了此支持。

// In ViewModelBase.cs
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
  this.VerifyPropertyName(propertyName);
  PropertyChangedEventHandler handler= this.PropertyChanged;
  if (handler!= null)
  {
    var e= new PropertyChangedEventArgs(propertyName);
     handler(this, e);
  }
}

[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
  // Verify that the property name matches a real,
  // public, instance property on this object.
 if (TypeDescriptor.GetProperties(this)[propertyName]== null)
{
    string msg= "Invalid property name:" + propertyName;

    if (this.ThrowOnInvalidPropertyName)
      throw new Exception(msg);
    else
      Debug.Fail(msg);
  }
} 復制代碼

圖?5

CommandViewModel類

  ViewModelBase?最簡單的子類是CommandViewModel。它展示了一個叫Command?的ICommand類型的屬性。MainWindowViewModel?通過它的Commands?屬性展示了這些對象集合。主界面的左邊的區域展示了MainWindowViewModel中每個CommandViewModel?的鏈接。例如"view?all?customers"?和"Create?new?customer"。當用戶點擊一個鏈接,就會執行他們其中的一個命令,一個工作區就會在commands中打開。CommandViewModel?類如下:

public class CommandViewModel : ViewModelBase
{
  public CommandViewModel(string displayName, ICommand command)
  {
    if (command== null)
      throw new ArgumentNullException("command");

    base.DisplayName= displayName;
    this.Command= command;
}
  public ICommand Command {get;private set; }
} 復制代碼

  在MainWindowResources.xaml文件中存在一個名為"CommandsTemplate"的數據模板,MainWindow用模板去映射前面提到的CommandViewModels?集合。?這個模板簡單的將每一個CommandViewModel?對象作為ItemsControl?的一個鏈接。每一個HyperLink的Command屬性綁定到CommandViewModel的Command屬性。XAML?在圖6中顯示:

<!-- In MainWindowResources.xaml-->
<!--
This template explains how to render the list of commands on
the left sidein the main window (the'Control Panel' area).
-->
<DataTemplate x:Key="CommandsTemplate">
  <ItemsControl ItemsSource="{Binding Path=Commands}">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <TextBlock Margin="2,6">
          <Hyperlink Command="{Binding Path=Command}">
            <TextBlock Text="{Binding Path=DisplayName}" />
          </Hyperlink>
        </TextBlock>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</DataTemplate>復制代碼

圖?6

MainWindowViewModel?類

  正如在前面的類圖中看到了,WorkspaceViewModel?類從ViewModelBase?繼承并且添加了關閉的功能。通過“Close”,在運行時可以將一些用戶界面從工作區移除。有三個類繼承自WorkspaceViewModel:MainWindowViewModel,?AllCustomersViewModel,和CustomerViewModel。MainWindowViewModel類的關閉請求是被App類來處理。它創建了MainWindow?以及它的ViewModel。如圖7

// In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
   base.OnStartup(e);
  MainWindow window= new MainWindow();

   // Create the ViewModel to which
   // the main window binds.
  string path= "Data/customers.xml";
  var viewModel= new MainWindowViewModel(path);

   // When the ViewModel asks to be closed,
  // close the window.
  viewModel.RequestClose+= delegate
  {
    window.Close();
  };

  // Allow all controls in the window to
  // bind to the ViewModel by setting the
  // DataContext, which propagates down
  // the element tree.
  window.DataContext= viewModel;
  window.Show();
}復制代碼

7

  MainWindow?包含了一個菜單項,它的Command?屬性綁定到MainWindowViewModel的CloseCommand?屬性。當用戶點擊菜單項時,App類調用window's?Close?來響應,如下:

<!-- In MainWindow.xaml-->
<Menu>
  <MenuItem Header="_File">
    <MenuItem Header="_Exit" Command="{Binding Path=CloseCommand}" />
  </MenuItem>
  <MenuItem Header="_Edit" />
  <MenuItem Header="_Options" />
  <MenuItem Header="_Help" />
</Menu> 復制代碼

  MainWIndowViewModel包含了一個?WorkspaceViewModel?類型對象的observable集合,名為Workspaces。主窗體包含了一個TabControl,它的ItemsSource?綁定到這個集合。每一個tab?項有一個關閉按鈕,它的Command屬性綁定到相應WorkspaceViewModel?實例的CloseCommand?。一個配置每個Tab項的模板的縮減版本顯示在下面的代碼中。這些代碼可以在MainWindowResources.xaml中找到,這些模板解釋了如何將Tab?項和關閉按鈕映射。

<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120">
  <Button
  Command="{Binding Path=CloseCommand}"
   Content="X"
   DockPanel.Dock="Right"
  Width="16" Height="16"
  />
  <ContentPresenter Content="{Binding Path=DisplayName}" />
</DockPanel>
</DataTemplate>復制代碼

  當用戶點擊Tab項的關閉按鈕,此工作區ViewModelCloseCommand?命令就會執行。引起RequestClose?事件被觸發。MainWindowViewModel?監視工作區的RequestClose?事件,并且根據請求將工作區移除。由于MainWindowTabControl??的ItemsSource屬性綁定到了WorkspaceViewModels的observable?集合,從集合中移除一項會導致TabControl移除相應的工作區。MainWindowViewModel?的邏輯如?圖8:

// In MainWindowViewModel.cs
ObservableCollection<WorkspaceViewModel> _workspaces;
public ObservableCollection<WorkspaceViewModel> Workspaces
{
  get
{
    if (_workspaces== null)
   {
     _workspaces = new ObservableCollection<WorkspaceViewModel>();
    _workspaces.CollectionChanged+= this.OnWorkspacesChanged;
   }
    return _workspaces;
 }
}

void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
  if (e.NewItems!= null && e.NewItems.Count!= 0)
  foreach (WorkspaceViewModel workspacein e.NewItems)
    workspace.RequestClose+= this.OnWorkspaceRequestClose;

  if (e.OldItems!= null && e.OldItems.Count!= 0)
  foreach (WorkspaceViewModel workspacein e.OldItems)
     workspace.RequestClose-= this.OnWorkspaceRequestClose;
}

void OnWorkspaceRequestClose(object sender, EventArgs e)
{
  this.Workspaces.Remove(senderas WorkspaceViewModel);
} 復制代碼

圖8

  在UnitTests?工程中,MainWindowViewModelTests.cs包含了方法驗證此功能是否正常工作。很容易為ViewModel?創建單元測試是MVVM的一個很大的亮點。因為它允許無需寫UI代碼的情況下進行一些簡單的功能測試。測試方法如?圖9:

// In MainWindowViewModelTests.cs
[TestMethod]
public void TestCloseAllCustomersWorkspace()
{
   // Create the MainWindowViewModel, but not the MainWindow.
  MainWindowViewModel target = new MainWindowViewModel(Constants.CUSTOMER_DATA_FILE);

  Assert.AreEqual(0, target.Workspaces.Count,"Workspaces isn't empty.");

   // Find the command that opens the "All Customers" workspace.
  CommandViewModel commandVM=
  target.Commands.First(cvm=> cvm.DisplayName== "View all customers");

   // Open the "All Customers" workspace.
  commandVM.Command.Execute(null);
  Assert.AreEqual(1, target.Workspaces.Count,"Did not create viewmodel.");

  // Ensure the correct type of workspace was created.
   var allCustomersVM= target.Workspaces[0]as AllCustomersViewModel;
   Assert.IsNotNull(allCustomersVM,"Wrong viewmodel type created.");

   // Tell the "All Customers" workspace to close.
   allCustomersVM.CloseCommand.Execute(null);
   Assert.AreEqual(0, target.Workspaces.Count,"Did not close viewmodel.");
} 復制代碼

9

將View運用到ViewModel

  MainWindowViewModel?間接的從主窗體的TabControl控件添加與移除MainWindowViewModel?對象。通過依賴數據綁定,TabItemContent?屬性接受到派生自ViewModelBase的類對象去顯示。ViewModelBase?不是UI元素,因此它沒有內在的支持去顯示自己。WPF默認展示一個不可視對象是通過TextBlock中調用此對象的ToString方法得到的字符串來顯示。這明顯不是你所想要的,除非用戶強烈的希望看到ViewModel?的類型名稱。

  你可以簡單的通過類型化的DataTemplate告訴WPF如何展示一個ViewModel?對象。類型化的DataTemplate沒有一個x:Key值分配給它,但是它有DataType屬性去設置為一個Type類的實例。如果WPF試著展示一個ViewModel?對象,它會確認資源系統是否有一個類型化的DataTemplate,它的DataType?與ViewModel?對象的類型是一樣的(或者是它的基類)。如果找到了,它用那個模板在Tab?itemContent屬性里去呈現引用的ViewModel對象。MainWindowResources.xaml文件有一個ResourceDictionary。那個字典被添加到主窗體的資源體系中,這意味著它包含的資源在窗口的資源范圍里。當一個Tab項的Content被設置為ViewModel?的對象時,字典中的一個DataTemplate提供一個View去顯示它。如圖?10

<!--
This resource dictionaryis used by the MainWindow.
-->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:DemoApp.ViewModel"
xmlns:vw="clr-namespace:DemoApp.View"
>

<!--
This template applies an AllCustomersView to an instance
of the AllCustomersViewModelclass shownin the main window.
-->
<DataTemplate DataType="{x:Type vm:AllCustomersViewModel}">
  <vw:AllCustomersView/>
</DataTemplate>

<!--
This template applies a CustomerView to an instance
of the CustomerViewModelclass shownin the main window.
-->
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
  <vw:CustomerView/>
</DataTemplate>

<!-- Other resources omittedfor clarity...-->
</ResourceDictionary> 復制代碼

圖?10

  你不需要寫任何代碼來決定哪一個View來顯示ViewModel?對象。WPF資源系統為你做好了一切,使你集中精力于更加重要的東西。在更多復雜的場景中,可能需要編程去選擇View,但是大多數情況下那是不需要的。

數據模型和資源庫

  你已經看到ViewModel對象如何被應用程序框架加載,顯示和關閉。?現在基本的框架已經搭建好了,你可以專注與程序的實現細節。在深入的了解程序的兩個工作區"All?Customers"?and?"New?Customer"之前,讓我們查看一個數據模型和數據訪問類。那些類的設計幾乎與MVVM模式無關。因為你可以創建一個ViewModel類去適應任何WPF支持的數據對象。示例中唯一的Model類是Customer。這個類有一些屬性代表了一個公司員工的信息,例如他們的姓名,Email地址。它通過實現標準的IDataErrorInfo?接口實現了驗證消息,這在WPF流行之前已經存在好幾年了。Customer?類沒有任何東西表明它被用在MVVM架構或是WPF程序中。這種類能很容易源自業務庫。

  數據必須來源并存在于某個地方,在這個程序中CustomerRepository?的一個實例加載并且存儲所有的Customer?對象。它從XML?文件中加載數據,但是外部的數據源是無關緊要的。數據可以源自數據庫、Web?Service、命名管道、磁盤文件、甚至信鴿,這完全都無所謂。只要你有一個包含了數據的.Net對象,不管它來自哪里,MVVM模式都可以得到數據并顯示在屏幕上。

  CustomerRepository?類提供了一些方法讓你訪問所有可用的Customer?對象,添加新的Customer到集合中,同時判斷Customer?是否已經存在。由于程序不允許用戶刪除customer,因此不能從集合中刪除一個customer。當一個新的用戶通過調用AddCustomer?被添加到CustomerRepository,CustomerAdded?事件被激發。明顯的,相對于實際的商務需求,這個程序的數據模型非常簡單,但那不是重點。重點是理解ViewModel?類如何使用Customer?和?CustomerRepository。注意CustomerViewModel?是一個Customer?對象的封裝。它暴露了Customer的狀態,以及被CustomerView?使用的狀態。CustomerViewModel?并不是復制Customer的狀態,它只是用過委托來暴露它。如下:

public string FirstName
{
  get {return _customer.FirstName; }
  set
  {
    if (value== _customer.FirstName)
      return;
   _customer.FirstName= value;
    base.OnPropertyChanged("FirstName");
  }
}復制代碼

  當用戶創建一個新的Customer并且點擊CustomerView的Save按鈕時,與View關聯的CustomerViewModel?將添加一個新的Customer?到Customer集合。這導致了CustomerAdded?事件被激活,這個事件通知AllCustomersViewModel應該將新的Customer添加到AllCustomers?集合。在某種意義上,CustomerRepository類在各種處理Customer對象的ViewModel類之間起到一個同步機制的作用。可能有人會把這當做Mediator?設計模式的使用。在下面的部分我將細述它是如何工作的,但是現在看一下圖11?對各部分如何組裝的有一個整體的了解。

圖?11

新Customer數據輸入表格

  當用戶點擊"Create?new?customer"鏈接,MainWindowViewModel?會在它的工作區列表中添加一個新的CustomerViewModel,同時一個新的CustomerView?控件去顯示它。當用戶將有效的類型值輸入到輸入框中,Save?按鈕變為可用狀態以便用戶可以保存新的Customer?的信息。沒有什么與眾不同的東西,僅僅是一個包含輸入驗證和Sava按鈕的數據表格。

  Customer?類已經通過實現IDataErrorInfo?接口內置了輸入驗證的支持。此驗證確保了Customer有一個FirstName,正確格式的e-mail?地址,并且如果用戶是一個人的話,還有一個LastName。如果Customer?的IsCompany返回?trueLastName?就不能有值(因為一個公司沒有LastName)。這個驗證的邏輯從客戶對象的角度來看可能是有意義的,但是它不滿足界面的需求。?用戶界面需要用戶去選擇一個新的Customer是人還是公司。客戶類型選擇器初始值為"Not?Specified"。那么如果一個CustomerIsCompany屬性僅僅允許true或者false的值,界面是怎樣去告訴用戶客戶類型是"Not?Specified"?的呢??

  假設你已經完成了整個軟件系統,你應該將IsCompany?屬性改為Nullable<bool>類型?類型,這可以允許"unselected"值。?但是,真實的世界沒有這么簡單,假設你不能改變Customer?類因為它來自你們公司的另一個團隊的類庫。如果存在數據庫的設計而不能保存"unselected"將會怎樣?如果其他應用程序已經使用Customer類而且依賴于它是一個正常的布爾值將會怎樣?又一次地,ViewModel來幫忙。圖12?的測試方法展示了此功能如何在CustomerViewModel工作的。CustomerViewModel?暴露了一個CustomerTypeOptions?的屬性以便Customer?的類型可以顯示為第三種字符串。它也暴露了一個CustomerType屬性來存儲選擇的字符串。當CostomerType賦值時,它將字符串值轉換為一個布爾值并提供給為潛在的Customer對象的IsCompany屬性。圖13顯示了這兩個屬性。

// In CustomerViewModelTests.cs
[TestMethod]
public void TestCustomerType()
{
Customer cust= Customer.CreateNewCustomer();
CustomerRepository repos= new CustomerRepository(
Constants.CUSTOMER_DATA_FILE);
CustomerViewModel target= new CustomerViewModel(cust, repos);

target.CustomerType= "Company"
Assert.IsTrue(cust.IsCompany,"Should be a company");
target.CustomerType= "Person";
Assert.IsFalse(cust.IsCompany,"Should be a person");

target.CustomerType= "(Not Specified)";
string error= (targetas IDataErrorInfo)["CustomerType"];
Assert.IsFalse(String.IsNullOrEmpty(error),"Error message should
be returned");
} 復制代碼

12

// In CustomerViewModel.cs

public string[] CustomerTypeOptions
{
  get
  {
    if (_customerTypeOptions== null)
    {
      _customerTypeOptions= new string[]
      {
        "(Not Specified)",
        "Person",
        "Company"
       };
    }
     return _customerTypeOptions;
  }
}
public string CustomerType
{
  get {return _customerType; }
  set
  {
    if (value== _customerType||
      String.IsNullOrEmpty(value))
    return;

    _customerType= value;

    if (_customerType== "Company")
    {
      _customer.IsCompany= true;
    }
    else if (_customerType== "Person")
    {
      _customer.IsCompany= false;
   }

    base.OnPropertyChanged("CustomerType");
    base.OnPropertyChanged("LastName");
   }
} 復制代碼

13

?  CustomerView控件包含一個ComboBox來綁定到這些屬性,如下:

<ComboBox
ItemsSource="{Binding CustomerTypeOptions}"
SelectedItem="{Binding CustomerType, ValidatesOnDataErrors=True}" /> 復制代碼

  當ComboBox?的選擇改變的時候,程序會查詢數據源的IDataErrorInfo接口以檢查新的值是否有效。此過程發生的原因是SelectedItem屬性綁定將ValidateOnDataErrors設置為true由于數據源是CustomerViewModel?對象,綁定系統在CustomerViewModel的?CutomerType屬性上查詢驗證錯誤。大多數情況下,CustomerViewModel?會代理所有Customer?對象包含的錯誤驗證請求。然而,由于Customer?的IsCompany屬性沒有為選擇狀態,CustomerViewModel?類必須處理在ComboBox?控件上新的選擇項的驗證。代碼如?圖14

// In CustomerViewModel.cs
string IDataErrorInfo.this[string propertyName]
{
  get
 {  
    string error= null;

    if (propertyName== "CustomerType")
    {
      // The IsCompany property of the Customer class
      // is Boolean, so it has no concept of being in
      // an "unselected" state. The CustomerViewModel
      // class handles this mapping and validation.
       error= this.ValidateCustomerType();
    }
    else
    {
      error= (_customeras IDataErrorInfo)[propertyName];
    }

    // Dirty the commands registered with CommandManager,
    // such as our Save command, so that they are queried
    // to see if they can execute now.
     CommandManager.InvalidateRequerySuggested();
    return error;
  }
}

string ValidateCustomerType()
{
  if (this.CustomerType== "Company" || this.CustomerType== "Person")
    return null;
  return "Customer type must be selected";
} 復制代碼

14

  代碼的關鍵是CustomerViewModel對IDataErrorInfo?接口的實現ViewModel指定屬性驗證的請求并且將其它的請求委托到Customer?對象。這允許你使用Model?類中的驗證邏輯,而且附加的屬性驗證僅僅對于ViewModel類有意義。

  通過SaveCommand?屬性使得CustomerViewModel?可以被保存。此命令運用RelayCommand?類去檢查CustomerViewModel?是否能保存自己并且當被告知保存它的狀態時該做什么。在此程序中保存一個新的Customer意味著把它添加到CustomerRepository。決定一個新的客戶是否能被保存需要兩方面方的同意:Customer?對象必須被詢問是否通過了驗證,并且CustomerViewModel?必須確定它是否通過驗證。這兩部分的決定是必要的,因為ViewModel指定的屬性已經在之前被檢查過。CustomerViewModel?的保存邏輯如?圖15

// In CustomerViewModel.cs
public ICommand SaveCommand
{
   get
  {
     if (_saveCommand== null)
    {
      _saveCommand= new RelayCommand(
        param=> this.Save(),
        param=> this.CanSave
      );
     }
     return _saveCommand;
  }
}

public void Save()
{
  if (!_customer.IsValid)
    throw new InvalidOperationException("...");

  if (this.IsNewCustomer)
     _customerRepository.AddCustomer(_customer);

  base.OnPropertyChanged("DisplayName");
}

bool IsNewCustomer
{
  get
  {
    return !_customerRepository.ContainsCustomer(_customer);
  }
}

bool CanSave
{
  get
 {
    return String.IsNullOrEmpty(this.ValidateCustomerType())&& _customer.IsValid;
 }
} 復制代碼

15

  在這里,使用ViewModel的使用使得下面這些事情變得更加簡單:創建一個現實Customer?對象的View以及允許Bool屬性為"unselected"?狀態。它也使得告知Customer保存它的狀態變得簡單。如果將Customer?直接綁定到ViewView可能需要許多代碼以使得程序正常工作。在一個設計良好的MVVM?架構中,大部分的Views?的后置代碼應該是空的,或者之多包含一些控制控件或資源的代碼。有時候在View的后置代碼中寫一些代碼與ViewModel?交互是必須得。比如捕獲一個事件或調用一個方法,這個方法直接從ViewModel對象自己調用時非常困難。

所有Customer視圖

  演示程序同樣包含了一個在ListView中顯示所有Customer的工作區,此類表中的Customer是通過它是工公司或是個人來分類的。用戶可以同時選擇一個或者多個Customer并且在底部右下角查看總銷售額。

  用戶界面是AllCustomersView控件,它對應一個AllCustomersViewModel對象。每一個列表項代表一個AllCustomersViewModel對象暴露的AllCustomers集合中的一個CustomerViewModel對象。在前面的章節中,你看到了一個CustomerViewModel怎樣對應一個數據輸入表格,現在同樣的CustomerViewModel對象被映射作為列表中的一項。CustomerViewModel類并不知道什么樣的可見的元素來顯示它,這就是為什么它可以重用的原因。AllCustomersView?在ListView上創建了分組。這是通過綁定ListView的ItemsSourceCollectionViewSource來完成的。如圖16

<!-- In AllCustomersView.xaml-->
<CollectionViewSource
  x:Key="CustomerGroups"
  Source="{Binding Path=AllCustomers}"
>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="IsCompany" />
</CollectionViewSource.GroupDescriptions>
  <CollectionViewSource.SortDescriptions>
    <!--
    Sort descending by IsCompany so that the' True' values appear first,
     which means that companies will always be listed before people.
    -->
    <scm:SortDescription PropertyName="IsCompany" Direction="Descending" />
    <scm:SortDescription PropertyName="DisplayName" Direction="Ascending" />
  </CollectionViewSource.SortDescriptions>
</CollectionViewSource> 復制代碼

圖16

  ListView'?的?ItemContainerStyle屬性確定了ListViewItem?和?CustomerViewModel?對象之間的聯系,此屬性的Style?被應用到每個ListViewItem,它使得ListViewItem?中的屬性綁定到CustomerViewModel對象。一個重要的綁定是在列表項的IsSelected屬性和CustomerViewModelIsSelected屬性之間創建鏈接,如下:

<Style x:Key="CustomerItemStyle" TargetType="{x:Type ListViewItem}">
<!-- Stretch the content of each cell so that we can right-align textin the Total Sales column.-->
  <Setter Property="HorizontalContentAlignment" Value="Stretch" />
  <!--
     Bind the IsSelected property of a ListViewItem to the
     IsSelected property of a CustomerViewModelobject.
  -->
  <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style> 復制代碼

  當CustomerViewModel?被選擇或是反選的時候,會導致所有選擇的Customer的總銷售額改變。AllCustomersViewModel?負責維護這個值,以便在列表之下能顯示正確的數值。圖17?顯示了AllCustomersViewModel怎樣監視每個客戶被選擇或反選,并通知view更新顯示數值。

// In AllCustomersViewModel.cs
public double TotalSelectedSales
{
  get
  {
     return this.AllCustomers.Sum(
    custVM=> custVM.IsSelected? custVM.TotalSales :0.0);
}
}

void OnCustomerViewModelPropertyChanged(object sender,
PropertyChangedEventArgs e)
{
  string IsSelected= "IsSelected";

  // Make sure that the property name we're
  // referencing is valid. This is a debugging
  // technique, and does not execute in a Release build.
 (senderas CustomerViewModel).VerifyPropertyName(IsSelected);

  // When a customer is selected or unselected, we must let the
  // world know that the TotalSelectedSales property has changed,
  // so that it will be queried again for a new value.
if (e.PropertyName== IsSelected)
    this.OnPropertyChanged("TotalSelectedSales");
} 復制代碼

  UI綁定到TotalSelectedSales?并且將值轉換為貨幣格式。通過返回一個字符串代替從TotalSelectedSales屬性得到的浮點數,ViewModel對象可以應用貨幣格式。在.NET框架3.5?SP1中,ContentPresenter添加了ContentStringFormat屬性,所以如果你用舊版本的WPF需要在代碼中應用貨幣格式。

<!-- In AllCustomersView.xaml-->
<StackPanel Orientation="Horizontal">
  <TextBlock Text="Total selected sales:" />
  <ContentPresenter Content="{Binding Path=TotalSelectedSales}" ContentStringFormat="c" />
</StackPanel>?復制代碼

總結

  WPF對應用程序開發者提供了許多,學習利用這個需要心態上的轉變。?MVVM設計模式對于設計和實現應用程序是簡單的并且有指導意義。它使得你可以創建數據、行為、展現的分離,使得控制軟件開發的混亂更加簡單。


點擊,代碼下載。


轉載于:https://www.cnblogs.com/jeriffe/articles/2287885.html

總結

以上是生活随笔為你收集整理的[译]WPF 应用程序和MVVM设计模式 ——Josh Smith的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

国产一区二区在线播放视频 | 在线视频观看亚洲 | 久久激五月天综合精品 | 亚洲精选视频在线 | 黄色a视频免费 | 精品国产免费av | 亚洲综合视频网 | 97国产精品一区二区 | 777奇米四色| 色鬼综合网 | 国产精品入口久久 | 一区二区电影网 | 欧美激情在线网站 | 国模视频一区二区 | 日韩av一区二区三区四区 | 日日夜夜狠狠干 | 91精品国产自产老师啪 | 激情久久伊人 | 国产精品久久综合 | 久久久www成人免费毛片麻豆 | 少妇自拍av | 亚洲性少妇性猛交wwww乱大交 | 免费观看91| 欧美日韩久久不卡 | 欧美视频二区 | 亚洲成av人电影 | 中文字幕黄色 | 久久国产区 | 激情欧美日韩一区二区 | 亚洲欧美少妇 | 最近日韩中文字幕中文 | 97精产国品一二三产区在线 | 伊人影院在线观看 | 天天射天天拍 | 四虎5151久久欧美毛片 | 国产午夜精品理论片在线 | 免费中文字幕视频 | 精品免费国产一区二区三区四区 | 中文字幕美女免费在线 | 午夜在线观看一区 | free. 性欧美.com | 丁香五月网久久综合 | 一区二区在线不卡 | 婷婷丁香五 | 国产又粗又猛又色又黄网站 | 五月天激情视频在线观看 | 亚洲一级电影视频 | 国产欧美综合视频 | 在线视频 一区二区 | 成人一级视频在线观看 | 国产美女视频黄a视频免费 久久综合九色欧美综合狠狠 | 波多野结衣在线观看视频 | 视频在线日韩 | 国产精品毛片网 | 国产自产在线视频 | 91尤物国产尤物福利在线播放 | 特级片免费看 | 永久免费看av | 国产午夜精品av一区二区 | 欧美欧美 | 免费三级av | 国产成人一区二 | 久久精品国产免费观看 | 四虎成人免费影院 | av丁香| 在线观看亚洲视频 | a在线免费 | 这里只有精彩视频 | 99久久久久免费精品国产 | 麻豆视频国产在线观看 | 网站你懂的| 久草免费在线视频观看 | 91日韩在线视频 | 国产亚洲视频在线 | 麻豆传媒视频在线免费观看 | 狠狠操电影网 | 五月天久久狠狠 | 5月丁香婷婷综合 | 精品国产人成亚洲区 | 在线观看第一页 | 97av视频在线 | 日韩在线观看免费 | 国产综合福利在线 | 成年人在线视频观看 | 久草精品在线观看 | 99免费在线视频观看 | 精品久久久久久久久久久久久 | 国产aaa免费视频 | 国产一级不卡毛片 | 日韩在线视频国产 | 久久手机看片 | 国产福利91精品张津瑜 | 久草视频首页 | 亚洲精品久久久久中文字幕二区 | 天天操天天操天天操天天操天天操 | 狠狠操.com | 欧美一区二区三区不卡 | 中文字幕亚洲不卡 | 日本久久视频 | 99tvdz@gmail.com| 91综合久久一区二区 | 在线观看黄色免费视频 | 丁香视频五月 | 免费亚洲黄色 | 日本午夜免费福利视频 | 91人人视频在线观看 | 欧美日韩性视频 | 国产精品久久久久aaaa | 久久久受www免费人成 | 欧美色插 | 中文av资源站| 又黄又爽的视频在线观看网站 | av中文字幕不卡 | 视频福利在线观看 | 伊人天天色 | 日韩免费视频在线观看 | 日韩天天干 | 色www精品视频在线观看 | 日韩一级片网址 | 国产精品免费视频久久久 | 国产精品久久99综合免费观看尤物 | 99 久久久久 | 久久久99精品免费观看 | 日韩国产欧美在线视频 | 亚洲欧美在线综合 | 久久久久成人精品 | 久久69av | 久久久久成人精品免费播放动漫 | 国产精品久久亚洲 | 日日操日日 | 九色视频网 | 国产91精品一区二区麻豆亚洲 | 久久国精品 | 一级黄色免费网站 | 日韩免费看的电影 | 国产高清久久久久 | 在线看一区 | 国产不卡在线视频 | 久久综合九色综合欧美就去吻 | 五月婷婷一区二区三区 | 日韩亚洲在线视频 | 国产日韩欧美在线一区 | 97国产精品视频 | 91丨九色丨国产丨porny精品 | 网站在线观看你们懂的 | 日韩免费在线观看视频 | 亚洲日日射 | 午夜av在线电影 | 国产韩国日本高清视频 | 中文字幕一区2区3区 | 精品久久在线 | 欧美性生活大片 | 亚洲国产精品免费 | 亚洲精品中文字幕在线观看 | 国产不卡免费视频 | 欧美日韩中文另类 | 免费黄色在线 | 日韩a免费| 欧美巨大荫蒂茸毛毛人妖 | 中文字幕亚洲精品在线观看 | 在线观看精品黄av片免费 | 国产精品va在线播放 | 国产精品麻豆视频 | 91九色综合 | 亚洲区精品视频 | 91麻豆精品国产91久久久无限制版 | 国产中文a | 国产精品福利在线 | 不卡电影免费在线播放一区 | 国产精品私人影院 | 国产探花在线看 | 欧美男女爱爱视频 | 国产 日韩 在线 亚洲 字幕 中文 | 97人人澡人人爽人人模亚洲 | 99热这里只有精品国产首页 | 国内成人综合 | 国内99视频| 久久a v电影 | 性日韩欧美在线视频 | 亚洲综合视频在线 | 久久精品视频在线观看 | 亚洲精品久久久蜜桃直播 | 国产亚洲综合精品 | 在线观看成人一级片 | 欧美激情综合五月色丁香 | 97超碰在线免费观看 | 欧美成年人在线视频 | 久久无码av一区二区三区电影网 | 亚洲黄色在线观看 | 九九爱免费视频在线观看 | 国产精品久久久久影院 | 午夜视频日本 | 色噜噜日韩精品欧美一区二区 | 免费视频成人 | 99色免费视频 | 国产成人资源 | 色视频在线观看免费 | 日韩精品久久久免费观看夜色 | 911国产| 日韩在线免费视频观看 | 亚洲资源网 | 日韩一级电影在线观看 | 在线亚洲激情 | 精品国产久 | 91丨九色丨蝌蚪丨老版 | 国产成人精品一区二区三区在线观看 | 亚洲国产精品一区二区久久,亚洲午夜 | 国产在线欧美日韩 | 99热这里精品 | 97精品国产97久久久久久 | 亚洲乱亚洲乱妇 | 久久99精品热在线观看 | 国产91全国探花系列在线播放 | 一级一片免费看 | 夜色资源站国产www在线视频 | av免费网| 91九色最新地址 | 欧美大片aaa | 片黄色毛片黄色毛片 | 四川bbb搡bbb爽爽视频 | 国产xvideos免费视频播放 | 色吊丝在线永久观看最新版本 | 九九热免费精品视频 | 麻豆视频91 | 婷婷综合导航 | 中文字幕资源网 国产 | 亚洲免费视频在线观看 | 中文字幕一区二区三区在线观看 | 深爱激情五月综合 | 色婷婷视频在线 | 色播99| 国产一区影院 | 国产成人av福利 | 永久免费视频国产 | 国产一线天在线观看 | 亚洲精品久久久蜜桃直播 | 成年人免费在线播放 | 中文字幕一区二区在线播放 | 99久久久国产精品 | 少妇视频一区 | 婷色在线 | 夜夜躁日日躁狠狠久久av | 日本久久中文字幕 | 国产精品欧美久久久久久 | 91精彩视频 | 亚洲综合五月 | 91福利国产在线观看 | 五月天中文字幕 | 国语黄色片 | 精品一区二区三区香蕉蜜桃 | 天天草天天干天天射 | 黄色1级毛片 | 九九99 | 色中文字幕在线观看 | 久草在线视频网 | 五月天堂网 | 亚洲一区日韩精品 | 4438全国亚洲精品在线观看视频 | 免费黄a大片| 在线看一级片 | 日韩久久午夜一级啪啪 | 日本在线观看中文字幕无线观看 | 日韩精品一区二区三区免费视频观看 | 精品亚洲视频在线 | 米奇影视7777 | av中文在线观看 | 在线观看免费av片 | 国产资源网| 天天拍天天色 | 在线观看国产高清视频 | 色婷婷电影 | 97久久精品午夜一区二区 | 97视频人人 | 日韩高清在线一区 | 亚洲三级在线 | 精品免费视频123区 午夜久久成人 | 成 人 黄 色视频免费播放 | 在线视频 精品 | 亚洲首页 | 婷婷色在线播放 | 色婷婷av一区二 | a天堂一码二码专区 | 黄色成人av网址 | 六月色丁香 | 国产日韩精品欧美 | 天天射天天爱天天干 | 97视频人人免费看 | 五月天久久狠狠 | 久久中文欧美 | 日韩欧美在线国产 | 久久伦理网 | 天天天干天天天操 | 91探花国产综合在线精品 | 激情小说 五月 | 免费观看性生活大片 | 国产视频 亚洲精品 | 91人人插| 精品视频久久 | 超级碰碰视频 | 久久久久久美女 | 日产乱码一二三区别在线 | 国产精品精品久久久久久 | 999久久久| 五月婷婷六月丁香 | 日韩在线视频国产 | 久草在线视频首页 | av色影院| 日韩在线视| 玖草在线观看 | 丝袜av网站 | 久久午夜网 | 成人国产精品免费观看 | 天天艹天天 | 99在线精品视频 | 成人在线免费观看网站 | 色的网站在线观看 | 国产精品99久久久精品免费观看 | 久久久96| 激情偷乱人伦小说视频在线观看 | 午夜 久久 tv | 国产成人在线播放 | 91精品久久久久久粉嫩 | 中文字幕观看av | 九九久久久久久久久激情 | 中文字幕在线色 | 亚洲国产精品一区二区久久hs | 97成人免费 | 亚洲成人二区 | 全久久久久久久久久久电影 | 国产精品一区二区你懂的 | 日本高清dvd | 色香蕉在线视频 | 麻豆va一区二区三区久久浪 | sesese图片 | 日韩毛片在线一区二区毛片 | 免费看黄色毛片 | 午夜视频色| 免费看片网址 | 免费在线观看成人 | 天天操天天干天天操天天干 | 国产精品久久久久久久久婷婷 | 中文字幕亚洲综合久久五月天色无吗'' | 精品国产美女在线 | 人人干在线 | 免费网站在线观看人 | 精品国产aⅴ一区二区三区 在线直播av | 天天射天天射 | 国产精品免费观看网站 | 欧美日韩在线观看一区二区 | 国产精品一区二区久久精品 | 麻豆视频在线观看 | 国产123av| 亚色视频在线观看 | 天天看天天操 | 最新av免费在线 | 久久激情视频 久久 | 日日天天干 | 国内久久 | 国产精品久久久久久久久久三级 | 国产精品第7页 | 中文字幕av一区二区三区四区 | 久99视频 | 亚洲精品国产综合久久 | 欧日韩在线| 免费在线观看a v | 日韩videos高潮hd | 成人h在线 | 亚州成人av在线 | 免费在线观看的av网站 | 久草电影免费在线观看 | 人人揉人人揉人人揉人人揉97 | 国产中文字幕久久 | 综合网婷婷 | 午夜婷婷在线观看 | 精品久久免费 | 日韩av午夜 | 亚洲国内精品在线 | 欧美乱大交 | 久久久国产精品人人片99精片欧美一 | 99久久久国产精品免费观看 | 黄免费在线观看 | 黄色影院在线免费观看 | 天堂入口网站 | 黄色一级大片在线免费看国产一 | 97视频播放| 天天撸夜夜操 | 久久 亚洲视频 | 日韩久久激情 | 免费看成年人 | 亚洲精品乱码久久久一二三 | 又黄又刺激 | 激情视频久久 | 午夜精品一区二区三区视频免费看 | 国产成人亚洲在线观看 | 超碰在线观看97 | 99热这里只有精品免费 | www.夜色321.com | 色综合久久久久久中文网 | 黄色a视频免费 | 国产精品成人免费精品自在线观看 | 欧美男同视频网站 | 狠狠干2018 | 亚洲国产欧美一区二区三区丁香婷 | 中文字幕在线观看1 | 精品高清美女精品国产区 | 91黄站| 超碰在线观看av.com | 热久久免费视频精品 | 国产色网 | 综合色伊人 | 亚洲做受高潮欧美裸体 | 麻豆视频免费观看 | 一区二区久久久久 | 91视频麻豆 | 日日夜日日干 | 欧美日韩国产综合网 | 中文字幕观看视频 | 奇米四色影狠狠爱7777 | 国产成人精品a | 国产五月天婷婷 | 香蕉视频免费在线播放 | 亚洲人人网 | 国产精品美女免费 | 极品国产91在线网站 | 在线亚洲午夜片av大片 | 蜜臀aⅴ国产精品久久久国产 | 亚洲欧美一区二区三区孕妇写真 | 中文字幕一区二区三区在线视频 | 欧美日本不卡 | 亚洲成人资源在线 | 欧美一级黄色网 | 国模一区二区三区四区 | av成人免费在线看 | 亚洲日本中文字幕在线观看 | 一级成人在线 | 中文字幕在线网 | 日韩精品不卡 | 美女黄视频免费 | 成人在线视频一区 | 97在线观看免费高清完整版在线观看 | 99久免费精品视频在线观看 | 中文字幕 国产精品 | 国产成人免费av电影 | 色婷婷狠狠 | 亚洲乱码国产乱码精品天美传媒 | 久久久久久久久久久免费视频 | 天天色综合三 | 成人影片在线免费观看 | 日韩精品久久久久久久电影99爱 | 久久夜视频| 国产精品亚洲视频 | 久久婷婷激情 | 久久a久久 | 国产色视频123区 | 久久久久五月 | 99视频精品视频高清免费 | 欧美日韩国产伦理 | 91精品国自产在线观看欧美 | 夜夜骑天天操 | 亚洲涩涩网站 | 亚洲成人软件 | 伊人久久五月天 | 欧美日韩精品国产 | 亚洲91精品 | 深夜免费福利视频 | 九九免费观看视频 | 99麻豆久久久国产精品免费 | 一级特黄aaa大片在线观看 | 4p变态网欧美系列 | 最近中文字幕完整高清 | 97av视频| 日韩激情视频在线 | 欧美视频99| 亚洲免费在线 | 中文字幕乱码电影 | 亚洲欧洲中文日韩久久av乱码 | 日本在线视频网址 | 国产精品福利小视频 | 五月婷婷激情六月 | 久草网在线视频 | 黄色大片视频网站 | 天天操天天射天天操 | 狠狠干中文字幕 | 99c视频高清免费观看 | 人人爽人人澡人人添人人人人 | 国产五月色婷婷六月丁香视频 | 欧美另类xxxxx| 久久五月婷婷丁香社区 | 开心激情网五月天 | 亚洲天堂va| 在线精品亚洲 | 青草视频免费观看 | 日韩一二三区不卡 | 日韩精品不卡 | 在线看v片 | 久久精品国产成人 | 国产只有精品 | 最新日韩在线观看视频 | 欧美国产日韩一区二区三区 | 国产色妞影院wwwxxx | 国产中文字幕视频在线观看 | 特及黄色片 | 精品国产99国产精品 | www.神马久久 | 亚洲视频观看 | 国产精品一区在线观看你懂的 | 韩国一区二区三区视频 | 久久99热精品 | 欧美精品一区二区三区一线天视频 | 国产 一区二区三区 在线 | 在线视频你懂得 | 欧美日韩视频一区二区 | www.国产在线视频 | 色狠狠久久av五月综合 | 丝袜精品视频 | 婷婷九月激情 | 99这里只有久久精品视频 | 国产精品一区二区久久精品爱涩 | 欧美xxxxx在线视频 | 美女黄久久 | 免费在线观看亚洲视频 | 久久99精品国产麻豆宅宅 | 婷婷5月激情5月 | 亚洲精品白浆高清久久久久久 | 国产精品成人a免费观看 | 国产成人在线观看免费 | 免费观看全黄做爰大片国产 | 日韩欧美综合 | 色综合婷婷 | 免费网站在线观看成人 | 久久影视中文字幕 | 91在线播放视频 | 日本三级在线观看中文字 | 在线免费观看一区二区三区 | 精品国产乱码久久久久 | 久久免费公开视频 | 精品电影一区二区 | 国产免费观看视频 | 97综合网| 九九热精品视频在线播放 | 91免费网址 | 欧美性生交大片免网 | zzijzzij亚洲日本少妇熟睡 | 91精品国产一区二区三区 | 久久久色| 国产精品aⅴ | 一区二区三区在线视频观看58 | 日韩在线视频一区 | 日日爱影视 | 亚洲视频播放 | 欧美一区日韩一区 | aaa免费毛片 | 欧美激情视频一区二区三区免费 | 久久免费99精品久久久久久 | 欧美精品中文字幕亚洲专区 | 日韩av一区二区三区在线观看 | 蜜桃视频成人在线观看 | 亚洲国产美女精品久久久久∴ | 久久伊人热| 精品999在线观看 | 黄a在线看 | 亚洲精品三级 | 最新三级在线 | 久久久久97国产 | 免费污片 | 亚洲精品成人网 | 日韩v在线91成人自拍 | 国产成人精品久久亚洲高清不卡 | 午夜精品一二区 | 成人av地址| 国产精品毛片一区视频播不卡 | 久久av福利| 亚洲午夜精品电影 | 成年人视频在线免费观看 | 国产99视频在线观看 | 国产精品综合av一区二区国产馆 | 欧美激情另类文学 | 视频99爱| 国产麻豆剧果冻传媒视频播放量 | 中文字幕永久 | 日韩三级在线观看 | 九九九九九九精品 | 欧美极度另类 | www.av在线.com| 久久avav | 国产很黄很色的视频 | 亚洲天堂网在线视频观看 | 日韩欧美69| av资源在线看 | 免费在线观看一级片 | 国产手机视频精品 | 九九99靖品 | 国产精品网红直播 | 91传媒免费在线观看 | 蜜臀av性久久久久蜜臀aⅴ涩爱 | 久青草视频| 伊人久久国产 | av电影不卡| 国产成人亚洲在线观看 | 夜夜操天天干 | 久久字幕精品一区 | 国产v在线播放 | www.xxxx变态.com | 丁香资源影视免费观看 | 久久久精品免费观看 | 91九色蝌蚪国产 | 激情欧美在线观看 | 国产区精品在线观看 | 日本中文乱码卡一卡二新区 | 国产精品欧美久久久久天天影视 | 久久午夜羞羞影院 | 超碰在线公开免费 | 久草视频在线播放 | 免费看片成年人 | 精品免费国产一区二区三区四区 | 国产一区二区久久 | 在线观看黄污 | 国偷自产视频一区二区久 | 日韩免费成人av | 欧美国产高清 | 开心激情久久 | 国产亚洲免费的视频看 | 国产美女久久 | a在线一区 | 黄色精品视频 | 在线视频麻豆 | 91天天操| 亚洲婷婷综合色高清在线 | av色影院 | 久久久久精| 国产免费a| 精品国产一区二区三区男人吃奶 | 亚洲最新av网站 | 在线观看中文字幕视频 | 色七七亚洲影院 | a级片韩国| 日日干网| 天天综合导航 | 狠狠干狠狠操 | 亚洲h色精品 | 久久久天堂 | 国产精彩在线视频 | 91尤物国产尤物福利在线播放 | 色大片免费看 | 18国产精品福利片久久婷 | 最近中文字幕高清字幕在线视频 | 欧美性生活免费看 | 91精品国产91久久久久 | 免费美女久久99 | 最近免费中文视频 | 91视频在线观看下载 | 成片视频在线观看 | 日韩欧美精品一区 | 狠狠狠狠干 | 午夜精品影院 | 日韩电影中文字幕在线 | 中文字幕亚洲精品在线观看 | 69av久久| 91自拍视频在线观看 | 色丁香婷婷 | 1区2区3区在线观看 三级动图 | 免费亚洲片| 国产精品视频免费 | 视频在线99 | 中文在线免费看视频 | 国产不卡在线视频 | 日韩中文字幕免费视频 | av线上看| 国产午夜精品久久 | 五月天久久综合网 | 亚洲欧洲久久久 | 99久久精品国产亚洲 | 狠狠狠狠狠狠干 | 亚洲aⅴ一区二区三区 | 香蕉网在线播放 | 国产精品久久久久久久久久久久冷 | 黄网站a| 最近中文字幕视频完整版 | 91av视频在线播放 | 日韩av一卡二卡三卡 | 日日碰狠狠躁久久躁综合网 | 日韩草比 | 伊人久久一区 | 亚洲免费不卡 | 2019中文字幕第一页 | 免费97视频 | 亚洲永久在线 | 国产区久久 | 99精品久久久久久久久久综合 | 久久色视频 | 成人在线观看你懂的 | 一级欧美日韩 | 色综合天天视频在线观看 | 精品久久一区二区三区 | 91伊人久久大香线蕉蜜芽人口 | 在线观看日本高清mv视频 | 日韩激情三级 | 精品国产乱码久久 | 在线免费观看国产视频 | 国外成人在线视频网站 | 国产精品精品国产色婷婷 | 久久精品爱视频 | www.色午夜.com | 久久色视频 | 久久精品免费电影 | 中文字幕黄色网址 | 夜夜躁狠狠躁 | 国产免费视频在线 | 日韩免费一区二区在线观看 | 久久涩视频 | 亚洲综合视频在线 | av大全在线免费观看 | 精品美女久久久久 | 日韩a在线看 | 久草久草视频 | 久久国产免费 | 大胆欧美gogo免费视频一二区 | 国产一级二级三级在线观看 | 久久无码av一区二区三区电影网 | 中文字幕一区在线观看视频 | 日本爽妇网 | 99精品成人 | 色一级片 | www国产亚洲精品 | 久久久久久中文字幕 | 成人黄色一级视频 | 久久免费国产电影 | 日本在线视频一区二区三区 | 91在线公开视频 | 色91在线视频 | 2023av在线 | 精品国产一区二区久久 | 免费成视频 | 黄色免费看片网站 | 国产精品一区免费在线观看 | 亚州精品在线视频 | 曰韩在线 | 精品久久久一区二区 | 性色av一区二区三区在线观看 | 96视频免费在线观看 | 国产午夜剧场 | 婷婷色中文字幕 | 在线观看完整版 | 国产超碰在线观看 | 日韩欧美大片免费观看 | 九色免费视频 | 9999在线视频 | 色婷婷综合视频在线观看 | 国产伦精品一区二区三区… | av免费看看 | 久久一区二区三区国产精品 | 国产玖玖精品视频 | 国产对白av | 欧美成人精品欧美一级乱黄 | 国产精品精品国产色婷婷 | 免费福利片 | 国产中文字幕在线观看 | 欧美一级免费高清 | 天堂在线一区二区 | 国产精品一区二区在线 | 久久久麻豆精品一区二区 | 又黄又爽又刺激的视频 | 在线中文字母电影观看 | 天天综合日 | 乱男乱女www7788 | 精品久久国产一区 | 99精品国产一区二区 | 激情久久久 | 日日射av | 四虎在线免费观看 | 久久成人国产精品一区二区 | 中文不卡视频在线 | 少妇性bbb搡bbb爽爽爽欧美 | 国产一区二区中文字幕 | 人人玩人人添人人澡超碰 | 丁香资源影视免费观看 | 999久久国产 | 99国产情侣在线播放 | av中文字幕亚洲 | 国产高清99| 综合久久一本 | 日韩在线欧美在线 | 久久永久免费视频 | 黄色午夜 | 亚洲综合成人婷婷小说 | 国产婷婷精品av在线 | 91网页版免费观看 | 久久精品8 | 亚洲激情校园春色 | 成年人免费在线观看网站 | 婷五月天激情 | 九九热国产视频 | 精品视频不卡 | 丁香花在线观看视频在线 | 国产美女免费观看 | 国产成人一区二区在线观看 | 夜色成人av | 亚洲精品视频网 | 人人玩人人添人人澡97 | 91高清在线 | 精品国产亚洲一区二区麻豆 | 日本中文字幕在线电影 | 久久99国产综合精品免费 | 成年美女黄网站色大片免费看 | 国产 在线观看 | 色噜噜日韩精品欧美一区二区 | 国产免费片 | 丁香婷婷综合五月 | 午夜精品久久久久久久99无限制 | 中文字幕一区三区 | 国产在线观看你懂的 | 亚洲乱亚洲乱妇 | 超碰国产人人 | 久久这里只精品 | 亚洲精品国产成人av在线 | 国产韩国日本高清视频 | 91福利视频一区 | 在线成人国产 | 一区精品久久 | 精品自拍网| 麻豆国产精品永久免费视频 | 四虎在线免费观看 | 天天人人综合 | 在线观看黄色小视频 | 欧美日韩中文字幕综合视频 | 欧美不卡视频在线 | 99爱视频在线观看 | 国产精品激情偷乱一区二区∴ | 国产免费av一区二区三区 | 日韩欧美中文 | 91精品视频免费看 | 精品国产免费av | 亚洲高清精品在线 | 日韩久久精品一区二区三区 | 久久久久国产精品免费 | 人人涩 | 亚洲男男gaygayxxxgv | 成人三级网站在线观看 | 日韩啪啪小视频 | 日韩午夜大片 | 能在线观看的日韩av | 久久国产免费 | 99热这里只有精品久久 | 国产亚洲永久域名 | 久久综合久久综合这里只有精品 | 久久99国产精品久久99 | 久草五月| 国产 精品 资源 | 国产精品国产三级在线专区 | 久久狠狠亚洲综合 | 日韩av电影中文字幕在线观看 | 色偷偷88888欧美精品久久久 | 一本一本久久a久久精品综合 | 天堂av色婷婷一区二区三区 | 在线观看视频一区二区 | 午夜美女wwww | 九七视频在线 | 日本黄色a级大片 | 国产精品第54页 | 国产成人精品亚洲精品 | 欧美一区免费在线观看 | 午夜精品久久久99热福利 | 97在线观看免费高清完整版在线观看 | 天天操天天添天天吹 | 欧美日韩午夜 | 久久视频在线观看中文字幕 | 97香蕉久久超级碰碰高清版 | 色婷婷伊人 | 亚洲美女精品区人人人人 | 中文字幕在线观看视频一区二区三区 | 亚洲成人av电影 | 色综合天天狠狠 | 西西www4444大胆视频 | av免费在线观看网站 | 国产91影院 | 91视频传媒 | 久草视频在线播放 | 一本一本久久a久久 | 欧美经典久久 | 国产精品久久久久久久免费观看 | av3级在线 | 国产精品一区二区果冻传媒 | 国产在线不卡视频 | а中文在线天堂 | 亚洲欧美日韩国产一区二区 | 射射射av | 人人射人人射 | 六月丁香综合 | av一级在线 | 久久综合九色99 | 天天操天天干天天干 | 国产乱对白刺激视频不卡 | 久久精品欧美一区二区三区麻豆 | 久草影视在线 | 黄色99视频| 日韩在线观看中文 | 九九九毛片 | japanese黑人亚洲人4k | 久久午夜影院 | 久青草视频在线观看 | 狠狠操导航 | 色综合久久中文字幕综合网 | 日韩精品一区二区不卡 | 超碰人人超碰 | 91在线看黄| 国产片免费在线观看视频 | 三级a视频 | 久草免费在线视频观看 | 成人啪啪18免费游戏链接 | 91在线视频免费 | 一区二区三区韩国免费中文网站 | 亚洲,国产成人av | 欧美日韩精品在线免费观看 | 欧美一区二区三区不卡 | 国产精品久久嫩一区二区免费 | 国产精品99久久久久久久久 | 国产h在线播放 | 日韩大片在线免费观看 | 精品国产一区二区三区久久久 | 日韩高清毛片 | 欧美性超爽 | 热re99久久精品国产99热 | 夜夜婷婷| 国内精品久久久久久久久久 | 91在线超碰 | 色婷婷国产精品一区在线观看 | 亚州精品视频 | 日韩精品一区二区三区水蜜桃 | 国产精品成人自拍 | 久久影院中文字幕 | 天天碰天天操视频 | 久一久久 | 亚洲激情电影在线 | 色播亚洲婷婷 | 四虎在线视频免费观看 | 中文字幕中文字幕在线中文字幕三区 | 国产精品18久久久久久vr | 丁香五香天综合情 | 99精品免费久久久久久久久日本 | 国产精品久久久久永久免费看 | 欧美a免费 | 黄色大片日本免费大片 | 黄影院 | www.久久久.com| 久久高清 | 国产精品久久中文字幕 | av免费在线播放 | 亚洲影院一区 | 草草草影院 | 天天摸天天舔 | 九九九视频在线 | 国色天香永久免费 | 一级一片免费视频 | 中日韩男男gay无套 日韩精品一区二区三区高清免费 | 伊人看片 | 日韩精品最新在线观看 | 国产精品久久伊人 | 福利视频网站 | 亚洲午夜激情网 | 国产视频黄| 日日操天天操夜夜操 | 一区二区精品在线 | 69精品视频 | 欧美日本三级 | 国产爽视频 | 久久99热这里只有精品 | 免费福利片 | 九九在线高清精品视频 | 免费在线成人av | 精品麻豆入口免费 | 黄色三级av| 91激情小视频 | 天天激情综合 | 日本公妇在线观看 | 97视频人人免费看 | 91人人澡| 午夜久久久久久久久久影院 | 在线观看a视频 | 免费精品视频在线观看 | 天天拍天天爽 | 99精品免费网 | 99草在线视频 | 韩国一区二区在线观看 | 日韩mv欧美mv国产精品 | 在线视频1卡二卡三卡 | 超碰最新网址 | 91av视屏| 黄色av观看 | 国产亚洲精品久久久久久大师 | 欧美一区二区在线 | 国产成人一区二区在线观看 | 97超碰中文字幕 | 91视频免费网址 | 91在线在线观看 | 丁香激情婷婷 | 色99色| 日韩欧美91| 天天干com |