簡體   English   中英

在WinForms中托管時管理WPF主題

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM