C# WPF自定义基本窗口类和样式

C# WPF自定义基本窗口类和样式,c#,wpf,mvvm,resourcedictionary,C#,Wpf,Mvvm,Resourcedictionary,我有一个可以打开许多窗口的应用程序,我希望所有窗口看起来都一样。我正在覆盖默认的Windows窗口chrome样式并创建自己的,因此任何打开的新窗口(不包括MessageBox)都应该具有相同的窗口样式。然而,无论我尝试什么,它都不起作用。我可以让它在一个窗口中工作,但是当我想让它成为一个全局样式时,它总是崩溃或者根本不能正常工作 这是我的密码: WindowBaseStyle.xaml <ResourceDictionary xmlns="http://schemas.microsoft

我有一个可以打开许多窗口的应用程序,我希望所有窗口看起来都一样。我正在覆盖默认的Windows窗口chrome样式并创建自己的,因此任何打开的新窗口(不包括MessageBox)都应该具有相同的窗口样式。然而,无论我尝试什么,它都不起作用。我可以让它在一个窗口中工作,但是当我想让它成为一个全局样式时,它总是崩溃或者根本不能正常工作

这是我的密码:

WindowBaseStyle.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MyProject.Styles"
                    xmlns:views="clr-namespace:Myproject.Views">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="GlobalStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>
    <Style TargetType="{x:Type views:WindowBase}" BasedOn="{StaticResource {x:Type Window}}">
        <Setter Property="AllowsTransparency" Value="False" />
        <Setter Property="BorderBrush" Value="Red" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="WindowState" Value="Normal" />
        <Setter Property="WindowStyle" Value="SingleBorderWindow" />
        <Setter Property="WindowChrome.WindowChrome">
            <Setter.Value>
                <WindowChrome CaptionHeight="30"
                              UseAeroCaptionButtons="False"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type views:WindowBase}">
                    <Border BorderBrush="Blue" BorderThickness="1" SnapsToDevicePixels="True">
                    <DockPanel Background="White" LastChildFill="True" >
                        <Grid Background="Blue" DockPanel.Dock="Top">
                            <StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center">
                                <Button Name="PART_SystemMenuButton" Command="{Binding MenuCommand}" Style="{DynamicResource SystemIconButton}">
                                    <Image Height="16" Width="16" Source="/Resources/icon.png" Stretch="Fill"/>
                                </Button>
                                <Viewbox Height="16" HorizontalAlignment="Stretch" Margin="14,2,0,0" >
                                    <TextBlock FontSize="12" Foreground="White" Text="{Binding Title,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" />
                                </Viewbox>
                            </StackPanel>
                                <StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
                                    <Button x:Name="PART_MinimizeButton" Command="{Binding MinimizeCommand}" Height="30" Margin="0,0,0,0" ToolTip="Minimize" Width="45">
                                        <Image Source="/Resources/minimize.png" Stretch="None" />
                                        <Button.Style>
                                            <Style TargetType="{x:Type Button}">
                                                <Setter Property="Background" Value="#0079CB" />
                                                <Setter Property="Template">
                                                    <Setter.Value>
                                                        <ControlTemplate TargetType="{x:Type Button}">
                                                            <Border Background="{TemplateBinding Background}">
                                                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                                            </Border>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                                <Style.Triggers>
                                                    <Trigger Property="IsMouseOver" Value="True">
                                                        <Setter Property="Background" Value="#64AEEC"/>
                                                    </Trigger>
                                                </Style.Triggers>
                                            </Style>
                                        </Button.Style>
                                    </Button>
                                    <Button x:Name="PART_MaximizeButton" Command="{Binding MaximizeCommand}" Height="30" Margin="0,0,0,0" Width="45">
                                        <Image>
                                            <Image.Style>
                                                <Style TargetType="{x:Type Image}">
                                                    <Style.Triggers>
                                                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Normal">
                                                            <Setter Property="Source" Value="/Resources/maximize.png" />
                                                            <Setter Property="Stretch" Value="None" />
                                                        </DataTrigger>
                                                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
                                                            <Setter Property="Source" Value="/Resources/unmaximize.png" />
                                                            <Setter Property="Stretch" Value="None" />
                                                        </DataTrigger>
                                                    </Style.Triggers>
                                                </Style>
                                            </Image.Style>
                                        </Image>
                                        <Button.Style>
                                            <Style TargetType="{x:Type Button}">
                                                <Setter Property="Background" Value="#0079CB" />
                                                <Setter Property="Template">
                                                    <Setter.Value>
                                                        <ControlTemplate TargetType="{x:Type Button}">
                                                            <Border Background="{TemplateBinding Background}">
                                                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                                            </Border>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                                <Style.Triggers>
                                                    <Trigger Property="IsMouseOver" Value="True">
                                                        <Setter Property="Background" Value="#64AEEC"/>
                                                    </Trigger>
                                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Normal">
                                                        <Setter Property="ToolTip" Value="Maximize" />
                                                    </DataTrigger>
                                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
                                                        <Setter Property="ToolTip" Value="Restore Down" />
                                                    </DataTrigger>
                                                </Style.Triggers>
                                            </Style>
                                        </Button.Style>
                                    </Button>
                                    <Button x:Name="PART_CloseButton" Command="{Binding CloseCommand}" Height="30" Margin="0,0,0,0" ToolTip="Close" Width="45" >
                                        <Image Source="/Resources/close.png" Stretch="None" />
                                        <Button.Style>
                                            <Style TargetType="{x:Type Button}">
                                                <Setter Property="Background" Value="#0079CB" />
                                                <Setter Property="Template">
                                                    <Setter.Value>
                                                        <ControlTemplate TargetType="{x:Type Button}">
                                                            <Border Background="{TemplateBinding Background}">
                                                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                                            </Border>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                                <Style.Triggers>
                                                    <Trigger Property="IsMouseOver" Value="True">
                                                        <Setter Property="Background" Value="Red"/>
                                                    </Trigger>
                                                </Style.Triggers>
                                            </Style>
                                        </Button.Style>
                                    </Button>
                                </StackPanel>
                            </StackPanel>
                        </Grid>
                        <!-- this ContentPresenter automatically binds to the content of the window -->
                        <ContentPresenter />
                    </DockPanel>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<local:WindowBase x:Class="MyProject.Views.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MyProject.Views"
        Height="750"
        Width="1125">
    <Grid>
    </Grid>
