如何使用MEF在插件DLL的元数据中包含图像?

5

C# .NET 4.0 WinForms

我开始实现来自这篇教程的MEF示例代码,其中描述了为元数据创建自定义ExportAttribute属性。一切都进行得很顺利,直到我尝试在元数据中包含来自资源文件的图像。目标是提取每个插件DLL的标题、描述和图标作为元数据,以构建主程序中的插件菜单。

现在我收到了编译错误:

"An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type"

所以现在我有一个问题,需要解决以下问题:

1)如何在属性中包含图像?

或者

2)如何在不使用属性的情况下包含MEF中的元数据?

这是我正在使用的代码:

在契约类中:

// Metadata contract interface
public interface IPlugInMetadata
{
    string PlugInTitle { get; }
    string PlugInDescription { get; }
    Image PlugInIcon { get; }
}

// Plug-In contract interface
public interface IPlugIn
{
    void StartPlugIn(object systemObject);
    void StopPlugin();
}

插件DLL中的自定义属性:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginMetadataAttribute : ExportAttribute
{
    public string title { get; set; }
    public string description { get; set; }
    public Image icon { get; set; }

    public PluginMetadataAttribute(string plugInTitle, string plugInDescription, Image plugInIcon) 
        : base(typeof(IPlugInMetadata))
    {
        title = plugInTitle;
        description = plugInDescription;
        icon = plugInIcon;
    }
}

最后,Plug-In DLL中的Program类:

[Export(typeof(IPlugIn))]
[PluginMetadata(ResourceFile.PlugInTitle, ResourceFile.PlugInDescription, ResourceFile.PlugInIcon24)]
public class Program : IPlugIn
{
    public void StartPlugIn(object systemObject)
    {
        Console.WriteLine("Start Plug-In: " + ResourceFile.PlugInTitle);
    }

    public void StopPlugin()
    {
        Console.WriteLine("Stop Plug-In: " + ResourceFile.PlugInTitle);
    }
}

这一行代码产生了错误。

[PluginMetadata(ResourceFile.PlugInTitle, ResourceFile.PlugInDescription, ResourceFile.PlugInIcon24)]

显然,ResourceFile不被视为常量,那么我如何将图片作为元数据使用呢?这是否可能?(请注意,该图像已设置为“嵌入在.resx文件中”)
感谢任何帮助或建议!
1个回答

3

找到了解决方案:

好的,据我所知,您无法在MEF元数据中使用图像或图标。 但是,您可以为DLL文件分配一个图标,就像可以为EXE文件一样。 然后可以使用内置于.NET的Icon类型的静态方法读取图标:

Icon MyDLLIcon = Icon.ExtractAssociatedIcon(DLLFilePath);

我仍然不确定为什么可以从资源文件中作为MEF元数据返回字符串,但不能返回嵌入的图标或图像。

由于我一直在努力组合一个具有插件菜单和图标的功能示例程序已经几天了,所以我想发布代码以帮助其他人。


这是一个具有以下功能的完全功能示例项目:

  • 旨在成为一个包含5个项目的单一解决方案(MainProgram、ContractInterfaces、PlugInA、PlugInB、PlugInC)

  • 后置生成事件将自动将每个项目的DLL复制到公共的“插件”文件夹中

  • MainProgram(WinForm)项目将构建可用的DLL插件目录,并使用每个插件的图标和元数据标题填充ListView

  • 双击ListView项将实例化插件(利用延迟实例化),并启动它。

  • 每个插件将在启动时接收对主窗体的引用,创建一个新的TextBox,并将其发布到主窗体以证明它已运行并且可以访问GUI。

  • 所选插件的标题、描述和版本元数据值将打印到控制台窗口


我为每个DLL分配了不同的图标(来自旧的Visual Studio 6 Common Graphics Misc文件夹)

Screenshot

这些图标是从DLL插件中提取出来创建ListView的,TextBox是由DLL在启动后创建并发布到GUI的(在双击ListView中的每个插件项之后)。


将以下代码添加到名为“MainProgram”的全新C# WinForm项目中(我使用的是VS 2010):

由于某种原因,代码示例解析器似乎不喜欢Using语句,因此这里将它们作为项目列出:

  • using System;
  • using System.Collections.Generic;
  • using System.ComponentModel.Composition;
  • using System.ComponentModel.Composition.Hosting;
  • using System.Drawing;
  • using System.IO;
  • using System.Windows.Forms;
  • using ContractInterfaces;
  • (namespace) MainProgram

