Как задать стиль окну если контрол определен в отдельной библиотеке?
Есть WPF User Control Library (.NET Framework) WPFLib, там определен Window
using System.Windows;
namespace WPFLib {
public class Window : System.Windows.Window {
static Window() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(Window), new FrameworkPropertyMetadata(typeof(Window)));
}
}
}
AssemblyInfo.cs
[assembly:ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
Themes/Generic.xaml
<Style x:Key="{x:Type local:Window}"
TargetType="{x:Type Window}">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="Background"
Value="Red"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<AdornerDecorator>
<ContentPresenter/>
</AdornerDecorator>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Window.ResizeMode"
Value="CanResizeWithGrip">
<Setter Property="Template"
Value="{StaticResource WindowTemplateKey}"/>
</Trigger>
</Style.Triggers>
</Style>
и WPF App где
<wpflib:Window x:Class="WPFHost.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:wpflib="clr-namespace:WPFLib;assembly=WPFLib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Button VerticalAlignment="Center" HorizontalAlignment="Center" Content="Button"/>
</wpflib:Window>
Themes/Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFHost"
xmlns:wpflib="clr-namespace:WPFLib;assembly=WPFLib">
<Style x:Key="{x:Type wpflib:Window}"
TargetType="{x:Type wpflib:Window}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="Background"
Value="Green"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type wpflib:Window}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<AdornerDecorator>
<ContentPresenter/>
</AdornerDecorator>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Window.ResizeMode"
Value="CanResizeWithGrip">
<Setter Property="Template"
Value="{StaticResource WindowTemplateKey}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
AssemblyInfo.cs
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
При запуске окно красное т.е. WPF взял стиль у Generic WPFLib. Мне нужно чтобы в WPFLib был стиль по умолчанию но и в WPFHost я имел возможность переопределять его. Как подобное сделать?
Ответы (2 шт):
Как мы можем сделать для окон Window, пример
WindowStyle.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
x:Class="Telebine.Styles.WindowStyle.WindowStyle"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="TopPanel" TargetType="{x:Type Button}">
<Setter Property="Background" Value="#F1F1F1"/>
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#EAEAEA"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type Window}">
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="30"
CornerRadius="8"
GlassFrameThickness="-2"
NonClientFrameEdges="None"
ResizeBorderThickness="5"
UseAeroCaptionButtons="True" />
</Setter.Value>
</Setter>
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Background" Value="Gray" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border x:Name="Title" BorderBrush="Transparent" BorderThickness="0" Background="{TemplateBinding Background}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<AdornerDecorator Grid.Row="1">
<ContentPresenter/>
</AdornerDecorator>
<Grid Grid.Row="0" VerticalAlignment="Top" Height="30" Background="#F1F1F1">
<StackPanel x:Name="Buttons" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Name="btnMinimize" Width="45" Style="{StaticResource TopPanel}" Click="MinimizeClick">
<Button.Content>
<Viewbox Width="12" Height="2">
<Path Stroke="Black" StrokeThickness="0.8" StrokeEndLineCap="Round" StrokeStartLineCap="Round" Data="M11 1L1 0.999935"></Path>
</Viewbox>
</Button.Content>
</Button>
<Button Name="btnRestore" Width="45" Click="MaximizeRestoreClick">
<Button.Style>
<Style BasedOn="{StaticResource TopPanel}" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<Grid>
<Border x:Name="Normal" BorderBrush="#000000" BorderThickness="1" Width="11" Height="11"></Border>
<Viewbox x:Name="Maximized" Visibility="Collapsed" Width="12" Height="12">
<Grid>
<Path Stroke="#000000" StrokeThickness="1"
Data="M8.77778 8.77778H11V1H3.22222V3.22222M8.77778 8.77778V3.22222H3.22222M8.77778 8.77778V11H1V3.22222H3.22222"></Path>
</Grid>
</Viewbox>
</Grid>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding WindowState, RelativeSource={RelativeSource AncestorType=Window}}" Value="Maximized">
<Setter Property="Visibility" Value="Collapsed" TargetName="Normal"></Setter>
<Setter Property="Visibility" Value="Visible" TargetName="Maximized"></Setter>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#EAEAEA"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Name="btnClose" Background="#F1F1F1" Width="45" Click="CloseClick" WindowChrome.IsHitTestVisibleInChrome="True">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#F1F1F1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="root" Background="{TemplateBinding Background}">
<Viewbox Width="14" Height="12">
<Grid>
<Path x:Name="s1" Stroke="Black" StrokeThickness="0.8" StrokeStartLineCap="Round" StrokeEndLineCap="Round"
Data="M1.3999 1.60447L10.3999 10.1951"></Path>
<Path x:Name="s2" Stroke="Black" StrokeThickness="0.8" StrokeStartLineCap="Round" StrokeEndLineCap="Round"
Data="M10.2003 1.40485L1.60034 10.3955" Cursor=""></Path>
</Grid>
</Viewbox>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#F14C4C" TargetName="root"/>
<Setter Property="Stroke" Value="#FFFFFF" TargetName="s1"></Setter>
<Setter Property="Stroke" Value="#FFFFFF" TargetName="s2"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="WindowState" Value="Maximized">
<Setter TargetName="Title" Property="BorderThickness" Value="{Binding Source={x:Static SystemParameters.WindowResizeBorderThickness}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
WindowStyle.cs code behavior
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Telebine.Styles.WindowStyle
{
public partial class WindowStyle : ResourceDictionary
{
private void CloseClick(object sender, RoutedEventArgs e)
{
var window = (Window)((FrameworkElement)sender).TemplatedParent;
window.Close();
}
private void MaximizeRestoreClick(object sender, RoutedEventArgs e)
{
var window = (Window)((FrameworkElement)sender).TemplatedParent;
if (window.WindowState == WindowState.Normal)
{
window.WindowState = WindowState.Maximized;
}
else
{
window.WindowState = WindowState.Normal;
}
}
private void MinimizeClick(object sender, RoutedEventArgs e)
{
var window = (Window)((FrameworkElement)sender).TemplatedParent;
window.WindowState = WindowState.Minimized;
}
}
}
Использование App.xaml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles/WindowStyle/WindowStyle.xaml" />
</Style>
</ResourceDictionary>
</Application.Resources>
Постолько поскольку в WindowStyle.xaml не задан ключ для стиля, стиль для окон Window будет применяться ко всем окнам, во всем приложении. И изменить стиль уже можете как вам угодно. В часть code behavior не имеет слысла лезть, так как во view есть все что вам нужно
Для начала создадим библиотеку под названием Window Style

