WPF入门教程系列十二——依赖属性(二)

二、 依赖属性的优先级

  由于WPF 允许我们可以在多个地方设置依赖属性的值,所以我们就必须要用一个标准来保证值的优先级别。比如下面的例子中,我们在三个地方设置了按钮的背景颜色,那么哪一个设置才会是最终的结果呢?是Black、Red还是Azure呢?

<Grid>
    <Button Name="myButton" Background="Azure" Margin="150">
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Setter Property="Background" Value="Black"/>
                <Style.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Background" Value="Red"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>
</Grid>

通过前面的简单介绍,我们了解了简单的依赖属性,每次访问一个依赖属性,它内部会按照下面的顺序由高到底处理该值。详细见下图

op=>operation: 属性系统强制转换
op1=>operation: 活动动画或具有Hold行为的动画
op2=>operation: 本地值
op3=>operation: TemplatedParent
op4=>operation: 隐式样式
op5=>operation: 样式触发器
op6=>operation: 模板触发器
op7=>operation: 样式Setter
op8=>operation: 默认(主题)样式
op9=>operation: 继承
op10=>operation: 来自依赖项属性元数据的默认值
io=>inputoutput: 输出

op->op1->op2->op3->op4->op5->op6->op7->op8->op9->op10

c3d.club

  由于这个流程图偏理想化,在实际的工作过程中我们会遇到各种各样的问题,我也不可能都碰到,也就无法彻底把这些问题说清楚,所以当我们遇到问题之后,再进行仔细分析,查找原因,不断总结、举一反三。

三、 依赖属性的继承

  属性值继承是 Windows Presentation Foundation (WPF) 属性系统的一项功能。 属性值继承使元素树中的子元素可以从父元素那里获取特定属性的值,并继承该值,就好像它是在最近的父元素中的任意位置设置的一样。 父元素还可以通过属性值继承来获得其值,因此系统有可能一直递归到页面根元素。 属性值继承不是属性系统的默认行为;属性必须用特定的元数据设置来建立,以便使该属性能够对子元素启动属性值继承。

​ 依赖属性继承的最初意愿是父元素的相关设置会自动传递给所有层次的子元素 ,即元素可以从其在树中的父级继承依赖项属性的值。这个我们在编程当中接触得比较多,如当我们修改窗体父容器控件的字体设置时,所有级别的子控件都将自动 使用该字体设置 (前提是该子控件未做自定义设置)。接下来,我们来做一个实际的例子。代码如下:

<Window x:Class="项目4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:项目4"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen" WindowState="Maximized" >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="101*"/>
            <RowDefinition Height="80"/>
            <RowDefinition Height="80"/>
        </Grid.RowDefinitions>
        <StackPanel>
            <Label>继承自Windows的FontSize</Label>
            <TextBlock Name="TextBlockInherited" FontSize="36" TextWrapping="WrapWithOverflow">
                重写了继承,没有继承Windows的FontSize
            </TextBlock>
        </StackPanel>
        <WrapPanel Grid.Row="1">
            <Label>窗体字体大小</Label>
            <ComboBox Name="drpWindFontSize"></ComboBox>
            <Button Name="ButtonFontSize">改变window字体</Button>
        </WrapPanel>
        <WrapPanel Grid.Row="2">
            <Label>文本字体大小</Label>
            <ComboBox Name="drpTxtFontSize"></ComboBox>
            <Button Name="ButtonTxt">改变TextBlock字体</Button>
        </WrapPanel>
    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;

namespace 项目4 {
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ButtonFontSize_Click(object sender, RoutedEventArgs e)
        {
            FontSize = Convert.ToInt32(drpWindFontSize.Text);
        }

        private void ButtonTxt_Click(object sender, RoutedEventArgs e)
        {
            TextBlockInherited.FontSize = Convert.ToInt32(drpTxtFontSize.Text);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            List<int> listFontSize=new List<int>();
            for (int i = 0; i < 61; i++)
            {
                listFontSize.Add(i+4);
            }

            drpTxtFontSize.ItemsSource = listFontSize;
            drpWindFontSize.ItemsSource = listFontSize;
        }
    }
}

c3d.club

  Window.FontSize 设置会影响所有的内部元素字体大小,这就是所谓的属性值继承,如上面代码中的第一个Label没有定义FontSize ,所以它继承了Window.FontSize的值。但一旦子元素提供了显式设置,这种继承就会被打断,如第二个TextBlock定义了自己的 FontSize,所以这个时候继承的值就不会再起作用了。

  这个时候你会发现一个很奇怪的问题:虽然StatusBar没有重写FontSize,同时它也是Window的子元素,但是它的字体大小却没 有变化,保持了系统默认值。那这是什么原因呢?作为初学者可能都很纳闷,官方不是说了原则是这样的,为什么会出现表里不一的情况呢?其实仔细研究才发现并 不是所有的元素都支持属性值继承。还会存在一些意外的情况,那么总的来说是由于以下两个方面:

  1. 有些Dependency属性在用注册的时候时指定Inherits为不可继承,这样继承就会失效了。

  2. 有其他更优先级的设置设置了该值,在前面讲的的“依赖属性的优先级”你可以看到具体的优先级别。

  属性值继承通过混合树操作。持有原始值的父对象和继承该值的子对象都必须是 FrameworkElementFrameworkContentElement,且都必须属于某个逻辑树。 但是,对于支持属性继承的现有 WPF 属性,属性值的继承能够通过逻辑树中没有的中介对象永久存在。 这主要适用于以下情况:让模板元素使用在应用了模板的实例上设置的所有继承属性值,或者使用在更高级别的页级成分(因此在逻辑树中也位于更高位置)中设置的所有继承属性值。 为了使属性值的继承在这两种情况下保持一致,继承属性必须注册为附加属性。

  这里的原因是部分控件如StatusBar、Tooptip和Menu等内部设置它们的字体属性值以匹配当前系统。这样用户通过操作系统的控制 面板来修改它们的外观。这种方法存在一个问题:StatusBar等截获了从父元素继承来的属性,并且不影响其子元素。比如,如果我们在 StatusBar中添加了一个Button。那么这个Button的字体属性会因为StatusBar的截断而没有任何改变,将保留其默认值。所以大家 在使用的时候要特别注意这些问题。