</local:WindowBase>
Window1.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MyProject.Styles"
                    xmlns:views="clr-namespace:Myproject.Views">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="GlobalStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>
    <Style TargetType="{x:Type views:WindowBase}" BasedOn="{StaticResource {x:Type Window}}">
        <Setter Property="AllowsTransparency" Value="False" />
        <Setter Property="BorderBrush" Value="Red" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="WindowState" Value="Normal" />
        <Setter Property="WindowStyle" Value="SingleBorderWindow" />
        <Setter Property="WindowChrome.WindowChrome">
            <Setter.Value>
                <WindowChrome CaptionHeight="30"
                              UseAeroCaptionButtons="False"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type views:WindowBase}">
                    <Border BorderBrush="Blue" BorderThickness="1" SnapsToDevicePixels="True">
                    <DockPanel Background="White" LastChildFill="True" >
                        <Grid Background="Blue" DockPanel.Dock="Top">
                            <StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center">
                                <Button Name="PART_SystemMenuButton" Command="{Binding MenuCommand}" Style="{DynamicResource SystemIconButton}">
                                    <Image Height="16" Width="16" Source="/Resources/icon.png" Stretch="Fill"/>
                                </Button>
                                <Viewbox Height="16" HorizontalAlignment="Stretch" Margin="14,2,0,0" >
                                    <TextBlock FontSize="12" Foreground="White" Text="{Binding Title,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" />
                                </Viewbox>
                            </StackPanel>
                                <StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
                                    <Button x:Name="PART_MinimizeButton" Command="{Binding MinimizeCommand}" Height="30" Margin="0,0,0,0" ToolTip="Minimize" Width="45">
                                        <Image Source="/Resources/minimize.png" Stretch="None" />
                                        <Button.Style>
                                            <Style TargetType="{x:Type Button}">
                                                <Setter Property="Background" Value="#0079CB" />
                                                <Setter Property="Template">
                                                    <Setter.Value>
                                                        <ControlTemplate TargetType="{x:Type Button}">
                                                            <Border Background="{TemplateBinding Background}">
                                                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                                            </Border>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                                <Style.Triggers>
                                                    <Trigger Property="IsMouseOver" Value="True">
                                                        <Setter Property="Background" Value="#64AEEC"/>
                                                    </Trigger>
                                                </Style.Triggers>
                                            </Style>
                                        </Button.Style>
                                    </Button>
                                    <Button x:Name="PART_MaximizeButton" Command="{Binding MaximizeCommand}" Height="30" Margin="0,0,0,0" Width="45">
                                        <Image>
                                            <Image.Style>
                                                <Style TargetType="{x:Type Image}">
                                                    <Style.Triggers>
                                                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Normal">
                                                            <Setter Property="Source" Value="/Resources/maximize.png" />
                                                            <Setter Property="Stretch" Value="None" />
                                                        </DataTrigger>
                                                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
                                                            <Setter Property="Source" Value="/Resources/unmaximize.png" />
                                                            <Setter Property="Stretch" Value="None" />
                                                        </DataTrigger>
                                                    </Style.Triggers>
                                                </Style>
                                            </Image.Style>
                                        </Image>
                                        <Button.Style>
                                            <Style TargetType="{x:Type Button}">
                                                <Setter Property="Background" Value="#0079CB" />
                                                <Setter Property="Template">
                                                    <Setter.Value>
                                                        <ControlTemplate TargetType="{x:Type Button}">
                                                            <Border Background="{TemplateBinding Background}">
                                                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                                            </Border>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                                <Style.Triggers>
                                                    <Trigger Property="IsMouseOver" Value="True">
                                                        <Setter Property="Background" Value="#64AEEC"/>
                                                    </Trigger>
                                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Normal">
                                                        <Setter Property="ToolTip" Value="Maximize" />
                                                    </DataTrigger>
                                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
                                                        <Setter Property="ToolTip" Value="Restore Down" />
                                                    </DataTrigger>
                                                </Style.Triggers>
                                            </Style>
                                        </Button.Style>
                                    </Button>
                                    <Button x:Name="PART_CloseButton" Command="{Binding CloseCommand}" Height="30" Margin="0,0,0,0" ToolTip="Close" Width="45" >
                                        <Image Source="/Resources/close.png" Stretch="None" />
                                        <Button.Style>
                                            <Style TargetType="{x:Type Button}">
                                                <Setter Property="Background" Value="#0079CB" />
                                                <Setter Property="Template">
                                                    <Setter.Value>
                                                        <ControlTemplate TargetType="{x:Type Button}">
                                                            <Border Background="{TemplateBinding Background}">
                                                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                                            </Border>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                                <Style.Triggers>
                                                    <Trigger Property="IsMouseOver" Value="True">
                                                        <Setter Property="Background" Value="Red"/>
                                                    </Trigger>
                                                </Style.Triggers>
                                            </Style>
                                        </Button.Style>
                                    </Button>
                                </StackPanel>
                            </StackPanel>
                        </Grid>
                        <!-- this ContentPresenter automatically binds to the content of the window -->
                        <ContentPresenter />
                    </DockPanel>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<local:WindowBase x:Class="MyProject.Views.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MyProject.Views"
        Height="750"
        Width="1125">
    <Grid>
    </Grid>
