Outils pour utilisateurs

Outils du site


helloworld:design_pattern:microkernel:csharp

Table des matières

Implementing a Plug-In Architecture in C# Archive du 01/01/2003 le 27/04/2020

Implementing a Plug-In Architecture in C# 2 Archive du 01/01/2003 le 27/04/2020

Implementing a Plug-In Architecture in C# 3 Archive du 01/01/2003 le 27/04/2020

Code source

Base commune

En cas d'exception :

PlugNotValidException.cs
using System;
 
namespace HostCommon
{
  public class PlugNotValidException : System.Exception
  {
    public PlugNotValidException(System.Type type, string Message) : base("The plug-in " + type.Name + " is not valid\n" + Message)
    {
      return;
    }
  }
}

Pour avoir le nom du plugin

PlugDisplayNameAttribute.cs
using System;
 
namespace HostCommon
{
  [AttributeUsage(AttributeTargets.Class)]
  public class PlugDisplayNameAttribute : System.Attribute
  {
    private string _displayName;
 
    public PlugDisplayNameAttribute(string DisplayName) : base()
    {
      _displayName=DisplayName;
      return;
    }
 
    public override string ToString()
    {
      return _displayName;
    }
  }
}

Pour avoir la description du plugin

PlugDescriptionAttribute.cs
using System;
 
namespace HostCommon
{
  [AttributeUsage(AttributeTargets.Class)]
  public class PlugDescriptionAttribute : System.Attribute
  {
    private string _description;
 
    public PlugDescriptionAttribute(string Description) : base()
    {
      _description=Description;
      return;
    }
 
    public override string ToString()
    {
      return _description;
    }
  }
}

L'interface qui contiendra les données de chaque plugin

IPlugData.cs
using System;
 
namespace HostCommon
{
  public interface IPlugData
  {
    event EventHandler DataChanged;
  }
}

Affichage graphique

PlugDataEditControl.cs
using System;
using System.Windows.Forms;
 
namespace HostCommon
{
  public abstract class PlugDataEditControl : System.Windows.Forms.UserControl
  {
    protected IPlugData _data;
 
    public PlugDataEditControl(IPlugData Data)
    {
      _data=Data;
    }
  }
}

L'interface de chaque plugin

IPlug.cs
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
 
namespace HostCommon
{
  public interface IPlug
  {
    // Les données de chaque plugin
    IPlugData[] GetData();
    // Localisation du rendu graphique
    PlugDataEditControl GetEditControl(IPlugData Data);
    // Fonction Enregistrer du plugin
    bool Save(string Path);
    // Fonction Imprimer du plugin
    bool Print(PrintDocument Document);
  }
}

IHM

L'interface graphique est un TreeView.

Chaque plugin est affiché depuis un TreeNode racine (PlugTreeNode) et un TreeNode pour les différentes données contenues dans chaque plugin (DataTreeNode).

DataTreeNode.cs
using System;
using System.Collections;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Xml;
using HostCommon;
 
namespace Host
{
  class DataTreeNode : System.Windows.Forms.TreeNode
  {
    private IPlugData _data;
    public IPlugData Data{get{return _data;}}
 
    public DataTreeNode(IPlugData Data) : base()
    {
      _data=Data;
      this.Text = _data.ToString();
      _data.DataChanged += new EventHandler(this._data_DataChanged);
      return;
    }
 
    private void _data_DataChanged(object sender, EventArgs e)
    {
      this.Text = _data.ToString();
      return;
    }
  }
}
PlugTreeNode.cs
using System;
using System.Collections;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Xml;
using HostCommon;
 
namespace Host
{
  class PlugTreeNode : System.Windows.Forms.TreeNode
  {
    private System.Type _type;
    private IPlug _instance;
 
    public PlugTreeNode(System.Type type) : base()
    {
      _type=type;
      // On crée le plugin
      _instance = (IPlug)Activator.CreateInstance(_type);
 
      // On crée un Node pour chaque donnée
      IPlugData[] data = _instance.GetData();
      foreach(IPlugData d in data)
      {
        this.Nodes.Add(new DataTreeNode(d));  
      }
 
      this.Text = this.DisplayName;
 
      return;
    }
 
