Managing WPF Theming while hosted in WinForms

I am replacing parts of the old WinForms application with WPF, hoping that eventually it will move to the MVVM paradigm.

One of the main problems that have been put to me with these efforts is the maintenance of the dynamic theme used by the WinForms components through the application. This is basically a collection of old Office 2007 and 2010 themes.

My plan was to create a singleton object that launched the WPF application, add a resource dictionary using DynamicResource connections for colors for various controls, and then dynamically replace another resource dictionary that actually contains color definitions as the WinForms hosting application modifies your topic.

This works fine while the WPF is hosted in the WPF window. If WPF is hosted in a WinForms container, the resource dictionary will definitely change, but the view will not be updated. I know this because as soon as I click on the button on the view, its color is updated.

I recently pulled the code into an independent solution to try testing it, so I will add them here. This sample code was an independent test to change the theme once in a simple WinForms project:

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, .

+4
1

, . , , - . , , , , .

MVVMLight . MVVMLight , . 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 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 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 ( ), .

, , , , , .

+2

Source: https://habr.com/ru/post/1655850/


All Articles