简体   繁体   English

在WinForms中托管时管理WPF主题

[英]Managing WPF Theming while hosted in WinForms

I am in the process of replacing portions of an old WinForms application with WPF, with hopes of eventually going towards the MVVM paradigm with it. 我正在用WPF替换旧的WinForms应用程序的部分,希望最终能够使用它来实现MVVM范例。

The one major concern that was brought to me with this effort is to maintain the dynamic theming being used by the WinForms components through out the application. 这项工作带给我的一个主要问题是在整个应用程序中保持WinForms组件使用的动态主题。 These are mostly a collection of old 2007 and 2010 Office themes. 这些主要是旧的2007年和2010年Office主题的集合。

My plan was to build a singleton object that launched the WPF application, add a resource dictionary with DynamicResource hook ups for colors for various controls, and then dynamically swap out another resource dictionary that actually contains the color definitions as the hosting WinForms application changes its theme. 我的计划是构建一个启动WPF应用程序的单例对象,添加一个资源字典,其中包含用于各种控件的颜色的DynamicResource连接,然后动态交换另一个实际包含颜色定义的资源字典,因为托管WinForms应用程序更改其主题。

This works perfectly as long as WPF is being hosted within a WPF window. 只要在WPF窗口中托管WPF,这就完美地工作。 If the WPF is being hosted within a WinForms container, the resource dictionary definitely gets swapped out, but the view does not get refreshed. 如果WPF托管在WinForms容器中,则资源字典肯定会被换出,但视图不会刷新。 I know this because once i mouse-over a button on the view, its color THEN gets updated. 我知道这一点,因为一旦我鼠标悬停在视图上的按钮上,它的颜色就会更新。

I recently ripped out the code into an independent solution to try testing it, so I'll add them here. 我最近将代码拆分成一个独立的解决方案来尝试测试它,所以我会在这里添加它们。 This example code was an independent test to change the theme once in a simple WinForms project: 此示例代码是一个独立的测试,在一个简单的WinForms项目中更改主题一次:

UserControlResourceDictionary.xaml 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 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 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
                }
            };
        }
    }
}

My goal was, when the hosting WinForms application changed its theme, I could wire up to the event, and have AppHost change out for the corresponding resource dictionary. 我的目标是,当托管WinForms应用程序更改其主题时,我可以连接到该事件,并让AppHost更改为相应的资源字典。 I left out that portion of the code for simplicity. 为简单起见,我遗漏了部分代码。 In the test that I'm running right now, it happens as a result of a button press in the view. 在我正在运行的测试中,它是在视图中按下按钮时发生的。 Again, if the view is hosted in a WPF container, it works fine, but hosted within a WinForms container, it does not refresh or repaint. 同样,如果视图托管在WPF容器中,它可以正常工作,但是在WinForms容器中托管,它不会刷新或重绘。

I tried every solution that I could find on the Internet. 我尝试了在互联网上找到的所有解决方案。 The only way that I could get this to work was to create a new copy of the view. 我能让它工作的唯一方法是创建视图的新副本。 This isn't the most elegant solution, but I figured that as long as you preserve the same instance of the underlying view model for each view, it's not a terrible solution. 这不是最优雅的解决方案,但我认为只要为每个视图保留底层视图模型的相同实例,它就不是一个糟糕的解决方案。

Now, I'm using MVVMLight in my effort to update this code base. 现在,我正在使用MVVMLight来更新此代码库。 MVVMLight comes with a nifty messenger utility that's handy for asynchronous communication between modules. MVVMLight带有一个漂亮的messenger实用程序,可以方便模块之间的异步通信。 I decided to utilize that in communicating between my AppHost class, and the WinForms hosts. 我决定在我的AppHost类和WinForms主机之间进行通信。

New AppHost.cs 新的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
                }
            };
        }
    }

So here, when AppHost receives notification from the WinForms host (Krypton in this instance), it swaps out for the correct color resource dictionary, and then sends out an asynchronous message using MVVMLight's messaging functionality to notify listeners that the theme has changed. 所以这里,当AppHost从WinForms主机接收通知(在这个实例中是Krypton)时,它会换出正确的颜色资源字典,然后使用MVVMLight的消息传递功能发出异步消息,通知监听器主题已经改变。

BaseFormsWrapper.cs 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);
    }
}

Before letting the GC handle the old view instance, I preserve its DataContext (view model) by stuffing it into the new view instance. 在让GC处理旧视图实例之前,我通过将其填充到新视图实例中来保留其DataContext(视图模型)。

I'm still working on my proof-of-concept work item to make sure all of this works when put through the paces, but seems to be snappy and functional in my initial testing. 我仍在研究我的概念验证工作项目,以确保所有这些工作在完成步骤时,但在我的初始测试中似乎是活泼和实用的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM