XList服务器控件

分享于 

30分钟阅读

Web开发

  繁體

Sample Image - xlist.gif

介绍

从我开始玩HTML的时候,我对于缺少一个水平滚动条是很烦恼的。 <选择> 所以我就把它放在 <选择> 变成 <div> 然后,在摆弄了一段时间之后,我得到了一个解决方案。

第一个问题是,如果你使用箭头键在列表中 cursor,你可以消失在底部。 <div> 未将选定项保留在可见区域的可见区域中 <div> 我在JavaScript中也修复了。

然后到了 ASP.NET。 我想封装解决方案在服务器控件中,但我想只要要做到这一点,我也可以更正 ListBox 服务器控件的三个

  • 尽管 ListBoxDropDownList 几乎都是相同的控件,但它们是独立的服务器控件。
  • ListBox ( 以及 DropDownList ) 不支持 <optgroup> 标记,它是 <选择> 元素模型的对象。
  • ListBox ( 还有 DropDownList ) 共享一个Microsoft调用"。按设计要求进行的"的Bug。 你可以将 Attributes 添加到 ListItem 中,但:
  • 它们不呈现,而且
  • 它们不被保存在ViewState中。
  • 这就是为什么他们首先在 ListItem 类中使用 Attributes 属性问题的问题。 对我来说,看起来他们开始实现它,但从来没有过来完成它。

组合列表框和 DropDownList

微软 ListControl 作为 ListBoxDropDownListCheckBoxListRadioButtonList的基础。 我必须假设最后两个没有用 <选择> 作为在 Page 上呈现的HTML元素,他们只是决定单独完成。 因为 ListBoxDropDownList 之间的区别是次要的。

我以前已经做过,我使用Lutz的反射器来捕获组成 ListBox 控件的代码。 这是必需的,因为 ListItem 不是可以继承的,你稍后将看到如何在该类中进行修改。

ListBoxListControl 继承,DropDownList 也继承。 我检查了两者之间的差异,并简单地使这些差异依赖于 Enum 我创建了。

PublicEnum XListType
 ListBox = 0 DropDownList = 1EndEnum'XListType

我在 XList 中创建了一个基于这里的属性 Enum :

<DefaultValue(0), Category("Behavior"), Description("XList_XListType")> _PublicOverridableProperty XListType() As XListType
 GetDim obj1 AsObject = Me.ViewState("XListType")
 If (Not obj1 IsNothing) ThenReturnCType(obj1, XListType)
 EndIfReturn XListType.ListBox
 EndGetSet(ByVal value As XListType)
 If ((value <XListType.ListBox) OrElse (value> _
 XListType.DropDownList)) ThenThrowNew ArgumentOutOfRangeException("value")
 EndIfMe.ViewState("XListType") = value
 EndSetEndProperty'XListType

你可以查看我在源代码中包含的OpenList,以查看我使代码有条件的所有位置。 例如,在 AddAttributesToRender 重写中,有一些代码 Having 与 SelectionMode 属性有关,它只与 ListBox 有关。 在 DropDownList 中不能有多个选择。

IfMe.XListType = XListType.ListBox Then writer.AddAttribute(HtmlTextWriterAttribute.Size, _
 Me.Rows.ToString(NumberFormatInfo.InvariantInfo))
 If (Me.SelectionMode = ListSelectionMode.Multiple) Then writer.AddAttribute(HtmlTextWriterAttribute.Multiple, "multiple")
 EndIfEndIf

如你所见,我只是在条件语句中包装了这段代码,因这里只在 XList 控件处于 ListBox 模式时才运行。

还有一些其他障碍。 一些原始代码引用了其他类中的方法,这些方法被标记为 好友 这对于微软来说是很好的,因为它们总是在同一个程序集中。 但我的控件位于单独的程序集中,因此出错,告诉我该方法不可访问。

我真的不想重新创建 Page 控件,这是导致这个问题的第一个控件。 所以我用反射来处理它。

