ImageListBox - 将本地化自定义对象集合公开为属性






4.74/5 (14投票s)
2002 年 5 月 31 日
8分钟阅读

248267

8042
一个控件,它公开一个自定义对象集合作为属性,该属性可由 VS.NET 窗体设计器编辑并支持本地化
目录
引言
在本文中,我将演示一种创建列表框控件的技术,该控件在每个项的文本左侧绘制一个小图像(参见图 1)。此控件公开 Items
集合属性,其中包含自定义项,这些项可以通过 Visual Studio 的集合编辑器进行编辑。

图 1 运行时控件外观
有几篇关于同一问题的文章,例如 nhgiang 的 ListBox with Icons。但这些文章都没有实现一个具有 Items
属性的控件,该属性可以通过 Visual Studio 的 Windows 窗体设计器进行编辑。它们使用一些手动创建的代码片段,例如
以编程方式添加项 - 就像在 C++ 中一样,没有 Visual C# 的优势。
imageListBox.Items.Add(new ImageListBoxItem("Text 1", 0));
imageListBox.Items.Add(new ImageListBoxItem("Text 2", 1));
imageListBox.Items.Add(new ImageListBoxItem("Text 3", 2));
本文描述的控件实现提供了可视化编辑功能,可加快开发过程。但是,当然,它仍然支持以编程方式管理项。
可视化添加的项 - 您不必记住图像列表中的哪个索引与图像相关联。所有编辑都在集合编辑器中完成,您无需触摸代码(参见图 2)。

图 2 设计时集合编辑
在接下来的段落中,我将简要描述我遇到的问题。为了实现,最好查看代码 - 代码本身说明了一切。
在项目中 Ya 永的控件
如果您想首先尝试演示项目,请不要忘记先编译它(至少是 ImageListBox 控件库),否则控件将不会出现在设计时的测试窗体中。

