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

时间:2023-01-08 08:47:53

1. 简单的HeaderedContentControl

上周五收到反馈,在一个ContentControl的ControlTemplate中放两个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" />

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

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

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

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

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

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

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

虽然没看过ContentControl的源码,但我了解到如果ContentPresenter在ContentControl的ControlTemplate中,当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的ControlTemplate里放两个ContentPresenter会出问题(绕口)的更多相关文章

  1. &lbrack;UWP&rsqb;为什么ContentControl的ContentTemplate里放两个ContentPresenter会出问题&lpar;绕口&rpar;

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

  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. &lbrack;大数据从入门到放弃系列教程&rsqb;在IDEA的Java项目里&comma;配置并加入Scala&comma;写出并运行scala的hello world

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

随机推荐

  1. Stream&comma;Reader&sol;Writer&comma;Buffered的区别(1)

    Stream: 是字节流形式,exe文件,图片,视频等.支持8位的字符,用于 ASCII 字符和二进制数据. Reader/Writer: 是字符流,文本文件,XML,txt等,用于16位字符,也就是 ...

  2. Windows PE3&period;0制作方法&lpar;从Win7中提取制作)

    Windows PE3.0制作方法(从Win7中提取制作 在d:新建文件夹winpe,在winpe中新建sources.pe3和new文件夹,把附件中提供的工具imagex连文件夹一起放到winpe目 ...

  3. 【HDU 5456】 Matches Puzzle Game (数位DP)

    Matches Puzzle Game Problem Description As an exciting puzzle game for kids and girlfriends, the Mat ...

  4. SSH整合笔记

    SSH:spring+struts+hibernate. 一:所需jar: 需要注意的是: hibernate+spring需要Spring-orm-xxx.jar struts+spring需要st ...

  5. vue-router 中踏过的坑

    1.做完页面滚动,然后再加上路由,发现路由一直跳转不了,经历千辛万苦才发现是BScroll没有配置click:true,当看过文档时心里一万只*奔腾而过,我预感到成长道路上还有多少坑在等着我. 2 ...

  6. python内置函数整理

    1. abs() 函数返回数字的绝对值 2 divmod() 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b). 3, input() 相等于 eval(ra ...

  7. Java知多少(39)interface接口

    在抽象类中,可以包含一个或多个抽象方法:但在接口(interface)中,所有的方法必须都是抽象的,不能有方法体,它比抽象类更加“抽象”. 接口使用 interface 关键字来声明,可以看做是一种特 ...

  8. html:模板

    http://www.mycodes.net/code_previewmap.php?id=3461 http://www.17sucai.com/pins/4120.html  欧美风格的CMS企业 ...

  9. 改变Vim保存文件路径

    1. vim 有个cd命令.用来更改当前文件夹.:cd sth进入sth文件夹.这样新文件保存之后就在当前文件夹.不过如果你打开一个已经保存的文件后然后更改当前文件夹是不会改变保存路径的.你必须为:w ...

  10. Android四大组件之Activity &amp&semi; Fragement(续)

    1.Activity和Fragment的异同. Activity是UI界面交互的主体,而fragment是这个主体上的元素. 一个activity可以包含0到n个fragment. fragment可 ...