# 為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的擺放方式。

~~~
<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>
~~~

我這寫的真是太丑了哎,做產品的時候可得好好調調了。
# 縮放視圖SemanticZoom
相信用過Windows Phone或者Windows 8/8.1/10的朋友對下面這張截圖肯定不陌生。這就是通過SemanticZoom來實現的,當數據過多時,這種控件尤其適用。它有一個放大視圖ZoomedInView和一個縮小試圖ZoomedOutView,前者主要用來顯示當前頁面的詳細信息,后者則致力于快速導航。

那么我就自己來動手實踐咯,首先我們在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>
~~~
那么代碼就到這里為止了,接下來自然就是截圖了。


(這種圖片如果看不清的話可以保存到電腦上再看。)
想了解字體相關的信息,可以看第九章的“使用更多字體”。
# 數據綁定介紹
### 簡單的數據綁定示例
相比于理論,我更傾向于從實踐中開始博客,尤其是對于數據綁定。那么,我們先來看看幾個簡單的例子。
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));
~~~
相信大家并不為覺得這個很難,相反我在學數據綁定的時候一上來就是一大堆理論,以至于我對數據一詞有了陰影——所以我學數據結構非常痛苦。

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;
}
~~~
但是……

很顯然我們用了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(數據容器)中。

在這里我們并沒有用到前面所重載的ToString()函數,因為我們已經分別將Title、Description、AlarmTime綁定到相應的TextBox控件了。那圖示中又為什么這些數據都是一行一行的表示呢,這都是布局控件StackPanel的功勞,全靠它的Orientation屬性。如果將這個屬性設置成Horizontal呢,那標題、描述已經時間就是全排在一行了。

3.數據綁定到ListBox
聽說ListBox和ComboBox很類似哦,它們都是Box……XBox呀。博主我有點懶,那可不可以直接將ComboBox的名字改成ListBox就直接運行呢,答案是可以哦!那么區別到底在哪里呢?看看這張圖就知道啦。

咦?怎么只有一條鬧鐘了?別驚慌……拖動右邊的滾動條就可以查看到全部的鬧鐘咯。我真的只把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>
~~~

當然了,還是用右邊的滾動條來下拉以查看所有的數據。不過ListView君的最佳姿勢不是這樣哦,將Height改為600才是呢。看下圖——這才是高大上的ListView君嘛!

好了不玩了,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即可。

~~~
<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。


簡單示例: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的不同顯示。


這里僅僅是一個比較簡單的示例,在項目產品中往往有數據綁定的地方都會有值轉換器的,就像C++中經常重載ostream一樣。