    public string DisplayName
    {
      get{return _type.GetCustomAttributes(typeof(PlugDisplayNameAttribute),false)[0].ToString();}
    }
 
    public string Description
    {
      get{return _type.GetCustomAttributes(typeof(PlugDescriptionAttribute),false)[0].ToString();}
    }
 
    public IPlug Instance
    {
      get
      {
        if (_instance==null)
          _instance = (IPlug)Activator.CreateInstance(_type);
 
        return _instance;
      }
    }
  }
}

IHM

HostForm.cs
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using HostCommon;
 
namespace Host
{
  class HostForm : System.Windows.Forms.Form
  {
    private MenuItem _file;
    private MenuItem _save;
    private MenuItem _print;
    private MenuItem _sep;
    private MenuItem _exit;
 
    private TreeView _tree;
    private Splitter _split;
    private Panel _panel;
    private StatusBar _status;
 
    public HostForm () : base ()
    {
      this.Menu = new MainMenu();
      _file = new MenuItem("File", new EventHandler(this._menuItem_Clicked));
      _print = new MenuItem("Print", new EventHandler(this._menuItem_Clicked));
      _save = new MenuItem("Save", new EventHandler(this._menuItem_Clicked));
      _sep = new MenuItem("-");
      _exit = new MenuItem("Exit",new EventHandler(this._menuItem_Clicked));
      _file.MenuItems.AddRange(new MenuItem[]{_print, _save,_sep,_exit});
      this.Menu.MenuItems.Add(_file);
 
      _tree = new TreeView();
      _tree.Dock = DockStyle.Left;
      _tree.AfterSelect += new TreeViewEventHandler(this._tree_AfterSelect);
 
      _split = new Splitter();
      _split.Dock = DockStyle.Left;
 
      _panel = new Panel();
      _panel.Dock = DockStyle.Fill;
      _panel.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
 
      _status = new StatusBar();
      _status.ShowPanels=true;
      _status.Panels.Add("Description");
 
      this.Controls.Add(_panel);
      this.Controls.Add(_split);
      this.Controls.Add(_tree);
      this.Controls.Add(_status);
 
      this.LoadPlugs();
 
      this.Size = new Size(400,350);
      this.Text = "Host Application";
      _status.Panels[0].Width=_status.Width;
      this.Show();
 
      return;
    }
 
    private PlugTreeNode GetSelectedPlug()
    {
      TreeNode node = _tree.SelectedNode as PlugTreeNode;
      if(node!=null)
        return (PlugTreeNode)node;
      else
      {
        node = _tree.SelectedNode.Parent as PlugTreeNode;
        if(node!=null)
          return (PlugTreeNode)node;
      }
 
      return null;
    }
 
    private void _menuItem_Clicked(object sender, EventArgs e)
    {
      if(sender==_print)
      {
        PrintDocument doc = new PrintDocument();
        PrintDialog pd = new PrintDialog();
        pd.Document = doc;
 
        if (pd.ShowDialog()==DialogResult.OK)
        {
          PlugTreeNode node = GetSelectedPlug();
          node.Instance.Print(doc);
        }
      }
      else if(sender==_save)
      {
        SaveFileDialog sfd = new SaveFileDialog();
        if (sfd.ShowDialog()==DialogResult.OK)
        {
          PlugTreeNode node = GetSelectedPlug();
          node.Instance.Save(sfd.FileName);
        }
      }
      else if(sender==_exit)
        this.Close();
    }
 
    private void _tree_AfterSelect(object sender, TreeViewEventArgs e)
    {
      foreach(Control c in _panel.Controls)
        c.Dispose();
      _panel.Controls.Clear();
 
      TreeNode node=null;
      node = e.Node as PlugTreeNode;
 
      if(node!=null)
        _status.Panels[0].Text = ((PlugTreeNode)node).Description;
      else
      {
        node = e.Node as DataTreeNode;
        if(node!=null)
        {
          _status.Panels[0].Text = ((PlugTreeNode)node.Parent).Description;
          _panel.Controls.Add( ((PlugTreeNode)node.Parent).Instance.GetEditControl(((DataTreeNode)node).Data));  
        }
      }  
 
      return;
    }
 
