使用VB.Net在Silverlight中进行多重绑定

时间:2011-07-13 15:44:36

标签: .net vb.net silverlight data-binding multibinding

我正在尝试使用VB.Net在Silverlight中实现多重绑定。我在C#here中找到了一个非常好的实现参考。我花了一些时间尝试使用各种转换器将其迁移到VB.Net但我仍然没有让它正常工作。所以..

我正在寻找一些参考资料来举例说明如何在VB.Net中完成MultiBinding

使用Silverlight 5 beta的示例也没问题(我在Stack Overflow上的帖子right here中读到它支持多重绑定)。

1 个答案:

答案 0 :(得分:5)

我已将该示例翻译成Silverlight的VB.NET,可以从here下载。对于后代,代码如下所示。请注意,两者仍处于原作者指定的任何许可条款下。

核心代码

<强> BindingUtil.vb

Imports System.Collections.Generic
Imports System.Net
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Documents
Imports System.Windows.Ink
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Shapes
Imports System.Windows.Data
Imports System.ComponentModel

Namespace SLMultiBinding
    ''' <summary>
    ''' Provides a mechanism for attaching a MultiBinding to an element
    ''' </summary>
    Public Class BindingUtil
#Region "DataContextPiggyBack attached property"

        ''' <summary>
        ''' DataContextPiggyBack Attached Dependency Property, used as a mechanism for exposing
        ''' DataContext changed events
        ''' </summary>
        Public Shared ReadOnly DataContextPiggyBackProperty As DependencyProperty = DependencyProperty.RegisterAttached("DataContextPiggyBack", GetType(Object), GetType(BindingUtil), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnDataContextPiggyBackChanged)))

        Public Shared Function GetDataContextPiggyBack(d As DependencyObject) As Object
            Return DirectCast(d.GetValue(DataContextPiggyBackProperty), Object)
        End Function

        Public Shared Sub SetDataContextPiggyBack(d As DependencyObject, value As Object)
            d.SetValue(DataContextPiggyBackProperty, value)
        End Sub

        ''' <summary>
        ''' Handles changes to the DataContextPiggyBack property.
        ''' </summary>
        Private Shared Sub OnDataContextPiggyBackChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
            Dim targetElement As FrameworkElement = TryCast(d, FrameworkElement)

            ' whenever the targeElement DataContext is changed, copy the updated property
            ' value to our MultiBinding.
            Dim relay As MultiBindings = GetMultiBindings(targetElement)
            relay.SetDataContext(targetElement.DataContext)
        End Sub

#End Region

#Region "MultiBindings attached property"

        Public Shared Function GetMultiBindings(obj As DependencyObject) As MultiBindings
            Return DirectCast(obj.GetValue(MultiBindingsProperty), MultiBindings)
        End Function

        Public Shared Sub SetMultiBindings(obj As DependencyObject, value As MultiBindings)
            obj.SetValue(MultiBindingsProperty, value)
        End Sub

        Public Shared ReadOnly MultiBindingsProperty As DependencyProperty = DependencyProperty.RegisterAttached("MultiBindings", GetType(MultiBindings), GetType(BindingUtil), New PropertyMetadata(Nothing, AddressOf OnMultiBindingsChanged))



        ''' <summary>
        ''' Invoked when the MultiBinding property is set on a framework element
        ''' </summary>
        Private Shared Sub OnMultiBindingsChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
            Dim targetElement As FrameworkElement = TryCast(depObj, FrameworkElement)

            ' bind the target elements DataContext, to our DataContextPiggyBack property
            ' this allows us to get property changed events when the targetElement
            ' DataContext changes
            targetElement.SetBinding(DataContextPiggyBackProperty, New Binding())

            Dim bindings As MultiBindings = GetMultiBindings(targetElement)

            bindings.Initialize(targetElement)
        End Sub

#End Region

    End Class
End Namespace

<强> IMultiValueConverter.vb

Imports System.Net
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Documents
Imports System.Windows.Ink
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Shapes
Imports System.Globalization

Namespace SLMultiBinding
    ''' <summary>
    ''' see: http://msdn.microsoft.com/en-us/library/system.windows.data.imultivalueconverter.aspx
    ''' </summary>
    Public Interface IMultiValueConverter
        Function Convert(values As Object(), targetType As Type, parameter As Object, culture As CultureInfo) As Object

        Function ConvertBack(value As Object, targetTypes As Type(), parameter As Object, culture As CultureInfo) As Object()

    End Interface
End Namespace

<强> MultiBinding.vb

Imports System.Diagnostics
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Collections.ObjectModel
Imports System.Windows.Markup
Imports System.ComponentModel
Imports System.Collections.Generic
Imports System.Globalization

