原文:[UWP]为什么ContentControl的ContentTemplate里放两个ContentPresenter会出问题(绕口)

1. 简单的HeaderedContentControl

上周五收到反馈,在一个ContentControl的ContentTemplate中放两个ContentPresenter会出错。出错的例子是我以前博客中HeaderedContentControl的代码,这个控件是UWP最简单的控件之一,它最简化的实现代码如下:

public  class HeaderedContentControl : ContentControl
{
public HeaderedContentControl()
{
this.DefaultStyleKey = typeof(HeaderedContentControl);
} public object Header
{
get => (object)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
} public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register(nameof(Header), typeof(object), typeof(HeaderedContentControl), new PropertyMetadata(default(object), OnHeaderChanged)); private static void OnHeaderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var oldValue = (object)args.OldValue;
var newValue = (object)args.NewValue;
if (oldValue == newValue)
return; var target = obj as HeaderedContentControl;
target?.OnHeaderChanged(oldValue, newValue);
} protected virtual void OnHeaderChanged(object oldValue, object newValue)
{
}
}

ControlTemplate如下:

<ControlTemplate TargetType="local:HeaderedContentControl">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<ContentPresenter Content="{TemplateBinding Header}"
x:Name="HeaderPresenter" />
<ContentPresenter />
</StackPanel>
</Border>
</ControlTemplate>

2. 两种错误

这个控件运行起来应该没有错误,但如果不按套路地给Header赋值,就会出现重复的内容:

<local:HeaderedContentControl Content="this is content" />

更奇怪的是,如果这样用还会报错:Value does not fall within the expected range.

<local:HeaderedContentControl Header="">
<TextBlock Text="this is content" />
</local:HeaderedContentControl>

更奇怪的是相同的代码在WPF完全没有问题。看到这两个奇怪的错误,我马上根据多年的经验知道了错误原因。

3. 问题产生的原因及解决方案

第一种错误,看起来是ContentControl将Content赋值给ContentTemplate的所有ContentPresenter了。而第二种错误印证了我这个猜测,因为Value does not fall within the expected range.这个错误(中文是值不再预期范围中)在我的印象中只会出现在同一个UIElement被重复添加到VisualTree中的情况。

虽然没看过ContentControl的源码,但我了解到如果ContentPresenter在ContentControl的ContentTemplate中,当ContentPresenter的Content为Null时会默认将自己的Content的绑定到ContentControl的Content。基于这个猜测我想到几个解决方案。

3.1 使用ContentControl

使用ContentControl代替Header的ContentPresenter是最简单直接的解决方案。我常常用ContentControl代替ContentPresenter,这没什么不好的。(因为在WPF中ContentPresenter比ContentControl少了一大堆文本相关的属性,所以在WPF常常这么做。)

经过测试这个方案能完美解决所有问题,唯一不足是Header用ContentControl,Content用ContentPresenter,破坏了对称的美感总感觉不太舒服。

3.2 直接使用WindowsCommunityToolkit的HeaderedContentControl

WindowsCommunityToolkit中也提供了HeaderedContentControl,以前我也写过一篇文章吐槽过这个控件。吐槽归吐槽,我对微软还是很信任的,直接使用这个HeaderedContentControl应该不会有什么问题。

事实证明微软提供的HeaderedContentControl可以解决第一个问题,解决不了第二个问题。摸摸我的1020,我对微软的好感度又下降了。(顺便一提黄色1020真是超漂亮)

3.3 改造WindowsCommunityToolkit的HeaderedContentControl

既然WindowsCommunityToolkit的HeaderedContentControl可以解决第一个问题,看起来它应该也意识到这里会产生问题并修复了,那么看看它是怎么解决的。

<StackPanel>
<ContentPresenter Content="{TemplateBinding Header}"
x:DeferLoadStrategy="Lazy"
x:Name="HeaderPresenter" />
<ContentPresenter />
</StackPanel>
private const string PartHeaderPresenter = "HeaderPresenter";

protected virtual void OnHeaderChanged(object oldValue, object newValue)
{
SetHeaderVisibility();
} protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
SetHeaderVisibility();
} private void SetHeaderVisibility()
{
if (GetTemplateChild(PartHeaderPresenter) is FrameworkElement headerPresenter)
{
headerPresenter.Visibility = Header != null
? Visibility.Visible
: Visibility.Collapsed;
}
}

也就是说HeaderedContentControl的Header等于null时索性就不显示headerPresenter,所以第一个问题得到了解决。但我们再看一次产生第二个问题的XAML:

<local:HeaderedContentControl Header="">
<TextBlock Text="this is content" />
</local:HeaderedContentControl>

可以看到Header不是为null,而是一个空字符串,也就是说ContentPresenter把空字符串也和null同样处理。将SetHeaderVisibility改为这样就可以解决这个问题:

private void SetHeaderVisibility()
{
if (GetTemplateChild(PartHeaderPresenter) is FrameworkElement headerPresenter)
{
var isEmpty = false;
if (Header is string headerText)
isEmpty = string.IsNullOrEmpty(headerText); headerPresenter.Visibility = (Header != null && isEmpty == false)
? Visibility.Visible
: Visibility.Collapsed;
}
}

4. 结语

