如何在app.xaml中设置默认的WPF窗口样式?

67

我正在尝试在我的WPF Windows应用程序的app.xaml中为每个窗口设置默认样式。到目前为止,我在app.xaml中有这个:

<Application.Resources>
    <ResourceDictionary>
        <Style x:Key="WindowStyle" TargetType="{x:Type Window}">
            <Setter Property="Background" Value="Blue" />
        </Style>
    </ResourceDictionary>
</Application.Resources>

当运行应用程序时(但在 VS 设计器中不行),我可以通过具体告诉窗口使用此样式来使窗口出现:

Style="{DynamicResource WindowStyle}

这个方法可行,但并不理想。那么我该怎么做才能:

  1. 让所有窗口自动使用样式(这样我就不需要在每个窗口上指定它了)?
  2. 让VS设计器显示样式?

谢谢!

8个回答

49

补充Ray所说的内容:

对于样式,您需要提供关键字/ID或指定目标类型。

如果FrameworkElement没有明确指定的样式,它将始终寻找样式资源,使用自身类型作为关键字。
- 《Programming WPF》(Sells, Griffith)

如果提供目标类型,则该类型的所有实例都将应用该样式。然而,派生类型似乎不会...。 <Style TargetType="{x:Type Window}">无法适用于所有自定义的派生/窗口。 <Style TargetType="{x:Type local:MyWindow}">仅适用于MyWindow。 因此,选项是:

  • 使用关键字样式,并将其指定为要应用样式的每个窗口的Style属性。设计器将显示已应用样式的窗口。

.

    <Application.Resources>
        <Style x:Key="MyWindowStyle">
            <Setter Property="Control.Background" Value="PaleGreen"/>
            <Setter Property="Window.Title" Value="Styled Window"/>
        </Style>
    </Application.Resources> ...
    <Window x:Class="MyNS.MyWindow" Style="{StaticResource MyWindowStyleKey}">  ...
  • 或者你可以从一个自定义的BaseWindow类派生(该类有其特点),在构造函数/初始化/加载阶段设置Style属性一次。所有的派生都会自动应用该样式。 但是设计者不会注意到你的样式,你需要运行你的应用程序才能看到样式被应用... 我猜设计者只是运行InitializeComponent(自动生成的代码)所以XAML被应用但不包括自定义代码后台。

所以我认为明确指定样式是最少工作量的。你可以随时集中更改样式的方面。


2
最后一行 "<Window x:Class ..." 应该放在窗口的 XAML 定义头中,而不是 App.resources 中。 - Anthony
您可以使用BasedOn属性来“级联”样式。 BasedOn = "{StaticResource {x:Type Window}}" 然而,我无法让Window接受未键入的样式。我猜这是因为它们不识别派生类型,正如您所说的那样。 - Benny Jobigan
你的代码充满了错误,甚至无法编译,实际上是毫无用处的,例如使用 MyWindowStyleKeyMyWindowStyle。这是近九年来未被接受的众多原因之一。 - Matt

24

虽然这已经是数年前的问题,但既然问题还在这里...

  1. 在你的项目中创建一个资源字典(右键单击项目...)

    我将在项目下创建一个名为“Assets”的新文件夹,并在其中放置“resourceDict.XAML”。

  2. 将代码添加到resourceDict.XAML中:

    <Style x:Key="WindowStyle" Target Type="Window" >
         <Setter Property="Background" Value="Blue" />
    </Style>
    
    在你的项目XAML文件中,在Window标签下添加以下内容:
  3. <Window.Resources>
        <ResourceDictionary>
            <!-- Believe it or not the next line fixes a bug MS acknowledges -->
            <Style TargetType="{x:Type Rectangle}" />
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Assets/resourceDict.XAML" />
            </ResourceDictionary.MergedDictionaries>
        <ResourceDictionary>
    </Window.Resources>
    

    参考以下网站:Trouble referencing a Resource Dictionary that contains a Merged Dictionary 存在一个 bug:如果所有默认样式都嵌套在深度为三(或更深)的合并字典中,则顶层字典不会被标记,因此搜索会跳过它。解决方法是在根字典中将默认样式设置为任何东西。 看起来这个方法修复了问题。真是想不到...

  4. 最后,在窗口下方,也许是在标题之后,但在最后的 Window '>' 之前:

  5. Style="{DynamicResource windowStyle}"
    
  6. 如果您希望应用这个样式到每个项目中,您需要将第三步和第四步的代码添加到每个项目中。

  7. 如果您想要使用渐变背景而不是纯色,请将以下代码添加到resourceDict.XAML中:

  8. <LinearGradientBrush x:Key="windowGradientBackground" StartPoint="0,0"
            EndPoint="0,1" >
    <GradientStop Color= "AliceBlue" Offset="0" />
    <GradientStop Color= "Blue" Offset=".75" />
    </LinearGradientBrush>
    
  9. 并且修改你的样式设置器来更改背景颜色为:

  10. <Setter Property="Background" Value="{DynamicResource
            windowGradientBackground}" />
    

需要在每个项目的XAML文件中重复执行步骤3和4,如上所述,但是这样可以使解决方案中的Windows统一!而且同样的过程也适用于任何您想要具有统一外观的控件,例如按钮等。

对于任何后来者,希望这可以帮助到您,因为我相信原始发布者早在多年前就已经搞定了这些。

保罗


