【问题标题】:Implicit styles in Application.Resources vs Window.Resources?Application.Resources 与 Window.Resources 中的隐式样式?
【发布时间】:2012-01-27 15:38:22
【问题描述】:

我在查看this question 并注意到在Application.Resources 中放置一个隐式TextBlock 样式会将该样式应用于所有TextBlock,甚至是其他控件中的那些,例如Buttons、ComboBoxes 等

<Application.Resources>
    <Style TargetType="{x:Type TextBlock}">
        <Setter Property="Foreground" Value="Blue" />
    </Style>
</Application.Resources>

将隐式样式置于Window.Resources does not cross control template boundaries 中,这样Buttons 和ComboBoxes 之类的内容将保持其默认的黑色文本。

<Window.Resources>
    <Style TargetType="{x:Type TextBlock}">
        <Setter Property="Foreground" Value="Blue" />
    </Style>
</Window.Resources>

此外,在Application.Resources 中添加默认样式可以使您无法用另一种隐式样式覆盖该样式。

<!-- Doesn't work if implicit style with same property is in Application.Resources -->
<ComboBox.Resources>
    <Style TargetType="{x:Type TextBlock}">
        <Setter Property="Foreground" Value="Red" />
    </Style>
</ComboBox.Resources>

我的问题是:

  • 这是为什么?

  • Application.Resources 和 Windows.Resources 之间还有其他区别吗?

  • 什么时候应该使用一个而不是另一个?

    我知道Application.Resources 适用于整个应用程序,而Window.Resources 仅适用于窗口,但是我想知道为什么Application 中的样式与Window 中的样式不同

【问题讨论】:

  • 关于 App.xaml 的隐式样式如何跨越 ControlTemplate 边界的好问题和见解。因此,我将某些资源字典导入/合并到应用程序的 每个 窗口中(尽管我实际上只是导入了一个主 rd 文件)。你有没有找到比我这样做更好的方法来处理这个问题? (能够只导入一次会更好——就像我们对 App.xaml 的导入资源所做的那样。)
  • 这个跨越 ControlTemplate 边界的问题对于像(App.xaml 级别)隐式 TextBlock 样式这样的情况尤其麻烦,其中 TextBlock 自动生成为控件的 ContentPresenter 的子项。使用 Rachel 提到的窗口级技巧为我解决了这个问题。
  • @Jason 通常我的 WPF 应用程序只有一个窗口,内容会根据需要进行更改。我不认为我曾经做过一个超过两个窗口(登录和应用程序)的窗口。也就是说,您可能可以使用 MEF 导入/导出资源字典,如 here 所述
  • 谢谢,很高兴知道。

标签: wpf resources


【解决方案1】:

这确实是添加到 WPF 中的唯一特殊处理,它是按设计完成的。实现它的代码可以在FrameworkElement 中找到,在方法@​​987654322@ 中,它有效地做到了:

internal static object FindImplicitStyleResource(FrameworkElement fe, object resourceKey, out object source)
{
        // ...
        DependencyObject boundaryElement = null;
        if (!(fe is Control))
            boundaryElement = fe.TemplatedParent;
        // ...
}

所以经验法则是隐式样式总是应用于控件(即派生自Control)。假设可以找到隐式 Style。

对于ControlTemplate 中使用的元素,它们不是从Control 派生的,例如TextBlock,隐式样式查找不会跨越它的模板化父元素。在上述情况下,这将是ComboBox。

我相信这样做是为了避免将 TextBlock 的非应用程序隐式样式无意中应用于控件模板中使用的 TextBlock 元素,开发人员可能知道也可能不知道这些元素存在。隐式样式只会应用于开发人员在他们自己的 XAML 中实际创建的 TextBlock。

应用程序的隐式样式仍然允许全局样式,例如增加字体大小。但可能造成的混乱多于其价值。

没有很好的答案来说明何时使用一种与另一种,因为它们各有其功能。显然,如果您不想影响应用程序中的每个TextBlock,则不应将样式放入应用程序资源中。

但请记住,这会影响任何非Control 元素,例如Shape 元素。

【讨论】:

  • 你知道为什么 Application.Resources 中的隐式样式会覆盖任何其他 UI 元素的 Resources 中定义的任何其他隐式样式吗?
  • @Rachel - 我不相信这是真的。对于控件,Window.Resources 中定义的隐式样式将优先于 Application.Resources 中的样式。对于不是从 Control 派生并且在模板中使用的 UIElements,则不会查询 Windows.Resources,因此在这种情况下将使用 Application.Resources。
  • 你是对的,它唯一的 UI 元素不是从 Control 派生的,无论如何都不会从隐式样式继承。谢谢你:)
【解决方案2】:

很简单很简单

如果您希望在整个应用程序之间共享资源,您可以使用 Application.Resources

如果您希望资源在整个窗口之间共享,您可以使用 Window.Resources

如果您希望资源在您将使用的单个控件之间共享(无论哪个控件)。资源

假设您有多个窗口,但您只需要一个默认样式而不需要另一个,那么您将使用 Windoe.Resources