public partial class Form1 : Form
{
    // Prerequisites to run:
    //      1)  Project, Add Reference, Projects, ContractInterface
    //      2)  Project, Add Reference, .NET, System.ComponentModel.Composition

    [ImportMany(typeof(IPlugIn))]
    private IEnumerable<Lazy<IPlugIn, IPlugInMetadata>> LoadedPlugIns;

    List<PlugInInfo> AvailablePlugIns = null;


    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        // Get a list of the available Plug-Ins
        AvailablePlugIns = GetPlugInList();

        // Prepare an ImageList to hold the DLL icons
        ImageList ImgList = new ImageList();
        ImgList.ColorDepth = ColorDepth.Depth32Bit;
        ImgList.ImageSize = new Size(32, 32);

        // Populate ImageList with Plug-In Icons
        foreach (var item in AvailablePlugIns)
        {
            ImgList.Images.Add(item.PlugInIcon.ToBitmap());
        }

        // Assign the ImageList to the ListView
        listView1.LargeImageList = ImgList;

        int imageIndex = 0;

        // Create the ListView items
        foreach (var item in AvailablePlugIns)
        {
            listView1.Items.Add(item.PlugInTitle, imageIndex);
            imageIndex++;
        }

        listView1.MouseDoubleClick += new MouseEventHandler(listView1_MouseDoubleClick);
    }

    void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
    {
        // Get the Plug-In index number 
        int plugInNum = listView1.SelectedItems[0].Index;

        PlugInInfo selectedPlugIn = AvailablePlugIns[plugInNum];

        // Call the StartPlugIn method in the selected Plug-In.
        // Lazy Instantiation will fully load the Assembly here
        selectedPlugIn.PlugIn.StartPlugIn(this);

        Console.WriteLine("Plug-In Title:          {0}", selectedPlugIn.PlugInTitle);
        Console.WriteLine("Plug-In Description:    {0}", selectedPlugIn.PlugInDescription);
        Console.WriteLine("Plug-In Version:        {0}", selectedPlugIn.PlugInVersion);
        Console.WriteLine();
    }



    private List<PlugInInfo> GetPlugInList()
    {
        // Create a List to hold the info for each plug-in
        List<PlugInInfo> plugInList = new List<PlugInInfo>();

        // Set Plug-In folder path to same directory level as Solution
        string plugInFolderPath = System.IO.Path.Combine(Application.StartupPath, @"..\..\..\Plug-Ins");

        // Test if the Plug-In folder exists
        if (!Directory.Exists(plugInFolderPath))
        {
            // Plug-In Folder is missing, so try to create it
            try
            { Directory.CreateDirectory(plugInFolderPath); }
            catch
            { MessageBox.Show("Failed to create Plug-In folder", "Folder Creation Error:", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); }
        }

        try
        {
            // Create a catalog of plug-ins
            var catalog = new DirectoryCatalog(plugInFolderPath, "*.dll");
            AggregateCatalog plugInCatalog = new AggregateCatalog();
            plugInCatalog.Catalogs.Add(catalog);
            CompositionContainer container = new CompositionContainer(plugInCatalog);

            // This line will fetch the metadata from each plug-in and populate LoadedPlugIns
            container.ComposeParts(this);

            // Save each Plug-Ins metadata
            foreach (var plugin in LoadedPlugIns)
            {
                PlugInInfo info = new PlugInInfo();

                info.PlugInTitle = plugin.Metadata.PlugInTitle;
                info.PlugInDescription = plugin.Metadata.PlugInDescription;
                info.PlugInVersion = plugin.Metadata.PlugInVersion;
                info.PlugIn = plugin.Value;

                plugInList.Add(info);
            }

            int index = 0;

            // Extract icons from each Plug-In DLL and store in Plug-In list
            foreach (var filePath in catalog.LoadedFiles)
            {
                plugInList[index].PlugInIcon = Icon.ExtractAssociatedIcon(filePath);
                index++;
            }
        }
        catch (FileNotFoundException fex)
        {
            Console.WriteLine("File not found exception : " + fex.Message);
        }
        catch (CompositionException cex)
        {
            Console.WriteLine("Composition exception : " + cex.Message);
        }
        catch (DirectoryNotFoundException dex)
        {
            Console.WriteLine("Directory not found exception : " + dex.Message);
        }

        return plugInList;
    }
}