为求简单我还是推荐第一种解决方案,即改用ContentControl的方案,毕竟用到ContentPresenter的地方那么多,总不能每次都写一大堆代码SetXXXVisibility。

顺便一提同样的代码在WPF完全没有问题,我总是按着WPF的经验写UWP的代码,偶尔还是会翻车。

话说回来,既然HeaderPresenter总是会被赋值的,那么x:DeferLoadStrategy="Lazy"这句根本就完全没起到作用的吧?

5. 参考

WindowsCommunityToolkit_Microsoft.Toolkit.Uwp.UI.Controls

[UWP]为什么ContentControl的ContentTemplate里放两个ContentPresenter会出问题(绕口)的更多相关文章

  1. [UWP]为什么ContentControl的ControlTemplate里放两个ContentPresenter会出问题(绕口)

    1. 简单的HeaderedContentControl 上周五收到反馈,在一个ContentControl的ControlTemplate中放两个ContentPresenter会出错.出错的例子是 ...

  2. Java web开发,在一个jsp里放太多java代码的后果,摘自 java web轻量级开发面试教程

    现要做一个简单的登录页面,如果用户通过验证,会显示Welcome用户名的欢迎词,反之则返回登录页面让用户再次输入 这部分的完整代码是JSPDemo项目里的login.jsp,下面来分析一下关键代码. ...

  3. 微信公众号里放XLS链接

    微信公众号里放XLS链接 我们都知道创建一个微信公众号在公众号中发布一些文章是非常简单的,但公众号添加附件下载的功能却被限制,如今可以使用小程序“微附件”进行在公众号中添加附件,如:xls,word等 ...

  4. Android TextView里显示两种颜色

    今天介绍一个小技巧,在Android的TextView里设置两种颜色,直接上代码: TextView TV = (TextView)findViewById(R.id.mytextview01); S ...

  5. 函数指针的返回值是指针数组,数组里放的是int;函数指针的返回值是指针数组,数组里放的是int指针

    函数指针的返回值是指针数组,数组里放的是int 函数指针的返回值是指针数组,数组里放的是int指针 #include <stdio.h> #include <stdlib.h> ...

  6. k8s集群启动了上万个容器(一个pod里放上百个容器,起百个pod就模拟出上万个容器)服务器超时,无法操作的解决办法

    问题说明: 一个POD里放了百个容器,然后让K8S集群部署上百个POD,得到可运行上万个容器的实验目的. 实验环境:3台DELL裸机服务器,16核+64G,硬盘容量忽略吧,上T了,肯定够. 1.一开始 ...

  7. 剑指offer40:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字

    1 题目描述 一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字. 2 思路和方法 (1)异或:除了有两个数字只出现了一次,其他数字都出现了两次.异或运算中,任 ...

  8. 项目里出现两个配置类继承WebMvcConfigurationSupport时,为什么只有一个会生效(源码分析)

    为什么我们的项目里出现两个配置类继承WebMvcConfigurationSupport时,只有一个会生效.我在网上找了半天都是说结果的,没有人分析源码到底是为啥,博主准备讲解一下,希望可以帮到大家! ...

  9. [大数据从入门到放弃系列教程]在IDEA的Java项目里,配置并加入Scala,写出并运行scala的hello world

    [大数据从入门到放弃系列教程]在IDEA的Java项目里,配置并加入Scala,写出并运行scala的hello world 原文链接:http://www.cnblogs.com/blog5277/ ...

随机推荐

  1. ajax跨域过程

  2. 为线程绑定CPU

    // learn gcc atomic variable #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> ...

  3. Swift编程语言初探

    继WWDC2014后,新的编程语言Swift浮出水面.它具有高速.现代.安全.可交互等特征,而且其语法简单,入门门槛低,有望替代语法复杂难懂的Objective-C语言.据其作者Chris Lattn ...

  4. codeforce 571 B Minimization

    题意:给出一个序列,经过合适的排序后.使得最小. 做法:将a升序排序后,dp[i][j]:选择i个数量为n/k的集合,选择j个数量为n/k+1的集合的最小值. 举个样例, a={1,2,3,4,5,6 ...

  5. Shiro结合Redis解决集群中session同步问题

    pom.xml文件中引入redis的依赖 在application.xml配置redis: <bean id="jedisConnectionFactory" class=& ...

  6. #学习笔记#——JavaScript 数组部分编程(三)

    3.在数组 arr 末尾添加元素 item.不要直接修改数组 arr,结果返回新的数组 主要考察数组的concat方法,代码如下: arr.concat(item); concat 方法不修改原数组. ...

  7. google dataflow model 论文

    http://www.chinacloud.cn/show.aspx?id=24446&cid=17

  8. deep-in-es6(六)

    箭头函数 Arrow Functions 箭头函数在JavaScript诞生是就存在,JavaScript建议在HTML注释内包裹行内脚本,这样可以避免不支持JS代码的浏览器将JS显示为文本. < ...

  9. dp之多重背包(未用二进制优化)

    hdu 2191: #include <iostream>#include <stdio.h>#include <string.h>using namespace ...

  10. VPS搭建与IPv6使用教程

    VPS搭建与IPv6使用教程 SoftEther命令: yum -y install gcc zlib-devel openssl-devel readline-devel ncurses-devel ...