OnPreRender 覆盖包含了这一行代码:

Me.Page.RegisterPostBackScript()

我用这个替换了它:

Dim methodInfo As methodInfo = _
 Me.Page.GetType.GetMethod("RegisterPostBackScript", _
 BindingFlags.Instance Or BindingFlags.NonPublic)IfNot (methodInfo IsNothing) Then methodInfo.Invoke(Me.Page, NewObject() {})EndIf

当你使用自定义控件时,你可能会发现自己经常遇到这个问题。 这就是它周围的路。

此外,微软代码使用了 宋体 我知道这将在下一版. NET 中可以用,但这并没有帮助我。 所以我不得不把这些实例转换成我可以实际使用的代码。

例如在 OnDataBinding 替代中,此行出现了:

Dim collection1 As ICollection = _
 TryCast(enumerable1, ICollection)

我用这个替换了它:

Dim collection1 As ICollectionIfTypeOf enumerable1 Is ICollection Then collection1 = CType(enumerable1, ICollection)Else collection1 = NothingEndIf

因为这本质上就是 宋体 如果可以,它将转换为你想要的Type,如果它不能返回,

这就是我把两个控件组合在一起的。 如我所说,你可以在源代码中找到它作为 OpenList

添加 <optgroup> 支持

实际上 ListBoxDropDownList 是独立的控制并不是那么大的交易。 但是当你像我一样懒惰时,Having 修改两个控件的前景似乎是无意义的。 而且由于 ListBoxDropDownList 都应该支持 <optgroup> 标记,组合它们,节省了我的时间和工作。

首先,我必须决定我要怎么做。 在 XTable 控件中,我添加了对 <螺纹> , <tbody><tfoot> 我决定在 TableTableRow 之间添加一个层次结构,我称之为 TableRowGroup。 我考虑在这里做同样的事情;使 XListItem 成为 OptGroup 类的子级,并在 XList 中 Having 类的OptGroupCollection 类。

幸运的是,在这之前我恢复了自己的状态,并决定只将 OptGroup 属性添加到 XListItem。 这将允许控件的数据绑定功能像以前一样继续,而 Having XListItem 在单独的OptGroup 中将使它的成为一个真正的混乱。 我可以处理 <optgroup> 方法 XListRenderContents 中的标记。 因此我在 XListItem 中创建了这个属性:

<DefaultValue("")> _PublicProperty OptGroup() AsStringGetReturnMe._optGroup
 EndGetSet(ByVal value AsString)
 Me._optGroup = value
 IfMe.IsTrackingViewState ThenMe._misc.Set(4, True)
 EndIfEndSetEndProperty'OptGroup

你可能想知道这一行是什么:

Me._misc.Set(4, True)

微软的程序员似乎决定在 ListItem 类中放置一个全局 BitArray 变量。 他们把 Const 类中标识这里 BitArray 中的各个元素所表示的内容的声明:

PrivateConst _SELECTED AsInteger = 0PrivateConst _MARKED AsInteger = 1PrivateConst _TEXTISDIRTY AsInteger = 2PrivateConst _VALUEISDIRTY AsInteger = 3

由于某些原因,他们选择不使用这些常数,而只是使用数值,但是有助于我知道 _misc 中的项。 我添加了以下内容:

PrivateConst _OPTGROUPISDIRTY AsInteger = 4

因此,当通过 Set 访问器修改 OptGroup 属性时,它将 XListItem 标记为已经修改的Having。 TextValue 属性已经用于 _misc(2)_misc(3)

我还为 OptGroup 添加了一些定制的状态管理代码。 在 LoadViewStateSaveViewState 中,我将 state 对象替换为 对象( ) ,并让 state(0) 替换用于 _misc 状态的state 变量。 我在 OptGroup 属性中使用了 state(1)

在重写的XListRenderContents 中,我在每次呈现之前添加了这里代码 <选项> 标签:

'render optgroups if they're enabledIfMe.EnableOptGroups ThenDim sPrevOptGroup AsStringDim sOptGroup AsString = item1.OptGroup
 'if the optgroup has changed, unless it's the first'optgroup, end the previous optgroupIfNot sOptGroup = sPrevOptGroup AndNot num2 = 0Then writer.WriteEndTag("optgroup")
 writer.WriteLine()
 EndIf'if it's the first optgroup, or if the optgroup'has changed, start a new optgroupIfNot sOptGroup = sPrevOptGroup Or num2 = 0Then writer.WriteBeginTag("optgroup")
 writer.WriteAttribute("label", sOptGroup)
 writer.Write(">"c)
 writer.WriteLine()
 sPrevOptGroup = sOptGroup
 EndIfEndIf

完成这里操作后,XList 控件支持 <optgroup> 标签,但没有问题。 但是我认为扩展数据绑定功能以使用 OptGroup 以及 TextValue 来填充控件是很好的。 因此,我添加了以下两个属性:

<Description("XList_DataOptGroupField"), _
 Category("Data"), DefaultValue("")> _PublicOverridableProperty DataOptGroupField() AsStringGetDim obj1 AsObject = Me.ViewState("DataOptGroupField")
 If (Not obj1 IsNothing) ThenReturnCType(obj1, String)
 EndIfReturnString.Empty
 EndGetSet(ByVal value AsString)
 Me.ViewState("DataOptGroupField") = value
 EndSetEndProperty'DataOptGroupField<Description("XList_DataOptGroupFormatString"), _
 DefaultValue(""), Category("Data")> _PublicOverridableProperty DataOptGroupFormatString() AsStringGetDim obj1 AsObject = Me.ViewState("DataOptGroupFormatString")
 If (Not obj1 IsNothing) ThenReturnCType(obj1, String)
 EndIfReturnString.Empty
 EndGetSet(ByVal value AsString)
 Me.ViewState("DataOptGroupFormatString") = value
 EndSetEndProperty'DataOptGroupFormatString

我修改了数据绑定涉及的各种方法,以包括这些属性。 你可以查看源代码,看看它是如何完成的。

哦,哪里,哦,在哪里,我的属性消失了。

我不知道为什么微软决定不支持 ListItemAttributes。 我使用 background-colorcolor 之类的样式 <选择> 有时,我甚至使用 label 属性来保存数据库中的额外数据。 发现微软知道这种缺乏功能,甚至不承认它是一个 Bug 是非常烦人的。

所以我决定解决他们。 你认为 Attributes 属性已经保留状态,因为它看起来如下所示:

<Browsable(False), _
 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _PublicReadOnlyProperty Attributes() As System.Web.UI.AttributeCollection
 GetIf (Me._attributes IsNothing) ThenMe._attributes = New _
 System.Web.UI.AttributeCollection(New StateBag(True))
 EndIfReturnMe._attributes
 EndGetEndProperty'Attributes

毕竟,什么是 新建 StateBag 如果它不能真正维持状态? 但显然没有我必须找到另一种方法。 我不能将 Attribute 属性保存在 SaveViewState 中并在 LoadViewState 中加载,因为 AttributeCollection 不是可以序列化的( 这就是为什么它不能保持状态开始的原因,现在我想)。 所以我决定使用 Pair的array。 我可能会用一个二维的array 字符串 s,但我不认为它会有什么差别,而且我很少会用 Pair的。

首先,我添加了一个新的全局常量,并将 _misc 更改为 BitArray(6):

PrivateConst _ATTRIBUTESISDIRTY AsInteger = 5

然后我将此行添加到 IAttributeAccessor.SetAttribute 方法中,以便使属性更改使 XListItem 与其他属性一样脏:

Me._misc.Set(5, True)

然后,它只是修改 SaveViewStateLoadViewState的问题,如下所示:

FriendSub LoadViewState(ByVal state AsObject)
 Dim arrState AsObject() = CType(state, Object())
 If (Not arrState(0) IsNothing) ThenIfTypeOf arrState(0) Is Pair ThenDim pair1 As Pair = CType(arrState(0), Pair)
 If (Not pair1.First IsNothing) ThenMe.Text = CType(pair1.First, String)
 EndIfMe.Value = CType(pair1.Second, String)
 ElseMe.Text = CType(arrState(0), String)
 EndIfEndIf'custom state management for OptGroupIfNot arrState(1) IsNothingThenMe.OptGroup = CType(arrState(1), String)
 EndIf'custom state management for AttributesIfNot arrState(2) IsNothingThenIfTypeOf arrState(2) Is Pair() ThenDim colAttributes As Pair() = CType(arrState(2), Pair())
 For i AsInteger = 0To colAttributes.Length - 1Me.Attributes.Add(colAttributes(i).First.ToString, _
 colAttributes(i).Second.ToString)
 Next i
 EndIfEndIfEndSub'LoadViewState

我将 Pair array 加载到 XListItemAttributes 属性中。

FriendFunction SaveViewState() AsObjectDim arrState(2) AsObjectIf (Me._misc.Get(2) AndAlsoMe._misc.Get(3)) Then arrState(0) = New Pair(Me.Text, Me.Value)
 ElseIfMe._misc.Get(2) Then arrState(0) = Me.Text
 ElseIfMe._misc.Get(3) Then arrState(0) = New Pair(Nothing, Me.Value)
 Else arrState(0) = NothingEndIf'custom state management for OptGroup arrState(1) = Me.OptGroup
 ''custom state management for AttributesIfMe.Attributes.Count> 0ThenReDim _attributes2(Me.Attributes.Count - 1)
 Dim i AsInteger = 0 
 Dim keys As IEnumerator = Me.Attributes.Keys.GetEnumerator
 Dim key AsStringWhile keys.MoveNext()
 key = CType(keys.Current, String)
 _attributes2(i) = New Pair(key, Me.Attributes.Item(key))
 i += 1EndWhile 
 arrState(2) = _attributes2
 EndIfReturn arrStateEndFunction'SaveViewState

我将 AttributesXListItem 复制到 Pair s的array 中,并保存到了 XListItem 视图中。

为了保持自己的状态,ListItemAttributes 所需的更改微不足道,而且我无法理解为什么微软控件不这样做。

水平滚动

以前的更改都是 跨浏览器 兼容的。 这个不是。

可以在任何浏览器中使用 XList 控件,但打开 EnableHScroll 将使它不能在大多数non-IE6+浏览器中工作。

<Category("Appearance"), DefaultValue(False), Description("XList_EnableHScroll")> _PublicOverridableProperty EnableHScroll() AsBooleanGetDim obj1 AsObject = Me.ViewState("EnableHScroll")
 If (Not obj1 IsNothing) ThenReturnCType(obj1, Boolean)
 EndIfReturnFalseEndGetSet(ByVal value AsBoolean)
 Me.ViewState("EnableHScroll") = value
 EndSetEndProperty'EnableHScroll

HeightWidth 属性不总是在 ListBox 中设置。 一般来说,Height 被忽略了 Size,它设置了一个单位等于一个 ListItem 高度的ListBox 高度。 当使用 Width 时,它通常设置得相当宽,因这里不要切断任何太长的ListItem

使用滚动 XList,需要 HeightWidth。 因此,我们在构造函数中添加默认值,如下所示:

IfMe.EnableHScroll ThenIfMe.Width.IsEmpty ThenMe.Width = Unit.Pixel(100)
 IfMe.Height.IsEmpty ThenMe.Height = Unit.Pixel(100)EndIf

我们重写 HeightWidth 属性,以确保它们永远不是空的:

<Browsable(True), _
 DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _PublicOverridesProperty Width() As Unit
 GetIfMe.EnableHScroll AndAlsoMyBase.Width.IsEmpty ThenReturn Unit.Pixel(100)
 EndIfReturnMyBase.Width
 EndGetSet(ByVal Value As Unit)
 MyBase.Width = Value
 EndSetEndProperty'Width

放置 <div> 围绕着控制意味着重写 RenderBeginTag,就像我们在 XTable 中做的一样。 同样,我们使它成为条件,因为我们只在 EnableHScrollTrue 我们以类似的方式修改 RenderEndTag:

PublicOverridesSub RenderEndTag(ByVal writer _
 As System.Web.UI.HtmlTextWriter)
 MyBase.RenderEndTag(writer)
 IfMe.EnableHScroll Then writer.RenderEndTag()
 EndIfEndSub'RenderEndTag

除了用于修改 RenderBeginTag的标准代码之外,我还向 <选择> :

MyBase.Attributes.Add("onchange", "javascript:XList_ShowOption(this);")MyBase.Attributes.Add("onresize", _
 "javascript:XList_ResizeSelect(this);XList_ShowOption(this);")

这些事件触发一个或者两个使控制工作正常的函数。 XList_ResizeSelect 使 <选择> 确定是使用显式宽度还是测量宽度的一种简便方法 <选择> of的宽度 <div> ,这里函数扩展了 <选择> 直到它填充了可以见空间的宽度 <div> 同样地,对于 <div> 如果数量为 <选项> 元素中的元素 <选择> 在底部的底部留出一个空间 <选择> 而的底部 <div> ,函数的高度增加了 <选择> 要匹配,则为 <选择> 被设置为 <选项> 元素中的元素 <选择>

function XList_ResizeSelect(objSelect){
 //check to see if the object is visibleif (objSelect.offsetHeight == 0){
 return;
 }
 //remove the onresize event so that it doesn't loop forever objSelect.onresize = null;
 //make sure it's a listbox and not a dropdown objSelect.size = objSelect.options.length <2? 2 : _
 objSelect.options.length;
 if (objSelect.offsetHeight <_
 objSelect.parentElement.offsetHeight - scrollbarWidth){
 objSelect.style.height = _
 objSelect.parentElement.offsetHeight - scrollbarWidth/2;
 }
 objSelect.style.width = "";
 if (objSelect.offsetWidth <objSelect.parentElement.offsetWidth_
 - scrollbarWidth){
 objSelect.style.width = (objSelect.parentElement.offsetWidth_
 - scrollbarWidth) + "px";
 } else {
 objSelect.style.width = "auto";
 }
}

当页面加载时,实用程序函数计算滚动条的宽度。 我从isaac的文章中收集了关于DHTML滚动条的有用技术。

我使用 resize 事件触发这个事件,因为它使得。 如果你添加或者删除 <选项><选择> 在客户端,你应该调用 XList_ResizeSelect 作为 <选择> 在你自己的客户端代码中,以确保 <选择> 根据它的新内容调整大小。

XList_ShowOption 稍微复杂一点。 就像我在本文开头所提到的( 请记住后面的方法? <of> ) 如果你使用这种技术和 cursor,则可以在 <div> ,你选择 <选项> 就会消失所以我需要找到一种方法 <div> 滚动,使选定 <选项> 可以使用 scrollIntoView 方法,但这种方法不适用于 <选项> 元素。

下面是使代码成为 <选项> 移动到视图中我将计算全部分开,以便更易于遵循( 当我最初处理这个问题时,更容易调试):

