C# 基于GUI的插件体系结构

C# 基于GUI的插件体系结构,c#,.net,plugins,C#,.net,Plugins,我正在开发一个大量使用插件的应用程序。这个应用程序是C语言的,我正在考虑用WPF构建配置GUI。我了解了插件架构,了解了如何管理实际的插件本身。然而,每个插件都有自己的配置,这就是我寻求帮助的地方 插件架构很简单——有一个插件实现的接口,我只需将所有插件加载到一个目录中。然而,插件的配置是从哪里来的?我希望有一些通用的方法来处理这个问题,这样每个插件就不必负责读取自己的配置文件。另外,我希望GUI能够扩展每个插件——也就是说,每个安装的插件都应该在GUI中添加一个选项卡,其中包含该插件的特定配置

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

插件架构很简单——有一个插件实现的接口,我只需将所有插件加载到一个目录中。然而,插件的配置是从哪里来的?我希望有一些通用的方法来处理这个问题,这样每个插件就不必负责读取自己的配置文件。另外,我希望GUI能够扩展每个插件——也就是说,每个安装的插件都应该在GUI中添加一个选项卡,其中包含该插件的特定配置选项。然后在保存时,将保存配置文件


<> P>我建议看一下DI/IOC框架是如何处理你所需要的额外要求的,也许可以考虑使用它。p>
但一般来说,您的插件应该支持一个接口、属性或构造函数,通过该接口,您可以从app.config文件等公共源注入配置对象。

为可配置插件定义一个接口:

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);
}
当然,这个选项限制性更大,也更安全,因为您可以完美地限制插件与配置窗口的交互方式。

看看。使用类似的方法应该可以让每个插件定义选项卡。然后,shell应用程序将只负责将每个模块加载到相应的模块视图中


Mono.Addins是一个非常好的库,用于实现任何类型的插件(即使是可以从网站进行自我更新的插件)。MEF也可以实现这一点,但它的级别较低。但是MEF将成为.NET 4的一部分。

Microsoft促使开发人员使用这种方法-

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

IHost
然后可以在所有插件之间提供所需的任何通用功能(如读写配置),这些插件也可以更清楚地了解您的主机应用程序,而无需锁定到特定的主机应用程序。例如,您可以编写一个Web服务应用程序和一个使用相同插件API的表单应用程序


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

我通常采用向插件添加IConfigurable(或任何类型)接口的方法。但是,我不认为我会让它显示自己的GUI。相反,允许它公开一组设置,您可以在自己的GUI中适当地呈现这些设置以配置插件。插件应该能够加载默认设置。但是,在配置之前,它不需要正常工作。本质上,您最终加载了一些插件,其中一些插件有用于设置管理器的插件。这有意义吗?您的设置管理器可以保留所有插件的设置(通常是程序数据或用户数据),并可以在应用程序启动时注入这些值


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

在我看来,这个问题最好作为两个独立的问题来解决

  • 允许每个插件指定一个配置对话框(或类似的东西,可能是标签的形式,如您所建议的)
  • 为插件提供一个集中的配置文件/数据库和一个API来存储它们的配置信息
  • 允许每个插件指定一个配置对话框 这里的基本方法是让每个插件定义一个属性,该属性将返回主机在选项卡中显示的
    面板

    interface IPlugin
    {
        ...
    
        System.Windows.Controls.Panel ConfigurationPanel { get; }
    }
    
    主机可以为每个加载的插件创建一个选项卡,然后将每个插件的配置面板放在正确的选项卡中

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

    另一种类似的方法是定义方法而不是属性。在这种情况下,每次显示配置对话框时都会调用该方法,并返回一个新的面板实例。这可能是斯洛威
    interface IPlugin
    {
        ...
    
        System.Windows.Controls.Panel ConfigurationPanel { get; }
    }
    
    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();
    }
    
    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");
    
    <configuration>
      <configSections>
         <add name="myCustomSection" type="MySharedAssembly.MyCustomSectionHandler, MySharedAssembly"/>
      </configSections>
      <myCustomSection>
        <add name="Implementation1" 
             type="Assembly1.Implementation1, Assembly1"/>
             settings="allowRead=false;allowWrite=true;runas=NT AUTHORITY\NETWORK SERVICE"/>
        <add name="Implementation2" 
             type="Assembly1.Implementation2, Assembly1"/>
             settings="allowRead=false;allowWrite=false;runas=NT AUTHORITY\LOCAL SERVICE"/>
      </myCustomSection>
      ....
    
    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<string,string>(split[0], split[1]))
                      .ToDictionary(k => k.Key, v => v.Value);
    
        newType.Initialize( values );
        //... do other stuff
    }
    
    interface IExposedProperties {
        Dictionary<string,string> GetProperties();
        void SetProperties(Dictionary<string,string> Properties);
    }
    
    private void SavePluginSettings(List<IExposedProperties> PluginProperties) {
    
        //  Feel free to get fancy with LINQ here
        foreach (IExposedProperties pluginProperties in PluginProperties) {
    
            foreach (KeyValuePair<string,string> 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
    
            } 
    
        }
    
    }
    
    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
    }
    
        class TabControlBehavior : Behavior<TabControl>
    {
        public static readonly DependencyProperty PluginsProperty =
           DependencyProperty.RegisterAttached("Plugins", typeof(IEnumerable<PluginVM>), typeof(TabControlBehavior));
    
    
        public IEnumerable<PluginVM> Plugins
        {
            get { return (IEnumerable<PluginVM>)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; }
    }
    
    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);
            }
        }
    
    }
    
    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;
    }