Я пишу на net.core 6
Создадим словарь ресурсов WindowStyle.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
x:Class="WinStyles.WindowStyle"
xmlns:local="clr-namespace:WinStyles"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--Стиль для кнопок у заголовка-->
<Style x:Key="TopPanelBtn" TargetType="{x:Type Button}">
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="root" Background="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=(local:WindowStyleDP.BackgroundCaption)}">
<ContentPresenter/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#EAEAEA" TargetName="root"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--Контент для кнопки развернуть окно-->
<DataTemplate x:Key="ContentBtnRestore">
<Grid>
<Grid.Resources>
<Viewbox x:Key="MaximizedCop" Visibility="Collapsed" Width="12" Height="12">
<Path Stroke="#000000" StrokeThickness="1"
Data="M8.77778 8.77778H11V1H3.22222V3.22222M8.77778 8.77778V3.22222H3.22222M8.77778 8.77778V11H1V3.22222H3.22222"></Path>
</Viewbox>
</Grid.Resources>
<Border x:Name="Normal" BorderBrush="#000000" BorderThickness="1" Width="11" Height="11"></Border>
<Viewbox x:Name="Maximized" Visibility="Collapsed" Width="12" Height="12">
<Path Stroke="#000000" StrokeThickness="1"
Data="M8.77778 8.77778H11V1H3.22222V3.22222M8.77778 8.77778V3.22222H3.22222M8.77778 8.77778V11H1V3.22222H3.22222"></Path>
</Viewbox>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding WindowState, RelativeSource={RelativeSource AncestorType=Window}}" Value="Maximized">
<Setter Property="Visibility" Value="Collapsed" TargetName="Normal"></Setter>
<Setter Property="Visibility" Value="Visible" TargetName="Maximized"></Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<!--Контент для кнопки свернуть окно-->
<DataTemplate x:Key="ContentBtnMinimize">
<Viewbox Width="12" Height="2">
<Path Stroke="Black" StrokeThickness="0.8" StrokeEndLineCap="Round" StrokeStartLineCap="Round">
<Path.Data>M11 1L1 0.999935</Path.Data>
</Path>
</Viewbox>
</DataTemplate>
<!--Стиль для кнопки закрыть окно-->
<Style x:Key="StyleBtnClose" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="root" Background="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=(local:WindowStyleDP.BackgroundCaption)}">
<Viewbox Width="14" Height="12">
<Grid>
<Path x:Name="s1" Stroke="Black" StrokeThickness="0.8" StrokeStartLineCap="Round" StrokeEndLineCap="Round"
Data="M1.3999 1.60447L10.3999 10.1951"></Path>
<Path x:Name="s2" Stroke="Black" StrokeThickness="0.8" StrokeStartLineCap="Round" StrokeEndLineCap="Round"
Data="M10.2003 1.40485L1.60034 10.3955" Cursor=""></Path>
</Grid>
</Viewbox>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#F14C4C" TargetName="root"/>
<Setter Property="Stroke" Value="#FFFFFF" TargetName="s1"></Setter>
<Setter Property="Stroke" Value="#FFFFFF" TargetName="s2"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--Стиль окна Window-->
<Style x:Key="CustomWindowStyle" TargetType="{x:Type Window}">
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="35"
GlassFrameThickness="-2"
NonClientFrameEdges="None"
ResizeBorderThickness="5"
UseAeroCaptionButtons="True" />
</Setter.Value>
</Setter>
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Background" Value="#293955" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border x:Name="Title" Background="{TemplateBinding Background}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<AdornerDecorator Grid.Row="1">
<ContentPresenter/>
</AdornerDecorator>
<Grid Grid.Row="0" VerticalAlignment="Top" Height="35" Background="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=(local:WindowStyleDP.BackgroundCaption)}">
<StackPanel x:Name="Buttons" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Name="btnMinimize" Style="{StaticResource TopPanelBtn}" ContentTemplate="{StaticResource ContentBtnMinimize}" Width="45" Command="{x:Static local:WindowStyle.MinimizeCommand}"></Button>
<Button Name="btnRestore" Style="{DynamicResource TopPanelBtn}" ContentTemplate="{StaticResource ContentBtnRestore}" Width="45" Command="{x:Static local:WindowStyle.MaximizeRestoreCommand}"></Button>
<Button Name="btnClose" Style="{StaticResource StyleBtnClose}" Width="45" Command="{x:Static local:WindowStyle.CloseCommand}" WindowChrome.IsHitTestVisibleInChrome="True"></Button>
</StackPanel>
</Grid>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!--Тригер срабатывает при разворачивание окна на весь экран и для панели с к онтентом задаётся BorderThickness равный толщине системной рамке-->
<Trigger Property="WindowState" Value="Maximized">
<Setter TargetName="Title" Property="BorderThickness" Value="{Binding Source={x:Static SystemParameters.WindowResizeBorderThickness}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Стилю Window дадим название CustomWindowStyle чтобы применить стиль в нашем приложение. Если не указать стиль явно для окна он не применится. К примеру если мы удалим ключ x:Key="CustomWindowStyle" это не будет работать для типа Window, но в случае с Custom Control, не стандартным окном стиль подхватится из темы автоматически.
В этом ответе примерно описано почему стили не будут работать для всех окон
Так как мы переопределили стиль окно сейчас у нас пустое и удален заголовок, края для растягивания окна, кнопки заголовка.
Чтобы вернуть функции заголовка, перемещение за него, развертывание экрана по двойному клику и возможность растягивать окно добавим WindowChrome и зададим настройки как у меня в коде.
ResizeBorderThickness отвечает за толщину краев у окна за которое можно растянуть окно.
CaptionHeight устанавливает высоту заголовка за которое мы можем перемещать окно.
Aero эффекты я оставил включенными, GlassFrameThickness устанавливает системную тень у окна.
Далее создадим класс WindowStyle.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace WinStyles
{
public partial class WindowStyle : ResourceDictionary
{
static WindowStyle()
{
CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(MinimizeCommand, MinimizeExecuted));
CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(MaximizeRestoreCommand, MaximizeRestoreExecuted));
CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(CloseCommand, CloseExecuted));
}
public static readonly RoutedCommand MinimizeCommand = new("Minimize", typeof(WindowStyle));
public static readonly RoutedCommand MaximizeRestoreCommand = new("MaximizeRestore", typeof(WindowStyle));
public static readonly RoutedCommand CloseCommand = new("Close", typeof(WindowStyle));
private static void MinimizeExecuted(object sender, ExecutedRoutedEventArgs e)
{
var window = (Window)sender;
window.WindowState = WindowState.Minimized;
}
private static void MaximizeRestoreExecuted(object sender, ExecutedRoutedEventArgs e)
{
var window = (Window)sender;
if (window.WindowState == WindowState.Normal)
{
window.WindowState = WindowState.Maximized;
}
else
{
window.WindowState = WindowState.Normal;
}
}
private static void CloseExecuted(object sender, ExecutedRoutedEventArgs e)
{
var window = (Window)sender;
window.Close();
}
}
}
В нём зарегистрируем команды для работы кнопок нашего стиля. Свернуть, развернуть окно, закрыть окно.
Можно было использовать события Click но тогда если переопределить стиль эти события останутся привязаны и придется их код весь прописать вручную. Лучше использовать команды)
Далее создадим последний класс для библиотеки, WindowStyleDP
Его создавать не обязательно, он в качестве примера что в стиль можно пробросить attached свойства и ими легко настраивать внешний вид для стиля не заглядывая в шаблон после реализации.
Код
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Media; using System.Windows;
namespace WinStyles { public class WindowStyleDP { #region BackgroundCaption цвет фона заголовка окна public static readonly DependencyProperty BackgroundCaptionProperty = DependencyProperty.RegisterAttached( nameof(GetBackgroundCaption)[3..], typeof(SolidColorBrush), typeof(WindowStyleDP), new PropertyMetadata(new SolidColorBrush((Color)ColorConverter.ConvertFromString("#293955"))));
public static SolidColorBrush GetBackgroundCaption(UIElement element) => (SolidColorBrush)element.GetValue(BackgroundCaptionProperty);
public static void SetBackgroundCaption(UIElement element, SolidColorBrush value) => element.SetValue(BackgroundCaptionProperty, value);
#endregion
}
}
Приступим к созданию проекта для проверки задачи.
Создадим проект WindowsStyle_Sample
В него добавим нашу библиотеку и подключим её в
App.xaml
<Application x:Class="WindowsStyle_Sample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WindowsStyle_Sample"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/WinStyles;component/WindowStyle.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Запускаем проект, видим что тема применилась успешно

Теперь переопределим тему для этого в дизайнере выберем окно Window и создадим копию шаблона
В коде можно нажать на код который принадлежит окну Window и я нажму на пкм в любом месте кроме самого окна чтобы не выбралась Grid.
В ресурсах window создалась копию нашего шаблона. Если пробовать запустить, то это работать не будет, так как стиль окна объявляется до проверки ресурсов окна. Поэтому код шаблона и сгенерированное пространство имён нужно перенести в App.xaml или создать словарь ресурсов и использовать переопределённый стиль.
Пример был с созданием стиля в библиотеки. Для кастомного окна все действия одинаковые кроме вызова окна в основном приложение.
