我正在用
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容器中托管,它不会刷新或重绘.
最佳答案 我尝试了在互联网上找到的所有解决方案.我能让它工作的唯一方法是创建视图的新副本.这不是最优雅的解决方案,但我认为只要为每个视图保留底层视图模型的相同实例,它就不是一个糟糕的解决方案.
现在,我正在使用MVVMLight来更新此代码库. MVVMLight带有一个漂亮的messenger实用程序,可以方便模块之间的异步通信.我决定在我的AppHost类和WinForms主机之间进行通信.
新的AppHost.cs
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;
private static KryptonManager _kryptonManager;
private static IMessenger _messengerInstance;
/// <summary>
/// Gets or sets an instance of a <see cref="IMessenger" /> used to
/// broadcast messages to other objects. If null, this class will
/// attempt to broadcast using the Messenger's default instance.
/// </summary>
private static IMessenger MessengerInstance
{
get
{
return _messengerInstance ?? Messenger.Default;
}
set
{
_messengerInstance = value;
}
}
public static Application CurrentApplication
{
get
{
lock (AppLock)
{
if (_application == null)
{
_application = new Application();
LoadDictionaries();
InitializeApplication();
ChangeTheme(PaletteModeManager.Custom);
KryptonManager.GlobalPaletteChanged += KryptonManagerGlobalPaletteChanged;
_kryptonManager = new KryptonManager();
}
}
return _application;
}
}
private static void KryptonManagerGlobalPaletteChanged(object sender, EventArgs e)
{
ChangeTheme(_kryptonManager.GlobalPaletteMode);
}
private static void InitializeApplication()
{
Application.Current.Resources.MergedDictionaries.Add(_resourceDictionary);
Application.Current.Resources.MergedDictionaries.Add(_controlDictionary);
}
public static void ChangeTheme(PaletteModeManager manager)
{
Application.Current.Resources.MergedDictionaries.Remove(_currentTheme);
switch (manager)
{
case PaletteModeManager.Office2007Blue:
_currentTheme = _themes["Office2007BlueStyle"];
Application.Current.Resources.MergedDictionaries.Add(_currentTheme);
break;
case PaletteModeManager.Office2007Black:
_currentTheme = _themes["Office2007BlackStyle"];
Application.Current.Resources.MergedDictionaries.Add(_currentTheme);
break;
default:
_currentTheme = _themes["Office2007BlueStyle"];
Application.Current.Resources.MergedDictionaries.Add(_currentTheme);
break;
}
MessengerInstance.Send(new ThemeChangedMessage());
}
private static void LoadDictionaries()
{
_resourceDictionary =
Application.LoadComponent(new Uri(@"/ApplicationHost;component/Resources/ResourceDictionary.xaml",
UriKind.Relative)) as ResourceDictionary;
_controlDictionary =
Application.LoadComponent(new Uri(@"/ApplicationHost;component/Resources/UserControlResourceDictionary.xaml",
UriKind.Relative)) as ResourceDictionary;
_themes = new Dictionary<string, ResourceDictionary>
{
{
"Office2007BlueStyle",
Application.LoadComponent(
new Uri(@"/ApplicationHost;component/Resources/Office2007BlueStyle.xaml",
UriKind.Relative))
as ResourceDictionary
},
{
"Office2007BlackStyle",
Application.LoadComponent(
new Uri(@"/ApplicationHost;component/Resources/Office2007BlackStyle.xaml",
UriKind.Relative))
as ResourceDictionary
}
};
}
}
所以这里,当AppHost从WinForms主机接收通知(在这个实例中是Krypton)时,它会换出正确的颜色资源字典,然后使用MVVMLight的消息传递功能发出异步消息,以通知侦听器主题已更改.
BaseFormsWrapper.cs
public class BaseFormsWrapper : UserControl
{
public Panel PanelBasePanel;
private ElementHost _wpfHost;
private IMessenger _messengerInstance;
public BaseFormsWrapper()
{
InitializeComponent();
MessengerInstance.Register<ThemeChangedMessage>(this, HandleThemeChanged);
}
private void HandleThemeChanged(ThemeChangedMessage obj)
{
var instance = Activator.CreateInstance(_wpfHost.Child.GetType());
var oldView = _wpfHost.Child;
_wpfHost.Child = (UIElement) instance;
var view = oldView as ViewBase;
var newView = instance as ViewBase;
if ((view != null) && (newView != null))
{
newView.DataContext = view.DataContext;
}
}
/// <summary>
/// Gets or sets an instance of a <see cref="IMessenger" /> used to
/// broadcast messages to other objects. If null, this class will
/// attempt to broadcast using the Messenger's default instance.
/// </summary>
private IMessenger MessengerInstance
{
get
{
return _messengerInstance ?? Messenger.Default;
}
set
{
_messengerInstance = value;
}
}
public UIElement HostedControl
{
get { return _wpfHost.Child; }
set { _wpfHost.Child = value; }
}
private void InitializeComponent()
{
PanelBasePanel = new Panel();
_wpfHost = new ElementHost();
PanelBasePanel.SuspendLayout();
SuspendLayout();
//
// panelBasePanel
//
PanelBasePanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
PanelBasePanel.Controls.Add(_wpfHost);
PanelBasePanel.Dock = DockStyle.Fill;
PanelBasePanel.Location = new Point(0, 0);
PanelBasePanel.Margin = new Padding(0);
PanelBasePanel.Name = "PanelBasePanel";
PanelBasePanel.Size = new Size(1126, 388);
PanelBasePanel.TabIndex = 0;
//
// wpfHost
//
_wpfHost.BackColor = SystemColors.ControlLightLight;
_wpfHost.BackgroundImageLayout = ImageLayout.None;
_wpfHost.Dock = DockStyle.Fill;
_wpfHost.Location = new Point(0, 0);
_wpfHost.Name = "_wpfHost";
_wpfHost.Size = new Size(1126, 388);
_wpfHost.TabIndex = 0;
_wpfHost.Child = null;
//
// BaseFormsWrapper
//
AutoScaleDimensions = new SizeF(6F, 13F);
AutoSize = true;
Controls.Add(PanelBasePanel);
Name = "BaseFormsWrapper";
Size = new Size(1126, 388);
PanelBasePanel.ResumeLayout(false);
ResumeLayout(false);
}
}
在让GC处理旧视图实例之前,我通过将其填充到新视图实例中来保留其DataContext(视图模型).
我仍在研究我的概念验证工作项目,以确保所有这些工作在完成步骤时,但在我的初始测试中似乎是活泼和实用的.