一、背景 

使用WPF的朋友,大家都很喜欢采用定义控件的公共样式,以便整个框架对该资源的使用,好处就是可以达到代码复用、系统风格统一等;

1. 定义资源

      <Style TargetType="Button" x:Key="ButtonStyle"> 
         <Setter Property="Content"> 
             <Setter.Value> 
                 <Image Source="Imgs\btn.jpg"/> 
             </Setter.Value> 
         </Setter> 
         <Setter Property="Width" Value="60"/> 
      </Style>

2. 其他地方对该资源的引用:

      <StackPanel> 
                    <Button Style="{DynamicResource ButtonStyle}"/> 
                    <Button Style="{DynamicResource ButtonStyle}"/> 
                    <Button Style="{DynamicResource ButtonStyle}"/> 
      </StackPanel>

该写法会出现一种情况,如下图所示(除了最后一个按钮能显示图片,而其他按钮却是“空白”,这让我的抑制不住我的好奇心,决定对该问题进行分析和思考:

二、代码实现 :分别采用三种写法来尝试

1. 采用原来Content的代码;(代码及图请参考上面)

2. 采用String来代替Content中的Image控件,具体代码和结果如下:

     <Style TargetType="Button" x:Key="ButtonWithOutStringStyle"> 
         <Setter Property="Content" Value="Button"/> 
         <Setter Property="Width" Value="60"/> 
     </Style>

3. 采用ContentTemplate来代替Content属性,具体代码和结果如下:

       <Style TargetType="Button" x:Key="ButtonContentTemplateStyle"> 
            <Setter Property="ContentTemplate"> 
                <Setter.Value> 
                    <DataTemplate> 
                        <Image Source="Imgs\btn.jpg"/> 
                    </DataTemplate> 
                </Setter.Value> 
            </Setter> 
            <Setter Property="Width" Value="60"/> 
        </Style>

结果出来了,这时能满足我的需求,但是我的好奇心更加强烈了,我非常想知道“为什么”?

三、分析 

我对以上3种情况,出现不同的结果非常好奇,我很想知道背后的运作原理;根据第一种情况,只有一个Button有图片,所以我进行了以下大胆的假设:

1. 假设出现该情况的原因是因为引用该资源的所有按钮使用了相同的一个Image控件,违背了“一个控件不能同存在可视树上两个不同节点上”;

2. 为什么String却可以正常显示;

3. ContentTemplate是渲染(呈现时)再进行实例化,所以显示的每个Image控件是不同的控件;

我带着这3个疑问进行了一步步的验证:

1. 验证:引用了ButtonStyle后是否使用了同一个对象;

      <StackPanel> 
                    <Button x:Name="G_1_A" Style="{DynamicResource ButtonStyle}"/> 
                    <Button x:Name="G_1_B" Style="{DynamicResource ButtonStyle}"/> 
                    <Button x:Name="G_1_C" Style="{DynamicResource ButtonStyle}"/> 
     </StackPanel>       Console.WriteLine(string.Format("{0}:\t{1}(HashCode)", G_1_A.Name, G_1_A.Content.GetHashCode())); 
      Console.WriteLine(string.Format("{0}:\t{1}(HashCode)", G_1_B.Name, G_1_B.Content.GetHashCode())); 
      Console.WriteLine(string.Format("{0}:\t{1}(HashCode)", G_1_C.Name, G_1_C.Content.GetHashCode()));

结果如下:

2. 疑问:为什么采用String却可以,为了验证该问题,用了以前我写的一个工具“逻辑树与可视化树工具”;

错误图(之前验证方法有误,误导了大家),以下进行纠正

正确图(进行了树控件的优化)

错误结论:以上3个Button的Content(String)的HashCode都不一样,所以他们能正常显示出来;我开始还以为String是同一个,因为String在编译的时候,如果值是相同时会放入到驻留池中(这个出乎我的意料)

正确结论:3个Button的Content(String)的HashCode是一样的,因为string重写了GetHashCode方法 ,需要进一步确认是否是相同对象;

验证是否是同个对象:

          Console.WriteLine(string.Format("{0} is {1}:{2}", G_2_A.Name, G_2_B.Name, object.ReferenceEquals(G_2_A.Content, G_2_B.Content)));

输出结果:证明string是同个对象,验证了string驻留池的说法是正确的;

          G_2_A is G_2_B:True

3. 疑问:ContentTemplate是渲染(呈现时)再进行实例化,所以显示的每个Image控件是不同的控件;

3.1 我需要对G_3_A Button的Content和ContentTemplate进行分析

          var content = G_3_A.Content;    
          var template = G_3_A.ContentTemplate;

结果如下:

3.2 我得到了一个结论是:ContentTemplate很有可能在渲染的时候才对Button的Content进行实例化,接下来的难题就是如何证明该观点:

我需要验证ContentTemplate里面的Content是否是同一个对象,结果如下:

          Console.WriteLine(string.Format("{0}:\t{1}(HashCode)", G_3_A.Name, G_3_A.ContentTemplate.LoadContent().GetHashCode()));    
          Console.WriteLine(string.Format("{0}:\t{1}(HashCode)", G_3_B.Name, G_3_B.ContentTemplate.LoadContent().GetHashCode()));     
          Console.WriteLine(string.Format("{0}:\t{1}(HashCode)", G_3_C.Name, G_3_C.ContentTemplate.LoadContent().GetHashCode()));

结果如下:

3.3 证明ContentTemplate里面是不同的对象;我写下了如下代码:

           var style = this.FindResource("ButtonContentTemplateStyle") as Style;    
           var setter = style.Setters[0] as Setter;     
           var template = setter.Value as DataTemplate;     
           Console.WriteLine(string.Format("第{0}次加载ButtonContentTemplateStyle:\tContent的HashCode({1})", count, template.LoadContent().GetHashCode())); 
          count++;

结果如下:

四、总结

WPF的树上是不允许有同一个控件存在两个不同的节点上;如果想要实现该功能,需要实例化两个对象然后存放到WPF的树上(包括逻辑树或可视化树);Silverlight也应该是一样的道理;WPF中的ContentControl定义样式时都不能采用Content来定义;

五、代码下载

点击下载代码

点击下载“逻辑树与可视化树工具”

【WPF】ContentControl Style定义与使用出现问题后 -- 引发的思考的更多相关文章

  1. 【Asp.net之旅】--因自己定义控件注冊而引发的思考

    前言 近期在开发远洋的SOA系统平台,开发使用的是.NET平台.对于Asp.net并不困难,但该系统的开发并非全然依靠Asp.net.而是自身封装好的框架.这套框架是远洋地产购买的微软的开发平台,项目 ...

  2. WPF 之 style文件的引用

    总结一下WPF中Style样式的引用方法. 一.内联样式: 直接设置控件的Height.Width.Foreground.HorizontalAlignment.VerticalAlignment等属 ...

  3. WPF(ContentControl和ItemsControl)

    WPF(ContentControl和ItemsControl) 2013-04-01 16:25 2188人阅读 评论(0) 收藏 举报  分类: .Net(C#)(31)  WPF(25)  版权 ...

  4. WPF 样式(定义样式、引用样式、样式作用域、Trigger触发器)

    1.定义 资源字典   <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presenta ...

  5. WPF的Style的TargetType不同写法的异同

    原文:WPF的Style的TargetType不同写法的异同 <Style TargetType="TextBlock"> <Setter Property=&q ...

  6. WPF中Style文件的引用——使用xaml代码或者C#代码动态加载

    原文:WPF中Style文件的引用--使用xaml代码或者C#代码动态加载 WPF中控件拥有很多依赖属性(Dependency Property),我们可以通过编写自定义Style文件来控制控件的外观 ...

  7. WPF 中style文件的引用

    原文:WPF 中style文件的引用 总结一下WPF中Style样式的引用方法: 一,内联样式: 直接设置控件的Height.Width.Foreground.HorizontalAlignment. ...

  8. Google C++ Style Guide在C++11普及后的变化

    转 http://www.cnblogs.com/chen3feng/p/5972967.html?from=timeline&isappinstalled=0&lwfrom=user ...

  9. [WPF自定义控件库] 让Form在加载后自动获得焦点

    原文:[WPF自定义控件库] 让Form在加载后自动获得焦点 1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录" ...

随机推荐

  1. Maven下使用Jetty进行Debug

    1.环境和条件 Maven-3.0.3Eclipse请阅读者事先具备一定maven知识 2 配置2.1 添加Jetty插件 在pom.xml中加入如下代码段 <plugin> <gr ...

  2. 批处理 —— 每天生成一个以日期命名的文件(Win XP)

    想达到这样一个效果:每天在某个目录下生成一个以日期命名的文件(如,0705.txt). 第一步,新建一个批处理文件 新建一个文件,比如[create_day_file.bat].编辑,输入以下内容 : ...

  3. leetcode:Rotate Array

    Rotate an array of n elements to the right by k steps. For example, with n = 7 and k = 3, the array  ...

  4. 好!maximum-product-of-word-lengths

    以后看到个数比较少,性能比较高,就要第一时间想到位操作! 这道题目mock没有通过.超时了...... 原来题目解法的思路非常非常好! 开始我关注于降低n*n的复杂度,但是这道题目复杂度高在每个字符串 ...

  5. bzoj4199

    看到这题我就伤心,当初想到了正解却因为各种sb原因没有写…… 好吧,其实我的正解是比较挫的…… 大家似乎都用了后缀数组,我用了后缀自动机(后缀树) 其实SAM是很好想得,用SAM建出后缀树后 我们考虑 ...

  6. linux下使用svn

    安装:apt-get install subversion CHECKOUT 将文件checkout到本地目录 svn checkout path(path是服务器上的目录)例如:svn checko ...

  7. echarts地图点定位的问题

    1,生成地图 2,如果需要产生地图上的点位,需要在配置中传入geoCoord具体数据为一下: { "海门": [121.15, 31.89], "鄂尔多斯": ...

  8. POJ 2828 (线段树 单点更新) Buy Tickets

    倒着插,倒着插,这道题是倒着插! 想一下如果 Posi 里面有若干个0,那么排在最前面的一定是最后一个0. 从后往前看,对于第i个数,就应该插在第Posi + 1个空位上,所以用线段树来维护区间空位的 ...

  9. HDU 1838 Chessboard

    dp[i][j]表示以(i,j)为右下角所含棋盘的最大规模, 如果 s[i][j] == s[i-1][j-1] && s[i][j] != s[i-1][j] && ...

  10. ASP.NET 4的Demo实践:URL路由改进支持

    从.NET框架3.5 SP1开始,微软推出了ASP.NET路由支持,从而实现了特定资源的URL与其对应的Web服务器上的物理文件之间的彻底解耦.借助于ASP.NET路由支持,开发人员可以定义一组路由规 ...