【讨论】:

  • 但我很困惑为什么在Application.Resources 中设置隐式TextBlock 样式会越过控件边界,而默认行为是隐式样式不跨越控件模板,除非元素是隐式样式应用于Control 类型。到目前为止,我所有的 WPF 应用程序都包含在一个窗口中,所以我总是使用 Window.Resources
  • 我个人避免使用隐式样式,因为它会使项目的新开发人员感到困惑。假设您有 10 万行或更多代码行,其中包含在 App.xaml 中合并的 20 多个资源字典,我在其中一个中添加了隐式样式。如果您从未见过代码,您会对自己说到底发生了什么!!!!我可以看到在自定义 USerControl 中使用隐式样式,其中它是一个测试或测验,或者每个 TextBlock 可能具有相同边距的东西。
  • 我经常使用隐式样式,因为我很懒,不喜欢为所有东西定义命名样式。在View 的顶部使用隐式样式定义通用控件的边距、对齐或颜色是很常见的,尽管现在我想起来了,我不经常在@987654326 中应用隐式样式应用程序范围@ 或 Application 资源(当然,隐式 DataTemplates 是另一回事)
  • 我真的建议在字典变得更复杂或者你加入一个拥有多个开发人员的团队时避免使用它们。因为您可以添加隐式样式,而另一个开发人员正在添加更多样式,而您 2 只是相互覆盖。我真的只会在一个简单的 UserControl 中使用它们,其中一切看起来都一样
【解决方案3】:

Rachel,我不认为“Styles”有什么特别之处。此外,不存在“跨越模板边界”的问题。原因是不同的,它涉及 WPF 应用程序中的不同“树”。 根据您的问题,我认为您正在描绘一个具有以下层次结构的世界:
- 应用程序 => 窗口 => 控件 => 控件内的元素

没有这样的层次结构。 WPF 应用程序中有不同的树,最著名的是逻辑树和可视树,但还有更多(路由事件树和资源查找树,语义略有不同)。

假设以下 XAML:

<Window x:Class="SO.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button x:Name="btn" Click="click">Click Me</Button>
    </Grid>
</Window>

对于此 XAML,逻辑树将如下所示:
- 窗口 => 网格 => 按钮 => 字符串

按钮内的文本块不是逻辑树的一部分(虽然它是 VisualTree 的一部分)。

通过 LogicalTree 查找资源,但有一点不同。到达顶部对象后,查找资源算法将查看Application资源字典,然后查看Theme资源字典,然后查看System 按此顺序排列的资源字典。

参见以下文章:

最后,为了证明我的观点,将以下资源添加到应用程序 XAML:

<Application x:Class="SO.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:clr="clr-namespace:System;assembly=mscorlib"
    StartupUri="MainWindow.xaml">
    <Application.Resources>
        <clr:String x:Key="MyResource">Hello Application Resource</clr:String>
    </Application.Resources>
</Application>

以及后面的代码:

private void click(object sender, RoutedEventArgs e)
{
    // Logical Children of btn
    Debug.WriteLine("Children of btn:");
    foreach( var x in LogicalTreeHelper.GetChildren(btn) ) {
        Debug.WriteLine("{0} : {1}", x, x.GetType());
    }

    // Walk the visual tree
    Debug.WriteLine("The Visual Tree:");
    WalkVisual(0, this);

    // Find the textblock within the button
    DependencyObject p = btn;
    while (p.GetType() != typeof(TextBlock))
        p = VisualTreeHelper.GetChild(p, 0);
     TextBlock tb = p as TextBlock;

    // Now climp the textblock through the logical tree
    while (p != null)
    {
        Debug.WriteLine("{0}", p.GetType());
        p = LogicalTreeHelper.GetParent(p);
    }

    // Find a resource for the textbox
    string s = tb.FindResource("MyResource") as string;
    Debug.WriteLine("MyResource Content: {0}", s);
}

private void WalkVisual(int indent, DependencyObject p)
{
    string fmt = string.Format("{{0,{0}}}{{1}}", indent * 4);
    Debug.WriteLine(fmt, "", p.GetType());
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(p); ++i)
    {
        WalkVisual(indent+1,VisualTreeHelper.GetChild(p, i));
    }
}

所以...一旦您理解了第一个问题(“为什么会这样”),其他问题就会分崩离析。应用程序资源和窗口资源的不同之处在于应用程序资源可以由应用程序中的任何 DependencyObject 调用,包括在其他程序集中定义的那些。当这是你想要达到的目标时,你会使用它:-)

你。

【讨论】:

  • WPF 中对目标类型不是从 Control 派生的 Styles 进行了特殊处理,例如 TextBlock。正如 Racheal 解释的那样,TextBlock 样式在放置在 Application.Resources 与 Window 中时的处理方式不同。
【解决方案4】:

区别在于样式的范围:

  • 当放置在 Application.Resources 中时,样式将应用于应用程序中的所有控件
  • 当放置在 Windows.Resources 中时,样式将应用于窗口中的所有控件

那里的区别非常微妙,但这意味着我们正在谈论的任何控件(包括另一个控件模板中的控件)都将从 application.Resources 获取样式。但只有窗口的直接子控件才能从 window.Resources 中获取样式。另一个控件模板中的控件将不具有在 Window.Resources 中定义的样式,因为它不直接在窗口中,而它将具有在 Application.Resources 中定义的样式,因为它在应用程序中。

至于你的第二点,我认为它与依赖属性优先级有关:

http://msdn.microsoft.com/en-us/library/ms743230.aspx

【讨论】:

  • 这不是真的。如果您在另一个Control 的ControlTemplate 中有一个Button(例如TextBox),则仍将应用Windows.Resources 中Button 的任何隐式样式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-24
  • 2011-05-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多