public class PlugInInfo
{
    public string PlugInTitle { get; set; }
    public string PlugInDescription { get; set; }
    public string PlugInVersion { get; set; }
    public Icon PlugInIcon { get; set; }
    public IPlugIn PlugIn { get; set; }
}

现在在主窗体中添加一个名为"listView1"的ListView控件,并将其保留在窗体的右侧。插件动态创建的文本框将显示在左侧。


接下来添加一个名为"ContractInterfaces"的类项目,然后包含以下代码:

  • using System.Windows.Forms;
  • (命名空间) ContractInterfaces

// Prerequisites to run:
//      1)  Project, Add Reference, .NET, "System.Windows.Forms"

public interface IPlugIn
{
    void StartPlugIn(Form mainForm);
}

public interface IPlugInMetadata
{
    string PlugInTitle { get; }
    string PlugInDescription { get; }
    string PlugInVersion { get; }
}

接下来添加一个名为“PlugInA”的类项目,然后包含以下代码:

  • using System;(使用系统)
  • using System.ComponentModel.Composition;(使用组件)
  • using System.Windows.Forms;(使用Windows窗体)
  • using ContractInterfaces;(使用合同接口)
  • (namespace) PlugInA(命名空间为PlugInA)

    // Prerequisites to run:
//      1)  Project, Add Reference, Projects, "ContractInterface"
//      2)  Project, Add Reference, .NET, "System.Windows.Forms"
//      3)  Project, Add Reference, .NET, "System.ComponentModel.Composition"
//      4)  Project, Properties, Build Events, Post-Build event command line:
//          xcopy "$(ProjectDir)$(OutDir)$(TargetFileName)"  "$(SolutionDir)Plug-Ins\" /Y
//      5)  Project, Properties, Build Events, Run the post-build event:, Always
//      6)  Project, Properties, Application, Icon and manifest, [Select an icon]

[Export(typeof(IPlugIn))]
[PluginMetadata]
public class Program : IPlugIn
{
    private Form MainForm;

    public void StartPlugIn(Form mainForm)
    {
        MainForm = mainForm;

        // Place a TextBox on the Main Form
        TextBox textBox = new TextBox();
        textBox.Text = "PlugInA";
        MainForm.Controls.Add(textBox);
        textBox.Width = 65;
        textBox.Height = 20;
        textBox.Top = 0;
        textBox.Left = 0;
    }
}

// Create a custom strong-typed Metadata Attribute for MEF
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginMetadataAttribute : ExportAttribute
{
    public string PlugInTitle { get; set; }
    public string PlugInDescription { get; set; }
    public object PlugInVersion { get; set; }

    public PluginMetadataAttribute()
        : base(typeof(IPlugInMetadata))
    {
        PlugInTitle = "Plug-In A";
        PlugInDescription = "This is Plug-In A";
        PlugInVersion = "1.0.0.0";
    }
}

接下来添加一个名为 "PlugInB" 的类项目,然后包含以下代码:

  • using System; (使用系统库)
  • using System.ComponentModel.Composition;(使用组件组合库)
  • using System.Windows.Forms;(使用窗体库)
  • using ContractInterfaces;(使用契约接口)
  • (namespace) PlugInB(命名空间为PlugInB)

// Prerequisites to run:
//      1)  Project, Add Reference, Projects, "ContractInterface"
//      2)  Project, Add Reference, .NET, "System.Windows.Forms"
//      3)  Project, Add Reference, .NET, "System.ComponentModel.Composition"
//      4)  Project, Properties, Build Events, Post-Build event command line:
//          xcopy "$(ProjectDir)$(OutDir)$(TargetFileName)"  "$(SolutionDir)Plug-Ins\" /Y
//      5)  Project, Properties, Build Events, Run the post-build event:, Always
//      6)  Project, Properties, Application, Icon and manifest, [Select an icon]

[Export(typeof(IPlugIn))]
[PluginMetadata]
public class Program : IPlugIn
{
    private Form MainForm;

    public void StartPlugIn(Form mainForm)
    {
        MainForm = mainForm;

        // Place a TextBox on the Main Form
        TextBox textBox = new TextBox();
        textBox.Text = "PlugInB";
        MainForm.Controls.Add(textBox);
        textBox.Width = 65;
        textBox.Height = 20;
        textBox.Top = 30;
        textBox.Left = 0;
    }
}

