Feb 07

Silverlight3小技巧:从Binding获取值

BlendSDK里面的交互模式很好用,不过交互的类全部从DependencyObject继承,没办法绑定很不方便。于是就弄了这个类出来:

   1: Public NotInheritable Class BindingEvaluator(Of T)

   2:     Private Shared ReadOnly DummyProperty As DependencyProperty = DependencyProperty.RegisterAttached("Dummy", GetType(T), GetType(BindingEvaluator(Of T)), New PropertyMetadata(DependencyProperty.UnsetValue))

   3:  

   4:     Private Shared Function GetDummy(ByVal element As DependencyObject) As T

   5:         ' IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!

   6:         If (element Is Nothing) Then

   7:             Throw New ArgumentNullException("element")

   8:         End If

   9:         Return CType(element.GetValue(BindingEvaluator(Of T).DummyProperty), T)

  10:     End Function

  11:     Private Shared Sub SetDummy(ByVal element As DependencyObject, ByVal value As T)

  12:         ' IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!

  13:         If (element Is Nothing) Then

  14:             Throw New ArgumentNullException("element")

  15:         End If

  16:         element.SetValue(BindingEvaluator(Of T).DummyProperty, value)

  17:     End Sub

  18:  

  19:     Public Shared Function GetValue(ByVal source As FrameworkElement, ByVal binding As Binding) As T

  20:         Dim value = GetRawValue(source, binding)

  21:         If value Is DependencyProperty.UnsetValue Then

  22:             Return Nothing

  23:         End If

  24:         Return CType(value, T)

  25:     End Function

  26:  

  27:     Public Shared Function GetRawValue(ByVal source As FrameworkElement, ByVal binding As Binding) As Object

  28:         If source Is Nothing Then

  29:             Throw New ArgumentNullException("source", "source is nothing.")

  30:         End If

  31:         If binding Is Nothing Then

  32:             Throw New ArgumentNullException("binding", "binding is nothing.")

  33:         End If

  34:         Dim newBinding As New Binding() With { _

  35:             .BindsDirectlyToSource = binding.BindsDirectlyToSource, _

  36:             .Converter = binding.Converter, _

  37:             .ConverterCulture = binding.ConverterCulture, _

  38:             .ConverterParameter = binding.ConverterParameter, _

  39:             .Mode = BindingMode.OneTime, _

  40:             .Path = binding.Path _

  41:         }

  42:         If Not String.IsNullOrEmpty(binding.ElementName) Then

  43:             newBinding.ElementName = binding.ElementName

  44:         ElseIf binding.RelativeSource IsNot Nothing Then

  45:             newBinding.RelativeSource = binding.RelativeSource

  46:         ElseIf binding.Source IsNot Nothing Then

  47:             newBinding.Source = binding.Source

  48:         Else

  49:             '    newBinding.Source = source.DataContext

  50:         End If

  51:  

  52:         source.SetBinding(DummyProperty, newBinding)

  53:         Dim value = GetDummy(source)

  54:         source.ClearValue(DummyProperty)

  55:  

  56:         Return value

  57:     End Function

  58:  

  59:     Private Sub New()

  60:  

  61:     End Sub

  62: End Class

SL4就要支持DependencyObject绑定了,期待。。。

Mar 21

ObjectReader, v2.0

呃。。。这次支持了数组和内嵌其它类型,另外发现Field和Property混用的话顺序会出错,所以加上了限制。

      
Imports System.Reflection
Imports System.IO
Imports System.Runtime.Serialization

