C# - Futási időben beépülő modul készítése

forráskód letöltése
Alkalmazásaink fejlesztésekor nem árt arra gondolni, hogy a program rendelkezzen a továbbfejleszthetőség képességével, vagyis az időközben felmerülő felhasználói igény kielégítése érdekében ne kelljen egy új programot írni. A .NET rendszer megfelelően támogatja a futási időben történő kódfelhasználást, így az alkalmazások kiegészítése új modulokkal csak tervezés kérdése. Cikkünkben elkészítünk egy példát arra, hogy miként használhatunk egy új modult arra, hogy új csomópont jelenjen meg egy már létező program fastruktúrájában.
A mellékelt példa három projektet tartalmaz. A BMClient projekt tartalmazza a Windows-os alkalmazás kódját, mely futási időben felhasználja a Modules mappában megtalálható (oda fordított) BMBuildinLibrary dinamikus könyvtárat. A BMLibrary projekt tartalmazza azokat a segéd-osztályokat és -metódusokat, melyek lehetővé teszik, hogy az új csomópont megjelenjen.
Az alkalmazás Form-ja két szegmensre bomlik. A bal oldali szegmens egy TreeView kontrol, melyben a csomópont megjelenik. A jobb oldali szegmens egy Panel kontrol, melynek helyére a csomóponthoz tartozó felület jelenik meg abban az esetben, ha a betöltődött csomópont két alpontja közül valamelyikre kattintunk. Amennyiben a Modules mappa üres, akkor a fastruktúra nem tartalmaz elemeket.
Vizsgáljuk meg az egyes projekteket.
BMLibrary projekt
A projektben létrehozunk két interfészt, melyekben deklaráljuk a beépülő modulban definiálandó metódusokat és eseményeket. Az első interfész deklarációja a következő:
public interface IModule
{
  IModuleData[] GetData();
  BMControl GetForm(IModuleData d);    
}
A másik interfészben létrehozzuk az eseményt.
public interface IModuleData
{
  event EventHandler DataChanged;
}
Megvalósítunk egy saját attribútumot is, hogy a beépülő csomópont osztályát minősíthessük. Ez az attribútum határozza meg, hogy mi lesz a csomópont felirata.
[AttributeUsage(AttributeTargets.Class)]
public class ModuleNameAttribute : System.Attribute
{
  private string displayName;
  public ModuleNameAttribute(string name) : base()
  {
    displayName = name;
  }
Az attribútumban felül kell írnunk az ősosztály ToString metódusát, hogy a minősítéskor ne az attribútum osztályának neve, hanem az általunk megadott név (jelen esetben „Csomópont” karakterlánc) jelenjen meg.
  public override string ToString()
  {
    return displayName;
  }
}
Végül létrehozunk egy absztrakt kontrol-osztályt, mely majd a csomópontra történő kattintáskor betöltődő szerkesztőablak őse lesz.
public abstract class BMControl : UserControl
{
  protected IModuleData data;
  public BMControl(IModuleData d)
  {
    data = d;
  }
}
BMBuildinLibrary projekt
A BMBuildinClasses.cs állományban létrehozzuk a fent említett interfészek metódusait és eseményét implementáló osztályokat. A BMBuildinData osztály az IModuleData interfész DataChanged eseményét implementálja. Erre akkor lesz szükség, amikor az adott csomópontra kattintva betöltődik a Form, és annak szövegmezőjében megváltoztatjuk a csomópont feliratát. Az osztály NodeName property-je határozza meg, hogy mi lesz a csomópont felirata induláskor.
A BMBuildinModule osztály az IModule interfészt implementálja, vagyis a két metódusának definícióit tartalmazza. A GetData metódusban létrehozzuk az új csomópontokat, és egy tömbben adjuk vissza ezek objektumait.
public IModuleData[] GetData()
{  
  IModuleData[] data = new BMBuildinData[]{new BMBuildinData("Első csomópont"),new BMBuildinData("Második csomópont")};
  return data;
}
A GetForm metódusban létrehozzuk a modul szerkesztőablakát reprezentáló kontrolt, mely a BMLibrary absztrakt osztályából származik.
public BMControl GetForm(IModuleData d)
{
  return new BMBuildinControl((BMBuildinData)d);
}
A beépülő modul kontroljában egy Label és egy TextBox kontrolt találunk. A szövegmező kontrol tartalmazza a csomópont nevét akkor, amikor a csomópontra kattintva megjelenik a kontrol felülete a Windows-os alkalmazás jobboldali szegmensében. Ha a szövegmezőben változik a szöveg, akkor az eredmény rögtön mutatkozik a csomópont feliratában.
private void textBox1_TextChanged(object sender, EventArgs e)
{
  if (sender == textBox1)
  {
    ((BMBuildinData)data).NodeName = textBox1.Text;
  }        
}
BMClient projekt
A kliensalkalmazásban létrehozunk két, a TreeNode osztályból származó osztályt, hogy megteremtsük az infrastruktúrát a leendő csomópontok számára. Ezek neve DataNode és ModuleNode.
A program indulásakor hívjuk meg a LoadModule metódust, melyben megvizsgáljuk, hogy a Modules mappában található-e beépíthető DLL állomány. Amennyiben igen, akkor mindegyikből generálunk egy Assembly osztályt, majd lekérdezzük a típusokat.
if (Directory.Exists("Modules"))
{
  string[] files = Directory.GetFiles("Modules", "*.dll");
  foreach(string f in files)
  {
    Assembly a = Assembly.LoadFrom(f);
    System.Type[] types = a.GetTypes();
Amennyiben a típusok között megtalálható az IModule interfész, vagyis a modul implementálja az interfész metódusait, akkor az adott típust a megfelelő csomópont-típusra konvertáljuk, és a csomópontot a fára fűzzük.
if(type.GetInterface("IModule")!=null)
{
  treeView1.Nodes.Add(new ModuleNode(type));
}
Amikor a Csomópont feliratú elem megjelenik, akkor a két alpontja közül bármelyikre kattinthatunk, betöltődik a BMBuildinControl kontrol, és a szövegmezőben az aktuális felirat jelenik meg. Ez szerkeszthető is. Ekkor úgy járunk el, hogy a létrehozott kontrol-példányt a Panel kontrol gyermekkontroljai közé felvesszük, és a kontrol GetForm metódusával lekérdezett dialógusablakot megjelenítjük.
panel1.Controls.Add(((ModuleNode)node.Parent).Instance.GetForm(((DataNode)node).Data));
Tehetünk egy próbát is a dinamikus beépítés továbbfokozására. Másoljunk egy BMBuildinLibrary.dll állományt a Modules mappába úgy, hogy a nevét megváltoztatjuk. Ekkor már kettő darab DLL áll rendelkezésre a betöltődéshez. A program indulása után két csomópont jelenik meg a fastruktúrában.