在组合框中嵌入一个ObjectListView

 

介绍 本文是我上一篇

  

 

文章的扩展,将一个DataGridView嵌入到一个组合框中。 在我写了一篇解释如何在ComboBox控件中使用DataGridView的技术的文章之后,我发现了一个惊人的开源替代品——ObjectListView。它有许多先进的内置功能,包括一个特殊的价值-通用快速搜索和过滤。这个特性使它成为一个完美的候选嵌入到一个ComboBox和结果看起来如下: 背景 本文基于以下几篇文章提出的想法: 使用toolstripcontrolhost将一个DataGridView嵌入到一个ComboBox中。在一个下拉列表框 首先,创建一个自定义ToolStripControlHost,然后使用它创建自定义组合框。我没有继续创建一个自定义的DataGridViewColumn,因为在DataGridView中使用ObjectListView这样优秀的控件是一件很遗憾的事情,而ObjectListView有一个通用的机制来添加任何编辑控件,包括这个。 使用的代码 要使用提供的控件,您需要创建一个新用户控件,从提供的InfoListControl类派生该控件。控件表示您希望在下拉列表中显示的对象的视图(一些列显示对象的属性)。控件背后的目的是允许开发人员在VS设计器中配置ObjectListView(将列绑定到特定数据源类型的属性,设置各种可视化属性,等等)。因此,您需要为将要使用的每个对象类型使用InfoListControl。如果使用标准的Visual Studio方法创建继承的UserControl,则不需要编写任何代码。创建继承的控件后,将显示一个ObjectListView designer视图。在这里,您可以配置所需的所有内容:设置databindings(列的AspectName)、配置列标头、可视化外观,等等。你可以在这里找到详细的描述。 在你为一个特定的数据源类型创建InfoListControl之后,你用几行代码就完成了所有的控制设置:

Dim cntr As AccListComboBox
dim view As InfoListControl = new yourInfoListControlInstance
view.DataSource = GetDataSource(whatever)
' a name of the property that should be used to set SelectedValue
' an empty string if the SelectedValue should contain the object itself
view .ValueMember = ""
' whether to accept a single click to confirm a user selection
view .AcceptSingleClick = True
cntr.AddDataListView(view)
' e.g.. when you want to display integer value 0 as an empty string
cntr.EmptyValueString = "0"

在InfoListControl中,我使用了ObjectListView—DataListView,因为它支持开箱即用的数据绑定。然而,如果你需要非标准的数据绑定(例如DataTable, hierarchy structure),你可以很容易地实现你自己的控制来支持它。 重要的 当前版本的ObjectListView有一个bug,当ObjectListViewComboBox被用作ObjectListView本身的编辑控件时,这个bug会导致异常。因此,您需要在ObjectListView源代码(CellEditKeyEngine类的属性ItemBeingEdited)中作出以下更正,并自己编译(即。,不要使用预编译的二进制文件):

/// <summary>
/// Gets the row of the cell that is currently being edited
/// </summary>
protected OLVListItem ItemBeingEdited {
get {
        OLVListItem olvi = (this.ListView == null ||
        this.ListView.CellEditEventArgs == null) ?
        null : this.ListView.CellEditEventArgs.ListViewItem;

        if (olvi != null && olvi.Index < 0)
        {
            if (olvi.RowObject == null)
                return olvi;
            for (int i = 0; i < this.ListView.Items.Count; i++)
            {
                if (this.ListView.GetItem(i).RowObject == olvi.RowObject)
                {
                    olvi = this.ListView.GetItem(i);
                    break;
                }
            }
        }

        return olvi;
    }
}

的兴趣点 创建InfoListControl 控件的基本部分是继承ToolStripControlHost的ObjectListViewToolStrip类。基本上,ToolStripControlHost类可以自己处理包括ObjectListView在内的任何控件,但是,在这种情况下,开发人员需要通过编程方式创建ObjectListView(这对用户不是很友好)。因此,我决定创建一个专用的用户控件,一方面保存整洁封装的所有ObjectListView特定逻辑,另一方面提供完整的设计器支持。 实际上,在InfoListControl中,我没有使用通用的ObjectListView,而是使用它的派生—DataListView(因为我需要数据绑定支持)。封装实际控件为重写通用ObjectListView或TreeListView (ObjectListView的另一个后代)的控件提供了一种简单的方法。 InfoListControl包含了一个由VS设计器添加的DataListView的实例,并提供了一个简单的公共构造函数以及四个几乎是自解释的带有后台字段的属性:

Private _AcceptSingleClick As Boolean = False
Private _ValueMember As String = ""
Private _FilterString As String = ""


''' <summary>
''' Whether single click is sufficient to choose an item.
''' </summary>
''' <remarks></remarks>
Public Property AcceptSingleClick() As Boolean
    Get
        Return _AcceptSingleClick
    End Get
    Set(ByVal value As Boolean)
        _AcceptSingleClick = value
    End Set
End Property

''' <summary>
''' A value object property that holds the required value (if any).
''' </summary>
''' <remarks></remarks>
Public Property ValueMember() As String
    Get
        Return _ValueMember
    End Get
    Set(ByVal value As String)
        If value Is Nothing Then value = ""
        _ValueMember = value
    End Set
End Property

''' <summary>
''' A <seecref="BindingSource">BindingSource_
</see> that wraps a value object list.
''' </summary>
''' <remarks></remarks>
Public Property DataSource() As Object
    Get
        Return baseDataListView.DataSource
    End Get
    Set(ByVal value As Object)
        baseDataListView.DataSource = value
    End Set
End Property

''' <summary>
''' A string that is used to filter the value object list.
''' </summary>
''' <remarks></remarks>
Public Property FilterString() As String
    Get
        Return _FilterString
    End Get
    Set(ByVal value As String)
        If value Is Nothing Then value = ""
        If value <> _FilterString Then
            _FilterString = value
            baseDataListView.AdditionalFilter = _
                TextMatchFilter.Contains(baseDataListView, _FilterString)
        End If
    End Set
End Property

Public Sub New()
    ' This call is required by the Windows Form Designer.
    InitializeComponent()

    baseDataListView.DefaultRenderer = New HighlightTextRenderer( _
        TextMatchFilter.Contains(baseDataListView, New String() {""}))
    baseDataListView.SelectColumnsMenuStaysOpen = True
End Sub

这里唯一感兴趣的是调用设置过滤器图形渲染器和过滤器(字符串)本身所需的ObjectListView方法。 接下来,InfoListControl将实现事件,将用户选择传递给父工具stripcontrolhost:

Friend Delegate Sub ValueSelectedEventHandler(ByVal sender As Object, ByVal e As ValueChangedEventArgs)
Friend Event ValueSelected As ValueSelectedEventHandler

Protected Sub OnValueSelected(ByVal e As ValueChangedEventArgs)
    RaiseEvent ValueSelected(Me, e)
End Sub

Protected Sub OnValueSelected(ByVal currentObject As Object, ByVal isCanceled As Boolean)

    If _ValueMember Is Nothing OrElse String.IsNullOrEmpty(_ValueMember) _
        OrElse currentObject Is Nothing Then

        RaiseEvent ValueSelected(Me, New ValueChangedEventArgs(currentObject, isCanceled))

    Else

        If baseDataListView.GetItemCount() < 1 OrElse baseDataListView.GetItem(0). _
            RowObject.GetType().GetProperty(_ValueMember.Trim, BindingFlags.Public _
            OrElse BindingFlags.Instance) Is Nothing Then

            RaiseEvent ValueSelected(Me, New ValueChangedEventArgs(Nothing, isCanceled))

        Else
            RaiseEvent ValueSelected(Me, New ValueChangedEventArgs( _
                GetValueMemberValue(currentObject), isCanceled))
        End If

    End If

End Sub

Public Class ValueChangedEventArgs
    Inherits EventArgs

    Private _SelectedValue As Object = Nothing
    Private _SelectionCanceled As Boolean = False

    Public ReadOnly Property SelectedValue() As Object
        Get
            Return _SelectedValue
        End Get
    End Property

    Public ReadOnly Property SelectionCanceled() As Boolean
        Get
            Return _SelectionCanceled
        End Get
    End Property

    Friend Sub New(ByVal newValue As Object, ByVal isCanceled As Boolean)
        _SelectedValue = newValue
        _SelectionCanceled = isCanceled
    End Sub