</local:WindowBase>

Window1.xaml.cs

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;

namespace MyProject.Views
{
    [TemplatePart(Name = "PART_MinimizeButton", Type = typeof(Button))]
    [TemplatePart(Name = "PART_MaximizeButton", Type = typeof(Button))]
    [TemplatePart(Name = "PART_CloseButton", Type = typeof(Button))]
    [TemplatePart(Name = "PART_SystemMenuButton", Type = typeof(Button))]
    public class WindowBase: Window
    {
        static WindowBase()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomWindow), new FrameworkPropertyMetadata(typeof(CustomWindow)));
        }

        public WindowBase()
        {
            Loaded += (sender, evnt) =>
            {
                var MinimizeButton = (Button)Template.FindName("PART_MinimizeButton", this);
                var MaximizeButton = (Button)Template.FindName("PART_MaximizeButton", this);
                var CloseButton = (Button)Template.FindName("PART_CloseButton", this);
                var SystemMenuButton = (Button)Template.FindName("PART_SystemMenuButton", this);

                MinimizeButton.Click += (s, e) => WindowState = WindowState.Minimized;
                MaximizeButton.Click += (s, e) => WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
                CloseButton.Click += (s, e) => Close();
                SystemMenuButton.Click += (s, e) => SystemCommands.ShowSystemMenu(this, GetMousePosition());
            };
        }
    }
}
using System.Windows;

namespace MyProject.Views
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1: WindowBase
    {
        public Window1()
        {
            InitializeComponent();
        }
    }
}    
使用System.Windows;
名称空间MyProject.Views
{
/// 
///Window1.xaml的交互逻辑
/// 
公共部分类Window1:WindowBase
{
公共窗口1()
{
初始化组件();
}
}
}    
我总体上遵循MVVM模式,从我在网上看到的所有文章和视频来看,大多数情况下,他们都遵循这一基本方法,他们说这一切都有效,但我似乎无法让它发挥作用

另一个注意事项是,每当我将自定义窗口控件添加到Window1.xaml文件时,它都会破坏设计器并说它是“无效标记”

另请注意,我将“WindowBaseStyle”资源字典作为合并资源字典添加到App.xaml文件中


