zoukankan      html  css  js  c++  java
  • 在组合框中嵌入一个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

  • 相关阅读:
    都不敢上CSDN了
    什么是函数(function)?
    今天3/8妇女节
    一件有意思的事情:关于std::string和std::auto_ptr
    转两篇Link相关的文章
    DevIL Downloads
    状态模式(State Pattern)
    访问者模式(Visitor Pattern)
    羊皮卷的故事第二章
    备忘录模式(Memento Pattern)
  • 原文地址:https://www.cnblogs.com/Dincat/p/13431040.html
Copyright © 2011-2022 走看看