End Class

这里感兴趣的是引发事件并将所选对象传递给事件args的OnValueSelected方法重载。该方法的目的是过滤用户选择输入,并且(在设置了ValueMember属性的情况下)不是返回对象本身,而是返回所需属性的值。所需属性的值是使用一个简单的助手方法GetValueMemberValue获取的,该方法使用了一个简单的反射,在一个尝试…捕获块以避免运行时异常。 最后InfoListControl需要处理用户输入(通过鼠标或键盘):

Private Sub baseDataListView_CellClick(ByVal sender As Object, _
    ByVal e As CellClickEventArgs) Handles baseDataListView.CellClick

    If Not e.Model Is Nothing AndAlso (e.ClickCount = 2 OrElse _
        (e.ClickCount = 1 AndAlso _AcceptSingleClick)) Then

        OnValueSelected(e.Model, False)

    End If

End Sub

Private Sub baseDataListView_KeyDown(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.KeyEventArgs) Handles baseDataListView.KeyDown

    If e.KeyData = Keys.Enter AndAlso Not baseDataListView.SelectedItem Is Nothing _
        AndAlso Not baseDataListView.SelectedItem.RowObject Is Nothing Then

        OnValueSelected(baseDataListView.SelectedItem.RowObject, False)
        e.Handled = True

    ElseIf e.KeyData = Keys.Back Then

        If _FilterString <> "" Then
            _FilterString = _FilterString.Substring(0, _FilterString.Length - 1)
            baseDataListView.AdditionalFilter = _
                TextMatchFilter.Contains(baseDataListView, _FilterString)
        End If
        e.Handled = True

    ElseIf e.KeyData = Keys.Delete Then

        If _FilterString <> "" Then
            _FilterString = ""
            baseDataListView.AdditionalFilter = _
                TextMatchFilter.Contains(baseDataListView, _FilterString)
        End If
        e.Handled = True

    ElseIf e.KeyData = Keys.Escape Then

        OnValueSelected(Nothing, True)
        e.Handled = True

    End If

End Sub