    private void LoadPlugs()
    {
      string[] files = Directory.GetFiles("Plugs", "*.plug");
 
      foreach(string f in files)
      {
        try
        {
          Assembly a = Assembly.LoadFrom(f);
 
          System.Type[] types = a.GetTypes();
          foreach(System.Type type in types)
          {
            if(type.GetInterface("IPlug")!=null)
            {
              if(type.GetCustomAttributes(typeof(PlugDisplayNameAttribute),false).Length!=1)
                throw new PlugNotValidException(type, "PlugDisplayNameAttribute is not supported");
              if(type.GetCustomAttributes(typeof(PlugDescriptionAttribute),false).Length!=1)
                throw new PlugNotValidException(type, "PlugDescriptionAttribute is not supported");
 
              _tree.Nodes.Add(new PlugTreeNode(type));
            }
          }
        }
        catch(Exception e)
        {
          MessageBox.Show(e.Message);
        }
      }
 
      return;
    }
 
    protected override void Dispose(bool disposing)
    {
      GC.SuppressFinalize(this);
 
      base.Dispose(disposing);
 
      return;
    }
 
    [STAThread]
    public static void Main(string[] args)
    {  
      Application.Run(new HostForm());  
      return;
    }
  }
}

Un plugin

Les données clients avec le pattern observé.

CustomerData.cs
using System;
using HostCommon;
 
internal class CustomerData : System.Object, IPlugData
{
  public event EventHandler DataChanged;
 
  internal CustomerData(string CompanyName)
  {
    _companyName = CompanyName;
    return;
  }
 
  public string _companyName;
  public string CompanyName
  {
    get
    {
      return _companyName;
    }
 
    set
    {
      _companyName=value;
      if(DataChanged!=null)
        DataChanged(this, new EventArgs());
 
      return;
    }
  }
 
  public override string ToString()
  {  
 
    return _companyName;
  }
}

Mise à jour de l'IHM.

CustomerDataControl.cs
using System;
using System.Drawing;
using System.Windows.Forms;
using HostCommon;
 
public class CustomerDataControl : PlugDataEditControl
{
  private Label _lblCompanyName;
  private TextBox _txtCompanyName;
 
  internal CustomerDataControl(CustomerData Data) : base(Data)
  {
    _lblCompanyName = new Label();
    _lblCompanyName.Text = "Company Name";
    _lblCompanyName.Size = new Size(100,15);
    _lblCompanyName.Location = new Point(10,20);
 
    _txtCompanyName = new TextBox();
    _txtCompanyName.Size = new Size(150,25);
    _txtCompanyName.Location = new Point(10,40);
    _txtCompanyName.Text = ((CustomerData)_data).CompanyName;
    _txtCompanyName.TextChanged += new EventHandler(this._text_TextChanged);
 
    this.Controls.Add(_lblCompanyName);
    this.Controls.Add(_txtCompanyName);
 
    return;
  }
 
  private void _text_TextChanged(object sender, EventArgs e)
  {
    if(sender==_txtCompanyName)
      ((CustomerData)_data).CompanyName=_txtCompanyName.Text;
  }
}

Le plugin.

CustomerPlug.cs
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
using HostCommon;
 
[PlugDisplayName("Customers")]
[PlugDescription("This plug is for managing customer relationships")]
public class CustomerPlug : System.Object, IPlug
{
  public CustomerPlug() : base()
  {
    return;
  }
 
  public IPlugData[] GetData()
  {
    IPlugData[] data = new CustomerData[]{
            new CustomerData("Laugh Factory")
            ,new CustomerData("Improv")
            };
 
    return data;
  }
 
  public PlugDataEditControl GetEditControl(IPlugData Data)
  {
    return new CustomerDataControl((CustomerData)Data);
  }
 
  public bool Save(string Path)
  {
    MessageBox.Show("todo: add SAVE implementation for CustomerPlug here");
 
    return true;
  }
 
  public bool Print(PrintDocument Document)
  {
    MessageBox.Show("todo: add PRINT implementation for CustomerPlug here");
 
    return true;
  }
}
helloworld/design_pattern/microkernel/csharp.txt · Dernière modification : 2020/04/27 09:36 de root