非常感谢您的帮助!!谢谢

好的,正如我们在评论中所讨论的,似乎您所描述的问题的最快解决方案是使用
静态资源
从资源字典中获取窗口的样式(或为窗口创建隐式样式)我质疑
CustomWindow
的角色,因为我认为这可能会导致默认样式覆盖出现问题。(请记住:如果您选择无外观控件路径并尝试使用
DefaultStyleKeyProperty
覆盖,则必须对该控件的每个子类执行此操作。)

不过,我认为这样做可以让您在viewmodels驱动的多个窗口中获得可重用的管道

弹出主机 从自定义窗口派生的类。此代码提供以下行为:

  • 允许viewmodel将自身标记为已达到其目的,从而导致窗口关闭
  • 允许通过可能影响窗口在屏幕上显示方式的单个视图指定附加属性,例如窗口标题
  • 可以扩展以通知显示的项目用户已尝试关闭窗口,从而允许执行拦截/取消或清理操作
  • 代码:

    可等待的ViewModelBase 一个简单的抽象视图模型,具有
    TaskCompletionSource
    。这允许弹出窗口和viewmodel协调关闭

    public abstract class AwaitableViewModelBase : ViewModelBase
    {
        protected TaskCompletionSource<bool> TaskCompletionSource { get; set; }
    
        public Task<bool> Task => TaskCompletionSource?.Task;
    
        public void RegisterTaskCompletionSource(TaskCompletionSource<bool> tcs)
        {
            var current = TaskCompletionSource;
            if (current != null && current.Task.Status == TaskStatus.Running)
                throw new InvalidOperationException();
    
            TaskCompletionSource = tcs;
        }
    
        public virtual void Cancel() => SetResult(false);
    
        protected void SetResult(bool result) => TaskCompletionSource?.TrySetResult(result);
    }
    

    应用程序崩溃时会出现什么错误,或者什么不起作用?此外,当你说你要打开许多窗口,但你想让它们看起来都一样时,除了标题之外,你是否预期会在chrome窗口中更改任何内容?我问这个问题是因为我使用的应用程序只有一个窗口类,可以重用,并通过服务将内容注入其中。就我个人而言,我会走这条路,而不是更新WindowBase的子类。@Guttsy错误有所不同,但主要是名称空间问题。例如,我所说的许多窗口在主窗口的顶部有一个菜单,当用户单击菜单选项时,将打开一个对话框窗口,其中包含该特定窗口的内容。目前,我正在通过“WindowsService”类注入viewmodel实例,但我不知道一个视图如何可以有两个viewmodel,因为主窗口有自己的视图模型,加上基础窗口的视图模型。@Guttsy,我在网上看到的另一种很有希望的方法(我开始实现)在解决方案中创建一个类库项目,并在其中创建“基”窗口类,然后将其导入WPF项目,并将其用作导入的控件。如果你想在整个应用程序中注入一个单一的窗口样式/基类,你会怎么做?你在使用MVVM吗?我们有一个自主开发的MVVM导航框架。棱柱体等都是矫枉过正。当您想要在窗口中显示某个内容时,PopupService将采用模态的viewmodel并将其设置为新窗口的内容。我们首先使用隐式数据模板创建viewmodel,但可以使用模板的键覆盖它。然而,我们并没有使用WPF主题系统,所以我对外部资源样式覆盖很生疏。无论位置如何,直接引用样式似乎都有效。您的
    自定义窗口是什么?如果我删除它并手动设置样式,它就可以正常工作。。。我想。@Guttsy,谢谢你的回复!直接在窗口控件中引用样式是可行的,但我有自定义窗口按钮(关闭、最大化、最小化等)和用于调整大小和停靠的逻辑,并且此代码隐藏需要转到样式以外的其他地方。如果不使用WindowChrome属性,我就无法按照自己的意愿设置窗口样式。事件需要绑定到我的自定义按钮,因为每个窗口实例都有一个viewmodel,所以特定屏幕本身也需要一个视图模型。我不知道如何实现这一点。这太棒了!一旦我能尝试这个,我会让你知道。谢谢
    
    public class WindowService
    {
        public async Task<bool> ShowModalAsync(AwaitableViewModelBase viewModel, string dataTemplateKey = null)
        {
            var tcs = new TaskCompletionSource<bool>();
            viewModel.RegisterTaskCompletionSource(tcs);
    
            Application.Current.Dispatcher.Invoke(() =>
            {
                var currentWindow = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive) ?? Application.Current.MainWindow;
                var window = new PopupHost(currentWindow, viewModel, dataTemplateKey);
                window.ShowDialog();
            });
    
            return await viewModel.Task;
        }
    }