参考书:《 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;