这里的提示是关于不要在MergedDictionaries中嵌套MergedDictionaries,这对我来说是解决方案。只需直接从App.xaml声明所有主题文件即可。所以谢谢! - jv_
1
请编辑。在资源文件中,属性名称为TargetType,而不是Target Type - aquaprogit
多年过去了,仍在帮助着。 - Dan Rayson
我不确定Paul所说的“project.xaml”是什么意思。是app.xaml吗?还是window.xaml?如果是后者,那这有什么帮助呢?我们仍然需要手动将样式应用到每个创建的窗口上。目标是为窗口创建一个通用的全局样式,而无需显式地将其应用于每个实例。可能是我没有理解,如果是这样,我希望有人能给我指点一下。我很想让它工作。 - Mike

8
您可以将此代码添加到您的App.xaml.cs文件中:
        FrameworkElement.StyleProperty.OverrideMetadata(typeof(Window), new FrameworkPropertyMetadata
        {
            DefaultValue = Application.Current.FindResource(typeof(Window))
        });

这样,应用于 Window 类型的样式也将应用于所有派生自 Window 的类型。


1
这是我“BaseWindow”类的唯一有效解决方案。谢谢! - pinki

8
设计师不起作用是因为您正在指定DynamicResource。 请将其更改为StaticResource,一切都会好起来。
要应用于所有窗口,您应该从样式中删除x:Key。设置TargetType隐含地将x:Key设置为TargetType中的任何内容。但是,在我的测试中,这似乎无效,所以我正在研究它。
如果我将TargetType设置为x:Type TextBlock,则设计师完美地工作,只是Window似乎表现出不同的行为。

1
是的,我也遇到了同样的问题。Gishu 上面说似乎 Window 的隐式样式不适用于你的窗口,因为它实际上是一个派生类。 - Benny Jobigan

4

我已经调查了几天了,通过我的自定义窗口类的构造函数使其工作:

public class KWindow : Window
{
        public KWindow()
        {
            this.SetResourceReference(StyleProperty, typeof(KWindow));
        }

        static KWindow()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(KWindow), new FrameworkPropertyMetadata(typeof(KWindow)));

        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // gets called finally
        }
}

希望能对某些人有所帮助。


这个方案非常出色。我终于能在样式中指定TargetType,并且在运行时让所有派生窗口应用它。因此,这是一个既适用于XAML设计器又适用于运行时的解决方案。 - Oyvind

1
对于那些在解决问题时遇到困难的人:如何使自定义样式自动应用于我所有的 Window 派生类型?以下是我想出的解决方案。
注意:出于特定项目的原因(我的产品的使用者使用我的通用可重用样式库并创建自己的布局/窗口等),我真的不想从 Window 类型派生或在每个窗口中插入 XAML 来强制更新样式等。所以我真的很有动力找到一个可行的解决方案,即使它带来了任何副作用,我也愿意接受。
需要遍历所有实例化的窗口,并简单地强制它们使用您为 Window 类型定义的新自定义样式。这对于已经上线的窗口非常有效,但是当实例化窗口或子窗口时,它不会知道要使用已声明为其基本类型(即普通的 Window 类型)的新/自定义类型。所以我能想到的最好的方法是在 MainWindow 上使用 LostKeyBoardFocus,当它失去焦点时转移到 ChildWindow(即当创建子窗口时),然后调用此 FixupWindowDerivedTypes()。
如果有更好的解决方案可以“检测”任何类型的窗口实例化并因此调用FixupWindowDerivedTypes(),那就太好了。在这个领域中处理WM_WINDOWPOSCHANGING可能会有一些有用的东西。
所以,这个解决方案本身并不优雅,但可以完成工作,而不需要我触碰与我的窗口相关的任何代码或XAML。
   public static void FixupWindowDerivedTypes()
    {
        foreach (Window window in Application.Current.Windows)
        {
           //May look strange but kindly inform each of your window derived types to actually use the default style for the window type

                    window.SetResourceReference(FrameworkElement.StyleProperty, DefaultStyleKeyRetriever.GetDefaultStyleKey(window));
                }
            }
        }
    }


//Great little post here from Jafa to retrieve a protected property like DefaultStyleKey without using reflection.
http://themechanicalbride.blogspot.com/2008/11/protected-dependency-properties-are-not.html

//Helper class to retrieve a protected property so we can set it
internal class DefaultStyleKeyRetriever : Control
{
    /// <summary>
    /// This method retrieves the default style key of a control.
    /// </summary>
    /// <param name="control">The control to retrieve the default style key 
    /// from.</param>
    /// <returns>The default style key of the control.</returns>
    public static object GetDefaultStyleKey(Control control)
    {
        return control.GetValue(Control.DefaultStyleKeyProperty);
    }
}

0
考虑到 Gishu 的回答,我想出了另一个解决方法。但可能有点奇怪。 如果您使用 MVVM 模式,则可以删除窗口的代码后端和 XAML 文件中的 x:Class 标记。这样,您将获得窗口或自定义窗口的实例,而不是从“Window”类派生并标记为部分的“MainWindow”类的某个实例。 我正在制作类似于 VS 的窗口,因此必须继承窗口类并扩展其功能。在这种情况下,将可以将新窗口类设置为部分,从而允许我们进行无需继承的代码后台。

-2
  1. 你将把所有的样式保存在一个XAML文件中(例如design.xaml)

  2. 然后在所有页面中以这种方式调用该(design.xaml)XAML文件

例如:

<ResourceDictionary.MergedDictionaries>
                <ResourceDictionary  Source="Design.xaml"/>                
</ResourceDictionary.MergedDictionaries>

1
OP的目标是不必每次都设置样式。 - Oyvind

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接