Namespace SLMultiBinding
    ''' <summary>
    ''' Implements MultiBinding by creating a BindingSlave instance for each of the Bindings.
    ''' PropertyChanged events for the BindingSlae.Value property are handled, and the IMultiValueConveter
    ''' is used to compute the converted value.
    ''' </summary>
    <ContentProperty("Bindings")> _
    Public Class MultiBinding
        Inherits Panel
        Implements INotifyPropertyChanged
        ''' <summary>
        ''' Indicates whether the converted value property is currently being updated
        ''' as a result of one of the BindingSlave.Value properties changing
        ''' </summary>
        Private _updatingConvertedValue As Boolean

#Region "ConvertedValue dependency property"

        Public Shared ReadOnly ConvertedValueProperty As DependencyProperty = DependencyProperty.Register("ConvertedValue", GetType(Object), GetType(MultiBinding), New PropertyMetadata(Nothing, AddressOf OnConvertedValuePropertyChanged))

        ''' <summary>
        ''' This dependency property is set to the resulting output of the
        ''' associated Converter.
        ''' </summary>
        Public Property ConvertedValue() As Object
            Get
                Return GetValue(ConvertedValueProperty)
            End Get
            Set(value As Object)
                SetValue(ConvertedValueProperty, value)
            End Set
        End Property

        Private Shared Sub OnConvertedValuePropertyChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
            Dim relay As MultiBinding = TryCast(depObj, MultiBinding)
            Debug.Assert(relay IsNot Nothing)
            relay.OnConvertedValuePropertyChanged()
        End Sub

        ''' <summary>
        ''' Handles propety changes for the ConvertedValue property
        ''' </summary>
        Private Sub OnConvertedValuePropertyChanged()
            OnPropertyChanged("ConvertedValue")

            ' if the value is being updated, but not due to one of the multibindings
            ' then the target property has changed.
            If Not _updatingConvertedValue Then
                ' convert back
                Dim convertedValues As Object() = Converter.ConvertBack(ConvertedValue, Nothing, ConverterParameter, CultureInfo.InvariantCulture)

                ' update all the binding slaves
                If Children.Count = convertedValues.Length Then
                    For index As Integer = 0 To convertedValues.Length - 1
                        DirectCast(Children(index), BindingSlave).Value = convertedValues(index)
                    Next
                End If
            End If
        End Sub

#End Region

#Region "CLR properties"

        ''' <summary>
        ''' The BindingMode
        ''' </summary>
        Public Property Mode() As BindingMode
            Get
                Return m_Mode
            End Get
            Set(value As BindingMode)
                m_Mode = value
            End Set
        End Property
        Private m_Mode As BindingMode

        ''' <summary>
        ''' The target property on the element which this MultiBinding is assocaited with.
        ''' </summary>
        Public Property TargetProperty() As String
            Get
                Return m_TargetProperty
            End Get
            Set(value As String)
                m_TargetProperty = value
            End Set
        End Property
        Private m_TargetProperty As String

        ''' <summary>
        ''' The Converter which is invoked to compute the result of the multiple bindings
        ''' </summary>
        Public Property Converter() As IMultiValueConverter
            Get
                Return m_Converter
            End Get
            Set(value As IMultiValueConverter)
                m_Converter = value
            End Set
        End Property
        Private m_Converter As IMultiValueConverter

        ''' <summary>
        ''' The configuration parameter supplied to the converter
        ''' </summary>
        Public Property ConverterParameter() As Object
            Get
                Return m_ConverterParameter
            End Get
            Set(value As Object)
                m_ConverterParameter = value
            End Set
        End Property
        Private m_ConverterParameter As Object

        ''' <summary>
        ''' The bindings, the result of which are supplied to the converter.
        ''' </summary>
        Public Property Bindings() As BindingCollection
            Get
                Return m_Bindings
            End Get
            Set(value As BindingCollection)
                m_Bindings = value
            End Set
        End Property
        Private m_Bindings As BindingCollection

#End Region

        Public Sub New()
            Bindings = New BindingCollection()
        End Sub

        ''' <summary>
        ''' Invoked when any of the BindingSlave's Value property changes.
        ''' </summary>
        Private Sub SlavePropertyChanged(sender As Object, e As PropertyChangedEventArgs)
            UpdateConvertedValue()
        End Sub

        ''' <summary>
        ''' Uses the Converter to update the ConvertedValue in order to reflect
        ''' the current state of the bindings.
        ''' </summary>
        Private Sub UpdateConvertedValue()
            Dim values As New List(Of Object)()
            For Each slave As BindingSlave In Children
                values.Add(slave.Value)
            Next

            _updatingConvertedValue = True
            ConvertedValue = Converter.Convert(values.ToArray(), GetType(Object), ConverterParameter, CultureInfo.CurrentCulture)
            _updatingConvertedValue = False
        End Sub

        ''' <summary>
        ''' Creates a BindingSlave for each Binding and binds the Value
        ''' accordingly.
        ''' </summary>
        Friend Sub Initialise(targetElement As FrameworkElement)
            Children.Clear()
            For Each binding As Binding In Bindings
                Dim slave As BindingSlave

                ' create a binding slave instance 
                If Not String.IsNullOrEmpty(binding.ElementName) Then
                    ' create an element name binding slave, this slave will resolve the 
                    ' binding source reference and construct a suitable binding.
                    slave = New ElementNameBindingSlave(targetElement, binding)
                Else
                    slave = New BindingSlave()
                    slave.SetBinding(BindingSlave.ValueProperty, binding)
                End If
                AddHandler slave.PropertyChanged, AddressOf SlavePropertyChanged
                Children.Add(slave)
            Next
        End Sub

