在 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

Silverlight 里如何实现隐式样式,ImplicitStyleManager 的实现思想相关推荐

  1. Scala 高阶函数(作为值的函数、匿名函数、闭包、柯里化)+隐式转换和隐式参数...

    Scala高级特性 1.    学习目标 1.1.   目标一:深入理解高阶函数 1.2.   目标二:深入理解隐式转换 2.    高阶函数 2.1.   概念 Scala混合了面向对象和函数式的特 ...

  2. 7.scala初识 柯里化、隐式参数、隐式转换、视图边界、上界、下界、协变、逆变

    1.前言: 学过java我们都知道,java中的继承是对类的增强,java中的代理.装饰是对对象方法的增强.而在scala中,隐式转换和隐式参数是Scala中两个非常强大的功能,隐式的对类的方法进行增 ...

  3. Scala _09样例类(case classes)隐式转换

    样例类(case classes) 概念理解 使用了case关键字的类定义就是样例类(case classes),样例类是种特殊的类.实现了类构造参数的getter方法(构造参数默认被声明为val), ...

  4. Scala的隐式转换详解

    隐式转换是在Scala编译器进行类型匹配时,如果找不到合适的类型,那么隐式转换会让编译器在作用范围内自动推导出来合适的类型. 1.隐式值与隐式参数 隐式值是指在定义参数时前面加上implicit.隐式 ...

  5. Scala隐式转换详解

    1.隐式转换 当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译,而这些方法就是隐式转换,Scala编译器所做的事情要比Java编译器做的事情要 ...

  6. c语言 隐式声明,关于C#:隐式函数声明和链接

    最近,我了解了C语言中的隐式函数声明.主要思想很明确,但在这种情况下,我对理解链接过程有些麻烦. 考虑以下代码(文件a.c): #include int main() { double someVal ...

  7. Silverlight 5 Beta新特性[5]隐式模板支持

    继续更新Silverlight 5 Beta新特性.在Silverlight 5 BEta更新出来新特性中有一些是WPF已经存在的影子.类似前面提到的多窗体[Multiple Window Suppo ...

  8. jQuery中事件及常用事件总结、jQuery中常见效果、隐式迭代、链式编程、样式操作、动画队列、不同元素绑定同一个事件

    jQuery事件: jQuery中的事件和javascript中的事件基本相似,不同的是jQuery中的事件处理函数写在事件后面的括号中,如: <script>$('input').cli ...

  9. scala 环境搭建 变量 值 数据类型 元组 表达式块 语句 函数 柯里化 集合 面向对象 隐式转换

    scala (scalable的简写) scala是一个比较冷门的语言,不太被人们所知道 为什么这么冷门的语言现在被我们使用 很多的大数据的项目的源码是是用scala语言编写的. 因为大数据技术不断被 ...

最新文章

  1. Spring Boot“内存泄漏”?看看美团大牛是如何排查的
  2. Informix执行onmonitor出错的解决办法
  3. 差分霍尔器件测量电路
  4. mysql事件调度定时任务_详解MySQL用事件调度器Event Scheduler创建定时任务
  5. RuntimeError: dimension specified as 0 but tensor has no dimensions
  6. 聚类分析matlab检验,「matlab聚类分析」聚类分析的Matlab 程序—系统聚类(附有案例分析) - 金橙教程网...
  7. 科学家研发多模态生物识别系统,基于脑纹独特性来防范身份欺骗
  8. tcpdump软件使用
  9. Linux学习笔记(五)
  10. 第3节 中间层创建与设置
  11. NVIDIA NVLink技术
  12. Mysql中INSTR(str, substr)函数
  13. ANSI SQL之殇
  14. 一阶线性偏微分方程通解法和特征线法(一)| 两个自变量情况 | 偏微分方程(七)
  15. 刘德华中国巡回演唱会2007
  16. 把手机自带计算机软件,怎样删除手机自带软件
  17. c语言 字符转二进制输出,C语言 如何把一个ASCII码转换为二进制输出?
  18. 线性规划的一些处理方法:取最值、找索引、条件约束
  19. 官宣!CATCTF不日开赛!!
  20. Centos7+LAMP+owncloud+数据库读写分离

热门文章

  1. 程序人生:给年轻程序员关于开发过程的10条忠告
  2. 程序员简历的 8 个建议
  3. linux netbeans 中文乱码,浅谈Linux Netbeans字体反锯齿处理
  4. mysql安装教程8.0.21安装,Windows系统下MySQL8.0.21安装教程(图文详解)
  5. 如何高效学习前端新知识,我推荐这些~
  6. 我在工作中是如何使用Git的
  7. 阿里大鱼短信介入demo分享
  8. Linux 终端环境安装 L2TP 客户端
  9. Android社会化分享详解
  10. Haproxy+多台MySQL从服务器(Slave) 实现负载均衡