Silverlight 里如何实现隐式样式,ImplicitStyleManager 的实现思想

在 WPF 中,我们可以方便的在全局范围定义一个样式,就可以应用到所有这种类型的对象,这就是所谓的隐式样式(implicit Style),比如:

<Window x:Class="WpfImplicitStyle.Window1"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    Title
="Window1" Height="300" Width="300">
    
<Grid>
        
<Grid.Resources>
            
<!-- 针对一种类型设置全局样式 -->
            
<Style TargetType="Button">
                
<Setter Property="Background" Value="AliceBlue" /> 
            
</Style>
        
</Grid.Resources>
        
<StackPanel>
            
<Button>Button a</Button>
            
<Button>Button b</Button>
        
</StackPanel>
    
</Grid>
</Window>


这样之后,两个按钮就都变成了浅蓝色的背景。

但是在 Silverlight 里没有办法这样做。我们必须手工对每一个需要设置样式的控件添加 Style="{StaticResource someStyle}" 这样的语句,挨个设置,非常麻烦。

好在 Silverlight Toolkit 里提供了一个类似的实现,叫做 ImplicitStyleManager (隐式样式管理器,可以简称 ISM)。

该类的使用方法,是在某个根元素上设置一个附加属性(Attached Property),然后,该元素下属的视觉树里符合特定类型的子元素的样式,就可以被自动应用隐式样式了。
例子如下:

<UserControl x:Class="ImplicitStyleTest.Page"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width
="400" Height="300"
        xmlns:theming
="clr-namespace:Microsoft.Windows.Controls.Theming;assembly=Microsoft.Windows.Controls.Theming">
    
<Grid x:Name="LayoutRoot" Background="White">
        
<Grid.Resources>
            
<Style TargetType="Button">
                
<Setter Property="Background" Value="AliceBlue" />
            
</Style>
        
</Grid.Resources>
        
<!-- 在根元素上设置一次就可以了 -->
        
<StackPanel theming:ImplicitStyleManager.ApplyMode="Auto">
            
<Button Content="Button a"></Button>
            
<Button Content="Button b"></Button>
        
</StackPanel>
    
</Grid>
</UserControl>

运行一下例子试试就会发现,两个按钮的样式都被设置了,这样就实现了类似 WPF 里的隐式样式行为。

在这个例子里可以看到,ApplyMode 属性被设置成了 Auto. 其实它一共有3个可选值,分别代表如下含义:

1. Auto
每当 layout updated 的时候,ISM 会重新应用隐式样式。在这种模式下如果元素在以后被动态的加入到视觉树中, 它们将被应用隐式样式。
需要注意的是,LayoutUpdated 事件发生的非常频繁,并且不光是当你添加元素后才发生。而我们又没有类似 ItemAddedToTree 的事件,如果视觉树比较大的话,ISM 遍历它的时候就会花费比较多的时间,这可能给性能带来一定的影响。但是为了方便,这里只好做一些折中的权衡,牺牲一点性能。如果视觉树很大的时候,可以考虑改用 OneTime 模式。

2. OneTime
仅在第一次加载时起作用,对后面动态加到 visual tree 里的元素不起作用。
有时候,你的视觉树很大,所以你不考虑用 Auto 模式。这时候你可以用 OneTime 模式一次性应用样式;同时,需要在添加新节点之后,在代码里手工调用 ISM 的 Apply 方法,这样可以重新应用一次样式。这样的办法可以避免 Auto 模式的一些性能损失。

3. None
效果跟没设置 ApplyMode 属性一样。

了解了 ISM 如何使用,我们来看看它是怎么实现的。

我们知道,Silverlight 元素里面的 Style 在运行时只能被设置一次,否则就会出错,ISM 也不例外,也要受这个制约。

ISM 的实现原理大致如下:

1. 定义一个叫做 ApplyMode 的附加属性(Attached Property),提供给需要设置样式的“根”元素使用。
而我们知道,附加属性可以在 xaml 里被设置,这就像上面的例子里所写的那样;同时,它有一个最大的好处,就是可以定义属性改变时触发的回调函数(注册时定义在 PropertyMetadata 里面)。这样,当我们在代码里设置了 ApplyMode 后,ISM 就能触发这个回调函数进行处理了。

2. 在这个回调函数中,注册元素的 LayoutUpdated 事件处理函数,这样,在该元素不管因为什么原因更新其 layout 的时候,就能够得到通知。
这里最巧妙的地方是:将元素 LayoutUpdated 事件的处理委托以依赖属性(DependencyProperty) 的形式存在该元素自身的属性中,这样就省去了自行管理很多 event handler 的烦恼了。。。依赖属性真的是个好东西啊!
代码:

/// <summary>
/// ApplyModeProperty property changed handler.
/// </summary>
/// <param name="dependencyObject">FrameworkElement that changed its 
/// ApplyMode.</param>
/// <param name="eventArgs">Event arguments.</param>
private static void OnApplyModePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
    FrameworkElement element 
= dependencyObject as FrameworkElement;
    
if (element == null)
    {
        
throw new ArgumentNullException("dependencyObject");
    }

    ImplicitStylesApplyMode oldMode 
= (ImplicitStylesApplyMode)eventArgs.OldValue;
    ImplicitStylesApplyMode newMode 
= (ImplicitStylesApplyMode)eventArgs.NewValue;

    ImplicitStyleManager.SetHasBeenStyled(element, 
false);

    EventHandler eventHandler 
= ImplicitStyleManager.GetLayoutUpdatedHandler(element);

    
// If element is automatically styled (once or always) attach event 
    
// handler.
    if ((newMode == ImplicitStylesApplyMode.Auto || newMode == ImplicitStylesApplyMode.OneTime)
        
&& oldMode == ImplicitStylesApplyMode.None)
    {
        
if (eventHandler == null)
        {
            eventHandler 
=
                (sender, args) 
=>
                {
                    ImplicitStyleManager.PropagateStyles(element, 
false);
                };

            ImplicitStyleManager.SetLayoutUpdatedHandler(element, eventHandler);
            element.LayoutUpdated 
+= eventHandler;
        }
    }
    
else if ((oldMode == ImplicitStylesApplyMode.Auto || oldMode == ImplicitStylesApplyMode.OneTime)
        
&& newMode == ImplicitStylesApplyMode.None)
    {
        
if (eventHandler != null)
        {
            element.LayoutUpdated 
-= eventHandler;
            ImplicitStyleManager.SetLayoutUpdatedHandler(element, 
null);
        }
    }
}

3. 在上述 LayoutUpdated 的事件处理函数中,遍历控件的视觉树,对符合条件的元素设置 Style(也只能设置一次)。
这里值得一说的是遍历树的代码技巧,为了避免递归或者类似方法遍历树造成的开销,这里实际使用了一种很巧妙的 Stack 来访问树节点。并且,在所有需要遍历的地方,尽可能的使用了 yield return, 以一种函数式编程的写法来延迟实际对节点的操作。

具体代码不细细解释了,这里把 MS 的代码贴来仅供欣赏一下 Functional Programming,有兴趣的朋友可以自己研究:

/// <summary>
/// This method propagates the styles in the resources associated with
/// a framework element to its descendents. This results in a  
/// style inheritance that mimics WPF's behavior.
/// </summary>
/// <param name="element">The element that will have its styles 
/// propagated to its children.</param>
/// <param name="recurse">Whether to recurse over styled elements that
/// are set to OneTime and have already been styled.</param>
private static void PropagateStyles(FrameworkElement element, bool recurse)
{
    BaseMergedStyleDictionary initialDictionary 
= GetMergedStyleDictionary(element);

    
// Create stream of elements and their base merged style 
    
// dictionaries by traversing the logical tree.
    IEnumerable<Tuple<FrameworkElement, BaseMergedStyleDictionary>> elementsToStyleAndDictionaries =
        FunctionalProgramming.Traverse(
            
new Tuple<FrameworkElement, BaseMergedStyleDictionary>(element, initialDictionary),
            (elementAndDictionary) 
=> 
                elementAndDictionary
                    .First
                    .GetLogicalChildrenDepthFirst()
                    .Select(childElement 
=> 
                        
new Tuple<FrameworkElement, BaseMergedStyleDictionary>(
                            childElement, 
                            
new MergedStyleResourceDictionary(
                                ImplicitStyleManager.GetExternalResourceDictionary(childElement) 
?? childElement.Resources,
                                elementAndDictionary.Second))),
            (elementAndDictionary) 
=> recurse ||
                (ImplicitStyleManager.GetApplyMode(elementAndDictionary.First) 
!= ImplicitStylesApplyMode.OneTime ||
                
!ImplicitStyleManager.GetHasBeenStyled(elementAndDictionary.First)));

    
foreach (Tuple<FrameworkElement, BaseMergedStyleDictionary> elementToStyleAndDictionary in elementsToStyleAndDictionaries)
    {
        FrameworkElement elementToStyle 
= elementToStyleAndDictionary.First;
        BaseMergedStyleDictionary styleDictionary 
= elementToStyleAndDictionary.Second;

        
bool styleApplied = false;

        
if (elementToStyle.Style == null)
        {
            Style style 
= styleDictionary[GetStyleKey(elementToStyle)];
            
if (style != null)
            {
                elementToStyle.Style 
= style;
                styleApplied 
= true;
            }
        }

        
if (ImplicitStyleManager.GetApplyMode(elementToStyle) == ImplicitStylesApplyMode.OneTime && (VisualTreeHelper.GetChildrenCount(elementToStyle) > 0 || styleApplied))
        {
            ImplicitStyleManager.SetHasBeenStyled(elementToStyle, 
true);
        }
    }
}



