前言
我們上一次說到了App的精靈:自定義控件。這一次,我們接着這一話題,說說自定義控件的兩個得力助手:
- 選擇器 - TemplateSelector
- 轉換器 – Converter
這兩個東西能幫助自定義控件更為簡單方便地被使用,所以必須掌握。
數值轉換器 Converter
這個大家可能不陌生,因為在MSDN里,介紹到Data Binding時,總會順帶着介紹一下數據轉換,比如這個網頁:
http://msdn.microsoft.com/library/windows/apps/xaml/hh464965.aspx/
最后的那個StringFormatter就是。
在博客園UAP中,我們使用了好幾個Converter,每個Converter實際上是一段cs類,派生自IValueConverter接口。它們都放在CNBlogs.Shared項目的ControlHelper目錄中。我們用其中的兩個做例子來說明一下。
Bool/Visibility的轉換
我們看這段代碼:
public class BoolToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { return (bool)value ? Visibility.Visible : Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } }
功能很直接,就是把bool值轉換成Visibility的枚舉值,然后綁定到一個控件的某個元素上,比如,在XAML里:
<Style TargetType="local:FavoriteAuthorControl"> <Setter Property="Template"> <Setter.Value>……
<TextBlock Grid.Column="0" Text="" FontFamily="Segoe UI Symbol" Foreground="Red" Visibility="{Binding Converter={StaticResource BoolToVisiblityConverter}, Path=HasNew}"/>
…… </Setter.Value> </Setter> </Style>
上面這段XAML是一個自定義控件的style描述,控件本身是“收藏”頁中的“關注的博主”控件,其目的是:當HasNew字段值是true時,在博主的頭像左側顯示一個紅色的小星星,表示該博主有新的博客發表了,你如果關注了該博主,可以點進去看他的新博客。當HasNew是false時,不顯示小星星,你就不需要進去看一圈,發現沒有新博客然后失望地離開了。
有的人也許會說:我在FavoriteAuthorControl.cs的code里,直接取HasNew值,看其是true還是false,然后直接用code寫Visibility = Visiable or Collapsed即可。
這樣做當然也可以啦,但是有幾個不方便的地方:
1)你需要寫code
2)讀你的程序的人,如果沒讀到你的code,就不知道這個邏輯
3)你需要命名那個TextBlock (x:Name=…)然后在code里用GetChildTemplate(“…")來得到這個控件,再指定其Visibility值。
4)在其它控件中,你依然還要寫code做類似的事
而用我們推薦的方法,其好處顯而易見:
1)在XAML中綁定即可,no code
2)這個轉換器可以重用
3)無需更多定義,在binding階段一次性解決(這也是WPF的優點之一),性能無需考慮
4)可讀性極好
bool/string轉換器
再舉一個例子鞏固一下,上code:
class NightModeLabelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
bool bValue = (bool)value;
if (bValue)
{
return "日間模式";
}
else
{
return "夜間模式";
}
}
public object ConvertBack(object value, Type targetType, object parameter, string culture)
{
throw new NotImplementedException();
}
}
在XAML中這樣使用:
<AppBarButton x:Name=”btn_Mode”Label=”{Binding Converter={StaticResource NightModeLabelConverter}, Path=Settings.NightMode”/>
這個綁定過程會先檢查Setting中NightMode的值,如果是True,會在按鈕下方顯示“日間模式”;如果是false,會顯示“夜間模式”。
模板選擇器 TemplateSelector
模板選擇器很少有人用到,因為介紹它的文檔很少,到目前為止我們沒發現,也是一個偶然的機會知道有這么個東西的,很好用!記得有幾次面試,我們都問到了這個問題:如果一條新聞有圖片,另一條新聞沒圖片,你准備如何顯示這兩個Control呢?如下圖所示,上面那個有圖片(特意用紅色矩形標記了一下),下面那個沒圖片。
我們得到兩種回答:
1)做成一個通用的Control,然后根據有無圖片的URL自動顯示右側的圖片
2)用code在自定義空間里控制右側圖片是否顯示
當然可以!這些都能實現這個目的。但要注意圖片左側還有一條灰色豎線喲。
我們今天推薦一個更為標准通用的方法:TemplateSelector。
先上code:
namespace CNBlogs.ControlHelper { class NewsControlTemplateSelector : DataTemplateSelector { public DataTemplate dtHasImage { get; set; } public DataTemplate dtNoImage { get; set; } protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { CNBlogs.DataHelper.DataModel.News news = item as CNBlogs.DataHelper.DataModel.News; if (string.IsNullOrEmpty(news.TopicIcon)) { return dtNoImage; } return dtHasImage; } } }
在Shared project中,ControlHelper目錄下,有個NewsControlTemplateSelector.cs。它從DataTemplaterSelector父類派生出來,先定義了兩個DataTemplate: dtHasImage和dtNoImage,分別對應有無圖片的兩種情況。然后在SelectTemplateCore重寫方法中,先獲得news實例,判斷TopicIcon是否存在,如果存在,返回dtHasImage;否則返回dtNoImage。
在MainPage.xaml中,這樣使用:
<Page.Resources> ...... <DataTemplate x:Key="NewsNoImageTemplate"> <local:NewsTitleTextControl/> </DataTemplate> <DataTemplate x:Key="NewsHasImageTemplate"> <local:NewsTitleTextImageControl/> </DataTemplate> ...... </Page.Resources>
首先在Page.Resource中定義兩個Template,給出兩個不一樣的Key值,一個叫做“NewsNoImageTemplate”,另一個叫做“NewsHasImageTemplate”,分別對應兩個自定義control:NewsTitleTextControl和NewsTitleTextImageControl (在Theme\Generic.xaml中定義的,一個只有標題文本,另一個有標題文本和右側圖片)。
然后在News PivotItem的ListView中這樣寫:
<PivotItem Margin="0" Tag="news"> ...... <ListView.ItemTemplateSelector> <ControlHelper:NewsControlTemplateSelector dtHasImage="{StaticResource NewsHasImageTemplate}" dtNoImage="{StaticResource NewsNoImageTemplate}"/> </ListView.ItemTemplateSelector> </ListView> </PivotItem>
ListView.ItemTemplateSelector用於定義當前的新聞用什么樣的模板來顯示。注意下面的語法:
1)ControlHelper:NewsControlTemplateSelector對應第一段cs代碼的類名
2)dtHasImage/dtNoImage對應cs代碼中的兩個返回值
3)StaticResource NewsHasImageTemplate對應Page.Resources中的x:Key=”NewsHasImageTemplate”,間接對應到了有圖片的自定義控件模板。
即:控件<—>資源文件<—>選擇器。這三者的關系容易搞混,尤其是名字,特別容易起錯,以至於最后自己都不記得什么對應什么。建議是控件用Control做結尾,如NewTitleTextControl;資源文件的Key用Template做結尾,如NewsNoImageTemplate;選擇器用dt做開始,如dtNoImage。這樣的話,如果有圖片,Selector就會返回dtHasImage,對應到了本頁資源中的StaticResource NewHasImageTemplate,從而使用NewsTitleTextImage自定義控件來顯示該條新聞。
小結
好了,掌握了第一個轉換器,我們不用再每個control里都寫code了,直接在XAML中綁定即可。掌握了第二個選擇器,我們可以充分分解頁面邏輯,把它們顆粒化到一個合理的程度,不需要再考慮如何用一個控件顯示兩種或多種樣式。
但是要注意,在以前的“App精靈”隨筆中,提到的PostControl控件,有時要顯示作者頭像,有時要顯示“朕已閱”等等,如果你要做成5個Control,再用TemplateSelector來選擇就是不對的了。基本原則是:在一個ListView中,如果有兩種以上的樣式選擇,用TemplateSelector;在兩個頁面中,用TemplateBinding外部配置來指定要不要顯示某個元素,比如AuthorAvatar.Visibility = {TemplateBinding ShowAvatar},使用它時在兩個頁面中分別直接指定Visable或Collapsed即可,從而允許使用一個控件搞定5種情況。
在Windows Phone 8.1和Windows 8.1中,一點兒區別都沒有,所以我們把這些cs都放在了shared project中,以便能夠在兩個項目中共享。
分享代碼,改變世界!
Windows Phone Store App link:
http://www.windowsphone.com/zh-cn/store/app/博客園-uap/500f08f0-5be8-4723-aff9-a397beee52fc
Windows Store App link:
http://apps.microsoft.com/windows/zh-cn/app/c76b99a0-9abd-4a4e-86f0-b29bfcc51059
GitHub open source link:
https://github.com/MS-UAP/cnblogs-UAP
MSDN Sample Code:
https://code.msdn.microsoft.com/CNBlogs-Client-Universal-477943ab
MS-UAP
2015/1/5

