通过对话框将 resource/import 图像添加到 VS 设计器中的自定义 UserControl 属性

Add resource/import image to custom UserControl attribute in VS Designer via Dialog(s)

我的目标是创建一个显示一些图像的自定义控件,该控件的用户可以 added/exchanged。因此,如果将其添加到窗体中,GUI 设计人员应该能够更改由编辑相应属性的控件提供的部分或全部图像。

在我的测试项目中,我有一个具有 4 个属性的简单控件:

 public Image MyImage { get; set; } = null;
 public List<int> MyListOfInt { get; set; } = new List<int>();
 public List<Image> MyListOfImages { get; set; } = new List<Image>();
 public ImageList MyImageList { get; set; } = new ImageList();

在 Windows 表单项目中使用此控件,单击

  1. MyImage 弹出 'Select resource' 对话框。好的

  2. MyListOfInt 打开 'Int32 Collection Editor' 对话框。好的

  3. MyListOfImages 弹出 'Image Collection Editor' 对话框,但使用 'Add' 按钮显示消息:

    'Cannot create an instance of System.Drawing.Image because it is an abstract class.'

  4. MyImageList显示为空列表,无法编辑。

我的问题是,如果可以告诉 VS Designer 在单击 'Add' 按钮时使用 'Select resource' 对话框,需要做什么?

从 Marwie 的评论开始,我能够解决问题。

There are three requirements that a collection should meet in order to be successfully persisted with the CollectionEditor:

  1. 该集合必须实现 IList 接口(在大多数情况下,从 System.Collections.CollectionBase 继承是最佳选择)。
  2. 该集合必须有一个索引器 属性。
  3. 集合 class 必须实现以下一种或两种方法:Add and/or AddRange

所以我创建了一个 class 'ImageItem' 包含

  • 一张图片

    [Category("ImageItem")]
    [DefaultValue(typeof(Image), null)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public Image Picture {
      get { return m_Picture; }
      set { m_Picture = value; }
    }
    
  • 一个名字(可选)

    [Category("ImageItem")]
    [DefaultValue(typeof(string), "")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public string Name {
      get { return m_Name; }
      set { m_Name = value; }
    }
    
  • 一个值(可选)

    [Category("ImageItem")]
    [DefaultValue(typeof(int), "-1")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public int Value {
      get { return m_Value; }
      set { m_Value = value; }
    }
    

和一个集合 'ImageCollection' 根据上述条件保存此 class 的实例:

  1. public class ImageCollection : CollectionBase
  2. public ImageItem this[int i]
  3. public ImageItem Add(ImageItem item)

然后我创建了一个只包含这个集合的控件,用一张图片初始化:

public partial class MyControl: UserControl
{
    public MyControl() {
        InitializeComponent();
    }

    private ImageCollection   m_MyImageCollection = new ImageCollection()
    { new ImageItem(0, "Failure", Properties.Resources.Cross), new ImageItem(1, "OK", Properties.Resources.Tickmark) };

    [Browsable(true), Category("A Test"), DisplayName("Image Collection (ImageCollection)"), Description("Edit image collection")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    [Editor(typeof(System.ComponentModel.Design.CollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
    public ImageCollection    MyImageCollection {
        get { return m_MyImageCollection; }
    }
}

编译此代码后,设计者显示 属性。现在可以使用常见的设计器 GUI 控件添加图像。

在我的窗体上使用它时,我试图更改编译到此控件中的默认图像,但我认识到,设计器无法删除内容。它只存储 'Add' 动作。所以我修改了代码以在集合中搜索具有相同 ID 的另一个项目。如果有一个可用,则该实例将被删除并替换为新实例。因此我也必须实施 AddRange 方法。

public ImageItem Add(ImageItem item) {
    for(int i = 0; i < InnerList.Count; i++) {
        if(InnerList[i] is ImageItem) {
            if(((ImageItem)InnerList[i]).Value == item.Value) {
                InnerList.RemoveAt(i);
            }
        }
    }
    this.InnerList.Add(item);
    return item;
}

public void AddRange(ImageItem[] array) {
    foreach(ImageItem item in array) {
        Add(item);
    }
}

所以我最后的 class 是:

public class ImageItem {
  private int               m_Value   = -1;
  private string            m_Name    = "ImageItem";
  private Image             m_Picture = null;

  [Category("ImageItem")]
  [DefaultValue(typeof(int), "-1")]
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
  public int Value {
    get { return m_Value; }
    set { m_Value = value; }
  }

  [Category("ImageItem")]
  [DefaultValue(typeof(string), "")]
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
  public string Name {
    get { return m_Name; }
    set { m_Name = value; }
  }

  [Category("ImageItem")]
  [DefaultValue(typeof(Image), null)]
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
  public Image Picture {
    get { return m_Picture; }
    set { m_Picture = value; }
  }

  public ImageItem() { }

  public ImageItem(int value, string name, Image image) {
    this.m_Value   = value;
    this.m_Name    = name;
    this.m_Picture = image;
  }
}

ImageCollection

public class ImageCollection : CollectionBase {
  public ImageCollection() {}

  public ImageItem this[int i]
  {
    get { return (ImageItem)this.InnerList[i]; }
    set { this.InnerList[i] = value; }
  }

  public ImageItem Add(ImageItem item) {
    for(int i = 0; i < InnerList.Count; i++) {
      if(InnerList[i] is ImageItem) {
        if(((ImageItem)InnerList[i]).Value == item.Value) {
          InnerList.RemoveAt(i);
        }
      }
    }
    this.InnerList.Add(item);
    return item;
  }

  public void AddRange(ImageItem[] array) {
    foreach(ImageItem item in array) {
      Add(item);
    }
  }

  public void Remove(ImageItem item) {
    this.InnerList.Remove(item);
  }

  public bool Contains(ImageItem item) {
    return this.InnerList.Contains(item);
  }

  public ImageItem[] GetValues() {
    ImageItem[] item= new ImageItem[this.InnerList.Count];
    this.InnerList.CopyTo(0, item, 0, this.InnerList.Count);
    return item;
  }

  protected override void OnInsert(int index, object value) {
    base.OnInsert(index, value);
  }
}

我从 MSDN 得到了另一个答案: How to edit UserControl attribute of type ImageList in Designer PropertyGrid (add/remove/exchange images)

我将简要描述一下这个想法。首先创建一个带有 ImageList 属性的新控件。

public partial class NewControl : UserControl {
    public NewControl() {
        InitializeComponent();
    }

    public ImageList MyImageList { get; set; } = null;
}
  1. 然后将此控件拖到任何窗体上。
  2. 另外从工具箱中拖一个 ImageList 控件到此 形式 - 我称之为 'MyImages'.
  3. 使用设计师编辑MyImages → Images
  4. 在属性网格
  5. 中将'MyImages'分配给NewControl的实例属性MyImageList

我在这里看到的唯一缺点是,如果控件已经具有初始化的 ImageList 属性,设计器将无法处理它。如果您在分配另一个列表之前尝试编辑 MyImageList,则设计器会显示该控件附带的控件默认列表。但是无法编辑该列表。

这个解决方案比上面第一个解决方案更容易处理,也更短,所以我更喜欢它。