原文:[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. How Javascript works (Javascript工作原理) (十二) 网络层探秘及如何提高其性能和安全性

    个人总结:阅读完这篇文章需要20分钟,这篇文章主要讲解了现代浏览器在网络层传输所用到的一些技术, 应当对 window.performance.timing 这个API所有了解. 这是 JavaScr ...

  2. 【CS Round #37 (Div. 2 only) B】Group Split

    [Link]:https://csacademy.com/contest/round-37/task/group-split/ [Description] 让你把一个数分成两个数a.b的和; (a,b ...

  3. snmpd修改端口

    http://blog.csdn.net/cau99/article/details/5077239 http://blog.csdn.net/gua___gua/article/details/48 ...

  4. ElasticSearch 工作原理

    ElasticSearch 工作原理图 文字说明,以后更新

  5. 洛谷P2818 天使的起誓

    题目描述 Tenshi非常幸运地被选为掌管智慧之匙的天使.在正式任职之前,她必须和其他新当选的天使一样要宣誓.宣誓仪式是每位天使各自表述自己的使命,他们的发言稿放在n个呈圆形排列的宝盒中.这些宝盒按顺 ...

  6. Javascript和jquery事件--事件冒泡和事件捕获

    jQuery 是一个 JavaScript 库,jQuery 极大地简化了 JavaScript 编程,在有关jq的描述中,jq是兼容现有的主流浏览器,比如谷歌.火狐,safari等(当然是指较新的版 ...

  7. pgrep---以名称为依据从运行进程队列中查找进程

    pgrep命令以名称为依据从运行进程队列中查找进程,并显示查找到的进程id.每一个进程ID以一个十进制数表示,通过一个分割字符串和下一个ID分开,默认的分割字符串是一个新行.对于每个属性选项,用户可以 ...

  8. 【翻译自mos文章】OGG的集成捕捉模式支持Oracle database标准版么?

    OGG的集成捕捉模式支持Oracle database标准版么? 来源于: Does OGG 11.2.1 Integrated Capture Work with Oracle Database S ...

  9. ViewPager 入门一

    使用ViewPager能够得到不同view的切换效果 例如以下图,实现了四个view间的相互滑动 一.新建项目,引入ViewPager控件 ViewPager.它是google SDk中自带的一个附加 ...

  10. 安装 VNC 和 XRDP

    安装 VNC 和 XRDPapt-get install vnc4server tightvncserver xrdp fluxbox xtermcat >vncstop.sh << ...