UI效果
“图1”
开始
1.打开Visual Studio创建一个名为DataBinding的Windows Phone应用程序。
2.添加一个用于绑定数据的类。在“解决方案”上右键项目选择“添加->新建项->类”,命名为“Person.cs”。
Person包括以下属性,这些属性用于绑定UI视图。Person包括以一个枚举属性和其他属性。如下代码。
public class Person { public enum Sex { Male, Female, } public string Name { get; set; } public bool Moustache { get; set; } public bool Goatee { get; set; } public bool Beard { get; set; } public Sex WhichSex { get; set; } public double Height { get; set; } public DateTime BirthDate { get; set; } public bool Favorite { get; set; } }
看得出来,这些属性映射到 “图1” UI上面的控件。布尔类型的属性是用于CheckBox或RadioButton的绑定(CheckBox和RadioButton的区别在于选择的属性是否是相互排斥的)。
创建UI界面
下一步的任务是创建用于绑定数据的UI视图。可以选择Expression Blend来设计UI:右键需要编辑的XAML页面选择“在Expression Blend打开”。个人推荐在Expression Blend上面设计UI视图,在Visual Studio上面编写托管代码。
这里将布局的Grid设置为6行2列,将相关的控件拖动到适当的位置。
下面是UI视图的XAML:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="24,0,0,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.384*" /> <ColumnDefinition Width="0.616*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.1*" /> <RowDefinition Height="0.2*" /> </Grid.RowDefinitions> <TextBlock x:Name="NamePrompt" TextWrapping="Wrap" Text="Name" Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Center" /> <TextBlock x:Name="SexPrompt" Grid.Row="2" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center" Text="Sex" /> <TextBlock x:Name="HeightPrompt" TextWrapping="Wrap" Text="Height, StringFormat=F3" HorizontalAlignment="Left" Grid.Row="3" d:LayoutOverrides="Height" VerticalAlignment="Center" /> <TextBlock x:Name="FavoritePrompt" TextWrapping="Wrap" Text="Favorite" HorizontalAlignment="Left" Grid.Row="4" d:LayoutOverrides="Height" VerticalAlignment="Center" /> <TextBox x:Name="Name" TextWrapping="Wrap" d:LayoutOverrides="Height" Grid.Column="1" HorizontalAlignment="Left" Width="200" VerticalAlignment="Center" Text="{Binding Name}" /> <StackPanel x:Name="BeardStackPanel" Grid.ColumnSpan="2" Grid.Row="1" Orientation="Horizontal"> <CheckBox x:Name="Moustache" Content="Moustache" HorizontalAlignment="Left" VerticalAlignment="Center" IsChecked="{Binding Moustache}" /> <CheckBox x:Name="Goatee" Content="Goatee" IsChecked="{Binding Goatee}" /> <CheckBox Content="Beard" IsChecked="{Binding Beard}"/> </StackPanel> <StackPanel x:Name="SexStackPanel" Grid.Column="1" Grid.Row="2" Orientation="Horizontal"> <RadioButton x:Name="Male" Content="Male" IsChecked="True" GroupName="Sex" /> <RadioButton x:Name="Female" Content="Female" GroupName="Sex" /> </StackPanel> <StackPanel x:Name="HeightStackPanel" Grid.Column="1" Grid.Row="3" Orientation="Horizontal"> <TextBlock TextWrapping="Wrap" Text="{Binding Height}" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,0,0" /> <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Margin="5,0,0,0" Text="meters" /> </StackPanel> <ToggleButton x:Name="Favorite" Content="Favorite" Grid.Column="1" Grid.Row="4" d:LayoutOverrides="Width, Height" HorizontalAlignment="Left" VerticalAlignment="Center" IsChecked="{Binding Favorite}" /> </Grid>
绑定
每一个交互控件都有一个用于设置数据绑定语法的属性。例如,在TextBox的Text属性使用绑定语法。如前面的XAML所示。
绑定语法:绑定语法使用花括号“{}”,并用键盘输入“Binding”,接着输入用于绑定的属性名称。
例如,下面的XAML中的TextBox的Text属性绑定了一个叫“Name”的公共属性。
<TextBox x:Name="Name" TextWrapping="Wrap" d:LayoutOverrides="Height" Grid.Column="1" HorizontalAlignment="Left" Width="200" VerticalAlignment="Center" Text="{Binding Name}" />
同样,在CheckBox的IsChecked属性绑定需要的属性:
<CheckBox x:Name="Moustache" Content="Moustache" HorizontalAlignment="Left" VerticalAlignment="Center" IsChecked="{Binding Moustache}" />
这个时候,你还不知道你要绑定这些属性(“Name”和“Moustache”)的对象(Object)。用于绑定的对象的属性是“DataContext”。“DataContext”可以是任何东西,但是在这里,我们绑定的是一个Person类的实例。新建一个Person类的实例对象,然后将这个对象设置到绑定UI对象的DataContext当中。
当你可以设置一个容器的DataContext。这个时候,在这个容器内的所有控件都能共享这个DataContext。你可以自由指定一个控件或多个控件的DataContext。
在托管代码指定绑定的实例对象
在托管代码的Loaded事件里面实例化Person类。Loaded事件在页面第一次加载和控件初始化的时候会触发。
下面是初始化Person类的代码:
private Person _currentPerson; private Random _rand = new Random(); public MainPage() { InitializeComponent(); Loaded += MainPage_Loaded; } void MainPage_Loaded( object sender, RoutedEventArgs e ) { _currentPerson = new Person { Beard = false, Favorite = true, Goatee = false, Height = 1.86, Moustache = true, Name = "Jesse", WhichSex = Person.Sex.Male }; }
设置ContentPannel控件的DataContext为刚刚实例化的_currentPerson对象(在Loaded事件里面)。
ContentPanel.DataContext = _currentPerson;
当控件知道它设置了DataContext属性之后,控件就会解析的绑定。此时TextBox就会解析Text里面的绑定语法获得Name属性的值(“Jesse”)。基本上所有控件都适用,每一个控件的绑定范围都应该限定在设置DataContext的对象。
运行程序,可以看到绑定数据。
更改DataContext
为了表明绑定和显示之间的关系,我们创建多个Person来切换显示。修改UI界面,增加一个Name为“Next”的Button控件。可以添加在Grid里面最底下的一行。
<Button Name="Next" Content="Next" Grid.Row="5" HorizontalAlignment="Center" VerticalAlignment="Center" />
下面是增加“Next”Button之后的托管代码。
void MainPage_Loaded( object sender, RoutedEventArgs e ) { SetDataContext(); Next.Click += Next_Click; } private void SetDataContext() { ContentPanel.DataContext = GeneratePerson(); } void Next_Click( object sender, RoutedEventArgs e ) { SetDataContext(); }
我们将在Loaded事件和Button的Click事件里面设置DataContext绑定对象,所以新增一个名叫“SetDataContext”的函数用于调用。为了显示修改之后的数据,增加一个名为“GeneratePerson”的方法用于产生一些随机属性的Person对象。
现在我们可以忽略生成Person数据的代码。我们已经用GerneratePerson的方法来代替手动编写Person类。
产生随机属性的Person对象
下面是完整的GerneratePerson方法。可以看出我们将产生Bool值的任务交给了FlipCoin方法。
private Person GeneratePerson() { var newPerson = new Person { Beard = FlipCoin(), Favorite = FlipCoin(), Goatee = FlipCoin(), Height = _rand.NextDouble() + 1, Moustache = FlipCoin(), Name = names[_rand.Next(0, names.Count - 1)] }; return newPerson; } private bool FlipCoin() { return _rand.Next( 1, 3 ) % 2 == 0; }
最后,新建一个含六个string的list<string>泛型集合供Name随机选择。
private readonly List<string> names = new List<string>() { "Stacey", "Robbie", "Jess", "Robin", "Syd", "J.J.", "Terri", "Moonunit", };
运行程序并点击“Next”按钮。每次点击,Grid的DataContext属性都会被重新设置。
INotifyPropertyChanged
当Person对象的某个属性发生变化的时候会发生什么事情呢?当其他用户对同一个对象或数据发生修改时,程序就需要在UI上面更新数据。
此时,我们需要将保存数据的类(例如Person)实现INotifyPropertyChanged接口。INotifyPropertyChanged接口在属性值发生修改的时候会通知UI做出修改。通常的做法是新建一个helper方法进行检查,确保事件能够被注册。这样的话,Helper方法会触发该事件,传递属性名称,更新UI。
实现:
增加一个叫“Change”的按钮。当按钮被点击的时候,改变_currentPerson对象的Name的值。
void Change_Click( object sender, RoutedEventArgs e ) { _currentPerson.Name = "Jacob"; }
如果Person没有实现INotifyPropertyChanged接口,Name属性发生变化的时候不会触发PropertyChanged时间,UI界面也不会发生数据的变化。
public class Person : INotifyPropertyChanged { public string _name; public string Name { get { return _name; } set { _name = value; PropChanged( "Name" ); } } // Other properties public event PropertyChangedEventHandler PropertyChanged; private void PropChanged(string propName) { if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs( propName ) ); } } }
用INotifyPropertyChanged接口,就可以在属性发生变化的时候更新UI上面的变化。
Two-Way Binding
当用户跟UI进行交互的时候,用户修改了控件的属性值,你希望被用于绑定的对象的值也发生相应的改变的时候,你可以使用双向绑定模式。(two-way binding)
(绑定分别有三种模式,one-way,two-way,OneTime)
修改程序中控件的绑定模式为two-way binding,例如:
<TextBox x:Name="Name" TextWrapping="Wrap" d:LayoutOverrides="Height" Grid.Column="1" HorizontalAlignment="Left" Width="200" VerticalAlignment="Center" Text="{Binding Name, Mode=TwoWay}" />
绑定语法的三种模式
-
- One-time 绑定意思是数据只绑定一次,后面数据发生更新之后,UI上的数据不再改变;
- One-way 是默认的绑定模式;意思是绑定的对象和UI只是单向的绑定,UI上的数据发生改变的时候,不会改变对象的值;
- Two-way 绑定允许数据和UI进行双向的绑定,即任何一边的改变都会改变另外一边。
Element Binding
把“Next”按钮移动到第六行,拖动一个Slider控件到第五行。有一些用来设置Slider控件的属性,包括
-
- Minimum
- Maximum
- Value
- LargeChange
- SmallChange
下面的Slider设置了相关属性,可以参考
<Slider x:Name="Likability" Grid.Row="5" Grid.Column="0" BorderBrush="White" BorderThickness="1" Background="White" Foreground="Blue" LargeChange="10" SmallChange="1" Minimum="0" Width="199" Maximum="100" Value="50" Height="90" />
Minimum和Maximum用来设置Slider的范围。因为要用于百分比,所以这里将Slider的范围设置为0到100。
Value是Silder当前的值,Value的范围为:Minimum <= value <= Maximum。
LargeChange和SmallChange更多的时候被用于滚轮或者键盘的箭头,这是用来调整一次“箭头”或“滚轮”跳转的值。
设置TextBlocks
在Slider的右侧增加三个TextBlocks,第一个和第三个用于显示固定值(分别用于显示“Likeability”和“%”)。第二个显示Silder的值。
将第二个的TextBlock的Text属性绑定为Slider的Value,键盘输入ElementName并指定Slider的Name属性
<StackPanel x:Name="LikeabilityPercentStackPanel" Grid.Row="5" Grid.Column="1" Orientation="Horizontal"> <TextBlock Text="Likeability: " HorizontalAlignment="Left" VerticalAlignment="Center" Margin="20,0,5,0" /> <TextBlock x:Name="SliderValue" Text="{Binding Value, ElementName=Likeability, StringFormat=F3}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5,0,0,0"/> <TextBlock Text="%" HorizontalAlignment="Left" VerticalAlignment="Center" /> </StackPanel>
运行程序并移动Slider,可以看到TextBlock的值会发生实时的改变。
Data Converters
有些时候,一些属性无法直接绑定到UI控件上面。或者,有时候,你希望对控件的绑定值有更高的控制权。
以上方法可以解决大多数时候的绑定,但是免不了我们的UI需要显示的结果和我们绑定的类的数据不一定是一致的。譬如,类内存储性别可能用的是一个Boolean类型,但是显示的时候我们可能要显示“男/女”。“Data Converter”就是被设计出来处理这种类似的问题的。例如,我们用类的Model可能是出生日期,但是我们要显示Person的年龄。这个时候“Data Converter”就有用武之地了。
下面是个简单的例子,目的是显示用户的出生日期
在MainPage.xaml.cs里面修改GeneratePerson方法,新建一个BirthDate字段。BirthDate的值限定在近20年内随机的一天。
BirthDate = DateTime.Now - TimeSpan.FromDays(_rand.Next(1,365*20)),
如果你直接绑定BirthDate属性,UI会直接显示出生的日期和时间。但是我们其实只是需要显示日期而已,不需要显示时分秒那么具体的时间。这时候我们采用“DataConverter”来实现我们的功能。
DataConverters的类必须实现IValueConverter接口。这个接口有必须实现的方法。
如下代码:
IValueConverter接口的两个方法
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) { throw new NotImplementedException(); } public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) { throw new NotImplementedException(); }
但是在这个我们只需要第一个方法(第二个方法在这里没有用处)。实现Convert的代码非常简单。
首先,保证转换之后的数据类型(tergetType)是一个字符串,传入的值(value)的类型是一个DateTime。然后,将value转化成DateTime类型并调用ToShortDateString方法。
以下是代码:
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) { if (targetType == typeof( string ) && value.GetType() == typeof( DateTime )) { return (( DateTime ) value).ToShortDateString(); } else // Unable to convert { return value; } } public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) { throw new NotImplementedException(); }
在XAML调用Converter首先得先引用,保证XAML能够访问地到Converter。将Converter设置为资源。打开“App.xaml”
*.在它的命名空间里面添加上converter所在的控件。譬如这里是这样添加的:
xmlns:mine="clr-namespace:DataBinding"
*.然后找到<Application.Resource>节点,将converter添加上。
<Application.Resources> <mine:DateConverter x:Key="dateConverter" /> </Application.Resources>
*.在需要显示的地方用如下绑定语法:
<TextBlock Grid.Row="6" Grid.Column="1" VerticalAlignment="Center" Text="{Binding BirthDate, Converter={StaticResource dateConverter}}" />
运行程序,就可以看到BirthDate的格式变成了短时间格式。即,只有日期没有时分秒的时间。
Powerful Applications
(最后一段不翻译)“Data binding allows you to create powerful Windows Phone applications that reliably manage the relationship between underlying data and the controls and views that display that data. In this article you saw how to create simple data binding and two-way data binding, how to bind to elements and how to use data converters to massage the data into the format you want.”
PS:难免有翻译不恰当的地方,欢迎各位指出指正。