我们的应用程序包含许多模块,这些模块可以在安装和运行后动态推送到我们的应用程序。所有这些模块可能需要在UI上显示一些。所以我认为我们可以构建一个UI exe,它可以从DLL(或任何其他类型的程序集)加载UI组件。假设module1和module2在机器上处于活动状态,我们将在UI的左侧框架中显示“module1”和“module2”。如果用户单击“module1”,右侧框架将打开module1的屏幕,该模块从另一个程序集(例如DLL)加载,该程序集与module1一起下推。
只是想知道这种可插入的UI架构是否可以在Windows窗体上实现。我在互联网上做了一些搜索,但没有找到任何有用的信息。
答案 0 :(得分:3)
是的,这是可能的,我自己就是这样做的。
执行此操作的最佳方法是在程序外部创建第二个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.
}
注意:这段代码只是复制并粘贴了很多我的代码,我不知道它是否会完美地运行,但这会让你接近你需要去的地方。
答案 1 :(得分:1)
这很有可能,并不是特定于WinForms。你需要一种方法让模块在某个时候给你UI。例如:您的模块将实现一个接口,它将有一个返回Panel的方法。当您调用它时,您可以在UI上执行任何操作。
例如:您的右侧面板可以是可以托管Panel的东西。
您的界面如下所示
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.
我认为你最好在你的应用程序中保持模块的'Is Active'状态,而不是询问模块是否已启用(rogue模块可以一直返回true)。
希望这有帮助。
更新:为确保安全,建议您将模块加载到单独的AppDomain中。虽然在它们之间传递UI对象时可能会遇到问题。
答案 2 :(得分:0)
我没有试过这个,所以我不能保证它会起作用,但从我看到的情况来看,它应该是。
如果在构建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; }
}
}
稍后,您编写Module2,并将其IsActive()
结果替换为true
。但签名不会改变,因此您不需要重新编译exe。在那之前,没有办法真正提升Module2。