参考:
http://www.beacosta.com/blog/?p=54
http://www.beacosta.com/blog/?p=55
(好像是 MS Silverlight 团队的一个美女,推荐订阅或关注她的博客)

转载于:https://www.cnblogs.com/RChen/archive/2008/12/16/1355906.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/300154.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何排查 .NET 内存泄漏

内存泄漏通常表示&#xff1a;一个应用程序的某些对象在完成它的的生命周期后&#xff0c;由于它被其他对象意外引用&#xff0c;导致后续gc无法对它进行回收&#xff0c;长此以往就会导致程序性能的下降以及潜在的 OutOfMemoryException。这篇我们通过一个内存泄漏工具对 .NET…

这就是你在妈妈肚子里尿尿的样子 | 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅其实“站着说话不腰疼”是有科学依据的不同姿势下腰椎承受的压力是这样的三思逍遥这是你在妈妈肚子里尿尿的样子不少人觉得葡萄上白色的这一层粉是“脏”的表现其实它是葡萄的“果粉”不仅对人体无害还可以代表这个葡萄很新鲜现实告诉…

C#:装箱和拆箱相关知识整理

1、装箱和拆箱是一个抽象的概念 2、 装箱是将值类型转换为引用类型 &#xff1b; 拆箱是将引用类型转换为值类型 利用装箱和拆箱功能&#xff0c;可通过允许值类型的任何值与Object 类型的值相互转换&#xff0c;将值类型与引用类型链接起来 例如&#xff1a; int val 100;…

Android 封装handler,android封装工作线程跟Handler工具类

直接上代码&#xff0c;不解说 - -基于MVP封装P的基类AbsHandlerThreadHelper.javaimport java.lang.ref.WeakReference;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import android.content.Context;import android.os.Handler;impor…

c# 实现MD5,SHA1,SHA256,SHA512等常用加密算法

