[英]Managing WPF Theming while hosted in WinForms
我正在用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(視圖模型)。
我仍在研究我的概念驗證工作項目,以確保所有這些工作在完成步驟時,但在我的初始測試中似乎是活潑和實用的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.