Jaa


博客园客户端UAP开发随笔--自定义控件的左膀右臂

前言

我们上一次说到了App的精灵:自定义控件。这一次,我们接着这一话题,说说自定义控件的两个得力助手:

  • 选择器 - TemplateSelector
  • 转换器 – Converter

这两个东西能帮助自定义控件更为简单方便地被使用,所以必须掌握。

数值转换器 Converter

这个大家可能不陌生,因为在MSDN里,介绍到Data Binding时,总会顺带着介绍一下数据转换,比如这个网页:

https://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:

https://www.windowsphone.com/zh-cn/store/app/博客园-uap/500f08f0-5be8-4723-aff9-a397beee52fc

Windows Store App link:

https://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