usingSystem;usingSystem.IO;usingSystem.Data;usingSystem.Text;usingSystem.Diagnostics;usingSystem.Security;usingSystem.Security.Cryptography;/**//* * .Net框架由于拥有CLR提供的丰富库支持&#xff0c;只需很少的代码即可实现先前使用C等旧式语言很难实现的加密算法…

WPF中TreeView.BringIntoView方法的替代方案

WPF中TreeView.BringIntoView方法的替代方案 周银辉 WPF中TreeView.BringIntoView&#xff08;&#xff09;方法并不是那么地好用&#xff0c;不少时候会没有效果&#xff0c;这里有一个替代方案&#xff0c;调用SelectItem&#xff08;&#xff09;方法可以展开并呈现TreeView…

C# WPF MVVM项目实战(进阶①)

这篇文章还是在之前用Caliburn.Micro搭建好的框架上继续做的开发&#xff0c;今天主要是增加了一个用户窗体TestFormView&#xff0c;然后通过TabControl&#xff0c;将新增的窗体加载到主界面上进行分页显示&#xff0c;新增的页面引用了WPF UI&#xff1a;WPF Datagrid合并表…

双十一变身大型奥数竞赛现场?数学不好的你请转场

当微信又被淘宝“助力”刷屏&#xff0c;我们开始意识到它来了&#xff0c;它真的来了它今天真的来了&#xff01;令人闻风丧胆的双十一又双叒叕要到了&#xff01;从最开始的光棍节变成现在的购物节每年双十一的优惠难度堪比南孚电池一节更比一节强小木&#xff1a;阿里&#…

android菜单和对话栏,Android回顾--(十一) 菜单和对话框

选择菜单(OptionsMenu)第一种使用方式&#xff1a;在res目录下面建立一个名称是menu的文件夹在menu下面建立一个xml文件(默认就是menu的类型)在建立的这个xml文件夹中添加菜单的选项&#xff0c;xml文件中有很多属性android:orderInCategory "2" //表示当前的item在…

树莓派:3安装NodeJS

上一节记录有mysql的安装&#xff0c;这一节就主要记录nodejs的安装&#xff0c;最开始的时候我是想直接使用命令直接安装&#xff0c;如&#xff1a; sudo apt-get install nodejs结果发现安装不了&#xff0c;好像是数据源有问题。既然这样不行那么我就自己编译吧&#xff0c…

Exchange Server 2003邮件服务器系统的基本部署思路

<?xml:namespace prefix st1 ns "urn:schemas-microsoft-com:office:smarttags" />以下内容摘自笔者编著的《网管员必读——网络应用》&#xff08;第2版&#xff09;一书&#xff1a; 6.1.4 Exchange Server 2003邮件服务器系统的基本部署思路<?xml:n…

css两栏式布局示例

请先看图,这里主要用到了float属性,该属性的值指出了对象是否及如何浮动 语法&#xff1a; float : none | left |right 参数&#xff1a; none :  对象不浮动;left :  对象浮在左边;right :  对象浮在右边 请看代码,请CSS高手指教,其他还可以用position来实现两栏,只…

HttpClient 禁用自动重定向

HttpClient 禁用自动重定向Intro前段时间写了一个小工具来帮助我们简化一个每个月一次的小任务&#xff0c;每个月我们公司的 BI Team 会给我们上个月访问量比较高的博客文章的 url&#xff0c;然后我们会根据 BI 提供博客的 url 去找到对应的博客 id&#xff0c;然后更新到配置…

OpenAI“单手解魔方”被公开质疑,Gary Marcus称七大问题涉嫌误导

全世界只有3.14 % 的人关注了青少年数学之旅近日&#xff0c;“OpenAI的机器手在4分钟内单手成功还原魔方”引起刷屏&#xff0c;然而&#xff0c;这一成就被著名机器学习怀疑论者马库斯质疑了&#xff0c;马库斯逐条列举OpenAI的误导性说法&#xff0c;机器学习圈却都撑OpenAI…

android 带弧形背景,[Android日常]绘制弧形渐变背景

最近要修改用户空间头部信息显示&#xff0c;参考了好多APP的用户空间&#xff0c;都有一个弧形的背景&#xff0c;看着挺漂亮的。实现这种效果&#xff0c;有两种实现方式&#xff1a;1、作图&#xff1b;2、通过代码进行绘制。今天就讲讲如何通过canvas进行绘制。一、用到的知…

超强的绕口令

今天看到这样一个绕口令&#xff0c;自己读了半天&#xff0c;越读错的越多&#xff0c;呵呵&#xff0c;贴出来大家一起来玩玩1、初入江湖&#xff1a;化肥会挥发 2、小有名气&#xff1a;黑化肥发灰&#xff0c;灰化肥发黑 3、名动一方&#xff1a;黑化肥发灰会挥发&…

世界顶级精英们的人生哲学 【转】

1.别为你自己和别人下定论&#xff0c;你所看到听到的可能只是一面&#xff0c;为这个失去可能的朋友&#xff0c;很不值。 2.你可以有喝醉的时候&#xff0c;我们可以接受&#xff0c;但是你要明白和真正的朋友一醉才能让伤心事方休&#xff0c;否则&#xff0c;你只会是别人的…

记一次 .NET 某云采购平台API 挂死分析

一&#xff1a;背景 1. 讲故事大概有两个月没写博客了&#xff0c;关注我的朋友应该知道我最近都把精力花在了星球&#xff0c;这两个月时间也陆陆续续的有朋友求助如何分析dump&#xff0c;有些朋友太客气了&#xff0c;给了大大的红包&#xff0c;哈哈????&#xff0c;手…

来自女朋友的灵魂拷问!| 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅【1】【2】【3】【4】【5】【6】【7】【8】【9】

android 栏目编辑,android – 编辑文本导致内存泄漏

介绍&#xff1a;我有一个应用程序具有以下结构&#xff1a;ActionBar顶部(ActionBarSherlock)ViewPagerIndicator下面(对于选项卡)ViewPager(主机片段)我有一个问题,我的一个碎片导致了相当大的内存泄漏.我将问题缩小到以下情况&#xff1a;导致泄漏的片段只会在其onCreateVie…