博客園客戶端UAP開發隨筆--自定義控件的左膀右臂


前言

我們上一次說到了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呢?如下圖所示,上面那個有圖片(特意用紅色矩形標記了一下),下面那個沒圖片。

image

我們得到兩種回答:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM