编辑几乎所有内容 - 第1部分






4.89/5 (17投票s)
2004 年 9 月 24 日
8分钟阅读

107939

3773
本文档配套源代码,用于编辑各种数据类型和/或复杂 DataSet 的控件。
更正
已更正 SekosPD.Sekos.Windows.Forms
中的一个小 bug,该 bug 会影响在 datagrid
中编辑的图像的尺寸。如果您正在使用此代码,则可能需要重新下载。
在我新的文章 “让您的 DataGrid 编辑几乎任何内容” 中,更详细地介绍了在 DataGrid
中编辑各种类型的内容。
引言
Visual Studio .NET 2003 中的 Windows Forms 提供了一系列标准控件。其中包括用于编辑 string
的 TextBox
,用于编辑布尔值的 CheckBox
,甚至还有 DateTimePicker
。然而,.NET 中存在着种类繁多的类型。此外,微软和开发社区不断地添加新类型。只有一小部分这些类型的数据是我们期望通过用户界面访问的。这就给我们留下了确保我们的控件能够处理这些类型的重大问题。
背景
这是 AgileStudio 对象编辑器的屏幕截图,该编辑器能够编辑 DataSet
中种类繁多的数据类型,这些数据类型可能包含一组复杂的关联表。作为对社区的服务,Sekos 免费提供用于实现此目的的各种控件的源代码,所有这些控件都支持数据绑定。其中包括:
DataToolBar
和DataButton
,它们支持用于导航的复杂绑定(到表或列表)PropertyEditor
,用于编辑大量离散类型DataPicture
,用于编辑图像- 一系列用于
DataGrid
的附加列 GenericEditor
,可以自动为DataSet
中的表生成合适的 UI
在本文中,我们将首先介绍单独使用以及在 DataGrid
列中使用 PropertyEditor
和 DataPicture
控件。
PropertyEditor
我们如何才能编辑大量离散类型,其中一些类型甚至可能还没有被编写出来?这似乎是一个艰巨的任务,但微软已经为我们准备好了所有基础设施:System.ComponentModel.TypeConverter
和 System.Drawing.Design.UITypeEditor
。使用 TypeConverter
和 Editor
属性来创建针对新数据类型的自定义 TypeConverter
和/或 UITypeEditor
的方法已有详细的文档说明。
除其他外,此基础设施由 PropertyGrid
控件在 Visual Studio 中的设计时使用,用于编辑控件的所有属性。在运行时,我们通常不希望访问控件的所有属性。但是,有时允许用户调整单个属性可能是可取的,例如控制 3D 图表的方向。(处理此问题的一种方法是使用某种支持 ICustomTypeDescriptor
的代理对象,以减少 PropertyGrid
可用的属性。)但是,为了实现最大的 UI 控制,最佳解决方案是编写一个实现 IWindowsFormsEditorService
的新控件。支持此服务有很多方面,包括:
- 绘制值图标,例如,颜色编辑器的颜色框样本
- 为枚举类型提供一个下拉列表以从中选择一组标准值
- 根据需要提供自定义下拉控件或弹出对话框。
为了使 PropertyEditor
在编辑特定 Value
时具有适当的行为,它有一个 PropertyType
属性,用于指定 Value
的类型。在设计时配置 PropertyEditor
时,用户输入一个类型名称,该名称通过 TypeTypeConverter.ConvertFrom()
转换为实际类型。
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
System.ComponentModel.Design.ITypeResolutionService typeResolver=
(System.ComponentModel.Design.ITypeResolutionService)context.GetService(
typeof(System.ComponentModel.Design.ITypeResolutionService));
Type t= null;
string typeName= (string)value;
if (typeResolver!=null)
t= typeResolver.GetType(typeName);
else
{
// In the unlikely case that this property is bound to another
// PropertyEditor
// so it can be changed at runtime, ITypeResolutionService won't
// be available
// so we resort to GetType instead.
t= Type.GetType(typeName);
// Hard Code System.Drawing in as an additional assembly to search
if (t==null)
t= typeof(System.Drawing.Color).Assembly.GetType(typeName);
}
return t;
}
return base.ConvertFrom (context, culture, value);
}
如您所见,这在通常情况下(在设计时调用)使用了 ITypeResolutionService
。当然,这在运行时将不可用,在这种情况下,它会回退到 Type.GetType()
。需要注意的一点是,除非为此函数提供完整的程序集限定类型名称,否则此方法仅在调用程序集和 mscorlib
中进行搜索。
我想提到的最后一个属性是 UseStringAsUnderlyingType
。在绑定到其他控件的属性时不需要此属性。但是,当绑定到 DataSet
中的列且数据类型不是 ADO.NET 当前支持的少数类型之一时,就需要此属性。对于不支持的数据类型,Value
将在数据类型中存储为 string
,并通过 TypeConverter.ConvertToInvariantString()
和 TypeConverter.ConvertFromInvariantString()
进行转换。在控件中显示数据时,将使用 TypeConverter.ConvertToString()
以确保数据格式特定于区域性。
因此,现在我们有了一个可以编辑任何数据类型的控件,我们是不是就完成了?唉,事情从来没有这么简单。Image
的默认编辑器假定我们将图像嵌入资源文件中,而不是将其上传到服务器。因此,我们将把这种情况作为特殊情况来处理,并介绍下一个控件。
DataPicture
有一个内置的 PictureBox
控件,其中包含一个 Image
属性。就其本身而言,它严格用于在应用程序设计时设置图像。DataPicture
允许在运行时设置图像。与 PictureBox
不同,它可以获取输入焦点,并具有一个上下文菜单,允许将图像复制到剪贴板和从剪贴板复制,以及从磁盘复制和从磁盘复制。
然而,需要克服的最大问题是 DataSet
中对 Image
数据类型的支持不足。最自然的可用数据类型是 byte[]
,它在 DataSet
架构中显示为 base64binary
。DataBinding
标准不支持 byte[]
和 Image
之间的转换,但可以使用事件处理程序对其进行增强。这是一个分两步完成的过程。首先,实现一个事件处理程序,用于在 DataBindings
集合发生更改时接收通知。在此处理程序中,为特定 Image
的 DataBinding
的 Format
和 Parse
事件附加单独的事件处理程序(如果存在)。这允许我们将所有这些混乱的转换代码封装在控件内部,而不是要求在应用程序本身中编写事件处理程序。
/// Construct a DataPicture
public DataPicture()
{
...
// Register an interest in the DataBindings collection
this.DataBindings.CollectionChanged+=new CollectionChangeEventHandler(
DataBindings_CollectionChanged);
}
// Check whether a Binding for Image has been added and if so
// attach event handlers to handle data conversion
private void DataBindings_CollectionChanged(object sender,
CollectionChangeEventArgs e)
{
Binding binding = this.DataBindings["Image"];
if (binding!=null)
{ binding.Format+=new ConvertEventHandler(binding_Format);
binding.Parse+=new ConvertEventHandler(binding_Parse);
}
}
// An event handler to handle the conversion from binary (byte[]) to Image
// of data entering the control
private void binding_Format(object sender, ConvertEventArgs ev)
{
if (!(ev.Value is Image) && ev.DesiredType==typeof(Image))
{
byte[] img = ev.Value as byte[];
ev.Value= sEmptyBitmap;
if (img!=null && img.Length>0)
{
System.IO.MemoryStream ms = new System.IO.MemoryStream();
try
{
int offset = 0;
ms.Write(img, offset, img.Length - offset);
Bitmap bmp = new Bitmap(ms);
ms.Close();
ev.Value= bmp;
}
catch (Exception)
{
}
}
}
}
// An event handler to handle the conversion from Image to binary (byte[])
// of data leaving the control
private void binding_Parse(object sender, ConvertEventArgs ev)
{
if ((ev.Value is Bitmap) && ev.DesiredType==
typeof(byte[]))
{ Bitmap bmp=
(Bitmap)ev.Value;
System.IO.MemoryStream ms = new System.IO.MemoryStream();
System.Drawing.Imaging.ImageFormat format= bmp.RawFormat;
if (format==
System.Drawing.Imaging.ImageFormat.MemoryBmp) format=
System.Drawing.Imaging.ImageFormat.Bmp;
bmp.Save(ms,format);
ms.Close(); ev.Value=
(Byte[])ms.GetBuffer();
}
else if (ev.Value==null || ev.Value==sEmptyBitmap)
ev.Value=System.DBNull.Value;
}
此机制最适合处理相对较小的图像。然而,将图像作为数据的另一个部分来处理确实具有简单性的优点。无需用图像上传表单使 UI 复杂化。当用户按 **保存** 按钮时,图像将与其他所有数据一起保存。这促进了数据管理的事务性方法。
在服务器端处理 base64binary
(例如,使用 MS SQLServer)相对容易,但超出了当前文章的范围。我只提一下 SELECT
语句中常用的方便的 "FOR BINARY BASE64
" 子句,它通常与 XML 一起使用。
增强 DataGrid
当然,支持简单绑定固然很好。然而,大多数实际应用程序都涉及数据列表,这使得 DataGrid
等复杂绑定控件变得至关重要。要为 DataGrid
实现一种新的列类型,就需要从 DataGridColumnStyle
派生一个新类。因此,提供了 DataGridPropertyEditorColumn
来支持 PropertyEditor
,并提供了 DataGridDataPictureColumn
来支持 DataPicture
。如何实现这一点在 MS 文档中有详细介绍,因此我在此不再赘述。
但简而言之,为了节约资源,DataGrid
不会为每个单元格创建控件。通常,每列有一个控件用于与用户交互,当该列中的某个单元格获得焦点时。否则,将使用 Paint()
方法直接将控件渲染到 Graphics
画布上。在这种情况下,将调用适当控件(PropertyEditor
或 DataPicture
)中实现的 DrawData()
方法,以将所有渲染保持在一个位置。如果所有控件都支持某种具有类似方法的接口,那么在 DataGrid
中就可以实现一个通用的列,这将节省大量工作。
关注点
本文开头的演示项目包含一个小型 MDI 示例,用于演示使用以下示例编辑非标准类型:
- 编辑控件的属性
- 编辑
DataSet
中的项 - 在
DataGrid
中编辑
文档的参考风格的库源代码可在 此处获得。
本文随附的代码基于 AgileStudio 产品的一部分,该产品扩展了 Visual Studio。请查看在 www.sekos.com/ 上的免费试用版,它会自动维护特定用户界面(用于 Windows 或 Web 应用程序)所需的数据集和 SQL StoreProcs。
结论
在本系列的下一部分中,我将讨论 DataGrid
的附加功能。我还将探讨数据导航以及为具有任意复杂表和关系的 dataset
自动生成直观的用户界面。
请通过评价本文来表达您对本系列下一部分的兴趣。
历史
- 2004 年 9 月 24 日:初始版本
许可证
本文没有明确的许可证,但可能包含文章文本或下载文件本身的使用条款。如有疑问,请通过下方的讨论区联系作者。您可以在 此处找到作者可能使用的许可证列表。