Private Sub baseDataListView_KeyPress(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles baseDataListView.KeyPress

    If Not Char.IsControl(e.KeyChar) AndAlso (Char.IsLetterOrDigit(e.KeyChar) _
        OrElse Char.IsPunctuation(e.KeyChar)) Then

        _FilterString = _FilterString & e.KeyChar
        baseDataListView.AdditionalFilter = _
            TextMatchFilter.Contains(baseDataListView, _FilterString)

    End If

End Sub

代码是不言自明的。标准事件句柄对用户输入进行评估,并生成值选出的事件以指示用户选择或修改筛选器字符串: 字母,数字或标点符号键添加到过滤字符串最后退格键删除过滤器字符串中的字符删除键清除过滤器字符串输入键增加ValueSelected事件与当前所选对象退出键提高ValueSelected事件与一个空对象和取消标志设置为true单鼠标单击提出ValueSelected事件与点击对象如果AcceptSingleClick设置为true双鼠标单击提出ValueSelected事件与点击对象 从外部的角度(父控件ToolStripControlHost控件的角度)来看,InfoListControl是一个公开4个自定义属性和1个自定义事件的控件。包含的DataListView控件也可以从外部对象访问,但是通常它只能使用VS设计器配置。 创建ObjectListViewToolsStrip 控件的基本部分是继承ToolStripControlHost的ObjectListViewToolStrip类。它是一个内部类(仅由ObjectListViewComboBox内部使用),处理InfoListControl: 提供了一个代理属性的FilterString属性封装InfoListControl GetDataSource提供代理访问方法的DataSource属性封装InfoListControl处理ValueSelected事件封装InfoListControl和封装的当前选中的值(SelectedValue)和取消标记(SelectionCanceled)提供了标准属性下拉大小(MinDropDownWidth和DropDownHeight) 具体的ObjectListViewToolStrip方法是构造函数和连接封装InfoListControl事件的方法:

Public Sub New(ByVal listView As InfoListControl)
    MyBase.New(listView)
    Me.AutoSize = False
    Me._MinDropDownWidth = listView.Width
    Me._DropDownHeight = listView.Height
End Sub

Private Sub OnDataListViewValueSelected(ByVal sender As Object, _
    ByVal e As InfoListControl.ValueChangedEventArgs)
    _SelectedValue = e.SelectedValue
    _SelectionCanceled = e.SelectionCanceled
    DirectCast(Me.Owner, ToolStripDropDown).Close(ToolStripDropDownCloseReason.ItemClicked)
End Sub

' Subscribe and unsubscribe the control events you wish to expose.
Protected Overrides Sub OnSubscribeControlEvents(ByVal c As Control)
    ' Call the base so the base events are connected.
    MyBase.OnSubscribeControlEvents(c)

    ' Cast the control to a InfoListControl control.
    Dim nDataListView As InfoListControl = DirectCast(c, InfoListControl)

    ' Add the event.
    AddHandler nDataListView.ValueSelected, AddressOf OnDataListViewValueSelected

End Sub

Protected Overrides Sub OnUnsubscribeControlEvents(ByVal c As Control)
    ' Call the base method so the basic events are unsubscribed.
    MyBase.OnUnsubscribeControlEvents(c)

    ' Cast the control to a InfoListControl control.
    Dim nDataListView As InfoListControl = DirectCast(c, InfoListControl)

    ' Remove the event.
    RemoveHandler nDataListView.ValueSelected, AddressOf OnDataListViewValueSelected

End Sub

Protected Overrides Sub OnBoundsChanged()
    MyBase.OnBoundsChanged()
    If Not Control Is Nothing Then
        DirectCast(Control, InfoListControl).Size = Me.Size
    End If
End Sub

构造函数设置封装的InfoListControl并关闭自动调整大小,将大小控制留给覆盖的OnBoundsChanged方法。由于未知原因,内建的自动调整方法在ToolStripControlHost中使用失败。 方法OnSubscribeControlEvents和OnUnsubscribeControlEvents将ValueSelected事件连接到OnDataListViewValueSelected处理程序。这将继续保存SelectedValue和selectioncancelled属性中的valuesselected事件参数,并关闭选择下拉列表。 创建ObjectListViewComboBox 控件本身继承ComboBox。它公开了以下公共属性和方法: HasAttachedInfoList——表明InfoListControl是否已经分配给控制instantbinding -是否立即更新数据源当用户选择一个值(而不是在验证)selectedvalue——当前选中的值对象或值对象InfoListControl的值。ValueMember属性(如果设置)EmptyValueString - SelectedValue字符串表达式(ToString)应该显示为一个空字符串(例如,如果你设置EmptyValueString =“0”,那么一个整数值0将显示为一个空字符串)filterstring——目前应用过滤器字符串(代理属性)infolistcontroldatasource——一个数据源的嵌套InfoListControl(代理属性)adddatalistview -添加一个新的InfoListControl控制,即初始化控制。以后不能更换插孔控制。 ObjectListViewComboBox类有一个私有变量myDropDown作为ToolStripDropDown,它充当ObjectListViewToolStrip的容器。ObjectListViewToolStrip本身的实例是由AddDataListView方法创建的:

 Public Sub AddDataListView(ByVal dataView As InfoListControl)

    If Not myListView Is Nothing Then Throw New InvalidOperationException( _
        "Error. DataListView is already assigned to the ObjectListViewComboBox.")

    myListView = New ObjectListViewToolStrip(dataView)

    If myDropDown Is Nothing OrElse myDropDown.IsDisposed Then

        myDropDown = New ToolStripDropDown()
        myDropDown.AutoSize = False
        myDropDown.GripStyle = ToolStripGripStyle.Visible
        AddHandler myDropDown.Closed, AddressOf ToolStripDropDown_Closed

    Else

        myDropDown.Items.Clear()

    End If

    myDropDown.Items.Add(myListView)
    myDropDown.Width = Math.Max(Me.Width, myListView.MinDropDownWidth)
    myDropDown.Height = myListView.Height

End Sub

ObjectListViewComboBox处理通过覆盖WndProc和拦截消息显示下拉列表。此方法的当前实现是从CodeProject文章Flexible ComboBox和EditingControl中复制的,如果需要手动输入支持,应该对其进行更改,因为它捕获了ComboBox所有区域的单击,从而阻止了文本输入。

Private Const WM_LBUTTONDOWN As UInt32 = &H201
Private Const WM_LBUTTONDBLCLK As UInt32 = &H203
Private Const WM_KEYF4 As UInt32 = &H134

Protected Overrides Sub WndProc(ByRef m As Message)

    '#Region "WM_KEYF4"
    If m.Msg = WM_KEYF4 Then
        Me.Focus()
        Me.myDropDown.Refresh()
        If Not Me.myDropDown.Visible Then

            ShowDropDown()

        Else
            myDropDown.Close()

        End If
        Return
    End If
    '#End Region

    '#Region "WM_LBUTTONDBLCLK"
    If m.Msg = WM_LBUTTONDBLCLK OrElse m.Msg = WM_LBUTTONDOWN Then
        If Not Me.myDropDown.Visible Then

            ShowDropDown()

        Else
            myDropDown.Close()

        End If
        Return
    End If
    '#End Region

    MyBase.WndProc(m)

End Sub

ObjectListViewComboBox方法实际上显示了下拉列表,主要处理下拉列表大小、定位和选择合适的DataListView行(它持有当前的SelectedValue):

Private Sub ShowDropDown()
    If Not myDropDown Is Nothing AndAlso Not Me.myListView Is Nothing Then

        If Not myDropDown.Items.Contains(Me.myListView) Then
            myDropDown.Items.Clear()
            myDropDown.Items.Add(Me.myListView)
        End If

        myDropDown.Width = Math.Max(Me.Width, Me.myListView.MinDropDownWidth)
        myListView.Size = myDropDown.Size

        myListView.SetSelectedValue(_SelectedValue)

        myDropDown.Show(Me, CalculatePoz)

        SendKeys.Send("{down}")

    End If

End Sub

Private Function CalculatePoz() As Point

    Dim point As New Point(0, Me.Height)

    If (Me.PointToScreen(New Point(0, 0)).Y + Me.Height + Me.myListView.Height) _
        > Screen.PrimaryScreen.WorkingArea.Height Then
        point.Y = -Me.myListView.Height - 7
    End If

    Return point

End Function

ObjectListViewComboBox通过重载SelectedValue属性(以绕过原生的ComboBox逻辑)和提供定制的setter方法来处理当前值的设置,该方法允许通过ValueMember设置值对象。

Private Sub ToolStripDropDown_Closed(ByVal sender As Object, _
    ByVal e As ToolStripDropDownClosedEventArgs)

    If e.CloseReason = ToolStripDropDownCloseReason.ItemClicked _
        AndAlso Not myListView Is Nothing AndAlso Not myListView.SelectionCanceled Then

        If Not MyBase.Focused Then MyBase.Focus()

        SetValue(myListView.SelectedValue)

        If _InstantBinding Then
            For Each b As Binding In MyBase.DataBindings
                b.WriteValue()
            Next
        End If

    End If

End Sub

Private Sub SetValue(ByVal value As Object)

    If value Is Nothing Then
        Me.Text = ""
    ElseIf Not _EmptyValueString Is Nothing AndAlso _
        Not String.IsNullOrEmpty(_EmptyValueString.Trim) AndAlso _
        value.ToString.ToLower.Trim = _EmptyValueString.Trim.ToLower Then
        Me.Text = ""
    Else
        Me.Text = value.ToString
    End If

    _SelectedValue = value

    MyBase.OnSelectedValueChanged(New EventArgs)

End Sub

从上面的代码中可以看到,ObjectListViewComboBox也实现了一个自定义属性InstantBinding。它本身并不是必需的,但在某些情况下,最好在实际值更改时而不是在验证时更新绑定。 以上就是全部内容,请欣赏ObjectListView! 本文转载于:http://www.diyabc.com/frontweb/news184.html

posted @ 2020-08-04 03:19  Dincat  阅读(227)  评论(0编辑  收藏  举报