【问题标题】:Load one or the other WPF DLL into main WPF application将一个或另一个 WPF DLL 加载到主 WPF 应用程序中
【发布时间】:2019-07-29 14:04:38
【问题描述】:

希望加载 XAML/事件/连接到 DLL 的用户控件的所有内容,该 DLL 在运行时加载到基于配置文件的另一个 WPF 项目中。

所以你有 2 个 DLL,只有一个会根据 JSON 配置加载,但是我希望我的所有按钮和一切都能正常工作。这是我有 ATM 的代码

Gray DLL 的用户控件

<UserControl x:Class="WPFGreyButtonTest.InstrumentUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPFGreyButtonTest"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Label x:Name="colourName" Content="GREY" HorizontalAlignment="Left" Height="93" Margin="284,88,0,0" VerticalAlignment="Top" Width="243" FontWeight="Bold" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="50" Foreground="#FF8B8B8B"/>
    </Grid>
</UserControl>

没有实际代码

Purple DLL 的用户控件

<UserControl x:Class="WPFPurpleButtonTest.InstrumentUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPFPurpleButtonTest"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Label x:Name="colourName" Content="PURPLE" HorizontalAlignment="Left" Height="93" Margin="284,88,0,0" VerticalAlignment="Top" Width="243" FontWeight="Bold" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="50" Foreground="#FFDC00FF"/>
        <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="365,253,0,0" VerticalAlignment="Top" Width="75"/>

    </Grid>
</UserControl>

相关代码(两个 DLL 具有相同的类名,即 InstrumentUserControl)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPFPurpleButtonTest
{
    /// <summary>
    /// Interaction logic for InstrumentUserControl.xaml
    /// </summary>
    public partial class InstrumentUserControl : UserControl
    {
        public static readonly DependencyProperty InnerButtonProperty = DependencyProperty.Register("InnerButton", typeof(Button), typeof(InstrumentUserControl));

        public Button InnerButton
        {
            get { return (Button)GetValue(InnerButtonProperty); }
            set { SetValue(InnerButtonProperty, value); }
        }

        public InstrumentUserControl()
        {
            InitializeComponent();
            InnerButton = button;
        }
    }
}

主要 WPF 应用程序代码

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPFSandBox
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        UserControl userControl = null;
        InstrumentEnum instrumentType = InstrumentEnum.Invalid;
        string dllToLoad = null;

        // When we first initialize our WPF app, in the constructor we can
        // allow a config file (such as a json) to be read and load up
        // an appropriate user control view
        public MainWindow()
        {
            InitializeComponent();
            ReadJson();
            LoadRunTimeDLL();

            //var ucs = new List<InstrumentUserControl>();
            var ucs = new List<UserControl>();

            ucs.Add(userControl);
            //ucs.Add(new UserControl1());
            //ucs.Add(new UserControl1());
            //ucs.Add(new UserControl1());

            ic.ItemsSource = ucs;
        }

        private void ReadJson()
        {
            using (StreamReader r = new StreamReader("../../Config/Config.json"))
            {
                string json = r.ReadToEnd();
                var jsonData = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);

                foreach (var entry in jsonData)
                {
                    if (entry.Key == "InstrumentType")
                    {
                        Enum.TryParse(entry.Value, out instrumentType);
                    }
                    else if (entry.Key == "DllToLoad")
                    {
                        dllToLoad = entry.Value;
                    }
                }
            }
        }

        private void LoadRunTimeDLL()
        {
            string assemblyName = string.Format("{0}\\{1}.dll", 
                new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName, dllToLoad);

            if (assemblyName != null)
            {
                Assembly asm = Assembly.LoadFile(assemblyName);
                Type[] tlist = asm.GetTypes();
                foreach (Type t in tlist)
                {
                    if (t.Name == "InstrumentUserControl")
                    {
                        userControl = Activator.CreateInstance(t) as UserControl;
                        break;
                    }
                }

                if (userControl != null)
                {
                    //contentControl.Content = userControl;
                }
            }
        }
    }
}

它是 MainWindow xaml

<Window x:Class="WPFSandBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFSandBox"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <!--<Grid>
        <ContentControl Grid.Row="1" x:Name="contentControl" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
    </Grid>-->
    <ItemsControl x:Name="ic" />
</Window>

如果我能确保无论哪个 DLL 被加载,按钮都能正常工作,任何帮助都将不胜感激。

我正在尝试在运行时加载 WPF DLL。工作正常,但按钮和事件绑定到主 WPF 应用程序是我不知道该怎么做

