C# Windows窗体应用程序的可插入UI组件?

C# Windows窗体应用程序的可插入UI组件?,c#,winforms,C#,Winforms,我们的应用程序包含许多模块,在安装和运行应用程序后,这些模块可以动态地推送到应用程序中。所有这些模块都可能需要在UI上显示。所以我认为我们可以构建一个UI exe,它可以从DLL(或任何其他类型的程序集)加载UI组件。假设module1和module2在机器上处于活动状态,我们将在UI的左侧框架显示“module1”和“module2”。如果用户单击“module1”,右侧框架将打开module1的屏幕,该屏幕是从另一个组件(如DLL)加载的,该组件与module1一起向下推 只是想知道这种可插

我们的应用程序包含许多模块,在安装和运行应用程序后,这些模块可以动态地推送到应用程序中。所有这些模块都可能需要在UI上显示。所以我认为我们可以构建一个UI exe,它可以从DLL(或任何其他类型的程序集)加载UI组件。假设module1和module2在机器上处于活动状态,我们将在UI的左侧框架显示“module1”和“module2”。如果用户单击“module1”,右侧框架将打开module1的屏幕,该屏幕是从另一个组件(如DLL)加载的,该组件与module1一起向下推


只是想知道这种可插拔的UI架构在Windows窗体上是否可行。我在互联网上做了一些搜索,没有找到任何有用的信息。

我还没有尝试过这个,所以我不能保证它会起作用,但从我所看到的来看,它应该会起作用

如果在构建exe时知道模块列表,则只需更新组件dll即可更新任何组件的功能。如果在dll中包含某种切换方式,以确定给定组件是否处于活动状态,则可以使用虚拟组件“填充”dll,稍后为其添加功能。例如:

using ComponentLibrary;
class Program
{
  static void Main()
  {
    //...
    if (Module1.IsActive()) listModules.Add(Library.Module1);
    if (Module2.IsActive()) listModules.Add(Library.Module2);
  }
  listModules_click()
  {
     // var m = clicked-on-module
     if (m is Module1)
     {
       component = new Module1();
     }
     else if (m is Module2)
     {
       component = new Module2();
     }
  }
}

namespace ComponentLibrary
{
  abstact class Module : Component
  {
     public abstract bool IsActive();
  }
  public class Module1 : Module
  {
     public bool IsActive() { return true; }
  }
  public class Module2 : Module
  {
     public bool IsActive() { return false; }
  }  
}

稍后,对模块2进行编码,并将其
IsActive()
结果替换为
true
。但是签名没有改变,所以您不需要重新编译exe。在那之前,没有办法真正启动模块2。

我还没有尝试过这个,所以我不能保证它会工作,但从我所看到的,它应该会工作

如果在构建exe时知道模块列表,则只需更新组件dll即可更新任何组件的功能。如果在dll中包含某种切换方式,以确定给定组件是否处于活动状态,则可以使用虚拟组件“填充”dll,稍后为其添加功能。例如:

using ComponentLibrary;
class Program
{
  static void Main()
  {
    //...
    if (Module1.IsActive()) listModules.Add(Library.Module1);
    if (Module2.IsActive()) listModules.Add(Library.Module2);
  }
  listModules_click()
  {
     // var m = clicked-on-module
     if (m is Module1)
     {
       component = new Module1();
     }
     else if (m is Module2)
     {
       component = new Module2();
     }
  }
}

namespace ComponentLibrary
{
  abstact class Module : Component
  {
     public abstract bool IsActive();
  }
  public class Module1 : Module
  {
     public bool IsActive() { return true; }
  }
  public class Module2 : Module
  {
     public bool IsActive() { return false; }
  }  
}

稍后,对模块2进行编码,并将其
IsActive()
结果替换为
true
。但是签名没有改变,所以您不需要重新编译exe。在那之前,没有办法真正启动Module2。

这是完全可能的,而且它不是WinForms特有的。您将需要一种方法,要求模块在某个时候将其UI提供给您。例如:您的模块将实现一个接口,它将有一个返回。当你调用它时,你可以在你的UI上用它做任何事情

例如:您的右面板可以是可以承载面板的东西

您的界面可以如下所示

interface IModule
{
    ...
    Panel GetUI();
    ...
}
因此,当用户单击左窗格中的模块时,您将运行类似这样的操作

var selectedModule = GetSelectedModule() 
// this method will do the Reflection to load your assemblies, 
// go through the Types, filter every type that implement IModule and load them.
// then get an instance of the module. (Let me know if you want help on Reflection)

if (!GetConfiguration().IsModuleEnables(selectedModule))
    return; // module not enabled. Ignore click ???

rightPane.Children.Clear();
rightPane.Children.Add(selectedModule.GetUI());
// might want to dock the module as 'Fill' as well.
我认为您最好在应用程序中保持模块的“处于活动”状态,而不是询问模块是否已启用(恶意模块可以始终返回true)

希望这有帮助


更新:为了保证安全,我建议您将模块加载到一个单独的文件夹中。尽管在它们之间传递UI对象时可能会遇到问题。