Namespace SAPStudio.Utils.IO

    <AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, AllowMultiple:=False)> _
    Public Class ObjectReaderWriterIncludeAttribute
        Inherits Attribute
    End Class
    <AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, AllowMultiple:=False)> _
    Public Class ArraySizeAttribute
        Inherits Attribute
        Private _size As Integer
        Public ReadOnly Property Size() As Integer
            Get
                Return _size
            End Get
        End Property
        Public Sub New(ByVal size As Integer)
            _size = size
        End Sub
    End Class
    <AttributeUsage(AttributeTargets.Class Or AttributeTargets.Struct, AllowMultiple:=False)> _
    Public Class ObjectReaderWriterOptionsAttribute
        Inherits Attribute
        Private _dataMember As DataMemberTypes
        Public ReadOnly Property DataMember() As DataMemberTypes
            Get
                Return _dataMember
            End Get
        End Property
        Sub New(ByVal dataMember As DataMemberTypes)
            _dataMember = dataMember
        End Sub
    End Class
    Public Enum DataMemberTypes
        Field
        [Property]
    End Enum
    Friend Structure Getter
        Public Method As MethodInfo
        Public InvokeWithAdditionalData As Boolean
        Public AdditionalData As Object()
        Public Function Invoke(ByVal reader As BinaryReader) As Object
            Return Method.Invoke(reader, If(InvokeWithAdditionalData, New Object() {reader, AdditionalData}, Nothing))
        End Function
    End Structure
    ''' <summary>
    ''' Construct a object using data from a <see cref="System.IO.BinaryReader" /> or <see cref="System.IO.Stream"  />.
    ''' </summary>
    ''' <typeparam name="T">Type of the object. Can be a ValueType</typeparam>
    ''' <remarks>Because Type.GetMembers only preserves declaration order of members with same type, You can only use either property or field to store data. Tested in .Net Framework 3.0, applies to .Net 2.0 too(I think). Apply ObjectReaderWriterOptions attribute to the type definition to choose which to use.</remarks>
    Public NotInheritable Class ObjectReader(Of T As New)
        Shared _gettersAndSetters As New List(Of KeyValuePair(Of Getter, MemberInfo))
        Shared Sub New()
            Dim options = CType(Attribute.GetCustomAttribute(GetType(T), GetType(ObjectReaderWriterOptionsAttribute)), ObjectReaderWriterOptionsAttribute)
            Dim dataMemberType As MemberTypes = If(options Is Nothing OrElse options.DataMember = DataMemberTypes.Field, MemberTypes.Field, MemberTypes.Property)
            For Each m In GetType(T).GetMembers(BindingFlags.Public Or BindingFlags.Instance)
                If m.MemberType <> dataMemberType Then Continue For
                Dim fieldType As Type
                If m.MemberType = MemberTypes.Field Then
                    Dim fi = CType(m, FieldInfo)
                    If fi.IsNotSerialized OrElse fi.IsInitOnly OrElse fi.IsLiteral Then Continue For
                    fieldType = fi.FieldType
                Else
                    Dim pi = CType(m, PropertyInfo)
                    If Not (pi.CanWrite AndAlso pi.CanRead) Then Continue For
                    fieldType = pi.PropertyType
                End If
                Dim getter As Getter
                If fieldType.IsPrimitive Then
                    getter = GetGetterPrimitive(fieldType)
                ElseIf fieldType.IsArray AndAlso Attribute.IsDefined(m, GetType(ArraySizeAttribute)) Then
                    Dim elementType = fieldType.GetElementType()
                    If elementType.IsPrimitive OrElse Attribute.IsDefined(m, GetType(ObjectReaderWriterIncludeAttribute)) Then
                        getter = GetGetterArray(elementType, CType(Attribute.GetCustomAttribute(m, GetType(ArraySizeAttribute)), ArraySizeAttribute).Size)
                    End If
                ElseIf Attribute.IsDefined(m, GetType(ObjectReaderWriterIncludeAttribute)) Then
                    getter = GetGetterNested(fieldType)
                Else
                    Continue For
                End If
                If getter.Method Is Nothing Then Continue For
                _gettersAndSetters.Add(New KeyValuePair(Of Getter, MemberInfo)(getter, m))
            Next
        End Sub

        Public Shared Function Read(ByVal br As BinaryReader) As T
            If _gettersAndSetters.Count = 0 Then Throw New NotSupportedException("No member in specified type is supported")
            If GetType(T).IsValueType Then Return ReadValueType(br) 'VB will preserve value of ValueType when calling a method if the type of the variable is Object
            Dim ret As New T
            For Each pair In _gettersAndSetters
                Dim value = pair.Key.Invoke(br)
                If pair.Value.MemberType = MemberTypes.Field Then
                    CType(pair.Value, FieldInfo).SetValue(ret, value)
                Else
                    CType(pair.Value, PropertyInfo).SetValue(ret, value, Nothing)
                End If
            Next
            Return ret
        End Function

        Public Shared Function Read(ByVal s As Stream, ByVal isLittleEndian As Boolean) As T
            Using wrapper As New StreamWrapper(s), br = If(isLittleEndian, New BinaryReader(wrapper), New BigEndianBinaryReader(wrapper))
                Return Read(br)
            End Using
        End Function

        Public Shared Function Read(ByVal s As Stream) As T
            Return Read(s, True)
        End Function

        Private Shared Function ReadValueType(ByVal br As BinaryReader) As T
            Dim ret As ValueType = CType(Activator.CreateInstance(GetType(T)), ValueType)
            For Each pair In _gettersAndSetters
                Dim value = pair.Key.Invoke(br)
                If pair.Value.MemberType = MemberTypes.Field Then
                    CType(pair.Value, FieldInfo).SetValue(ret, value)
                Else
                    CType(pair.Value, PropertyInfo).SetValue(ret, value, Nothing)
                End If
            Next
            Return CType(DirectCast(ret, Object), T)
        End Function

        Private Shared Function GetGetterPrimitive(ByVal type As Type) As Getter
            Dim brType = GetType(BinaryReader)
            Dim ret = New Getter With {.Method = brType.GetMethod("Read" & type.Name, BindingFlags.Instance Or BindingFlags.Public, Nothing, CallingConventions.Any, New Type() {}, Nothing), .InvokeWithAdditionalData = False}
            Return ret
        End Function

        Private Shared Function GetGetterNested(ByVal type As Type) As Getter
            Dim orType = GetType(ObjectReader(Of )).MakeGenericType(type)
            Try
                Return New Getter With { _
                                .Method = orType.GetMethod("ReadNested", BindingFlags.Static Or BindingFlags.NonPublic, Nothing, CallingConventions.Any, New Type() {GetType(BinaryReader), GetType(Object())}, Nothing), _
                                .InvokeWithAdditionalData = True}
            Catch ex As Exception
                Return Nothing
            End Try
        End Function

        Private Shared Function ReadNested(ByVal reader As BinaryReader, ByVal dummy As Object()) As T
            Return Read(reader)
        End Function

        Private Shared Function GetGetterArray(ByVal type As Type, ByVal count As Integer) As Getter
            Dim elementGetter As Getter
            If type.IsPrimitive Then
                elementGetter = GetGetterPrimitive(type)
            Else
                elementGetter = GetGetterNested(type)
            End If

            Try
                Return New Getter With { _
                                        .Method = GetType(ObjectReader(Of T)).GetMethod("ReadArray", BindingFlags.Static Or BindingFlags.NonPublic, Nothing, CallingConventions.Any, New Type() {GetType(BinaryReader), GetType(Object())}, Nothing).MakeGenericMethod(type), _
                                        .InvokeWithAdditionalData = True, _
                                        .AdditionalData = New Object() {elementGetter, count}}
            Catch ex As Exception
                Return Nothing
            End Try
        End Function

        Private Shared Function ReadArray(Of U As New)(ByVal reader As BinaryReader, ByVal data() As Object) As U() 'data: element getter, array size
            Dim arraySize As Integer = CType(data(1), Integer)
            If GetType(U) Is GetType(Byte) Then 'Optimized method
                Dim ret = reader.ReadBytes(arraySize)
                If ret.Length < arraySize Then
                    Throw New EndOfStreamException()
                End If
                Return CType(CType(ret, Object), U())
            Else
                Dim elementGetter As Getter = CType(data(0), Getter)
                Dim ret As New List(Of U)
                For i = 0 To arraySize - 1
                    ret.Add(CType(elementGetter.Invoke(reader), U))
                Next
                Return ret.ToArray()
            End If
        End Function

        Private Sub New()

        End Sub
    End Class

End Namespace

        

Feb 26

ObjectReader

功能:使用BinaryReader读取数据构造一个类,支持除IntPtr以外的基本类型

      
Public NotInheritable Class ObjectReader(Of T As New)
    Shared _gettersAndSetters As New List(Of KeyValuePair(Of MethodInfo, MemberInfo))
    Shared Sub New()
        For Each m In GetType(T).GetMembers(BindingFlags.Public Or BindingFlags.Instance)
            If m.MemberType <> MemberTypes.Field AndAlso m.MemberType <> MemberTypes.Property Then Continue For
            If m.MemberType = MemberTypes.Field Then
                Dim fi = CType(m, FieldInfo)
                If Not fi.FieldType.IsPrimitive Then Continue For
                Dim getter = GetGetter(fi.FieldType)
                If getter Is Nothing Then Continue For
                _gettersAndSetters.Add(New KeyValuePair(Of MethodInfo, MemberInfo)(getter, fi))
            Else
                Dim pi = CType(m, PropertyInfo)
                If Not pi.PropertyType.IsPrimitive Then Continue For
                If Not pi.CanWrite Then Continue For
                Dim getter = GetGetter(pi.PropertyType)
                If getter Is Nothing Then Continue For
                _gettersAndSetters.Add(New KeyValuePair(Of MethodInfo, MemberInfo)(getter, pi))
            End If
        Next
    End Sub

    Public Shared Function Read(ByVal br As BinaryReader) As T
        If _gettersAndSetters.Count = 0 Then Throw New NotSupportedException("No member in specified type is supported and writable")
        If GetType(T).IsValueType Then Return ReadValueType(br) 'VB对结构使用反射时不能声明为Object。。。
        Dim ret As New T
        For Each pair In _gettersAndSetters
            Dim value = pair.Key.Invoke(br, Nothing)
            If pair.Value.MemberType = MemberTypes.Field Then
                CType(pair.Value, FieldInfo).SetValue(ret, value)
            Else
                CType(pair.Value, PropertyInfo).SetValue(ret, value, Nothing)
            End If
        Next
        Return ret
    End Function

    Private Shared Function ReadValueType(ByVal br As BinaryReader) As T
        Dim ret As ValueType = CType(Activator.CreateInstance(GetType(T)), ValueType)
        For Each pair In _gettersAndSetters
            Dim value = pair.Key.Invoke(br, Nothing)
            If pair.Value.MemberType = MemberTypes.Field Then
                CType(pair.Value, FieldInfo).SetValue(ret, value)
            Else
                CType(pair.Value, PropertyInfo).SetValue(ret, value, Nothing)
            End If
        Next
        Return CType(DirectCast(ret, Object), T)
    End Function

    Private Shared Function GetGetter(ByVal type As Type) As MethodInfo
        Dim brType = GetType(BinaryReader)
        Dim ret = brType.GetMethod("Read" & type.Name, BindingFlags.Instance Or BindingFlags.Public, Nothing, CallingConventions.Any, New Type() {}, Nothing)
        Return ret
    End Function

    Private Sub New()

    End Sub
End Class