function XList_ShowOption(objSelect){
 idx = objSelect.selectedIndex
 if (idx == -1){
 return;
 }
 if (objSelect.length == 0){
 return;
 }
 objDiv = objSelect.parentElement;
 HeightOfSelect = objSelect.clientHeight;
 OptionsInSelect = objSelect.options.length;
 HeightOfOption = HeightOfSelect/OptionsInSelect;
 HeightOfDiv = objDiv.clientHeight;
 OptionsInDiv = HeightOfDiv/HeightOfOption;
 OptionTopFromTopOfSelect = HeightOfOption * idx;
 OptionTopFromTopOfDiv = OptionTopFromTopOfSelect - objDiv.scrollTop;
 OptionBottomFromBottomOfDiv = HeightOfDiv - _
 OptionTopFromTopOfDiv - HeightOfOption;
 if (OptionTopFromTopOfDiv <0) {
 objDiv.scrollTop = OptionTopFromTopOfSelect;
 } elseif (OptionBottomFromBottomOfDiv <0 && _
 OptionBottomFromBottomOfDiv> 0 - HeightOfOption) {
 objDiv.scrollTop = objDiv.scrollTop + HeightOfOption;
 } elseif (OptionBottomFromBottomOfDiv <0) {
 objDiv.scrollTop = OptionTopFromTopOfSelect;
 }
}

这两个函数以及出现滚动条宽度的实用程序,不管页面上有多少个 XList 实例,都会写入页面。 我将 RegisterClientScriptBlock 用于。 但是,我们还需要对脚本进行另一件事,它必须为每个实例分别完成,这是 RegisterStartupScript 所用的。

加载页面时, <选择> 需要调整大小。该控件的每个实例都必须发生这种情况。 此外,如果控件的SelectedIndex GREATER 超过 -1,则该页必须标记正确 <选项> 选择并运行 XList_ShowOption,以确保它是可见的。 你可以看到在源代码中注册的脚本。

但是,在设计环境中,脚本不运行。 所以我稍微修改了一下 XListDesignerGetDesignTimeHtml 方法在两个点调用 MyBase.GetDesignTimeHtml。 我用一个调用 GetDesignTimeResize的新方法替换了那些调用。 下面是修改后的GetDesignTimeHtml,以及新的GetDesignTimeResize:

PublicOverridesFunction GetDesignTimeHtml() AsStringDim collection1 As XListItemCollection = Me._xList.Items
 If (collection1.Count> 0) Then'Return MyBase.GetDesignTimeHtmlReturn GetDesignTimeResize()
 EndIfIfMe.IsDataBound Then collection1.Add("bound")
 Else collection1.Add("unbound")
 EndIf'Dim text1 As String = MyBase.GetDesignTimeHtmlDim text1 AsString = GetDesignTimeResize()
 collection1.Clear()
 Return text1EndFunction'GetDesignTimeHtmlPublicOverridableFunction GetDesignTimeResize() AsStringDim _xList As XList = CType(Component, XList)
 If _xList.EnableHScroll ThenDim str AsString = MyBase.GetDesignTimeHtml
 str = Replace(str, "<select", _
 "<select style=width:" & _xList.Width.ToString)
 Dim _itemCount AsInteger = _xList.Items.Count
 Dim _selectTagEnd AsInteger = InStr(str, ">")
 _selectTagEnd = InStr(_selectTagEnd + 1, str, ">")
 Dim _sizeAttribute AsInteger = InStr(str, " size=")
 If _sizeAttribute> 0And _sizeAttribute <_selectTagEnd Then str = Replace(str, "size=""4""", "size=""" & _
 IIf(_itemCount <2, 2, _itemCount).ToString & """")
 Else str = Replace(str, "<select", "<select size=""" _
 & IIf(_itemCount <2, 2, _itemCount).ToString & """")
 EndIfReturn str
 ElseReturnMyBase.GetDesignTimeHtml
 EndIfEndFunction'GetDesignTimeResize

我找到的唯一事情是在 PreFilterProperties 末尾添加几行,以确保 DataOptGroupField 处理的方式与 DataTextFieldDataValueField 相同。

结束语

这相当重要控件在三种模式下工作: DropDownListListBox,滚动 ListBox,以及在每个情况下,可以呈现 OptGroup s。 所以就像一个in的六个控件。


Server  控制  
相关文章