这是很有可能的,而且这不是WinForms特有的。您将需要一种方法,要求模块在某个时候将其UI提供给您。例如:您的模块将实现一个接口,它将有一个返回。当你调用它时,你可以在你的UI上用它做任何事情

例如:您的右面板可以是可以承载面板的东西

您的界面可以如下所示

interface IModule
{
    ...
    Panel GetUI();
    ...
}
因此,当用户单击左窗格中的模块时,您将运行类似这样的操作

var selectedModule = GetSelectedModule() 
// this method will do the Reflection to load your assemblies, 
// go through the Types, filter every type that implement IModule and load them.
// then get an instance of the module. (Let me know if you want help on Reflection)

if (!GetConfiguration().IsModuleEnables(selectedModule))
    return; // module not enabled. Ignore click ???

rightPane.Children.Clear();
rightPane.Children.Add(selectedModule.GetUI());
// might want to dock the module as 'Fill' as well.
我认为您最好在应用程序中保持模块的“处于活动”状态,而不是询问模块是否已启用(恶意模块可以始终返回true)

希望这有帮助


更新:为了保证安全,我建议您将模块加载到一个单独的文件夹中。尽管在它们之间传递UI对象时可能会遇到问题。

是的,这是可能的,我自己也做过

最好的方法是在程序外创建第二个DLL。在该DLL中,您定义了一个插件将实现的接口。然后在主EXE中使窗体加载目录中的所有DLL,并查看它是否包含实现该接口的任何类。在插件DLL中,您还引用了相同的DLL,并让您的模块实现该接口。您希望所有插件共用的任何函数都需要放到
IMyPlugin
中,因为这是您将在UI中对所有内容进行转换的界面,因此只有这些函数才可见

//In a 2nd project that compiles as a DLL
public interface IMyPlugin
{
    Control GetControl();
}

///////////////////

//In your main project
private List<IMyPlugin> pluginsList;   

private void MainForm_Load(object sender, EventArgs e)
{ 
    foreach(string pluginPath in Directory.EnumerateFiles(Application.StartupPath + @"\Plugins\", "*.dll"))
    {
        try
        {
            //load the assembly
            Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);

            //Find all types defined in the assembly.
            Type[] types = pluginAssembly.GetTypes();

            //Filter the types to only ones that implment IMyPlugin
            var plugins = types.Where(x => typeof(IMyPlugin).IsAssignableFrom(x));

            //Filter the plugins to only ones that are createable by Activator.CreateInstance
            var constructablePlugins = plugins.Where(x => !x.ContainsGenericParameters && x.GetConstructor(Type.EmptyTypes) != null);

            foreach (var pluginType in constructablePlugins)
            {
                //instantiate the object
                IMyPlugin plugin = (IMyPlugin)Activator.CreateInstance(pluginType);

                pluginsList.Add(plugin);
            }
        }
        catch (BadImageFormatException ex)
        {
            //ignore this exception -- probably a runtime DLL required by one of the plugins..
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString(), "MainForm.MainForm_Load()");
        }
    }

    //Suspend the layout for the update
    this.SuspendLayout();
    this.someFlowLayoutPanelToStoreMyPlugins.SuspendLayout();

    foreach(IMyPlugin plugin in pluginsList)
    {
        this.someFlowLayoutPanelToStoreMyPlugins.Controls.Add(plugin.GetControl());
    }
    //resume the layout
    this.someFlowLayoutPanelToStoreMyPlugins.ResumeLayout(false);
    this.someFlowLayoutPanelToStoreMyPlugins.PerformLayout();
    this.ResumeLayout();
}


//////////////////////


// In your plugin DLL.

public class Plugin : UserControl, IMyPlugin
{
    public Plugin()
    {
        //The code in the main form requires there be a public 
        //  no parameter constructor (either explicitly or implicitly),
        //  UserControls usually have one anyway for InitializeComponent.

        InitializeComponent();
    }

    public Control GetControl()
    {
        return this;
    }

    // The rest of your code.
}
//在编译为DLL的第二个项目中
公共接口IMyPlugin
{
控件GetControl();
}
///////////////////
//在你的主要项目中
私有列表插件列表;
私有void主窗体加载(对象发送方、事件参数e)
{ 
foreach(目录.EnumerateFiles(Application.StartupPath+@“\Plugins\”,“*.dll”)中的字符串pluginPath)
{
尝试
{
//加载程序集
Assembly-pluginAssembly=Assembly.LoadFrom(pluginPath);
//查找程序集中定义的所有类型。
Type[]types=pluginAssembly.GetTypes();
//将类型筛选为仅实现IMyPlugin的类型
var plugins=types.Where(x=>typeof(IMyPlugin).IsAssignableFrom(x));
//将插件筛选为只能由Activator.CreateInstance创建的插件
var constructablePlugins=plugins.Where(x=>!x.ContainsGenericParameters&&x.GetConstructor(Type.EmptyTypes)!=null);
foreach(constructablePlugins中的var-pluginType)
{
//实例化对象
IMyPlugin plugin=(IMyPlugin)Activator.CreateInstance(pluginType);
pluginsList.Add(插件);
}
}
捕获(BadImageFormatException-ex)
{
//免疫球蛋白