DataGridView 图像按钮单元格






4.69/5 (28投票s)
一个可点击的按钮单元格,

引言
DataGridView
有一个可以显示文本的按钮列,也有一个图像列。但是,它没有一个可以显示图像的按钮列。这是一个问题,因为我希望我新项目的视觉效果与使用 C++ Builder 和 TMS Software 的 Advanced StringGrid 编写的一些早期项目保持一致。在这些早期的 C++ 项目中,图像用于显示信息,按钮用于执行操作。你永远不会点击图像来执行操作。带有文本的按钮不是一个可行的选择,因为与带有图标的小按钮相比,它们占用的空间太大了。除了让按钮显示图像外,我还希望能够根据编程或安全逻辑来启用或禁用该按钮。
我遇到的主要技术障碍是,当调用 DataGridView.Columns.Add()
方法时,在 .NET Framework 的后台,只会调用相应 DataGridViewButtonCell
类的空构造函数。因此,创建一个将图像作为构造函数参数传递的扩展按钮单元格类并非设计选项。为每种类型的图像按钮单元格编写硬编码类会产生大量冗余代码,这也是一个问题。
我提出的解决方案是编写一个派生自 DataGridViewButtonCell
类的抽象 DataGridViewImageButtonCell
类。该类有几个具体的方法和一个名为 LoadImages()
的抽象方法。当你从此类派生一个新单元格时,编译器会强制你编写一个新的 LoadImages()
例程。
因此,为了创建一个特定的按钮(例如,一个删除按钮),有三个步骤:
步骤 1:从抽象的 DataGridViewImageButtonCell
类派生一个特定的 DataGridViewImageButtonDeleteCell
类。重写抽象的 LoadImages()
方法,用一个新方法加载代表该新单元格动作的图像。在这个例子中,我会加载删除图像。根据你加载图像的方法,这个例程可能只有三行代码。
public class DataGridViewImageButtonDeleteCell : DataGridViewImageButtonCell
{
public override void LoadImages()
{
// Load the Normal, Hot and Disabled "Delete" images here.
// Load them from a resource file, local file, hex string, etc.
_buttonImageHot = Image.FromFile("C:\\delete_16_h.bmp");
_buttonImageNormal = Image.FromFile("C:\\delete_16.bmp");
_buttonImageDisabled = Image.FromFile("C:\\delete_d.bmp");
}
}
步骤 2:创建一个派生自 DataGridViewButtonColumn
的 DataGridViewImageButtonDeleteColumn
来显示删除按钮单元格。
public class DataGridViewImageButtonDeleteColumn : DataGridViewButtonColumn
{
public DataGridViewImageButtonDeleteColumn()
{
this.CellTemplate = new DataGridViewImageButtonDeleteCell();
this.Width = 22;
this.Resizable = DataGridViewTriState.False;
}
}
步骤 3:将该列添加到网格中以显示删除图像按钮。
DataGridViewImageButtonDeleteColumn columnDelete =
new DataGridViewImageButtonDeleteColumn();
dataGridView1.Columns.Add(columnDelete);
虽然这个解决方案实现了我想要的功能,但我不想在每个我想使用该按钮列的项目中使用本地图像文件或向资源文件中添加图像。我希望能够创建一个删除按钮列,然后将其插入到我拥有的任何项目中,而无需进行任何额外的步骤。因此,我决定将图像嵌入到每个类中,作为字节数组。为了做到这一点,我编写了一个小工具来读取位图并将其转换为十六进制字符串。
using System.IO; // MemoryStream
using Microsoft.VisualBasic;
// Hex function. Also add as a resource to the project.
//-----------------------------
StringBuilder sb = new StringBuilder();
Image image = Image.FromFile("Enter filename here");
MemoryStream ms = new MemoryStream();
image.Save(ms, ImageFormat.Bmp);
byte[] byteArray = ms.ToArray();
for (int idx = 0; idx < byteArray.Length; idx++)
{
// After writing 16 values, write a newline.
if (idx % 15 == 0)
{
sb.Append("\n");
}
// Prepend a "0x" before each hex value.
sb.Append("0x");
// If the hex value is a single digit, prepend a "0"
if (byteArray[idx] < 16)
{
sb.Append("0");
}
// Use the Visual Basic Hex function to convert the byte.
sb.Append(Conversion.Hex(byteArray[idx]));
sb.Append(", ");
}
TextBox1.Text = sb.ToString();
这个工具生成的十六进制字符串会被硬编码到派生的 DataGridViewImageButtonDeleteCell
类的 LoadImages()
方法中。这样,删除图像就成为了类的一部分。通过在我的硬盘驱动器上创建一组派生自抽象 DataGridViewImageButtonCell
类且包含嵌入式图像的具体类,我可以将它们链接到我编写的任何项目中,并且它们看起来都一样,无需额外的努力。这也使得与其他程序员共享它们变得更容易,因为列类、单元格类和嵌入式图像现在都在一个文本文件中。
杂项说明
每个单元格都有一个普通、鼠标悬停和禁用图像。如果你的桌面设置为 Windows 经典主题,或者你的程序中禁用了视觉样式,那么只会看到普通和禁用图像。如果你的程序中启用了 Windows XP 或 Vista 主题并启用了视觉样式,那么鼠标悬停在按钮上时会激活鼠标悬停图像。
另外,这段代码是为 16x16 的图标在 22x22 的单元格中显示的。使用不同尺寸的图标或单元格需要你调整抽象类 Paint()
方法中的变量和常量。
背景
在我早期的程序中,我的网格会显示各种资源,并且每行上的按钮会改变状态或打开表单来修改数据。这些资源可以通过点击按钮来调度、停用、标记为可用等。在项目旁边有一系列按钮可以轻松完成此操作。
在本文和随附的示例中,我使用了保存、打印和删除来展示网格按钮的工作原理。但这仅用于说明目的。在一个实际程序中,我只会选择一行网格然后点击删除按钮,或者将保存和打印放在用户期望看到的文件菜单上。我并不是建议将这些特定的按钮放在网格中是好的 GUI 设计。
待办事项
我无法让我的按钮实现透明效果,但我只为此工作了一天,所以以后如果有需要可能会有更多进展。我不希望在网格按钮上同时放图标和文本,但对于需要这样做的人来说,这可能是一个选项。用于创建十六进制字符串的工具非常基础。如果能将其制作成一个功能齐全的程序,带有一个对话框来选择图像,并允许各种格式而不是仅限于硬编码的 BMP,那就更好了。
历史
- 06/20/08:版本 1.0 发布
- 06/22/08:文章更新,使创建按钮列的过程更加清晰。
我将每个部分标记为步骤 1、步骤 2 和步骤 3,并提供了加载图像的明确示例。