Windows App开发之集合控件与数据绑定
最后更新于:2022-04-01 16:23:40
# 为ListView和GridView添加数据
ListView采用垂直堆叠得方式显示数据,而GridView则采用水平堆叠得方式。
长相的话嘛,它们都差不多。
~~~
<Grid Name="grid1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="listView1" SelectionChanged="listView1_SelectionChanged">
<x:String>Item 1</x:String>
<x:String>Item 2</x:String>
</ListView>
<GridView x:Name="gridView1" SelectionChanged="gridView1_SelectionChanged">
<x:String>Item 1</x:String>
<x:String>Item 2</x:String>
</GridView>
</Grid>
~~~
当然,也可以在后台代码上添加。我只是为了将它们放在一起比较而已,这些代码堆一起肯定是很丑的。
~~~
ListView listView1 = new ListView();
listView1.Items.Add("Item 1");
listView1.Items.Add("Item 2");
listView1.Items.Add("Item 3");
listView1.SelectionChanged += listView1_SelectionChanged;
grid1.Children.Add(listView1);
GridView gridView1 = new GridView();
gridView1.Items.Add("Item 1");
gridView1.Items.Add("Item 2");
gridView1.SelectionChanged += gridView1_SelectionChanged;
grid1.Children.Add(gridView1);
~~~
如果只是像上面这样来添加内容会不会比较麻烦呢,我们也可以把这些Item 1、Item 2之类的全部放在List中。
~~~
List<String> itemsList = new List<string>();
itemsList.Add("Item 1");
itemsList.Add("Item 2");
ListView listView1 = new ListView();
listView1.ItemsSource = itemsList;
listView1.SelectionChanged += listView1_SelectionChanged;
grid1.Children.Add(listView1);
~~~
这样一来所显示的ListView就是两行,非常简陋,完全不能够满足要求。那么我们可以用它的ItemTemplate属性来再里面添加一些东西,如下所示,我们可以在Grid中写一个Image绑定头像,用TextBlock绑定用户的ID,再来一个TextBlock绑定用户的消息,还可以来写边框呀什么的。而这些乱七八糟的Binding之类的,以后我们也会一起讲的哦,现在只要它们是数据绑定就好。
~~~
<Page.Resources>
<CollectionViewSource x:Name="collectionVS" Source="{Binding Items}"/>
</Page.Resources>
<Grid Name="grid1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="listView1" ItemsSource="{Binding Source={StaticResource collectionVS}}"
SelectionChanged="listView1_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
~~~
还可以像下面这样哦,通过WrapGrid来决定这些Item的摆放方式。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fd4fef5.jpg "")
~~~
<Grid Name="grid1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView VerticalAlignment="Bottom">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Vertical" MaximumRowsOrColumns="2"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<Rectangle Height="100" Width="100" Fill="Wheat" />
<Rectangle Height="100" Width="100" Fill="White" />
<Rectangle Height="100" Width="100" Fill="Gainsboro" />
<Rectangle Height="100" Width="100" Fill="Violet" />
<Rectangle Height="100" Width="100" Fill="DarkBlue" />
<Rectangle Height="100" Width="100" Fill="RosyBrown" />
<Rectangle Height="100" Width="100" Fill="SaddleBrown" />
<Rectangle Height="100" Width="100" Fill="AliceBlue" />
<Rectangle Height="100" Width="100" Fill="Fuchsia" />
<Rectangle Height="100" Width="100" Fill="Aqua" />
<Rectangle Height="100" Width="100" Fill="Tan" />
</ListView>
</Grid>
~~~
当然啦,对于ListView和GridView而言,知道用户选择了哪一项是很重要的。SelectionMode属性决定了ListView和GridView的选择模式:单个、多个、无、扩展。
下面这个函数将选择的项给了selectedItems啦。我们还可以通过IsItemClickEnabled来启用ListView和GridView的点击事件,但是务必要注意将SelectionMode设置为None。
~~~
private void listView1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selectedItems = (List<object>)e.AddedItems;
}
~~~
# 为ListView和GridViewt添加分组
本文承接“为ListView和GridView添加数据”。
在上一节中我们已经了解了怎样将数据绑定到ListView或GridView,但既然要用到这两个控件往往是因为数据繁多,那么几乎就不可避免的要让其能够分组。我们所绑定的数据源可能是项列表,其中的每个项甚至还有其自己的项,那么问题就来了。
一时不会也想不出什么宏伟的例子,就做一个简单的闹钟的时间表的ListView和GridView吧。那么先在项目中添加一个类,最好在Shared下。内容都是很简易的,闹钟的标题、时间、备注等,为了增加一级目录就加了一个AlarmMode,就算作学习和生活吧,学习生活两不误……
~~~
public class Alarm
{
public string Title { get; set; }
public DateTime AlarmClockTime { get; set; }
public string Description { get; set; }
public string AlarmMode { get; set; }
}
~~~
~~~
public class AlarmMode
{
public AlarmMode()
{
alarmMode = new ObservableCollection<Alarm>();
}
public string Name { get; set; }
public ObservableCollection<Alarm> alarmMode { get; private set; }
}
~~~
首先,先来定义一个全局的时间,然后在页面加载时加载两个函数(将在下一步定义)。
~~~
DateTime globalTime;
~~~
~~~
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DateTime.TryParse("1/1/2115", out globalTime);
AddAlarm();
AddAlarmMode();
}
~~~
一大波数据正在靠近!
~~~
private void AddAlarm()
{
List<Alarm> listAlarm = new List<Alarm>();
listAlarm.Add(new Alarm()
{
Title = "Alarm1",
Description = "First Alarm",
AlarmClockTime = globalTime.AddHours(1),
AlarmMode = "Alarm In Life"
});
listAlarm.Add(new Alarm()
{
Title = "Alarm2",
Description = "Second Alarm",
AlarmClockTime = globalTime.AddHours(11),
AlarmMode = "Alarm In Life"
});
listAlarm.Add(new Alarm()
{
Title = "Alarm3",
Description = "Third Alarm",
AlarmClockTime = globalTime.AddDays(1),
AlarmMode = "Alarm In Life"
});
listAlarm.Add(new Alarm()
{
Title = "Alarm1",
Description = "First Alarm",
AlarmClockTime = globalTime.AddHours(12),
AlarmMode = "Alarm In Study"
});
listAlarm.Add(new Alarm()
{
Title = "Alarm2",
Description = "Second Alarm",
AlarmClockTime = globalTime.AddHours(15),
AlarmMode = "Alarm In Study"
});
listAlarm.Add(new Alarm()
{
Title = "Alarm3",
Description = "Third Alarm",
AlarmClockTime = globalTime.AddMonths(1),
AlarmMode = "Alarm In Study"
});
ar alarmSetting = from ala in listAlarm
group ala
by ala.AlarmMode
into alaSetting
orderby alaSetting.Key
select alaSetting;
collectionVSAlarm.Source = alarmSetting;
}
private void AddAlarmMode()
{
List<AlarmMode> listAlarmMode = new List<AlarmMode>();
AlarmMode am1 = new AlarmMode();
am1.Name = "Alarm In Life";
am1.alarmMode.Add(new Alarm()
{
Title = "Alarm1",
Description = "First Alarm",
AlarmClockTime = globalTime.AddHours(1),
});
am1.alarmMode.Add(new Alarm()
{
Title = "Alarm2",
Description = "Second Alarm",
AlarmClockTime = globalTime.AddHours(11),
});
am1.alarmMode.Add(new Alarm()
{
Title = "Alarm3",
Description = "Third Alarm",
AlarmClockTime = globalTime.AddDays(1),
});
listAlarmMode.Add(am1);
AlarmMode am2 = new AlarmMode();
am2.Name = "Alarm In Study";
am2.alarmMode.Add(new Alarm()
{
Title = "Alarm1",
Description = "First Alarm",
AlarmClockTime = globalTime.AddHours(12),
});
am2.alarmMode.Add(new Alarm()
{
Title = "Alarm2",
Description = "Second Alarm",
AlarmClockTime = globalTime.AddHours(15),
});
am2.alarmMode.Add(new Alarm()
{
Title = "Alarm3",
Description = "Third Alarm",
AlarmClockTime = globalTime.AddMonths(1),
});
listAlarmMode.Add(am2);
collectionVSAlarmMode.Source = listAlarmMode;
}
~~~
这些数据都是乱七八糟啦,大家凑合着看。这是两个函数,数据我都是用List<>来定义的,将数据通过Add函数添加到listAlarm和listAlarmMode中即可。最后再从listAlarm中根据AlarmMode挑出数据到alaSetting,同时还要根据Key值进行排序最后选出并连接到collectionVSAlarm的Source属性中。这个是需要在MainPage.xaml中定义的哦,就像
~~~
<UserControl.Resources>
<CollectionViewSource x:Name="collectionVSAlarm" IsSourceGrouped="True"/>
<CollectionViewSource x:Name="collectionVSAlarmMode" IsSourceGrouped="True" ItemsPath="alarmMode"/>
</UserControl.Resources>
~~~
然后我们还需要创建一个ListGridGroupStyle类来继承GroupStyleSelector,重载它的SelectGroupStyleCore方法,并且返回ListGridGroupStyleResource资源,这个资源在博客后文中有定义,其定义在App.xaml中。相应的代码如下咯:
~~~
public class ListGridGroupStyle : GroupStyleSelector
{
protected override GroupStyle SelectGroupStyleCore(object group, uint level)
{
return (GroupStyle)App.Current.Resources["ListGridGroupStyleResource"];
}
}
~~~
方法重载好之后就需要在前面的UserControl.Resources中加上以下这条代码啦。
~~~
<local:ListGridGroupStyle x:Key="ListGridGroupStyleResource"/>
~~~
然后我们来一系列的基本样式到App.xaml中就好啦,关于资源文件的使用我们在后面会系统的来学习。这里的DataTemplate和GroupStyle都在资源字典中,前者是Template模板,后者是Style风格。内容的排版大家都随意啦,记得设置好Key值。
~~~
<Application.Resources>
<ResourceDictionary>
<DataTemplate x:Key="dataTemplateListView">
<StackPanel Width="700" Margin="10">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" FontWeight="Bold" Margin="12"/>
<TextBlock Text="{Binding AlarmClockTime}" TextWrapping="NoWrap" Margin="12"/>
<TextBlock Text="{Binding Description}" TextWrapping="NoWrap" Margin="12"/>
</StackPanel>
</StackPanel>
</DataTemplate>
<GroupStyle x:Key="ListGridGroupStyleResource">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Background="LightGray" >
<TextBlock Text='{Binding Key}' Foreground="CornflowerBlue" Margin="12" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ResourceDictionary>
</Application.Resources>
~~~
那么这些各种资源都定义好了之后就在MainPage.xaml把下面这些敲进去。各种资源的调用在这里尤其需要注意,其实对于稍微复杂一丁点的程序而言,名称就已经变得让人崩溃了。所以拥有一个良好的命名习惯很重要。
~~~
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<GridView Grid.Column="0" ItemsSource="{Binding Source={StaticResource collectionVSAlarmMode}}"
Margin="12,120,12,12" MaxHeight="600" >
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="18">
<TextBlock Text="{Binding Title}" FontWeight="ExtraBold" />
<TextBlock Text="{Binding AlarmClockTime}" FontWeight="Light" TextWrapping="NoWrap" />
<TextBlock Text="{Binding Description}" TextWrapping="NoWrap" />
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid MaximumRowsOrColumns="2"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Background="Green" Margin="12">
<TextBlock Text='{Binding Name}'
Foreground="Bisque" Margin="36"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
<ListView Grid.Column="1" ItemsSource="{Binding Source={StaticResource collectionVSAlarm}}"
ItemTemplate="{StaticResource dataTemplateListView}"
GroupStyleSelector="{StaticResource ListGridGroupStyleResource}"
Margin="120" />
</Grid>
~~~
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fd6391f.jpg "")
我这写的真是太丑了哎,做产品的时候可得好好调调了。
# 缩放视图SemanticZoom
相信用过Windows Phone或者Windows 8/8.1/10的朋友对下面这张截图肯定不陌生。这就是通过SemanticZoom来实现的,当数据过多时,这种控件尤其适用。它有一个放大视图ZoomedInView和一个缩小试图ZoomedOutView,前者主要用来显示当前页面的详细信息,后者则致力于快速导航。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fd7d1f4.jpg "")
那么我就自己来动手实践咯,首先我们在XAML中添加大致的界面,就像画画要先画轮廓一样。
~~~
<Grid Name="grid1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<SemanticZoom x:Name="semanticZoom" VerticalAlignment="Center" HorizontalAlignment="Center">
<SemanticZoom.ZoomedOutView>
</SemanticZoom.ZoomedOutView>
<SemanticZoom.ZoomedInView>
</SemanticZoom.ZoomedInView>
</SemanticZoom>
</Grid>
~~~
然后分别在这两个视图中添加你想要加入的东西。这里的核心就是,ZoomedOutView和ZoomedInView都是使用的同一个CollectionViewSource对象作为自己的数据集的。而这个属性我们在“为ListView和GridView分组”谈到过。
我们先把后台代码写好,我就像一篇那样装模作样写一个类吧。
~~~
public class Alarm
{
public string Title { get; set; }
public DateTime AlarmClockTime { get; set; }
public string Description { get; set; }
}
~~~
然后用一个函数来添加一大堆数据……一大堆数据。
~~~
private Alarm[] AddAlarmData()
{
return new Alarm[]
{
new Alarm {Title="Alarm 1",AlarmClockTime=globalTime.AddHours(17),Description="First Alarm for Study" },
new Alarm {Title="Alarm 2",AlarmClockTime=globalTime.AddHours(2),Description="Second Alarm for Study" },
new Alarm {Title="Alarm 3",AlarmClockTime=globalTime.AddHours(7),Description="Third Alarm for Study" },
new Alarm {Title="Alarm 4",AlarmClockTime=globalTime.AddHours(4),Description="4th Alarm for Study" },
new Alarm {Title="Alarm 5",AlarmClockTime=globalTime.AddHours(5),Description="First Alarm for Fun" },
new Alarm {Title="Alarm 6",AlarmClockTime=globalTime.AddHours(1),Description="First Alarm for Fun" },
new Alarm {Title="Alarm 7",AlarmClockTime=globalTime.AddHours(15),Description="Second Alarm for Fun" },
new Alarm {Title="Alarm 8",AlarmClockTime=globalTime.AddHours(9),Description="Third Alarm for Fun" },
new Alarm {Title="Alarm 9",AlarmClockTime=globalTime.AddHours(20),Description="4th Alarm for Fun" },
new Alarm {Title="Alarm 10",AlarmClockTime=globalTime.AddHours(14),Description="Second Alarm for Sleep" },
new Alarm {Title="Alarm 11",AlarmClockTime=globalTime.AddHours(9),Description="First Alarm for Sleep" }
};
}
~~~
因为我们最后要把放大视图变成缩小视图,记得缩小视图上面有一些ABCD之类的字母么,这里我们用的是时间,就分成中午晚上等好啦。就通过下面这样的一个函数来搞定。其用了一个键值对,用time作为参数。后面再将这些数据筛选出来,绑定到新添加的CollectionViewSource中。至于gridView1和gridView2是即将添加到XAML中,这里可以先不填,一回再补上。
~~~
Func<int, string> SwitchTime = (time) =>
{
if (time <= 10 && time >= 6)
return "上午";
else if (time > 10 && time < 14)
return "中午";
else if (time >= 14 && time <= 20)
return "下午";
else
return "晚上";
};
var varTime = from t in AddAlarmData()
orderby t.AlarmClockTime.Hour
group t by SwitchTime(t.AlarmClockTime.Hour);
CollectionViewSource collectionVS = new CollectionViewSource();
collectionVS.IsSourceGrouped = true;
collectionVS.Source = varTime;
this.gridView1.ItemsSource = collectionVS.View.CollectionGroups;
this.gridView2.ItemsSource = collectionVS.View;
~~~
我们先来写主视图(也就是放大视图)。
~~~
<GridView x:Name="gridView2" IsSwipeEnabled="True" HorizontalAlignment="Center" VerticalAlignment="Center" ScrollViewer.IsHorizontalScrollChainingEnabled="False" Width="1800" Height="1000">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12" HorizontalAlignment="Left" Background="White">
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" Foreground="Red" FontFamily="Harrington"
Width="150" Height="100" FontSize="26" FontWeight="Light"/>
<TextBlock Text="{Binding AlarmClockTime}" Foreground="Red" TextWrapping="Wrap" Width="150" Height="100" FontFamily="Harrington" FontSize="26" FontWeight="Light"/>
<TextBlock Text="{Binding Description}" Foreground="Red" TextWrapping="Wrap" Width="150" Height="100" FontFamily="Harrington" FontSize="26" FontWeight="Light"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid MaximumRowsOrColumns="8"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text='{Binding Key}' Foreground="{StaticResource ApplicationForegroundThemeBrush}" Margin="12" FontSize="30" FontFamily="华文彩云" FontWeight="ExtraBold" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
~~~
相信大家都能看得懂,另外稍后我会在截图中添加一些注释的哦。然后是缩小视图。
~~~
<GridView Name="gridView1" Background="Wheat" ScrollViewer.IsHorizontalScrollChainingEnabled="False" HorizontalAlignment="Center" VerticalAlignment="Center" Width="600" Height="200">
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Width="100" Height="100" Text="{Binding Group.Key}" FontFamily="华文行楷" FontWeight="Normal" FontSize="24" />
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid ItemWidth="100" ItemHeight="100" MaximumRowsOrColumns="2"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="Margin" Value="12" />
<Setter Property="Padding" Value="3" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Background" Value="Green"/>
</Style>
</GridView.ItemContainerStyle>
</GridView>
~~~
那么代码就到这里为止了,接下来自然就是截图了。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fd94d93.jpg "")
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fda8bc8.jpg "")
(这种图片如果看不清的话可以保存到电脑上再看。)
想了解字体相关的信息,可以看第九章的“使用更多字体”。
# 数据绑定介绍
### 简单的数据绑定示例
相比于理论,我更倾向于从实践中开始博客,尤其是对于数据绑定。那么,我们先来看看几个简单的例子。
1.数据绑定到TextBox
我们依旧使用前面的闹钟类来开始。在下面的代码中,我们有属性、构造函数,还有一个ToString()方法的重载。之所以重载这个方法是因为我们想在最后绑定的时候,这三个属性能够在TextBox上显示得更加工整。
~~~
public class Alarm
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime AlarmTime { get; set; }
public Alarm() { }
public Alarm(string title, string description,DateTime alarmTime)
{
Title = title;
Description = description;
AlarmTime = alarmTime;
}
public override string ToString()
{
return "Title: " + Title +"\n"+ "Time: "+ AlarmTime.ToString("d") + "\n"+ "Description: " + Description;
}
}
~~~
接下来再在XAML中添加TextBox控件如下,因为TextBox此时是用作显示而非输入,所以建议设置其的只读属性。数据绑定的核心就是Text属性中的那么一个Binding关键字。
~~~
<TextBox x:Name="textBox1" FontSize="28" Height="150" Width="400"
TextWrapping="Wrap" Text="{Binding}" IsReadOnly="True"/>
~~~
但是光这样还不够,我们还需要在后台代码中将数据绑定到textBox1的DataContext(数据上下文)中。
~~~
textBox1.DataContext = new Alarm(
"First Alarm", "I need to study!", new DateTime(2015, 4, 11));
~~~
相信大家并不为觉得这个很难,相反我在学数据绑定的时候一上来就是一大堆理论,以至于我对数据一词有了阴影——所以我学数据结构非常痛苦。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fdc9b46.jpg "")
2.数据绑定到ComboBox
才保存一个闹钟没太大意思,我们多来几个。
~~~
public ObservableCollection<Alarm> UsefulAlarm = new ObservableCollection<Alarm>();
public MainPage()
{
this.InitializeComponent();
UsefulAlarm.Add(new Alarm("First Alarm", "I need to study!", new DateTime(2015, 4, 11)));
UsefulAlarm.Add(new Alarm("First Alarm", "Read a magzine!", new DateTime(2015, 4, 12)));
UsefulAlarm.Add(new Alarm("First Alarm", "Write a blog!", new DateTime(2015, 4, 15)));
UsefulAlarm.Add(new Alarm("First Alarm", "Travel", new DateTime(2015, 5, 15)));
textBox1.DataContext = UsefulAlarm;
}
~~~
但是……
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fdde03c.jpg "")
很显然我们用了ObservableCollection< T >类,它为数据绑定提供了一个集合,这是因为它实现了INotifyPropertyChanged和INotifyCollectionChanged接口。顾名思义,当属性改变时,它可以通知它所绑定的控件,并且如果你希望该空间能够同步更新,则将用于绑定的对象也实现INotifyPropertyChanged接口。这个类好归好,但相对于TextBox而言算有些高端了,以至于它无法显示出来。但是我们可以用ComboBox来代替它,我们的类并不需要修改,前面的UsefulAlarm实例化也都不用改,只需要将textBox1改成comboBox1即可。以下是新的ComboBox代码。
~~~
<ComboBox Name="comboBox1" ItemsSource="{Binding}" FontSize="28" Height="150" Width="400">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="8">
<TextBox Width="350" TextWrapping="Wrap" Text="{Binding Title}" IsReadOnly="True"/>
<TextBox Width="350" TextWrapping="Wrap" Text="{Binding Description}" IsReadOnly="True"/>
<TextBox Width="350" TextWrapping="Wrap" Text="{Binding AlarmTime}" IsReadOnly="True"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
~~~
在图示中我们也容易发现TextBox和ComboBox两个控件的Width属性的应用区别。在TextBox中,我们将数据绑定到Text中;而在ComboBox中,我们则是将数据绑定到ItemsSource中,简单的说就是ComboBox拿来所有的数据,再将它们分成小的细节发给它的子对象,这些子对象都在ComboBox的DataTemplate(数据容器)中。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fdefd54.jpg "")
在这里我们并没有用到前面所重载的ToString()函数,因为我们已经分别将Title、Description、AlarmTime绑定到相应的TextBox控件了。那图示中又为什么这些数据都是一行一行的表示呢,这都是布局控件StackPanel的功劳,全靠它的Orientation属性。如果将这个属性设置成Horizontal呢,那标题、描述已经时间就是全排在一行了。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe119f3.jpg "")
3.数据绑定到ListBox
听说ListBox和ComboBox很类似哦,它们都是Box……XBox呀。博主我有点懒,那可不可以直接将ComboBox的名字改成ListBox就直接运行呢,答案是可以哦!那么区别到底在哪里呢?看看这张图就知道啦。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe24d92.jpg "")
咦?怎么只有一条闹钟了?别惊慌……拖动右边的滚动条就可以查看到全部的闹钟咯。我真的只把ComboBox改成ListBox还有相应的Name属性(包括后台代码中的名字哦),以下就是完整的代码啦,我会骗你?
~~~
<ListBox Name="listBox1" ItemsSource="{Binding}" FontSize="28" Height="150" Width="400">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="8">
<TextBox Width="350" TextWrapping="Wrap" Text="{Binding Title}" IsReadOnly="True"/>
<TextBox Width="350" TextWrapping="Wrap" Text="{Binding Description}" IsReadOnly="True"/>
<TextBox Width="350" TextWrapping="Wrap" Text="{Binding AlarmTime}" IsReadOnly="True"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
~~~
4.数据绑定到ListView
看了前面的代码相信我没有骗你吧,童鞋们看到ListBox有没有想到ListView呢?我要是想说还是和前面一样只用改名字等就可以用ListView,你还是不信么?
~~~
<ListView Name="listView1" ItemsSource="{Binding}" FontSize="28" Height="150" Width="400">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="8">
<TextBox Width="350" TextWrapping="Wrap" Text="{Binding Title}" IsReadOnly="True"/>
<TextBox Width="350" TextWrapping="Wrap" Text="{Binding Description}" IsReadOnly="True"/>
<TextBox Width="350" TextWrapping="Wrap" Text="{Binding AlarmTime}" IsReadOnly="True"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
~~~
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe379b2.jpg "")
当然了,还是用右边的滚动条来下拉以查看所有的数据。不过ListView君的最佳姿势不是这样哦,将Height改为600才是呢。看下图——这才是高大上的ListView君嘛!
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe4f49c.jpg "")
好了不玩了,GridView也是可以这样弄得,不信你试试。
### 再谈数据绑定
1.我们为什么要用数据绑定
很显然,我们不可能把所有的数据全部固定在特定的控件上。比如,游戏的积分、设定的闹钟、天气预报甚至的通讯类的消息,它们都并非是一成不变的。但是也并非所有的控件都需要绑定,比如你的App的名字、发送消息时所用的发送按钮上面的文本等。
2.那数据和UI之间又有哪些关系呢
首先我们得明确,数据的显示和其后台的管理是不一样的。数据与UI绑定之后,我们的数据就可以在这两者之间进行沟通,如果数据发生变化时,绑定到数据的UI则会自动将相应的属性进行调整,不仅仅是前面用到的Text属性,还有FontSize、Width、Foreground、Image属性都可以。
3.数据绑定到底是绑定什么
首先,我们得有绑定源,这些就是我们需要绑定的数据,没有数据,即使你绑定了,它也显示不出来。
其次,我们还需要绑定目标,也就是Framework类的DependencyProperty属性,说得白话文点就是将数据绑定到UI的相应属性上。
最后,我们还需要一个Binding对象,它就像是搬运工,没有它,数据也是无法动弹的。它能够帮助我们将数据从数据源移动到绑定目标,并且将绑定目标的相应消息通知给绑定源。它还有一些巧妙的工具,能够将绑定源的数据加工成特定的格式。
4.绑定源有哪些
所有的公共语言运行时对象,我们前面用的Alarm类就是这种对象,另外UI元素也是哦。
5.听说有的搬运工只能将数据源的数据一次性搬到绑定目标后就不再搬了,而有的搬运工则会在数据修改后再搬一次,甚至还有的能够在绑定目标更改后再将数据搬回到数据源
OneTime绑定:这个搬运工的工作就是第一种,它只负责在创建时将源数据更新到绑定目标。
OneWay绑定:这是系统默认的搬运工,它是第二种,负责在创建时以及源数据发生更改时更新绑定目标。
TwoWay绑定:这个搬运工则是第三种,它能够在绑定源和绑定目标的一边发生更改时同时更新绑定源和绑定目标。但它在一种时候却会偷懒,那就是对于TextBox.Text每次点击之后,它就不会将这个Text属性的更改更新到绑定源。不过如果碰到Boss,它也只能继续搬了。那就是将Binding.UpdateSourceTrigger设置成PropertyChanged。而默认情况下,只有TextBox失去焦点时才会去更新。
以下分别是OneWay和TwoWay的例子:
~~~
<StackPanel Width="240" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Slider Name="slider1" Minimum="0" Maximum="100"/>
<TextBox FontSize="30"
Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}" />
</StackPanel>
~~~
拖动滑动条,就可以看到在TextBox中显示它的值的变化了。如果希望它只变化一次,那就将代码中的OneWay改成OneTime即可。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe64641.jpg "")
~~~
<StackPanel Width="240" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox FontSize="30" Name="textBox" Height="60"
Text ="{Binding ElementName=listBox1, Path=SelectedItem.Content, Mode=TwoWay}">
</TextBox>
<ListBox FontSize="30" Name="listBox1">
<ListBoxItem Content="Item 1"/>
<ListBoxItem Content="Item 2"/>
<ListBoxItem Content="Item 3"/>
<ListBoxItem Content="Item 4"/>
</ListBox>
</StackPanel>
~~~
如下图所示,点击Item 1后TextBox则会显示相应的Item 1,将TextBox中的Item 1修改为Item 5后再ListBox中也自动修改成了Item5。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe76859.jpg "")
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fe8a5fd.jpg "")
简单示例:Foreground的数据绑定
前面已经说到了Foreground也可以绑定,想不想试试呢。我们现在TextBox中写一个TextBox,然后在后台代码中添加一个绑定就可以了。这个和前面的比较简单,这里只是用来引出后面的东东哦
~~~
<TextBox Name="textBox" Width="200" Height="100" IsReadOnly="True"
FontSize="32" Text="Text" Foreground="{Binding ForeBrush}"/>
~~~
~~~
textBox.Foreground = new SolidColorBrush(Colors.BlueViolet);
~~~
# 更改通知
1.Silder绑定到TextBlock,不使用更改通知
首先定义一个简单的类BindingSlider,同时在XAML中作如下定义。
~~~
public class BindingSlider
{
private int sliderValue;
public int SliderValue
{
get
{
return sliderValue;
}
set
{
sliderValue = value;
}
}
}
~~~
~~~
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Slider Name="slider1" Minimum="0" Maximum="100" Width="200" Value="{Binding SliderValue,Mode=TwoWay}"/>
<Button x:Name="button" Content="Button" Width="200" Click="button_Click"/>
<TextBlock Name="textBlock" FontSize="30"/>
</StackPanel>
~~~
虽然这里只是用到了OneWay传递,但还是需要使用TwoWay。因为在这里OneWay是指从BindingSlider类的SliderValue属性单向传递到Slider控件的Value属性。但我们需要的则是Slider控件的Value属性单向传递到BindingSlider类的SliderValue属性,所以才得使用TwoWay方式。
~~~
BindingSlider bindingSlider = new BindingSlider();
public MainPage()
{
this.InitializeComponent();
slider1.DataContext = bindingSlider;
}
private void button_Click(object sender, RoutedEventArgs e)
{
textBlock.Text = bindingSlider.SliderValue.ToString();
}
~~~
首先实例化BindingSlider类,再在后台代码中奖bindingSlider对象绑定到slider1的数据上下文。最后通过Click事件来将bindingSlider对象的SliderValue属性传递给textBlock控件的Text属性。
这里的效果就是,拖动Slider但是TextBlock不会有变化,而需要Button来不断的更改TextBlock的Text。如果想要TextBlock的Text能够根据Slider实时的更改,这就需要”更改通知“了。
2.Silder绑定到TextBlock,使用更改通知
既然要使用通知更改的技术,那就可以在XAML代码中将Button控件删除掉了,包括后台代码中的Click事件。
紧接着来修改BindingSlider类,首先得使用INotifyPropertyChanged接口。这个接口有PropertyChanged事件,而这个事件则会告知绑定目标绑定源已经发生修改,这样绑定目标也会实时的进行更改。在新的set中,我们将SliderValue值传递到NotifyPropertyChanged中。
~~~
public class BindingSlider :INotifyPropertyChanged
{
private int sliderValue;
public int SliderValue
{
get
{
return sliderValue;
}
set
{
sliderValue = value;
NotifyPropertyChanged("SliderValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
~~~
最后我们还需要将bindingSlider对象绑定到textBlock的数据上下文。
~~~
BindingSlider bindingSlider = new BindingSlider();
public MainPage()
{
this.InitializeComponent();
slider1.DataContext = bindingSlider;
textBlock.DataContext = bindingSlider;
}
~~~
这样一来就全部更改完成了,试试就会发现TextBlock的Text会根据Slider的拖动而实时修改了。
# 值转换器
有时候默认的输出方式不能满足我们的需要,比如前面的OneWay示例,可能我们需要的是在TextBox中显示“开始加载“、”加载一半了“、”很快就加载完了“以及”已经加载好“等,甚至还可以让其能够转换成英文哦。
那么首先新建一个类SliderValueConverter.cs,然后实现IValueConverter接口。然后按自己的需要写它的Converter方法即可。
~~~
public class SliderNotifyAndConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
string valueTextBlock;
string parameterValue = parameter.ToString();
double valueSlider = (double)value;
if (valueSlider > 0&&valueSlider<=5)
{
if (parameterValue == "zh-cn")
valueTextBlock = "开始加载";
else
valueTextBlock = "Starts to load";
}
else if (valueSlider >= 45 && valueSlider <= 55)
{
if (parameterValue == "zh-cn")
valueTextBlock = "加载一半了";
else
valueTextBlock = "loaded half";
}
else if (valueSlider >= 90&&valueSlider<100)
{
if (parameterValue == "zh-cn")
valueTextBlock = " 很快就加载完了";
else
valueTextBlock = "finished loading very quickly";
}
else if (valueSlider == 100)
{
if (parameterValue == "zh-cn")
valueTextBlock = " 已经加载好";
else
valueTextBlock = "loaded";
}
else
{
if (parameterValue == "zh-cn")
valueTextBlock = "加载中";
else
valueTextBlock = "Loading";
}
return valueTextBlock;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
~~~
最后还需要在XAML中添加如下代码哦,值转换器Converter所使用的静态资源已经在
~~~
<Page.Resources>
<local:SliderNotifyAndConverter x:Key="SliderNotifyAndConverterResources"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Name="stackPanel" Width="450" Orientation="Vertical"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Slider Name="slider1" Minimum="0" Maximum="100"
Value="95"/>
<TextBlock FontSize="30"
Text="{Binding ElementName=slider1, Path=Value,
Converter={StaticResource SliderNotifyAndConverterResources},
ConverterParameter='zh-cn'}"/>
<TextBlock FontSize="30"
Text="{Binding ElementName=slider1, Path=Value,
Converter={StaticResource SliderNotifyAndConverterResources},
ConverterParameter='en-us'}"/>
</StackPanel>
</Grid>
~~~
以下是Slider的Value取不同值时TextBlock的不同显示。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033fea0dc4.jpg "")
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a033feb2d7e.jpg "")
这里仅仅是一个比较简单的示例,在项目产品中往往有数据绑定的地方都会有值转换器的,就像C++中经常重载ostream一样。