【发布时间】:2016-09-26 13:36:51
【问题描述】:
我正在用 WPF 替换旧的 WinForms 应用程序的一部分,希望最终能用它实现 MVVM 范例。
这项工作给我带来的一个主要问题是在整个应用程序中保持 WinForms 组件使用的动态主题。这些主要是 2007 年和 2010 年 Office 旧主题的集合。
我的计划是构建一个启动 WPF 应用程序的单例对象,为各种控件添加一个带有 DynamicResource 挂钩的资源字典,然后动态换出另一个实际包含颜色定义的资源字典作为宿主 WinForms 应用程序改变它的主题。
只要 WPF 托管在 WPF 窗口中,它就可以完美运行。如果 WPF 托管在 WinForms 容器中,则资源字典肯定会被换出,但视图不会刷新。我知道这一点,因为一旦我将鼠标悬停在视图上的按钮上,它的颜色就会更新。
我最近将代码撕成一个独立的解决方案来尝试对其进行测试,所以我将在此处添加它们。此示例代码是在一个简单的 WinForms 项目中更改主题一次的独立测试:
UserControlResourceDictionary.xaml
<SolidColorBrush x:Key="WhiteBrush" Color="White" />
<!--Region Containers-->
<Style TargetType="{x:Type UserControl}">
<Setter Property="Background" Value="{DynamicResource DefaultBackgroundBrush}"/>
</Style>
<Style TargetType="{x:Type Panel}">
<Setter Property="Background" Value="{DynamicResource DefaultBackgroundBrush}"/>
</Style>
<Style TargetType="{x:Type Grid}" BasedOn="{StaticResource {x:Type Panel}}"/>
<Style TargetType="{x:Type StackPanel}" BasedOn="{StaticResource {x:Type Panel}}"/>
<!--End Region Containers-->
<!--Region TextBox-->
<Style TargetType="{x:Type TextBox}">
<Setter Property="FontSize" Value="11" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border BorderThickness="1"
BorderBrush="{DynamicResource TextBoxBorderBrush}"
Background="{DynamicResource TextBoxBackgroundBrush}"
x:Name="Border">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Border" Property="Background"
Value="{DynamicResource TextBoxMouseOverBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter TargetName="Border" Property="Background" Value="{DynamicResource TextBoxKeyboardFocusBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--End Region TextBox-->
<!--Region Button-->
<Style TargetType="{x:Type Button}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Foreground" Value="{DynamicResource FontColorBrush}" />
<Setter Property="FontSize" Value="11" />
<Setter Property="Width" Value="90" />
<Setter Property="Height" Value="25" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border CornerRadius="{DynamicResource ButtonCornerRadius}"
BorderThickness="1"
BorderBrush="{DynamicResource DefaultButtonBorderBrush}"
Background="{DynamicResource DefaultButtonBrush}"
x:Name="Border">
<ContentPresenter Margin="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Border" Property="Background"
Value="{DynamicResource DefaultMouseOverBrush}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Border" Property="Background" Value="{DynamicResource ButtonPressBrush}" />
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource ButtonPressBorderBrush}" />
</Trigger>
<Trigger Property="IsDefaulted" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="Black" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background" Value="{DynamicResource DefaultDisabledBrush}" />
<Setter TargetName="Border" Property="BorderBrush"
Value="{DynamicResource DisabledBorderBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--End Region Button-->
Office2007BlackStyle.xaml
<!--Region Colors-->
<Color x:Key="ButtonLight">
#EDEEF0
</Color>
<Color x:Key="ButtonDark">
#BBC0C6
</Color>
<Color x:Key="ButtonDisableLight">
#F3F6F8
</Color>
<Color x:Key="ButtonDisableDark">
#CBD5DF
</Color>
<Color x:Key="ButtonPressLight">
#F4BC81
</Color>
<Color x:Key="ButtonPressDark">
#EB7A05
</Color>
<Color x:Key="ButtonMouseOverLight">
#FBEDBD
</Color>
<Color x:Key="ButtonMouseOverDark">
#F4B100
</Color>
<Color x:Key="DefaultButtonBorderColor">
#898785
</Color>
<Color x:Key="TextBoxBorderColor">
#ABC1DE
</Color>
<Color x:Key="DisabledBorderColor">
#A1BDCF
</Color>
<Color x:Key="ButtonPressBorderColor">
#9B8259
</Color>
<Color x:Key="FontColor">
#464646
</Color>
<Color x:Key="BackgroundColor">
#535353
</Color>
<Color x:Key="GroupBoxColor">
#1E1E1E
</Color>
<!--End Region Colors-->
<CornerRadius x:Key="ButtonCornerRadius">
2
</CornerRadius>
<!--Region Brushes-->
<SolidColorBrush x:Key="DefaultButtonBorderBrush" Color="{DynamicResource DefaultButtonBorderColor}" />
<SolidColorBrush x:Key="TextBoxBorderBrush" Color="{DynamicResource TextBoxBorderColor}"/>
<SolidColorBrush x:Key="DisabledBorderBrush" Color="{DynamicResource DisabledBorderColor}" />
<SolidColorBrush x:Key="DefaultLabelBrush" Color="{DynamicResource ButtonLight}" />
<SolidColorBrush x:Key="ButtonPressBorderBrush" Color="{DynamicResource ButtonPressBorderColor}"/>
<SolidColorBrush x:Key="FontColorBrush" Color="{DynamicResource FontColor}"/>
<SolidColorBrush x:Key="DefaultBackgroundBrush" Color="{DynamicResource BackgroundColor}"/>
<SolidColorBrush x:Key="GroupBoxColorBrush" Color="{DynamicResource GroupBoxColor}"/>
<SolidColorBrush x:Key="TextBoxBackgroundBrush" Color="White"/>
<SolidColorBrush x:Key="TextBoxMouseOverBrush" Color="White"/>
<SolidColorBrush x:Key="TextBoxKeyboardFocusBrush" Color="White"/>
<LinearGradientBrush x:Key="DefaultButtonBrush" StartPoint="0,0" EndPoint="0,1.0">
<GradientStop Color="{DynamicResource ButtonLight}" Offset="0" />
<GradientStop Color="{DynamicResource ButtonDark}" Offset=".5" />
<GradientStop Color="{DynamicResource ButtonLight}" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultDisabledBrush" StartPoint="0,0" EndPoint="0,1.0">
<GradientStop Color="{DynamicResource ButtonDisableLight}" Offset="0" />
<GradientStop Color="{DynamicResource ButtonDisableDark}" Offset=".5" />
<GradientStop Color="{DynamicResource ButtonDisableLight}" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ButtonPressBrush" StartPoint="0,0" EndPoint="0,1.0">
<GradientStop Color="{DynamicResource ButtonPressLight}" Offset="0" />
<GradientStop Color="{DynamicResource ButtonPressDark}" Offset=".5" />
<GradientStop Color="{DynamicResource ButtonPressLight}" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="DefaultMouseOverBrush" StartPoint="0,0" EndPoint="0,1.0">
<GradientStop Color="{DynamicResource ButtonMouseOverLight}" Offset="0" />
<GradientStop Color="{DynamicResource ButtonMouseOverDark}" Offset=".5" />
<GradientStop Color="{DynamicResource ButtonMouseOverLight}" Offset="1" />
</LinearGradientBrush>
<!--End Region Brushes-->
AppHost.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Threading;
namespace EmbeddedWPFTest
{
public static class AppHost
{
private static readonly object AppLock = Guid.NewGuid();
private static Application _application;
private static ResourceDictionary _currentTheme;
private static ResourceDictionary _controlDictionary;
private static ResourceDictionary _resourceDictionary;
private static Dictionary<string, ResourceDictionary> _themes;
public static Dispatcher Dispatcher { get; set; }
public static Application CurrentApplication
{
get
{
lock (AppLock)
{
if (_application == null)
{
_application = new Application();
LoadDictionaries();
InitializeApplication();
}
}
return _application;
}
}
private static void InitializeApplication()
{
Application.Current.Resources.MergedDictionaries.Add(_resourceDictionary);
Application.Current.Resources.MergedDictionaries.Add(_controlDictionary);
_currentTheme = Application.LoadComponent(
new Uri(@"/EmbeddedWPFTest;component/Resources/Office2007BlueStyle.xaml",
UriKind.Relative))
as ResourceDictionary;
Application.Current.Resources.MergedDictionaries.Add(_currentTheme);
}
public static void ChangeTheme()
{
Application.Current.Resources.MergedDictionaries.Remove(_currentTheme);
InitializeApplication();
_currentTheme = Application.LoadComponent(
new Uri(@"/EmbeddedWPFTest;component/Resources/Office2007BlackStyle.xaml",
UriKind.Relative))
as ResourceDictionary;
Application.Current.Resources.MergedDictionaries.Add(_currentTheme);
}
private static void LoadDictionaries()
{
_resourceDictionary =
Application.LoadComponent(new Uri(@"/EmbeddedWPFTest;component/Resources/ResourceDictionary.xaml",
UriKind.Relative)) as ResourceDictionary;
_controlDictionary =
Application.LoadComponent(new Uri(@"/EmbeddedWPFTest;component/Resources/UserControlResourceDictionary.xaml",
UriKind.Relative)) as ResourceDictionary;
_themes = new Dictionary<string, ResourceDictionary>
{
{
"Office2007BlueStyle",
Application.LoadComponent(
new Uri(@"/EmbeddedWPFTest;component/Resources/Office2007BlueStyle.xaml",
UriKind.Relative))
as ResourceDictionary
},
{
"Office2007BlackStyle",
Application.LoadComponent(
new Uri(@"/EmbeddedWPFTest;component/Resources/Office2007BlackStyle.xaml",
UriKind.Relative))
as ResourceDictionary
}
};
}
}
}
我的目标是,当托管 WinForms 应用程序更改其主题时,我可以连接到事件,并让 AppHost 更改为相应的资源字典。为简单起见,我省略了那部分代码。在我现在正在运行的测试中,它是由于在视图中按下按钮而发生的。同样,如果视图托管在 WPF 容器中,它可以正常工作,但托管在 WinForms 容器中,它不会刷新或重新绘制。
【问题讨论】:
-
第一个问题很好!
-
最好的答案怎么样? :) 实际上,我现在有一个可能的解决方案,我会在完成尝试后发布。这有点像 hack,但这是我能想到的最好的了。
标签: c# wpf winforms xaml resourcedictionary