【问题讨论】:

  • 你有什么问题? (我没有投反对票,但我猜其他人也基于同样的问题投了反对票)
  • 是的(悲伤的时候:()。我试图在运行时加载 WPF DLL。工作正常,但按钮和事件与主 WPF 应用程序相关联是我不知道的怎么办
  • 在一个不相关的说明中,您可能会考虑让您的所有仪器控件从某个基类扩展,以便您的主类可以有一些常见的方式与加载的任何内容进行交互,以及在搜索加载的内容时DLL 为所需的类型,您可以搜索从该基类扩展的类型(只是为了更具体,而不是搜索具有该名称的任何内容)。
  • 如果有共同的事件,一个基类会解决这个问题。这样,所有按钮都会引发基类事件,您的主应用程序可以简单地订阅这些常见事件。否则,您将不得不使用一些反射来查找事件并订阅它们。
  • jhilgeman 您能否为您刚才所说的内容提供一个示例 + 可能对最初的问题有所帮助?

标签: c# wpf xaml dll


【解决方案1】:

好的,假设您目前有这个设置:

Solution: MainApp
- Project: MainApp (WPF App) 
- Project: PurpleButton (Class Library) 
- Project: GreyButton (Class Library) 

我说的是添加另一个项目,像这样:

Solution: MainApp
- Project: MainApp (WPF App) 
- Project: MainAppPlugins (Class Library) <-----
- Project: WPFPurpleButtonTest (Class Library) 
- Project: WPFGreyButtonTest (Class Library) 

在 MainAppPlugins 库中,添加一个基于 UserControl 的新“PluginButton”类(但不要添加带有设计器或任何东西的实际用户控件)。它可能看起来像这样:

using System;
using System.Windows.Controls;

namespace MainAppPlugins
{
    public abstract class PluginButton : UserControl
    {
        public event EventHandler OnProcessedSomeData;
    }
}

然后您更新所有其他项目(MainApp、WPFPurpleButtonTest 和 WPFGreyButtonTest)并添加对 MainAppPlugins 的项目引用(以便每个项目都知道“MainAppPlugins.PluginButton”是什么)。

在 Purple 和 Grey 中,您可以将用户控件更改为从 MainAppPlugins.PluginButton 扩展,而不是从 UserControl 扩展。所以你在两个地方这样做。首先在您的代码隐藏(.cs 文件)中:

namespace WPFGreyButtonTest
{
    public partial class InstrumentUserControl : PluginButton // <-- 
    {
      ...your existing code...
    }
}

然后在 XAML 中添加对 MainAppPlugins 程序集的 XML 命名空间引用,然后更改基类型以反映 PluginButton:

<mainappplugins:PluginButton x:Class="WPFGreyButtonTest.InstrumentUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:mainappplugins="clr-namespace:MainAppPlugins;assembly=MainAppPlugins"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:WPFGreyButtonTest"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
...
</mainappplugins:PluginButton>

此时,两个按钮现在都从 PluginButton 类扩展/继承,因此它们现在都有一个名为 OnProcessedSomeData 的公共事件,并且它们都可以在适当的时候引发该事件。

您可以通过修改 MainAppPlugins 项目中的 PluginButton 类然后重新构建来继续向两个按钮添加通用代码/事件。

现在,当我们回到 MainApp 并寻找类型时,您可以检查类型的 BaseType,而不是查看名称:

// if (t.Name == "InstrumentUserControl")
if(t.BaseType == typeof(MainAppPlugins.PluginButton))
{
   ...
}

...您还可以在创建控件时使用更具体的常用 PluginButton 类型:

 // userControl = Activator.CreateInstance(t) as UserControl;
 userControl = Activator.CreateInstance(t) as MainAppPlugins.PluginButton;

...这意味着 MainApp 代码现在可以引用所有常见事件/代码,例如我们自定义的 OnProcessedSomeData 事件:

userControl.OnProcessedSomeData += ...您的事件处理程序在这里...

因此,灰色和紫色按钮可能有 90% 不同的代码,但另外 10% 由事件、属性等定义... - 在 PluginButton 中找到的任何代码、事件和属性。由于 MainApp 知道 PluginButton,它可以通过引用该公共代码来查看这两个按钮中的任何一个并与之交互。

让我们更进一步,假设您希望两个按钮都通过 GetData() 方法返回数据。在您的 MainAppPlugins.PluginButton 类中,您将定义一个带有虚拟标志的方法:

public abstract class PluginButton : UserControl
{
    ...

    public virtual string GetData()
    {
        return "Default value";
    }
}

并且在每个按钮的代码隐藏中,您将覆盖该方法以返回特定于按钮的结果:

namespace WPFGreyButtonTest
{
    public partial class InstrumentUserControl : PluginButton 
    {
        ...your existing code...

        public override string GetData()
        {
            return "Data from the Grey button";
        }
    }
}

namespace WPFPurpleButtonTest
{
    public partial class InstrumentUserControl : PluginButton 
    {
        ...your existing code...

        public override string GetData()
        {
            return "Data from the Purple button";
        }
    }
}

..在 MainApp 中,您可以调用 userControl.GetData(),它会调用正确的、特定于按钮的 GetData() 调用。

对于任何实现动态加载的 DLL/插件的应用程序,我的最终建议是考虑应用程序的安全性,以防恶意用户使用恶意代码构建自己的 DLL,然后将其命名为 WPFPurpleButtonTest.dll,然后能够用他们的文件覆盖你的文件。

如果这种情况发生的可能性很小,我强烈建议您对要加载的 DLL 实施某种限制,除非它们由内部受信任的 CA 进行数字签名。这个建议是一个更大的话题,因此您可能必须自己研究如何对 DLL 进行数字签名,然后在加载 DLL 之前检查该签名。这样一来,您的应用就不会加载您未明确信任的 DLL。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多