// Create a custom strong-typed Metadata Attribute for MEF
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginMetadataAttribute : ExportAttribute
{
    public string PlugInTitle { get; set; }
    public string PlugInDescription { get; set; }
    public object PlugInVersion { get; set; }

    public PluginMetadataAttribute()
        : base(typeof(IPlugInMetadata))
    {
        PlugInTitle = "Plug-In B";
        PlugInDescription = "This is Plug-In B";
        PlugInVersion = "1.0.0.1";
    }
}

接下来添加一个名为“PlugInC”的类项目,然后包含以下代码:

  • 使用System;
  • 使用System.ComponentModel.Composition;
  • 使用System.Windows.Forms;
  • 使用ContractInterfaces;
  • (命名空间) PlugInC

// Prerequisites to run:
//      1)  Project, Add Reference, Projects, "ContractInterface"
//      2)  Project, Add Reference, .NET, "System.Windows.Forms"
//      3)  Project, Add Reference, .NET, "System.ComponentModel.Composition"
//      4)  Project, Properties, Build Events, Post-Build event command line:
//          xcopy "$(ProjectDir)$(OutDir)$(TargetFileName)"  "$(SolutionDir)Plug-Ins\" /Y
//      5)  Project, Properties, Build Events, Run the post-build event:, Always
//      6)  Project, Properties, Application, Icon and manifest, [Select an icon]

[Export(typeof(IPlugIn))]
[PluginMetadata]
public class Program : IPlugIn
{
    private Form MainForm;

    public void StartPlugIn(Form mainForm)
    {
        MainForm = mainForm;

        // Place a TextBox on the Main Form
        TextBox textBox = new TextBox();
        textBox.Text = "PlugInC";
        MainForm.Controls.Add(textBox);
        textBox.Width = 65;
        textBox.Height = 20;
        textBox.Top = 60;
        textBox.Left = 0;
    }
}

// Create a custom strong-typed Metadata Attribute for MEF
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginMetadataAttribute : ExportAttribute
{
    public string PlugInTitle { get; set; }
    public string PlugInDescription { get; set; }
    public object PlugInVersion { get; set; }

    public PluginMetadataAttribute()
        : base(typeof(IPlugInMetadata))
    {
        PlugInTitle = "Plug-In C";
        PlugInDescription = "This is Plug-In C";
        PlugInVersion = "1.0.0.2";
    }
}

解决方案应该长这样: Solution 右键单击解决方案,然后选择“项目依赖项...”。设置如下依赖项:
- MainProgram - 依赖于 - ContractInterface - PlugInA - 依赖于 - ContractInterface - PlugInB - 依赖于 - ContractInterface - PlugInC - 依赖于 - ContractInterface - ContractInterface - 依赖于 - [无]
右键单击解决方案,然后选择“项目构建顺序...”。构建顺序如下:
1. ContractInterface 2. PlugInA 3. PlugInB 4. PlugInC 5. MainProgram
构建并运行程序。你应该可以在与解决方案文件(*.sln)在同一目录级别的新“Plug-Ins”文件夹中看到三个DLL文件被复制。如果没有,请检查项目构建顺序、依赖项以及是否按照插件代码中的注释输入了Post-Build事件。如果文件存在,则窗体中的ListView应该被插件条目填充。双击每个ListView条目以启动插件。
玩得开心,希望这对某个人有所帮助....

1
你遇到的限制实际上是属性问题,而不是MEF的问题。它们在能够保存的内容方面相当有限。例如,你可以将资源的路径作为字符串存储,然后拥有一个Image属性,你不需要设置它,但它会使用路径来读取嵌入式资源。 - flq
我想通过元数据传递图标,这样我就不必实例化插件程序集来创建菜单了。上面的解决方案解决了这个问题,但我认为你是正确的——这是嵌入属性中的对象类型限制的问题。 - user1689175
1
如果我在元数据中包含Icon类型,一切都可以编译并运行到语句**container.ComposeParts(this);**,但会返回错误:属性“PlugInA.PluginMetadataAttribute.PlugInIcon”的类型为“System.Drawing.Icon”,这是无效的元数据类型。元数据只能包含可以嵌入到属性中的已编译可用类型的值。有关有效类型的详细信息,请参阅C#规范17.1.3节。 - user1689175
我认为您的解决方案无论如何都会实例化插件,因为您在GetPlugInList方法中使用了"info.PlugIn = plugin.Value"。 - MTR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接