#Region "INotifyPropertyChanged Members"

        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

        Protected Sub OnPropertyChanged(name As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
        End Sub

#End Region
    End Class

    ''' <summary>
    ''' A simple element with a single Value property, used as a 'slave'
    ''' for a Binding.
    ''' </summary>
    Public Class BindingSlave
        Inherits FrameworkElement
        Implements INotifyPropertyChanged
#Region "Value"

        Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Object), GetType(BindingSlave), New PropertyMetadata(Nothing, AddressOf OnValueChanged))

        Public Property Value() As Object
            Get
                Return GetValue(ValueProperty)
            End Get
            Set(value As Object)
                SetValue(ValueProperty, value)
            End Set
        End Property

        Private Shared Sub OnValueChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
            Dim slave As BindingSlave = TryCast(depObj, BindingSlave)
            Debug.Assert(slave IsNot Nothing)
            slave.OnPropertyChanged("Value")
        End Sub

#End Region

#Region "INotifyPropertyChanged Members"

        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

        Protected Sub OnPropertyChanged(name As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
        End Sub

#End Region

    End Class

    ''' <summary>
    ''' A binding slave that performs 'ElementName' binding.
    ''' </summary>
    Public Class ElementNameBindingSlave
        Inherits BindingSlave
        Private _multiBindingTarget As FrameworkElement

        ''' <summary>
        ''' The source element named in the ElementName binding
        ''' </summary>
        Private _elementNameSource As FrameworkElement

        Private _binding As Binding

        Public Sub New(target As FrameworkElement, binding As Binding)
            _multiBindingTarget = target
            _binding = binding

            ' try to locate the named element
            ResolveElementNameBinding()

            AddHandler _multiBindingTarget.LayoutUpdated, AddressOf MultiBindingTarget_LayoutUpdated
        End Sub

        ''' <summary>
        ''' Try to locate the named element. If the element can be located, create the required
        ''' binding.
        ''' </summary>
        Private Sub ResolveElementNameBinding()
            _elementNameSource = TryCast(_multiBindingTarget.FindName(_binding.ElementName), FrameworkElement)
            If _elementNameSource IsNot Nothing Then
                SetBinding(BindingSlave.ValueProperty, New Binding() With { _
                 .Source = _elementNameSource, _
                 .Path = _binding.Path, _
                 .Converter = _binding.Converter, _
                 .ConverterParameter = _binding.ConverterParameter _
                })
            End If
        End Sub

        Private Sub MultiBindingTarget_LayoutUpdated(sender As Object, e As EventArgs)
            ' try to locate the named element 
            ResolveElementNameBinding()
        End Sub
    End Class

    Friend Delegate Sub BindingCollectionChangedCallback()

    Public Class BindingCollection
        Inherits Collection(Of BindingBase)
        ' Fields
        ' TODO: Private ReadOnly _collectionChangedCallback As BindingCollectionChangedCallback


        Protected Overrides Sub ClearItems()
            MyBase.ClearItems()
            OnBindingCollectionChanged()
        End Sub

        Protected Overrides Sub InsertItem(index As Integer, item As BindingBase)
            If item Is Nothing Then
                Throw New ArgumentNullException("item")
            End If
            ValidateItem(item)
            MyBase.InsertItem(index, item)
            OnBindingCollectionChanged()
        End Sub

        Private Sub OnBindingCollectionChanged()
            ' TODO: RaiseEvent _collectionChangedCallback()
        End Sub

        Protected Overrides Sub RemoveItem(index As Integer)
            MyBase.RemoveItem(index)
            OnBindingCollectionChanged()
        End Sub

        Protected Overrides Sub SetItem(index As Integer, item As BindingBase)
            If item Is Nothing Then
                Throw New ArgumentNullException("item")
            End If
            ValidateItem(item)
            MyBase.SetItem(index, item)
            OnBindingCollectionChanged()
        End Sub

        Private Shared Sub ValidateItem(binding As BindingBase)
            If Not (TypeOf binding Is Binding) Then
                Throw New NotSupportedException("BindingCollectionContainsNonBinding")
            End If
        End Sub
    End Class
End Namespace

<强> MultiBindings.vb

Imports System.Collections.ObjectModel
Imports System.Linq
Imports System.Reflection
Imports System.Windows
Imports System.Windows.Data
Imports System.Windows.Markup

Namespace SLMultiBinding
    ''' <summary>
    ''' Manages the construction of multiple MultiBinding instances
    ''' </summary>
    <ContentProperty("Bindings")> _
    Public Class MultiBindings
        Inherits FrameworkElement
        Private _targetElement As FrameworkElement

        ''' <summary>
        ''' Gets / sets the collection of MultiBindings
        ''' </summary>
        Public Property Bindings() As ObservableCollection(Of MultiBinding)
            Get
                Return m_Bindings
            End Get
            Set(value As ObservableCollection(Of MultiBinding))
                m_Bindings = Value
            End Set
        End Property
        Private m_Bindings As ObservableCollection(Of MultiBinding)

        Public Sub New()
            Bindings = New ObservableCollection(Of MultiBinding)()
        End Sub

        ''' <summary>
        ''' Sets the DataContext of each of the MultiBinding instances
        ''' </summary>
        Public Sub SetDataContext(dataContext As Object)
            For Each relay As MultiBinding In Bindings
                relay.DataContext = dataContext
            Next
        End Sub

        ''' <summary>
        ''' Initialises each of the MultiBindings, and binds their ConvertedValue
        ''' to the given target property.
        ''' </summary>
        Public Sub Initialize(targetElement As FrameworkElement)
            _targetElement = targetElement

            Const DpFlags As BindingFlags = BindingFlags.[Public] Or BindingFlags.[Static] Or BindingFlags.FlattenHierarchy

            For Each relay As MultiBinding In Bindings
                relay.Initialise(targetElement)

                ' find the target dependency property
                Dim targetType As Type = Nothing
                Dim targetProperty As String = Nothing

                ' assume it is an attached property if the dot syntax is used.
                If relay.TargetProperty.Contains(".") Then
                    ' split to find the type and property name
                    Dim parts As String() = relay.TargetProperty.Split("."c)
                    targetType = Type.[GetType]("System.Windows.Controls." & parts(0) & ", System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e")
                    targetProperty = parts(1)
                Else
                    targetType = targetElement.[GetType]()
                    targetProperty = relay.TargetProperty
                End If

                Dim sourceFields As FieldInfo() = targetType.GetFields(DpFlags)
                Dim targetDependencyPropertyField As FieldInfo = sourceFields.First(Function(i) i.Name = targetProperty & "Property")
                Dim targetDependencyProperty As DependencyProperty = TryCast(targetDependencyPropertyField.GetValue(Nothing), DependencyProperty)

                ' bind the ConvertedValue of our MultiBinding instance to the target property
                ' of our targetElement
                Dim binding As New Binding("ConvertedValue") With { _
                 .Source = relay, _
                 .Mode = relay.Mode _
                }
                targetElement.SetBinding(targetDependencyProperty, Binding)
            Next
        End Sub
    End Class
End Namespace

样品

<强> TitleConverter.vb

Imports System.Net
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Ink
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Shapes

Namespace SLMultiBinding
    Public Class TitleConverter
        Implements IMultiValueConverter
#Region "IMultiValueConverter Members"

        Public Function Convert(values As Object(), targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements IMultiValueConverter.Convert
            Dim forename As String = TryCast(values(0), String)
            Dim surname As String = TryCast(values(1), String)

            Return String.Format("{0}, {1}", surname, forename)
        End Function

        Public Function ConvertBack(value As Object, targetTypes As Type(), parameter As Object, culture As System.Globalization.CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
            Dim source As String = TryCast(value, String)
            Dim pos = source.IndexOf(", ")

            Dim forename As String = source.Substring(pos + 2)
            Dim surname As String = source.Substring(0, pos)

            Return New Object() {forename, surname}
        End Function

#End Region
    End Class

End Namespace