带GUI的插件架构

我正在开发一个大量使用插件的应用程序。 该应用程序在C#中,我正在考虑在WPF中构建配置GUI。 在如何管理实际插件本身方面,我得到了插件架构。 但是,每个插件都有自己的配置,这就是我在寻求帮助的地方。

插件架构很简单 – 有一个插件工具的接口,我只是加载目录中的所有插件。 但是,插件从哪里获取配置? 我想有一些通用的方法来处理这个,所以每个插件都不负责读取自己的配置文件。 此外,我希望GUI随每个插件一起扩展 – 也就是说,安装的每个插件都应该向GUI添加一个选项卡,其中包含该插件的特定配置选项。 然后在保存时,将保存配置文件。

我最好的办法是什么?

为可配置插件定义接口:

public interface IConfigurable { public void LoadConfig(string configFile); public void ShowConfig(); // Form or whatever, allows you to integrate it into another control public Form GetConfigWindow(); } 

只需调用IConfigurable接口即可配置插件。

如果你想要你可以让界面以另一种方式工作,让主应用程序为插件提供一个容器(例如框架或底座),但是我会推荐相反的方法。

 public interface IConfigurable { void LoadConfig(string configFile); void ShowConfig(DockPanel configurationPanel); } 

最后,您可以通过严格定义插件可以提供的配置选项来做到这一点。

 public interface IMainConfigInterop { void AddConfigurationCheckBox(ConfigurationText text); void AddConfigurationRadioButton(ConfigurationText text); void AddConfigurationSpinEdit(Confguration text, int minValue, int maxValue); } public interface IConfigurable { void LoadConfig(string configFile); void PrepareConfigWindow(IMainConfigInterop configInterop); } 

当然,这个选项是更具限制性和更安全的选项,因为您可以完全限制插件如何与配置窗口交互。

我实现处理完全相同的问题(因为dll文件无法读取其配置文件)的方式是我为插件IPlugin定义了两个接口,为主机IHost定义了一个接口。 插件是通过对主机的引用(实现IHost接口)创建和初始化的。

 interface IHost { string[] ReadConfiguration(string fileName); } interface IPlugin { void Initialize(IHost host); } class MyPlugin : IPlugin { public void Initialize(IHost host) { host.ReadConfiguration("myplygin.config"); } } 

然后, IHost可以在所有插件之间提供所需的任何常用function(如读取和写入配置),并且插件也更加了解您的主机应用程序,而不会被锁定到特定的主机应用程序。 例如,您可以编写一个Web Service应用程序和一个使用相同插件API的Forms应用程序。

另一种方法是使用主机应用程序读取的配置信息初始化插件,但是,我需要插件能够读取/写入其配置,并根据需要执行其他操作。

看看Composite WPF 。 使用这样的东西应该允许你让每个插件定义标签。 然后,shell应用程序仅负责将每个模块加载到该模块的相应视图。

  • 使用Composite WPF的样本
  • 使用WPF Composite进行演练
  • MSDN使用Prism

我看待它的方式,这个问题最好解决为两个独立的问题。

  1. 启用每个插件以指定配置对话框(或类似的东西,可能以标签的forms,如您所建议的那样)。
  2. 为插件提供集中配置文件/数据库和API,以存储其配置信息。

允许每个插件指定配置对话框

这里的基本方法是让每个插件定义一个属性,该属性将返回一个Panel以供主机显示在选项卡中。

 interface IPlugin { ... System.Windows.Controls.Panel ConfigurationPanel { get; } } 

主机可以为每个加载的插件创建一个选项卡,然后将每个插件的配置面板放在正确的选项卡中。

这里有很多种可能性。 例如,插件可以通过返回null表示它没有任何配置选项。 主机当然需要检查这个并决定是否创建选项卡。

另一种类似的方法是定义方法而不是属性。 在这种情况下,每次显示配置对话框时都会调用该方法,并返回一个新的Panel实例。 这可能会更慢,并且不是一个好主意,除非属性方法导致多次向可视树添加/删除元素的问题。 我没有遇到过这些问题,所以你应该善待房产。

可能没有必要提供这种灵活性。 如上所述,您可以为插件提供API,以便从文本框,复选框,combobox等字段构建其配置对话框。这可能最终会比简单的Panel属性更复杂。

当配置关闭时,您可能还需要回调插件,告诉它保存其配置数据,我将很快得到它。 这是一个例子:

 interface IPlugin { ... // Inside this method you would get the data from the Panel, // or, even better, an object bound to the Panel. // Then you would save it with something like in the next section. void SaveConfiguration(); } 

提供中央配置数据库

这可以通过以下API解决:

 static class Configuration { static void Store(string key, object data); static object Retrieve(string key); } 

然后主机将控制读取并将配置写入磁盘。

插件的示例用法:

 Configuration.Store("Maximum", 10); int max = (int)Configuration.Retrieve("Maximum"); 

这将使配置数据充当字典。 您可能能够使用序列化来存储文件中的任意对象。 或者,您可以将配置限制为存储字符串或类似内容。 这取决于需要存储的配置类型。

最简单的方法存在一些安全问题。 插件可以读取和修改其他插件的数据。 但是,如果主机通过插件ID或类似名称为密钥添加前缀,则可以有效地将配置划分到每个插件的单独容器中。

不希望每个插件读取自己的配置文件的问题是您必须有一个中央文件知道要包含哪些配置设置; 这违背了插件的概念,我给你一个插件,你对此一无所知。

当然,这不一样,作为一个中央配置文件,其中包含有关要加载的插件的信息。

与其他人所说的相反,有些方法可以让DLL从他们自己的配置文件中读取配置设置,但是如果你特别不想这样做,我就不会进入它们。

我很惊讶没有人提到注册表。 这是每个人都希望锁定在地下室的家庭秘密,但这正是它的意义所在:用于存储任何人(即任何插件)都可以访问的应用程序设置的集中位置。

然而。 如果您希望主应用程序访问和存储插件配置,最好的方法(IMO)是有一个接口来查询插件的属性(并将其传回)。 如果您保持通用,则可以将其重复用于其他应用。

 interface IExposedProperties { Dictionary GetProperties(); void SetProperties(Dictionary Properties); } 

主应用程序将查询每个插件的一组属性,然后将/更新设置添加到其配置文件中,如下所示:

 private void SavePluginSettings(List PluginProperties) { // Feel free to get fancy with LINQ here foreach (IExposedProperties pluginProperties in PluginProperties) { foreach (KeyValuePair Property in pluginProperties.GetProperties()) { // Use Property.Key to write to the config file, and Property.Value // as the value to write. // // Note that you will need to avoid Key conflict... you can prepend // the plugin name to the key to avoid this } } } 

我还没有测试过,只是在几个空闲的时刻快速地把它扔掉了; 我稍后会检查,以确保没有任何愚蠢的错误。 但是你明白了。

至于扩展GUI,其他建议几乎已经确定了……选择是你是否要将应用程序的控件传递给插件,或者查询插件页面并添加它。

通常,您会看到应用程序将对象传递给插件…请注意, 它必须是允许插件完全控制的对象 。 IE你不会通过标签控件本身,但你可以让应用程序创建标签页并将其传递给插件,基本上说’用它做你想做的’。

“返回null如果没有选项卡”参数在这里有一些权重,但我更喜欢让应用程序处理它使用的所有对象的创建和清理。 不能给你一个很好的理由……也许是那些随心所欲的风格偏好之一。 我不会批评任何一种方法。

 interface IPluginDisplay { bool BuildTab(TabPage PluginTab); // returns false if couldn't create } // tab or no data interface IPluginDisplay { TabPage GetTab(); // creates a tab and returns it } 

HTH,
詹姆士

PS思想我会提到所有插件应用程序的母亲:Windows资源管理器

你说它,探险家得到它。 任何应用程序都可以将属性页添加到任何文件类型,右键单击菜单动态查询DLL以查找条目…如果您想了解插件的灵活性,请阅读Shell编程。 Code Project的一些优秀文章……第五部分讨论了如何在文件的属性页面对话框中添加选项卡。

http://www.codeproject.com/Articles/830/The-Complete-Idiot-s-Guide-to-Writing-Shell-Extens

Microsoft促使开发人员使用这种方法 – WPF和Silverlight的复合应用指南

我通常遵循向插件添加IConfigurable(或其他)类型接口的方法。 但是,我不认为我会让它显示它自己的GUI。 相反,允许它公开您在自己的GUI中正确呈现的设置集合,以便配置插件。 该插件应该能够使用默认设置加载。 但是,在配置之前无需正常运行。 基本上,您最终会加载插件,其中一些插件具有设置管理器的插件。 那有意义吗? 您的设置管理器可以保留所有插件的设置(通常用于编程数据或用户数据),并可以在应用程序启动时注入这些值。

“设置”界面需要包含呈现它所需的信息。 这将包括类别,标题,描述,帮助ID,小部件,小部件参数,数据范围等。我通常使用某种forms的分隔标题来分隔成子类别。 我还发现有一个有效属性和“应用值与未应用值”支持允许取消所有设置更改的表单被丢弃是有帮助的。 在我当前的产品中,我有Real,Bool,Color,Directory,File,List,Real和String类型的设置。 它们都inheritance自相同的基类。 我还有一个渲染引擎,它将每个绑定到一个匹配的控件,并绑定每个参数上的各种参数。

如何为实现共享接口的每个类创建自定义配置部分?

就像是:

       settings="allowRead=false;allowWrite=true;runas=NT AUTHORITY\NETWORK SERVICE"/>  settings="allowRead=false;allowWrite=false;runas=NT AUTHORITY\LOCAL SERVICE"/>  .... 

有一个Initialize(IDictionary keyValuePairs)方法,您将配置节名称传递给它。 它可以加载其关联的部分然后使用

 var section = ConfigurationManager.GetSection("/myCustomSection") as MyCustomSection foreach( var addElement in section.Names ) { var newType = (IMyInterface)Activator.CreateInstance(addElement.Type); var values = (from element in addElement.Value.Split(';') let split = element.Split('=') select new KeyValuePair(split[0], split[1])) .ToDictionary(k => k.Key, v => v.Value); newType.Initialize( values ); //... do other stuff } 

我建议看一下Castle Windsor DI / IoC框架如何处理您所需的额外要求,并考虑使用它。

但一般来说,您的插件应该支持接口,属性或构造函数,通过它们可以从公共源(例如app.config文件)注入配置对象。

Mono.Addins是一个非常好的库,用于实现任何类型的插件(甚至可以从网站更新自己的插件)。 MEF也可以做到这一点,但它的水平有点低。 但MEF将成为.NET 4的一部分。

我将回答有关GUI的问题,每个插件都有一个选项卡。 基本上,我为我的WPF控件选项卡附加了一个行为类,然后针对每个插件进行迭代,该插件具有一个带有IsEnabled属性的viewmodel包装器和一个在其接口内声明的方法Icon()

行为类

  class TabControlBehavior : Behavior { public static readonly DependencyProperty PluginsProperty = DependencyProperty.RegisterAttached("Plugins", typeof(IEnumerable), typeof(TabControlBehavior)); public IEnumerable Plugins { get { return (IEnumerable)GetValue(PluginsProperty); } set { SetValue(PluginsProperty, value); } } protected override void OnAttached() { TabControl tabctrl = this.AssociatedObject; foreach (PluginVM item in Plugins) { if (item.IsEnabled) { byte[] icon = item.Plugin.Icon(); BitmapImage image = new BitmapImage(); if (icon != null && icon.Length > 0) { image = (BitmapImage)new Yemp.Converter.DataToImageConverter().Convert(icon, typeof(BitmapImage), null, CultureInfo.CurrentCulture); } Image imageControl = new Image(); imageControl.Source = image; imageControl.Width = 32; imageControl.Height = 32; TabItem t = new TabItem(); t.Header = imageControl; t.Content = item.Plugin.View; tabctrl.Items.Add(t); } } } protected override void OnDetaching() { } } 

插件界面

 public interface IPlugin { string Name { get; } string AuthorName { get; } byte[] Icon(); object View { get; } } 

和PluginVM类

 public class PluginVM : ObservableObjectExt { public PluginVM(IPlugin plugin) { this.Plugin = plugin; this.IsEnabled = true; } public IPlugin Plugin { get; private set; } private bool isEnabled; public bool IsEnabled { get { return isEnabled; } set { isEnabled = value; RaisePropertyChanged(() => IsEnabled); } } } 

tabcontrol将仅显示已启用的插件及其Icon()方法返回的自定义图标。

插件实现中的Icon()方法的代码应如下所示:

 public byte[] Icon() { var assembly = System.Reflection.Assembly.GetExecutingAssembly(); byte[] buffer = null; using (var stream = assembly.GetManifestResourceStream("MyPlugin.icon.png")) { buffer = new byte[stream.Length]; stream.Read(buffer, 0, buffer.Length); } return buffer; }