0

Our application contains many modules which could be dynamically pushed to the our application after it is installed and running. All those modules might require some display at the UI. So I am thinking that we could build a UI exe which could load UI component from a DLL (or any other type of assembly). Let's say module1 and module2 are active at the machine, we would display a "module1" and "module2" at the left frame of the UI. If user clicks on "module1", the right frame would open the screen for module1 which is loaded from the another assembly (such as DLLs) which is pushed down together with module1.

Just wonder if this pluggable UI architecture is even possible at the Windows Form or not. I did some search on internet and I didn't find any useful information around this.

4

3 に答える 3

3

はい、可能です。私はこれを自分で行いました。

これを行う最善の方法は、プログラムの外部で 2 つ目の DLL を作成することです。その DLL 内で、プラグインが実装するインターフェイスを定義します。次に、メイン EXE 内のフォームにディレクトリ内のすべての DLL をロードさせ、そのインターフェイスを実装するクラスが含まれているかどうかを確認します。プラグイン DLL でも同じ DLL を参照し、モジュールにそのインターフェイスを実装させます。すべてのプラグインに共通したい関数IMyPluginは、UI ですべてをキャストするインターフェイスであるため、それらの関数のみが表示されるようにする必要があります。

//In a 2nd project that compiles as a DLL
public interface IMyPlugin
{
    Control GetControl();
}

///////////////////

//In your main project
private List<IMyPlugin> pluginsList;   

private void MainForm_Load(object sender, EventArgs e)
{ 
    foreach(string pluginPath in Directory.EnumerateFiles(Application.StartupPath + @"\Plugins\", "*.dll"))
    {
        try
        {
            //load the assembly
            Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);

            //Find all types defined in the assembly.
            Type[] types = pluginAssembly.GetTypes();

            //Filter the types to only ones that implment IMyPlugin
            var plugins = types.Where(x => typeof(IMyPlugin).IsAssignableFrom(x));

            //Filter the plugins to only ones that are createable by Activator.CreateInstance
            var constructablePlugins = plugins.Where(x => !x.ContainsGenericParameters && x.GetConstructor(Type.EmptyTypes) != null);

            foreach (var pluginType in constructablePlugins)
            {
                //instantiate the object
                IMyPlugin plugin = (IMyPlugin)Activator.CreateInstance(pluginType);

                pluginsList.Add(plugin);
            }
        }
        catch (BadImageFormatException ex)
        {
            //ignore this exception -- probably a runtime DLL required by one of the plugins..
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString(), "MainForm.MainForm_Load()");
        }
    }

    //Suspend the layout for the update
    this.SuspendLayout();
    this.someFlowLayoutPanelToStoreMyPlugins.SuspendLayout();

    foreach(IMyPlugin plugin in pluginsList)
    {
        this.someFlowLayoutPanelToStoreMyPlugins.Controls.Add(plugin.GetControl());
    }
    //resume the layout
    this.someFlowLayoutPanelToStoreMyPlugins.ResumeLayout(false);
    this.someFlowLayoutPanelToStoreMyPlugins.PerformLayout();
    this.ResumeLayout();
}


//////////////////////


// In your plugin DLL.

public class Plugin : UserControl, IMyPlugin
{
    public Plugin()
    {
        //The code in the main form requires there be a public 
        //  no parameter constructor (either explicitly or implicitly),
        //  UserControls usually have one anyway for InitializeComponent.

        InitializeComponent();
    }

    public Control GetControl()
    {
        return this;
    }

    // The rest of your code.
}

注: このコードは、多くのコードをコピーして貼り付けただけです。そのままで完全に機能するかどうかはわかりませんが、必要な場所に近づくことができます。

于 2012-10-03T21:10:53.167 に答える
1

これはかなり可能であり、WinForms に固有のものではありません。ある時点で、モジュールにその UI を提供するように要求する方法が必要になります。例: モジュールはインターフェイスを実装し、 Panelを返すメソッドを持ちます。これを呼び出すと、UI でそれを使って何でもできます。

例: 右側のパネルは、パネルをホストできるものにすることができます。

インターフェイスは次のようになります

interface IModule
{
    ...
    Panel GetUI();
    ...
}

したがって、ユーザーが左側のペインでモジュールをクリックすると、次のようなものが実行されます。

var selectedModule = GetSelectedModule() 
// this method will do the Reflection to load your assemblies, 
// go through the Types, filter every type that implement IModule and load them.
// then get an instance of the module. (Let me know if you want help on Reflection)

if (!GetConfiguration().IsModuleEnables(selectedModule))
    return; // module not enabled. Ignore click ???

rightPane.Children.Clear();
rightPane.Children.Add(selectedModule.GetUI());
// might want to dock the module as 'Fill' as well.

モジュールが有効になっているかどうかをモジュールに尋ねるよりも、アプリケーション内のモジュールの「アクティブ」状態を維持する方がよいと思います (不正なモジュールが常に true を返す可能性がある場合)。

お役に立てれば。

更新:物事を安全に保つために、モジュールを別のAppDomainにロードすることをお勧めします。ただし、それらの間で UI オブジェクトを渡す際に問題が発生する場合があります。

于 2012-10-03T21:07:28.250 に答える
0

私はこれを試したことがないので、それが機能することを保証することはできませんが、私が見たところ、それは機能するはずです。

exeをビルドするときにモジュールのリストがわかっている場合は、コンポーネントdllを更新するだけで、コンポーネントの機能を更新するのは簡単です。特定のコンポーネントがアクティブであるかどうかに関係なく、dll内から切り替える方法を含めると、後で機能を追加するダミーコンポーネントをdllに「埋める」ことができます。例えば:

using ComponentLibrary;
class Program
{
  static void Main()
  {
    //...
    if (Module1.IsActive()) listModules.Add(Library.Module1);
    if (Module2.IsActive()) listModules.Add(Library.Module2);
  }
  listModules_click()
  {
     // var m = clicked-on-module
     if (m is Module1)
     {
       component = new Module1();
     }
     else if (m is Module2)
     {
       component = new Module2();
     }
  }
}

namespace ComponentLibrary
{
  abstact class Module : Component
  {
     public abstract bool IsActive();
  }
  public class Module1 : Module
  {
     public bool IsActive() { return true; }
  }
  public class Module2 : Module
  {
     public bool IsActive() { return false; }
  }  
}

後で、Module2をコーディングし、IsActive()その結果を。に置き換えtrueます。ただし、署名は変更されないため、exeを再コンパイルする必要はありません。それまでは、実際にModule2をプルアップする方法はありません。

于 2012-10-03T20:43:56.507 に答える