一 简介
首先本文假定你已经熟悉VBNET和Visual StudioNET Windows表单设计器
在开发定制Windows表单控件时提供我们自己的下拉框类型编辑器来操作控件的属性常常是非常方便的定制的类型编辑器不仅可以提供更为丰富的设计时刻体验而且可能成为用户是否喜欢你的控件的决定因素
如果你决定创建你自己的下拉式类型编辑器那么它应该遵循与内置的下拉框类型编辑器相类似的模式让我们以Anchor属性为例一种典型的操作该属性的设计时刻用户交互描述如下
· 用户选择属性格子中的Anchor属性并且点击属性格右边的下拉按钮
· 一种良好的图形控件是下拉框它能够允许用户使用鼠标点击边缘或者使用箭头键来高亮某个边缘并使用空格键选择/取消选择它
· 用户可以通过按下ENTER键或点击下拉控件的外部来接收变化为了取消这一变化用户可以按下ESC键
下面让我们来讨论具体的实现技术
二 实现
首先让我们构建一个ResourceImageEditor类型编辑器它允许从当前文件系统中选择一个图像文件(就象内置的ImageEditor类一样)或者从一个程序集的manifest文件中选择一个图像资源而且在用户体验方面该ResourceImageEditor的行为应该类似于系统内置的类型编辑器下面是对我们要求的概述
当用户从属性格子中选择一个属性时该格子就会显示出来—以一个下拉框UI形式显示可以编辑的属性
当点击下拉按钮时当前程序集中的所有图像资源将显示出来
当用户选择一个图像资源项相应的图像即可以从程序集中进行加载
允许选择一个图像文件并且在下拉列表框中的最后一项将标记为Browse当用户点按Browse项将显示经典的打开文件对话框用户能够从中选择一个图像文件
通过单击鼠标或使用箭头键高亮某项并按回车键实际选择它从而允许用户从该下拉列表框中选择一项这个下拉选择可以通过按下ESC键取消
ResourceImageEditor是一个类型编辑器因此它直接或间接地派生自SystemDrawingDesignUITypeEditor类我决定从内置的SystemDrawingDesignImageEditor类进行派生是因为它已经实现了图像文件选择功能也就是说ImageEditorEditValue实现将显示一个文件打开对话框以允许用户从文件系统中选择一个图像文件然后从我的派生类中调用这一功能只需要简单地调用MyBaseEditValue即可
为了实现上面第一个要求(在属性格子中显示下拉箭头按钮)我必须重载GetEditStyle方法以从UITypeEditorEditStyle枚举中返回适当的常数
Public Overloads Overrides Function GetEditStyle( _
ByVal context As ITypeDescriptorContext) As UITypeEditorEditStyle
Return UITypeEditorEditStyleDropDown
End Function
为了显示图像资源列表我必须列举一个给定程序集中的所有资源并且仅在列表中显示图像资源为了简化我决定使用一种简单的约定当一个资源名以一个有效图像文件扩展名(bmpjpggif)结束时我们就认为这是一种图像资源并且把它包括到该下拉列表框中而且我使用图像资源名的集合来填充这个下拉ListBox控件后面详预以详述
开始时被枚举以查询图像资源的程序集就是包含ResourceImageEditor类的程序集然而我们可以通过把ResourceImageEditorResourceAssembly属性设置为任何有效的SystemReflectionAssembly参考来改变它
当用户从列表框中选择一个图像资源名时该图像应该即可从给定的程序集中的manifest文件中加载这是在LoadResourceImage方法内实现的
Private Function LoadResourceImage(ByVal resourceName As String) As Image
DebugAssert(Not resourceName Is Nothing)
Dim ImageStream As SystemIOStream = MeResourceAssemblyGetManifestResourceStream(resourceName)
Return SystemDrawingBitmapFromStream(ImageStream)
End Function
下拉用户接口是通过在重载的EditValue方法内动态地创建和填充一个ListBox控件实现的编辑器也处理由ListBox生成的Click和KeyDown事件因为这是拦截ENTER和ESC键所必需的下列伪码显示了在EditValue方法中的实现逻辑
Public Overloads Overrides Function EditValue()
存储上下文信息以用于下拉ListBox事件处理器
创建并使用可用的图像资源名填充该ListBox
添加我们的特殊Browse项
绑定ListBox事件
在一个下拉窗口中显示该ListBox
End Function
三 几个关键问题与解案
为了开发ResourceImageEditor我创建了一个重载Image属性的MyPictureBox(派生自SystemWindowsFormsPictureBox)以便把ResourceImageEditor指定为该Image属性的类型编辑器
然后我编译这个控件的代码之后就可以把该MyPictureBox控件放到一个表单上并且调用下拉框用户接口
鼠标接口工作得很好然而当我使用键盘选择一项然后按下回车键时该下拉列表框消失而且我的选定内容丢失了(也就是说前一个选择图像并没有改变)我很快发现当按下回车键时该ListBox并没有生成KeyDown事件
尽管ESC键也产生KeyDown事件但这不是一个问题因为该下拉列表框会被自动关闭而且我不必处理当前选择项
很明显在ListBox控件能够处理它们之前这个属性格屏蔽了ENTER和ESC键
为了简化而且还要解决问题我要使用ProcessDialogKey方法在消息预处理期间(处理对话字符例如TABRETURNESCAPE和箭头键)时调用这个方法这个方法是在SystemWindowsFormsControl类内声明的—它简单地把该调用代理给该控件的父级(如果有的话)我已经子类化该ListBox控件并且重载了ProcessDialogKey方法来拦截回车键如下所示
Protected Overrides Function ProcessDialogKey(ByVal keyData As Keys) As Boolean
If keyData = SystemWindowsFormsKeysReturn Then
RaiseEvent EnterPressed(Me EventArgsEmpty)
Return True True意味着我们已经处理了相应的键
Else
Return MyBaseProcessDialogKey(keyData)
End If
End Function
不是从ProcessDialogKey实现内部生成KeyDown事件我决定使用一种更为直接的方式EnterPressed事件为了我修改了ResourceImageEditorEditValue的实现以处理这一事件(而不是KeyDown事件)而且一切都非常顺利
你可以使用这一技术来拦截任何Control派生的类(你使用它来实现你的类型编辑器中的下拉UI)中的ENTER键