參考書:《 visual C# 從入門到精通》
第四部分 用C#構建UMP應用
第26章 在WP應用中顯示和搜索數據
文章目錄
26.1 實現Model-View-ViewModel模式
MVVM模式中,Model提供應用程序需要的數據,View則指定數據在UI中的顯示方式。ViewModel包含用於連接兩者的邏輯,它獲取用戶輸入並將其轉換成對模型執行業務操作的指令;它還從模型獲取數據,並以視圖要求的方式格式化。由於應用程序可能提供相同數據的多個視圖。ViewModel的一個工作是確保來自相同模型的數據能由不同視圖顯示和處理。UWP應用中,視圖可配置數據綁定以連接ViewModel提供的數據。另外,視圖可調用由ViewModel事項的命令,請求ViewModel更新模型中的數據或執行業務操作。
26.1.1 通過數據綁定顯示數據
我們繼續上一章節的應用開發。但我們稍微變動一下,讓控件在藍色背景上顯示,這樣可以更顯眼一些。我們利用Rectangle
控件來創建藍色背景。XAML標記如下:
<Rectangle Grid.Row="0" Grid.RowSpan="6" Grid.Column="1" Grid.ColumnSpan="7">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5.0">
<GradientStop Color="#FF0E3895"/>
<GradientStop Color="#FF141415" Offset="0.929"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
- 右擊項目新建一個公共類,命名爲
Customer.cs
,代碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace C_25_2_Customers
{
public class Customer
{
private int _customerID;
public int CustomerID
{
get { return this._customerID;}
set { this._customerID = value; }
}
private string _title;
public string Title
{
get { return this._title; }
set { this._title = value; }
}
private string _firstName;
public string FirstName
{
get { return this._firstName; }
set { this._firstName = value; }
}
private string _lastName;
public string LastName
{
get { return this._lastName; }
set { this._lastName = value; }
}
private string _emailAddress;
public string EmailAddress
{
get { return this._emailAddress; }
set { this._emailAddress = value; }
}
private string _phone;
public string Phone
{
get { return this._phone; }
set { this._phone = value; }
}
}
}
- 修改所有的TextBox控件中的Text屬性:
<TextBox Grid.Row="0" Grid.Column="1" x:Name="cid" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="{Binding CustomerID}" VerticalAlignment="Center" FontSize="20" IsReadOnly="True"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="cfirstName" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="{Binding FirstName}" VerticalAlignment="Center" FontSize="20" />
<TextBox Grid.Row="3" Grid.Column="1" x:Name="clastName" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="{Binding LastName}" VerticalAlignment="Center" FontSize="20" />
...;
<TextBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="3" x:Name="cemail" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="{Binding EmailAddress}" VerticalAlignment="Center" Width="400" FontSize="20"/>
<TextBox Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="3" x:Name="cphone" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="{Binding Phone}" VerticalAlignment="Center" Width="200" FontSize="20"/>
- 在
MainPage.xaml.cs
中添加如下代碼:
public MainPage()
{
this.InitializeComponent();
Customer customer = new Customer
{
CustomerID = 1,
Title = "Mr",
FirstName = "John",
LastName = "Sharp",
EmailAddress = "[email protected]",
Phone = "111-1111"
};
this.DataContext = customer;
}
運行以後效果如下:
注意到一點,就是在窄視圖下修改TextBox中的數據回車,回到寬視圖數據又會變回來。說明數據綁定是單向操作,對顯示的數據進行的操作不會寫回數據源。
26.1.2 通過數據綁定修改數據
我們現在要實現雙向的數據綁定,應該怎麼作呢。先不看書上怎麼說,自己想想的話首先想到的是通過控件的相關的事件處理方法。但這樣要一個一個的寫代碼會有點麻煩。實際上由更簡單的方法。本質上也是事件響應,但方便很多。
- 對所有的TextBox控件的XAML標記作修改:
<TextBox Grid.Row="1" Grid.Column="1" x:Name="id" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="{Binding CustomerID,Mode=TwoWay}" VerticalAlignment="Center" FontSize="20" IsReadOnly="True"/>
。 - 修改
Customer.cs
中的代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace C_25_2_Customers
{
public class Customer:INotifyPropertyChanged
{
private int _customerID;
public int CustomerID
{
get { return this._customerID;}
set { this._customerID = value; this.OnPropertyChanged(nameof(CustomerID)); }
}
private string _title;
public string Title
{
get { return this._title; }
set { this._title = value;this.OnPropertyChanged(nameof(Title)); }
}
private string _firstName;
public string FirstName
{
get { return this._firstName; }
set { this._firstName = value;this.OnPropertyChanged(nameof(FirstName)); }
}
private string _lastName;
public string LastName
{
get { return this._lastName; }
set { this._lastName = value;this.OnPropertyChanged(nameof(LastName)); }
}
private string _emailAddress;
public string EmailAddress
{
get { return this._emailAddress; }
set { this._emailAddress = value; this.OnPropertyChanged(nameof(EmailAddress)); }
}
private string _phone;
public string Phone
{
get { return this._phone; }
set { this._phone = value;this.OnPropertyChanged(nameof(Phone)); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
這樣數據綁定就是雙向的了。
26.1.3 爲ComboBox控件使用數據綁定
ComboBox
控件比較特殊。
- 首先在ComboBox控件的XAML標記中設定屬性
SelectedValue
:<ComboBox Grid.Row="1" ... SelectedValue="{Binding Title,Mode=TwoWay}">
- 在
MainPage.xaml.cs
中添加如下代碼:
public MainPage()
{
this.InitializeComponent();
List<string> titles = new List<string> { "Mr", "Mrs", "Ms", "Miss" };
this.title.ItemsSource = titles;
this.ctitle.ItemsSource = titles;
...;
}
這樣就可以了。
26.1.4 創建ViewModel
ViewModel在視圖和模型之間建立了連接,實現了應用程序的業務邏輯。業務邏輯應獨立於視圖和模型。ViewModel通過一組命令向視圖公開業務邏輯。UI可根據用戶在應用中的導航方式來觸發命令。
我們接着前面的來。
- 右擊項目新建一個公共類,命名爲
ViewModel.cs
,代碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace C_25_2_Customers
{
public class ViewModel
{
private List<Customer> customers;
private int currentCustomer;
public ViewModel()
{
this.currentCustomer = 0;
this.customers = new List<Customer>
{
new Customer
{
CustomerID = 1,
Title = "Mr",
FirstName = "John",
LastName = "Sharp",
EmailAddress = "[email protected]",
Phone = "111-1111"
},
new Customer
{
CustomerID = 2,
Title = "Mrs",
FirstName = "Diana",
LastName = "Sharp",
EmailAddress = "[email protected]",
Phone = "111-1112"
},
new Customer
{
CustomerID = 3,
Title = "Ms",
FirstName = "Francesca",
LastName = "Sharp",
EmailAddress = "[email protected]",
Phone = "111-1113"
}
};
}
public Customer Current
{
get { return this.customers.Count > 0 ? this.customers[currentCustomer] : null; }
}
}
}
- 稍微修改
MainPage.xaml.cs
中的代碼:
public MainPage()
{
this.InitializeComponent();
List<string> titles = new List<string> { "Mr", "Mrs", "Ms", "Miss" };
this.title.ItemsSource = titles;
this.ctitle.ItemsSource = titles;
ViewModel viewModel = new ViewModel();
this.DataContext = viewModel;
}
- 修改所有TextBox空間的XAML標記:
<TextBox Grid.Row="0" Grid.Column="1" x:Name="cid" Text="{Binding Current.CustomerID,Mode=TwoWay}" .../>
運行效果沒有變化。
26.1.5 向ViewModel添加命令
ViewModel所公開的命令必須實現ICommand接口,控件的操作才能和命令綁定。該接口定義了以下方法和事件:
- CanExecute
- Execute
- CanExecuteChanged
過程稍微有些繁瑣,這裏直接把代碼放上來了。
- 新建一個公共類,命名爲
Command.cs
,代碼如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.UI.Xaml;
namespace C_25_2_Customers
{
public class Command:ICommand
{
private Action methodToExecute = null;
private Func<bool> methodToDetectCanExecute = null;
private DispatcherTimer canExecuteChangedEventTimer = null;
public Command(Action methodToExecute, Func<bool> methodToDetectCanExecute)
{
this.methodToExecute = methodToExecute;
this.methodToDetectCanExecute = methodToDetectCanExecute;
this.canExecuteChangedEventTimer = new DispatcherTimer();
this.canExecuteChangedEventTimer.Tick += canExecuteChangedEventTimer_Tick;
this.canExecuteChangedEventTimer.Interval = new TimeSpan(0, 0, 1);
this.canExecuteChangedEventTimer.Start();
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
if (this.methodToDetectCanExecute == null)
{
return true;
}
else
{
return this.methodToDetectCanExecute();
}
}
public void Execute(object parameter)
{
this.methodToExecute();
}
void canExecuteChangedEventTimer_Tick(object sender,object e)
{
if (this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this,EventArgs.Empty);
}
}
}
}
- 修改
ViewModel.cs
中的代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace C_25_2_Customers
{
public class ViewModel:INotifyPropertyChanged
{
private List<Customer> customers;
private int currentCustomer;
public Command NextCustomer { get; set; }
public Command PreviousCustomer { get; set; }
public ViewModel()
{
this.currentCustomer = 0;
this.IsAtEnd = false;
this.IsAtStart = true;
this.NextCustomer = new Command(this.Next, () => { return this.customers.Count > 0 && !this.IsAtEnd; }) ;
this.PreviousCustomer = new Command(this.Previous, () => { return customers.Count > 0 && !this.IsAtStart; });
this.customers = new List<Customer>
{
new Customer
{
CustomerID = 1,
Title = "Mr",
FirstName = "John",
LastName = "Sharp",
EmailAddress = "[email protected]",
Phone = "111-1111"
},
new Customer
{
CustomerID = 2,
Title = "Mrs",
FirstName = "Diana",
LastName = "Sharp",
EmailAddress = "[email protected]",
Phone = "111-1112"
},
new Customer
{
CustomerID = 3,
Title = "Ms",
FirstName = "Francesca",
LastName = "Sharp",
EmailAddress = "[email protected]",
Phone = "111-1113"
}
};
}
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"); }
}
public Customer Current
{
get { return this.customers.Count > 0 ? this.customers[currentCustomer] : null; }
}
public void Next()
{
if (this.customers.Count - 1 > this.currentCustomer)
{
this.currentCustomer++;
this.onPropertyChanged(nameof(Current));
this.IsAtStart = false;
this.IsAtEnd = (this.customers.Count - 1 == this.currentCustomer);
}
}
public void Previous()
{
if (this.currentCustomer > 0)
{
this.currentCustomer--;
this.onPropertyChanged(nameof(Current));
this.IsAtEnd = false;
this.IsAtStart = (this.currentCustomer == 0);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void onPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
- 在XAML標記中的底部的
</Page>
前添加如下標記:
...;
<Page.TopAppBar>
<CommandBar>
<AppBarButton x:Name="previousCustomer" Icon="Previous" Label="Previous" Command="{Binding Path=PreviousCustomer}"/>
<AppBarButton x:Name="NextCustomer" Icon="Next" Label="Next" Command="{Binding Path=NextCustomer}"/>
</CommandBar>
</Page.TopAppBar>
</Page>
運行結果如下:
26.2 用Cortana 搜索數據
pass;