C# 基于GUI的插件体系结构
我正在开发一个大量使用插件的应用程序。这个应用程序是C语言的,我正在考虑用WPF构建配置GUI。我了解了插件架构,了解了如何管理实际的插件本身。然而,每个插件都有自己的配置,这就是我寻求帮助的地方 插件架构很简单——有一个插件实现的接口,我只需将所有插件加载到一个目录中。然而,插件的配置是从哪里来的?我希望有一些通用的方法来处理这个问题,这样每个插件就不必负责读取自己的配置文件。另外,我希望GUI能够扩展每个插件——也就是说,每个安装的插件都应该在GUI中添加一个选项卡,其中包含该插件的特定配置选项。然后在保存时,将保存配置文件C# 基于GUI的插件体系结构,c#,.net,plugins,C#,.net,Plugins,我正在开发一个大量使用插件的应用程序。这个应用程序是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应用程序将只负责将每个模块加载到相应的模块视图中
IPlugin
,另一个用于主机IHost
。插件是通过对主机的引用创建和初始化的(实现IHost
接口)
IHost
然后可以在所有插件之间提供所需的任何通用功能(如读写配置),这些插件也可以更清楚地了解您的主机应用程序,而无需锁定到特定的主机应用程序。例如,您可以编写一个Web服务应用程序和一个使用相同插件API的表单应用程序
另一种方法是使用主机应用程序读取的配置信息初始化插件,但是,我需要插件能够根据需要读取/写入它的配置,并执行其他操作。我通常采用向插件添加IConfigurable(或任何类型)接口的方法。但是,我不认为我会让它显示自己的GUI。相反,允许它公开一组设置,您可以在自己的GUI中适当地呈现这些设置以配置插件。插件应该能够加载默认设置。但是,在配置之前,它不需要正常工作。本质上,您最终加载了一些插件,其中一些插件有用于设置管理器的插件。这有意义吗?您的设置管理器可以保留所有插件的设置(通常是程序数据或用户数据),并可以在应用程序启动时注入这些值
“设置”界面需要包含呈现该界面所需的信息。这将包括类别、标题、描述、帮助ID、小部件、小部件参数、数据范围等。我通常使用某种形式的分隔标题来划分子类别。我还发现,有一个有效的属性和“应用值与未应用值”支持,允许取消一个表单,其中所有更改的设置都将被丢弃,这对我很有帮助。在我当前的产品中,我有类型Real、Bool、Color、Directory、File、List、Real和String的设置。它们都继承自同一基类。我还有一个渲染引擎,它将每个控件与匹配控件绑定,并将每个控件上的各种参数绑定起来。在我看来,这个问题最好作为两个独立的问题来解决
面板
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;
}