图 3 控件的主要属性
如果您想在窗体上 Ya 永 ImageListBox
,请也将 ImageList
控件添加到窗体,用图像填充它,然后设置 ImageListBox
的 ImageList
属性,然后开始向 Items
属性添加项(参见图 3)。
控件类
关于控件类没什么好说的。ImageListBox
继承自标准的 ListBox
控件。我们应该实现 ImageList
属性,它允许提供要绘制在列表框中的图像源。另一个步骤是重写 ListBox
的 Items
属性。唯一重要的是设置 [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
属性,以告知设计器我们在设计时希望序列化集合的内容,而不是集合本身。DrawMode
属性设置为 OwnerDrawFixed
值,然后通过 [Browsable(false)]
属性隐藏在属性浏览器中。绘制在 OnDrawItem
受保护的方法中进行,没有什么复杂的需要描述。
集合类实现
有几种实现集合的方法,例如内部类或嵌套类,或者其他方式。在本演示中,我们将使用基类 Items
集合来存储我们集合的对象,因此最好将集合实现为嵌套类,以便访问控件的私有成员。集合类应实现 IList
、ICollection
和 IEnumerable
接口。在这种情况下,我们不能使用 CollectionBase
继承,因为其行为比简单的项存储更复杂。我建议您查看代码以了解实现的详细信息。另一个重要问题是为集合类实现 this[int index]
属性,这将允许访问集合成员。
项类实现。方法 I
从头开始实现项类(无基类)
首先,我们需要为项类实现至少两个构造函数。第一个是无参构造函数,它允许集合编辑器创建“空”项,所有属性都设置为默认值。另一个构造函数是具有最多参数的构造函数,这些参数足以完全重建项对象的所有可编辑属性。在我们的情况下,它是 ImageListBoxItem(string Text, int ImageIndex)
。
项类实现了 ISerializable
和 ICloneable
接口(我不确定是否需要 ICloneable
,但这只是复制了 ListViewItem
实现的接口集)。
ISerializable
接口由集合编辑器使用,以允许在编辑器中设置项属性。每个项都序列化两个属性:Text
(字符串)和 ImageIndex
(整数)。这些属性出现在集合编辑器右侧的属性网格中(参见图 4)。

图 4 ImageListBoxItem
属性。方法 I。
另一个值得注意的问题是如何让集合编辑器显示 ImageIndex
属性的正确值(以及缩略图)。我们将分三个步骤完成此操作。
- 向项类添加
ImageList
只读属性。通过[Browsable(false)]
属性将其隐藏在属性浏览器中。此属性由分配给ImageIndex
属性的UITypeEditor
使用,它是此值编辑器从中获取图像以在组合框中显示的图像列表。
ImageIndex
属性实现之前添加 [TypeConverter(typeof(ImageIndexConverter))]
属性。这告诉编辑器它需要从与项关联的 ImageList 中获取索引。ImageIndex
属性实现之前添加 [Editor("System.Windows.Forms.Design.ImageIndexEditor", typeof(UITypeEditor))]
属性。这告诉集合编辑器为图像索引属性使用标准的 UI 编辑器。
图 5 带有控件 ImageList
中图像的组合框
瞧!这是所需的填充了图像的组合框(参见图 5)。
项类型转换器类实现
最后一步,我们需要 Windows 窗体设计器将以下代码自动添加到我们的窗体的 InitializeComponent()
函数中。
this.listBox1.Items.AddRange(
new Controls.Development.ImageListBoxItem[] {
new Controls.Development.ImageListBoxItem("Granite", 0),
new Controls.Development.ImageListBoxItem("Marble", 1),
new Controls.Development.ImageListBoxItem("Stone", 2),
new Controls.Development.ImageListBoxItem("Stucco", 3)
}
);
这通过实现继承自 TypeConverter
的类型转换器类来完成。我们将使用 ExpandableTypeConverter
(就像 Microsoft 对 ListViewItem
所做的那样)来减少需要重写的函数数量。我们唯一应该重写的函数是 CanConvertTo
和 ConvertTo
。我们应该只转换为 Windows 窗体设计器使用的 InstanceDescriptor
类型。ConvertTo
函数通过提供将出现在设计器添加的代码中的构造函数的 ConstructorInfo
来构建 InstanceDescriptor
对象(在我们的情况下是 ImageListBoxItem(string Text, int ImageIndex)
)。然后,我们通过 [TypeConverter(typeof(ImageListBoxItemConverter))]
属性将此转换器类与 ImageListBoxItem
关联起来。现在,控件已准备好由设计器进行编辑。
方法 I 的优缺点
优点
- 该类没有任何基类(除了
System.Object
)。您可以完全控制您创建的类。
缺点
- 本地化不直接且尚未实现
- 您需要实现类型转换器类
项类实现。方法 II(可本地化!)
另一种解决方案是将集合项类派生自 System.ComponentModel.Component
。在这种情况下,您将无需实现类型转换器类,也无需序列化。所有其他方法和属性都将相同。在这种情况下,集合编辑器外观将有所不同(参见图 6),生成的代码也将不同。

图 6 ImageListBoxItem
属性。方法 II。
自动添加的代码将分为两部分。首先,所有项的成员变量将被添加到您的窗体类中。然后,在窗体的 InitializeComponent
方法中,将为项调用构造函数,将项添加到列表框,然后设置它们的属性。
// Members added to form class automatically
private Controls.Development.ImageListBoxItem graniteItem;
private Controls.Development.ImageListBoxItem marbleItem;
private Controls.Development.ImageListBoxItem stoneItem;
private Controls.Development.ImageListBoxItem stuccoItem;
...
private void InitializeComponent()
{
...
// Constructors first called for the class
members this.graniteItem = new
Controls.Development.ImageListBoxItem();
this.marbleItem = new
Controls.Development.ImageListBoxItem();
this.stoneItem = new
Controls.Development.ImageListBoxItem();
this.stuccoItem = new
Controls.Development.ImageListBoxItem();
...
// Items added to the ImageListBox
this.listBox1.Items.AddRange(
new Controls.Development.ImageListBoxItem[] {
this.graniteItem,
this.marbleItem,
this.stoneItem,
this.stuccoItem});
...
// Member properties set
...
//
// graniteItem
//
this.graniteItem.ImageIndex = 0;
this.graniteItem.Text = "Granite";
...
}
方法 II 的优缺点
优点
- 此方法的最显著优点是实现简单,并且现在可以通过 Windows 窗体设计器本地化控件。
- 无需类型转换器类
- 类实现比方法 I 简单
缺点
- 唯一的缺点是项由于派生自
Component
类而变得有点“重量级”。
结论
本文演示了将集合公开为属性的方法。此技术可能适用于许多其他控件(OutlookBar 的面板、自定义网格控件的列标题和行标题等)。如果您花费几天时间实现此技术中所述的一组类,这将为您使用设计的控件的项目节省时间。无需添加代码行,只需单击、键入即可享受。我已经做到了。
问题
仍然未解决的主要问题是方法 I 的本地化。我 Ya 永 [Localizable(true)]
属性用于所有我想要本地化的成员,但这似乎不起作用。当我将窗体的 Localizable
属性设置为 true
,然后尝试在语言之间切换时,ImageListBox
的内容保持不变。如果有人已经解决了这个问题,请尽可能与我联系。我注意到 ListView
控件也遇到了几乎相同的问题。
更新
- 感谢 James T. Johnson,工具箱位图现在可以正确显示。我添加了一个嵌入的位图资源(ImageListbox.bmp),它出现在 Visual Studio 的工具箱中。资源名称必须与控件名称匹配(减去项目默认名称空间)。
- 已添加另一种实现支持本地化的项类的方法。