none
ExpandableObjectConverter mit DefaultValue und Changed Notifikation RRS feed

  • Frage

  • Hallo,

    tja erstmal Hallo. Prinzipiell bin ich erst mal neu hier in diesem Forum. Ich hoffe das ist nicht etwa unpassend aber ich habe zum ExpandableObjectConverter gleich zwei Fragen.

    1.) Wie kann ich dieser Lösung ein DefaultValue mitgeben?

    2.) Warum erfolgt keine Changed-Notifikation wenn einzelne Werte innerhalt der Klasse geändert werden?

    Ich hoffe der Codeblock ist nicht zu ausführlich, aber zur Erklärung wohl unerläßlich.

    Die Klasse "Edges" wird in einer anderen Klasse als Eigenschaft verwendet. Folgende Problemstellung habe ich nun. Ich kann der Eigenschaft Radius keinen Defaultwert mitteilen. Also im PropertyGrid wird der Wert immer Fett dargestellt und zweitens erfolgt bei Änderung kein Event in der ParentKlasse. Ich hoffe ich habe mich verständlich ausgedrückt. Ein Beispiel-Code sagt mehr als tausend Wort.

    Public Class MeineKlasse
    
      ' Auf die Implementierung von INotifyPropertyChanged wurde hier nur in der Darstellung verzichtet - Im Original ist das vorhanden
    
      Private _Radius As Edges = New Edges()
      <Category("Layout")> _
      Public Property Radius() As Edges
       Get
         Return _Radius
       End Get
       Set(ByVal value As Edges)
         _Radius = value
         OnPropertyChanged("Radius")
       End Set
      End Property
    
    End Class
    
    
    <TypeConverter(GetType(EdgesConverter)), Serializable()> _
    Public Class Edges
      Implements System.ComponentModel.INotifyPropertyChanged
    
      Public Sub New()
    
      End Sub
    
    #Region "Properties"
    
      Private _TopLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken oberen Ecke an."), _
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property TopLeft() As Integer
       Get
         Return _TopLeft
       End Get
       Set(ByVal value As Integer)
         _TopLeft = value
         OnPropertyChanged("TopLeft")
       End Set
      End Property
    
      Private _TopRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten oberen Ecke an."), _
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property TopRight() As Integer
       Get
         Return _TopRight
       End Get
       Set(ByVal value As Integer)
         _TopRight = value
         OnPropertyChanged("TopRight")
       End Set
      End Property
    
      Private _BottomLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken unteren Ecke an."),
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property BottomLeft() As Integer
       Get
         Return _BottomLeft
       End Get
       Set(ByVal value As Integer)
         _BottomLeft = value
         OnPropertyChanged("BottomLeft")
       End Set
      End Property
    
      Private _BottomRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten unteren Ecke an."),
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property BottomRight() As Integer
       Get
         Return _BottomRight
       End Get
       Set(ByVal value As Integer)
         _BottomRight = value
         OnPropertyChanged("BottomRight")
       End Set
      End Property
    
    #End Region
    
    #Region "Events / Eventhandler"
    
      Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
       RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
      End Sub
    
      <NonSerialized()> _
      Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
       Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    
    #End Region
    
    End Class
    
    Public Class EdgesConverter
      Inherits ExpandableObjectConverter
    
      Public Overloads Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, _
                              ByVal destinationType As System.Type) As Boolean
       If (destinationType Is GetType(Edges)) Then
         Return True
       End If
       Return MyBase.CanConvertFrom(context, destinationType)
      End Function
    
      Public Overloads Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, _
                               ByVal sourceType As System.Type) As Boolean
       If (sourceType Is GetType(String)) Then
         Return True
       End If
       Return MyBase.CanConvertFrom(context, sourceType)
      End Function
    
      Public Overloads Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, _
                              ByVal culture As System.Globalization.CultureInfo, _
                              ByVal value As Object) As Object
       If TypeOf value Is String Then
         'Try
         Dim s As String = CType(value, String)
         Dim RadiusParts() As String
         s = s.Replace(";", " ")
         s = s.Replace(" ", " ")
         RadiusParts = Split(s, " ")
         Dim _Edges As Edges = New Edges()
    
         If Not IsNothing(RadiusParts) Then
          If RadiusParts.Length = 1 Then
            ' Nur ein Wert gilt dann für alle
            _Edges.TopLeft = RadiusParts(0)
            _Edges.TopRight = RadiusParts(0)
            _Edges.BottomLeft = RadiusParts(0)
            _Edges.BottomRight = RadiusParts(0)
          Else
            If Not IsNothing(RadiusParts(0)) Then
             _Edges.TopLeft = RadiusParts(0)
             If Not IsNothing(RadiusParts(1)) Then
               _Edges.TopRight = RadiusParts(1)
               If Not IsNothing(RadiusParts(2)) Then
                _Edges.BottomLeft = RadiusParts(2)
                If Not IsNothing(RadiusParts(3)) Then
                  _Edges.BottomRight = RadiusParts(3)
                End If
               End If
             End If
            End If
          End If
         End If
         Return _Edges
         'Catch ex As Exception
         '  Throw New ArgumentException(value & " kann nicht zu Edges konvertiert werden.")
         'End Try
       End If
       Return MyBase.ConvertFrom(context, culture, value)
      End Function
    
      Public Overloads Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, _
                             ByVal culture As System.Globalization.CultureInfo, _
                             ByVal value As Object, ByVal _
                             destinationType As System.Type) As Object
       If (destinationType Is GetType(System.String) AndAlso TypeOf value Is Edges) Then
         Dim _Edges As Edges = CType(value, Edges)
    
         If _Edges.TopLeft = _Edges.TopRight And
          _Edges.TopRight = _Edges.BottomLeft And
          _Edges.BottomLeft = _Edges.BottomRight Then
          ' Nur ein Wert der gilt dann für alle
          Return _Edges.TopLeft.ToString
         Else
          Return _Edges.TopLeft & "; " & _Edges.TopRight & "; " & _
              _Edges.BottomLeft & "; " & _Edges.BottomRight
         End If
       End If
       Return MyBase.ConvertTo(context, culture, value, destinationType)
      End Function
    End Class
    

     

     

     

    Samstag, 18. September 2010 21:48

Antworten

  • Hallo,

    In einfachen Fällen kann man über das DefaultValue Attribut einen Wert mitgeben.
    Für komplexere Fällen unterstützt der Designer weitere Methoden,
    zum einen Reset<EigenschaftsName> für das Zurücksetzen
    und zum anderen ShouldSerialize<Eigenschaftsname>, um zu bestimmen,
    ob eine Eigenschaft serialisiert werden muß, weil sie vom Standard abweicht
    (und wird auch vom Eigenschaftseditor ausgewertet)

    Eine mögliche Implementation in einem Steuerlement wäre:

      ' Vermutlich sinnfreie Werte nur zur Demo
      Private _radius As Edges = New Edges(1, 2, 3, 4)
      <Category("Layout")> _
      <RefreshProperties(RefreshProperties.Repaint)> _
      Public Property Radius() As Edges
        Get
          Return _Radius
        End Get
        Set(ByVal value As Edges)
          _radius = value
          RaiseEvent RadiusChanged(Me, EventArgs.Empty)
        End Set
      End Property
    
      Private Sub ResetRadius()
        Me.Radius = New Edges(1, 2, 3, 4)
      End Sub
    
      Private Function ShouldSerializeRadius() As Boolean
        'Return Not Me.Radius.IsEmpty
        Return Not (Me.Radius.TopLeft = 1 AndAlso Me.Radius.TopRight = 2 AndAlso Me.Radius.BottomLeft = 3 AndAlso Me.Radius.BottomRight = 4)
      End Function
    
      <Category("PropertyChanged")> _
      Public Event RadiusChanged As EventHandler
    
    
    

    Das vermutlich sinnvfrei (1, 2, 3, 4) habe ich nur zur Verdeutlichung verwendet.
    Verwendet man Standardwerte wie Leer sollte man sich zur Vereinfachung eine Eigenschaft dafür erstellen, siehe IsEmpty (unten).
    Auch sollte man für Windows Forms explizite Ereignisse in der Form <EigenschaftsName>Changed verwenden,
    anstatt INotifyPropertyChanged, damit die Ereignisse später im Eigenschaftsfenster auftauchen.

    Das von Dir verwendete NotifyParentProperty Attribut wirkt nur auf eine Eigenschaft
    gleichen Namens im übergeordneten Element hat.

    Ich empfehle die Lektüre von Erweitern der Entwurfszeitunterstützung wo einiges mehr erläutert wird.

     

    Ein TypeConverter sollte für die Entwicklungszeit das Erzeugen eines InstanceDescriptor unterstützen,
    und unabhängig von der eingestellten Kultur funktonieren  - schon englisch, deutsch haben unterschiedliche Listentrennzeichen.
    Ich habe Deinen Konverter mal umgeschrieben (auf Basis einer meiner Konverter und nicht intensiv getestet):

    Option Explicit On
    Option Strict On
    Option Infer On
    
    Imports System.ComponentModel
    Imports System.ComponentModel.Design.Serialization
    Imports System.Globalization
    
    <TypeConverter(GetType(EdgesConverter)), Serializable()> _
    Public Class Edges
      Implements System.ComponentModel.INotifyPropertyChanged
    
      Public Sub New()
      End Sub
    
      ''' <summary>Konstruktor für gleiche Werte.</summary>
      ''' <remarks>auch vom EdgesConverter verwendet.</remarks>
      Public Sub New(ByVal all As Integer)
        Me.TopLeft = all
        Me.TopRight = all
        Me.BottomLeft = all
        Me.BottomRight = all
      End Sub
    
      ''' <summary>Konstruktor für unterschiedliche Werte.</summary>
      ''' <remarks>auch vom EdgesConverter verwendet.</remarks>
      Public Sub New(ByVal topLeft As Integer, ByVal topRight As Integer, ByVal bottomLeft As Integer, ByVal bottomRight As Integer)
        Me.TopLeft = topLeft
        Me.TopRight = topRight
        Me.BottomLeft = bottomLeft
        Me.BottomRight = bottomRight
      End Sub
    
      ''' <summary>
      ''' Hilfsfunktion für einfache Abfragen
      ''' </summary>
      <Browsable(False)> _
      Public ReadOnly Property IsEmpty As Boolean
        Get
          Return (Me._TopLeft = 0 AndAlso Me._TopRight = 0 AndAlso Me._BottomLeft = 0 AndAlso Me._BottomRight = 0)
        End Get
      End Property
    
    
    #Region "Properties"
    
      Private _TopLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken oberen Ecke an."), _
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property TopLeft() As Integer
        Get
          Return _TopLeft
        End Get
        Set(ByVal value As Integer)
          _TopLeft = value
          OnPropertyChanged("TopLeft")
        End Set
      End Property
    
      Private _TopRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten oberen Ecke an."), _
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property TopRight() As Integer
        Get
          Return _TopRight
        End Get
        Set(ByVal value As Integer)
          _TopRight = value
          OnPropertyChanged("TopRight")
        End Set
      End Property
    
      Private _BottomLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken unteren Ecke an."),
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property BottomLeft() As Integer
        Get
          Return _BottomLeft
        End Get
        Set(ByVal value As Integer)
          _BottomLeft = value
          OnPropertyChanged("BottomLeft")
        End Set
      End Property
    
      Private _BottomRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten unteren Ecke an."),
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property BottomRight() As Integer
        Get
          Return _BottomRight
        End Get
        Set(ByVal value As Integer)
          _BottomRight = value
          OnPropertyChanged("BottomRight")
        End Set
      End Property
    
    #End Region
    
    #Region "Events / Eventhandler"
    
      Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
      End Sub
    
      <NonSerialized()> _
      Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
       Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    
    #End Region
    
    End Class
    
    Public Class EdgesConverter
      Inherits TypeConverter
    
      Public Overrides Function CanConvertFrom(ByVal context As ITypeDescriptorContext, ByVal sourceType As Type) As Boolean
        Return sourceType Is GetType(String) OrElse MyBase.CanConvertFrom(context, sourceType)
      End Function
    
      Public Overrides Function CanConvertTo(ByVal context As ITypeDescriptorContext, ByVal destinationType As Type) As Boolean
        Return destinationType Is GetType(InstanceDescriptor) OrElse MyBase.CanConvertTo(context, destinationType)
      End Function
    
      Public Overrides Function ConvertFrom( _
        ByVal context As ITypeDescriptorContext, _
        ByVal culture As CultureInfo, _
        ByVal value As Object) As Object
    
        ' Kein String dann Standardbehandlung
        Dim stringValue As String = TryCast(value, String)
        If stringValue IsNot Nothing Then
          stringValue = stringValue.Trim()
        End If
        If String.IsNullOrEmpty(stringValue) Then
          Return MyBase.ConvertFrom(context, culture, value)
        End If
    
        If culture Is Nothing Then
          ' aktuelle Kultur als Vorgabe
          culture = CultureInfo.CurrentCulture
        End If
        ' Trennzeichen lt. Cultur
        Dim separatorChar As Char = culture.TextInfo.ListSeparator.Chars(0)
        ' Für Konvertierung von Integer 
        Dim integerConverter As TypeConverter = TypeDescriptor.GetConverter(GetType(Integer))
    
        Dim stringArray = stringValue.Split(New Char() {separatorChar})
    
        Dim integerValues As Integer() = New Integer(stringArray.Length - 1) {}
        For index = 0 To integerValues.Length - 1
          integerValues(index) = CInt(integerConverter.ConvertFromString(context, culture, stringArray(index)))
        Next
        If integerValues.Length = 1 Then
          Return New Edges(integerValues(0))
        ElseIf integerValues.Length = 2 Then
          ' Top und Bottom gleich
          Return New Edges(integerValues(0), integerValues(0), integerValues(1), integerValues(1))
        ElseIf integerValues.Length = 4 Then
          Return New Edges(integerValues(0), integerValues(1), integerValues(2), integerValues(3))
        Else
          Throw New ArgumentException("Ungültige Angabe")
        End If
      End Function
    
      Public Overrides Function ConvertTo( _
        ByVal context As ITypeDescriptorContext, _
        ByVal culture As CultureInfo, _
        ByVal value As Object, _
        ByVal destinationType As Type) As Object
    
        If destinationType Is Nothing Then
          Throw New ArgumentNullException("destinationType")
        End If
        If TypeOf value Is Edges Then
          Dim edgeValue = DirectCast(value, Edges)
          Dim isAll = (edgeValue.TopLeft = edgeValue.TopRight _
            AndAlso edgeValue.TopLeft = edgeValue.BottomLeft _
            AndAlso edgeValue.TopLeft = edgeValue.BottomRight)
          Dim isDual = (edgeValue.TopLeft = edgeValue.TopRight _
            AndAlso edgeValue.BottomLeft = edgeValue.BottomRight)
    
    
          If destinationType Is GetType(String) Then
            If (culture Is Nothing) Then
              culture = CultureInfo.CurrentCulture
            End If
            Dim separator As String = culture.TextInfo.ListSeparator(0) & " "
            Dim integerConverter As TypeConverter = TypeDescriptor.GetConverter(GetType(Integer))
    
            Dim stringArray As String() = New String() { _
              integerConverter.ConvertToString(context, culture, edgeValue.TopLeft), _
              integerConverter.ConvertToString(context, culture, edgeValue.TopRight), _
              integerConverter.ConvertToString(context, culture, edgeValue.BottomLeft), _
              integerConverter.ConvertToString(context, culture, edgeValue.BottomRight)}
    
            If isAll Then
              Return stringArray(0)
            ElseIf isDual Then
              Return String.Join(separator, stringArray(0), stringArray(2))
            Else
              Return String.Join(separator, stringArray)
            End If
          ElseIf destinationType Is GetType(InstanceDescriptor) Then
            If isAll Then
              Return New InstanceDescriptor(GetType(Edges).GetConstructor( _
                  New Type() {GetType(Integer)}), New Object() {edgeValue.TopLeft})
            Else
              Return New InstanceDescriptor(GetType(Edges).GetConstructor( _
                New Type() {GetType(Integer), GetType(Integer), GetType(Integer), GetType(Integer)}), _
                New Object() {edgeValue.TopLeft, edgeValue.TopRight, edgeValue.BottomLeft, edgeValue.BottomRight})
            End If
          End If
        End If
        Return MyBase.ConvertTo(context, culture, value, destinationType)
      End Function
    
      Public Overrides Function GetCreateInstanceSupported(ByVal context As ITypeDescriptorContext) As Boolean
        Return True
      End Function
    
      Public Overrides Function CreateInstance(ByVal context As ITypeDescriptorContext, _
        ByVal propertyValues As IDictionary) As Object
        If context Is Nothing Then
          Throw New ArgumentNullException("context")
        End If
        If propertyValues Is Nothing Then
          Throw New ArgumentNullException("propertyValues")
        End If
        Dim value As Edges = DirectCast(context.PropertyDescriptor.GetValue(context.Instance), Edges)
        Return New Edges( _
          CInt(propertyValues.Item("TopLeft")), _
          CInt(propertyValues.Item("TopRight")), _
          CInt(propertyValues.Item("BottomLeft")), _
          CInt(propertyValues.Item("BottomRight")))
      End Function
    
      Public Overrides Function GetPropertiesSupported(ByVal context As ITypeDescriptorContext) As Boolean
        Return True
      End Function
    
      Public Overrides Function GetProperties(ByVal context As ITypeDescriptorContext, _
        ByVal value As Object, ByVal attributes As Attribute()) As PropertyDescriptorCollection
        Return TypeDescriptor.GetProperties(GetType(Edges), attributes).Sort( _
          New String() {"TopLeft", "TopRight", "BottomLeft", "BottomRight"})
      End Function
    
    End Class
    
    
    

    Darin u. a. gezeigt wie man 1, 2 oder 4 Werte anzeigen/konvertieren
    und den Designer bei der Anzeige von Eigenschaften beeinflussen kann.

    Gruß Elmar

     

    Sonntag, 19. September 2010 10:49
    Beantworter
  •  

    Hallo Community

    hier nun das Endergebnis für Alle. Eine Menge Text um 4 Werte zu verwalten :-)

    Imports System.ComponentModel
    Imports System.ComponentModel.Design.Serialization
    Imports System.Globalization
    
    
    <TypeConverter(GetType(EdgesConverter)), Serializable()> _
    Public Class Edges
      Implements System.ComponentModel.INotifyPropertyChanged
    
    #Region "Konstruktor"
    
      ''' <summary>Default Kostruktor</summary>
      ''' <remarks></remarks>
      Public Sub New()
      End Sub
    
      ''' <summary>Konstruktor für gleiche Werte.</summary>
      ''' <remarks>auch vom EdgesConverter verwendet.</remarks>
      Public Sub New(ByVal all As Integer)
       Me.TopLeft = all
       Me.TopRight = all
       Me.BottomLeft = all
       Me.BottomRight = all
      End Sub
    
      ''' <summary>Konstruktor für unterschiedliche Werte.</summary>
      ''' <remarks>auch vom EdgesConverter verwendet.</remarks>
      Public Sub New(ByVal topLeft As Integer, ByVal topRight As Integer, ByVal bottomLeft As Integer, ByVal bottomRight As Integer)
       Me.TopLeft = topLeft
       Me.TopRight = topRight
       Me.BottomLeft = bottomLeft
       Me.BottomRight = bottomRight
      End Sub
    
      ''' <summary>
      ''' Hilfsfunktion für einfache Abfragen
      ''' </summary>
      <Browsable(False)> _
      Public ReadOnly Property IsEmpty As Boolean
       Get
         Return (Me._TopLeft = 0 AndAlso Me._TopRight = 0 AndAlso Me._BottomLeft = 0 AndAlso Me._BottomRight = 0)
       End Get
      End Property
    
    #End Region
    
    #Region "Properties"
    
      Private _TopLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken oberen Ecke an."), _
      DefaultValue(0)> _
      Public Property TopLeft() As Integer
       Get
         Return _TopLeft
       End Get
       Set(ByVal value As Integer)
         _TopLeft = value
         OnPropertyChanged("TopLeft")
       End Set
      End Property
    
      Private _TopRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten oberen Ecke an."), _
      DefaultValue(0)> _
      Public Property TopRight() As Integer
       Get
         Return _TopRight
       End Get
       Set(ByVal value As Integer)
         _TopRight = value
         OnPropertyChanged("TopRight")
       End Set
      End Property
    
      Private _BottomLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken unteren Ecke an."),
      DefaultValue(0)> _
      Public Property BottomLeft() As Integer
       Get
         Return _BottomLeft
       End Get
       Set(ByVal value As Integer)
         _BottomLeft = value
         OnPropertyChanged("BottomLeft")
       End Set
      End Property
    
      Private _BottomRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten unteren Ecke an."),
      DefaultValue(0)> _
      Public Property BottomRight() As Integer
       Get
         Return _BottomRight
       End Get
       Set(ByVal value As Integer)
         _BottomRight = value
         OnPropertyChanged("BottomRight")
       End Set
      End Property
    
    #End Region
    
    #Region "Events / Eventhandler"
    
      Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
       RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
      End Sub
    
      <NonSerialized()> _
      Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
      Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    
    #End Region
    
    #Region "Methoden"
    
      Public Overrides Function Equals(ByVal other As Object) As Boolean
       Return TypeOf other Is Edges AndAlso Me = CType(other, Edges)
      End Function
    
      Public Overrides Function GetHashCode() As Integer
       Return (Me._TopLeft Xor Me._TopRight) << 16 Or (Me._BottomLeft Xor Me._BottomRight)
      End Function
    
      Public Shared Operator =(ByVal left As Edges, ByVal right As Edges) As Boolean
       Return (left._TopLeft = right._TopLeft AndAlso _
        left._TopRight = right._TopRight AndAlso _
        left._BottomLeft = right._BottomLeft AndAlso _
        left._BottomRight = right._BottomRight)
      End Operator
    
      Public Shared Operator <>(ByVal left As Edges, ByVal right As Edges) As Boolean
       Return Not (left = right)
      End Operator
    
    #End Region
    
    End Class
    
    Public Class EdgesConverter
      Inherits TypeConverter
    
    #Region "Methoden"
    
      ''' <summary>
      ''' CanConvertFrom
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="sourceType"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function CanConvertFrom(ByVal context As ITypeDescriptorContext, ByVal sourceType As Type) As Boolean
       Return sourceType Is GetType(String) OrElse MyBase.CanConvertFrom(context, sourceType)
      End Function
    
      ''' <summary>
      ''' CanConvertTo
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="destinationType"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function CanConvertTo(ByVal context As ITypeDescriptorContext, _
                         ByVal destinationType As Type) As Boolean
       Return destinationType Is GetType(InstanceDescriptor) OrElse MyBase.CanConvertTo(context, destinationType)
      End Function
    
      ''' <summary>
      ''' ConvertFrom
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="culture"></param>
      ''' <param name="value"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function ConvertFrom(ByVal context As ITypeDescriptorContext, _
                         ByVal culture As CultureInfo, _
                         ByVal value As Object) As Object
    
       ' Kein String dann Standardbehandlung
       Dim stringValue As String = TryCast(value, String)
       If stringValue IsNot Nothing Then
         stringValue = stringValue.Trim()
       End If
       If String.IsNullOrEmpty(stringValue) Then
         Return MyBase.ConvertFrom(context, culture, value)
       End If
    
       If culture Is Nothing Then
         ' aktuelle Kultur als Vorgabe
         culture = CultureInfo.CurrentCulture
       End If
       ' Trennzeichen lt. Cultur
       Dim separatorChar As Char = culture.TextInfo.ListSeparator.Chars(0)
       ' Für Konvertierung von Integer 
       Dim integerConverter As TypeConverter = TypeDescriptor.GetConverter(GetType(Integer))
    
       Dim stringArray As String() = stringValue.Split(New Char() {separatorChar})
    
       Dim integerValues As Integer() = New Integer(stringArray.Length - 1) {}
       For index As Integer = 0 To integerValues.Length - 1
    
         integerValues(index) = CInt(integerConverter.ConvertFromString(context, culture, stringArray(index)))
       Next
       If integerValues.Length = 1 Then
         Return New Edges(integerValues(0))
       ElseIf integerValues.Length = 2 Then
         ' Top und Bottom gleich
         Return New Edges(integerValues(0), integerValues(0), integerValues(1), integerValues(1))
       ElseIf integerValues.Length = 4 Then
         Return New Edges(integerValues(0), integerValues(1), integerValues(2), integerValues(3))
       Else
         Throw New ArgumentException("Ungültige Angabe")
       End If
      End Function
    
      ''' <summary>
      ''' ConvertTo
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="culture"></param>
      ''' <param name="value"></param>
      ''' <param name="destinationType"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function ConvertTo( _
       ByVal context As ITypeDescriptorContext, _
       ByVal culture As CultureInfo, _
       ByVal value As Object, _
       ByVal destinationType As Type) As Object
    
       If destinationType Is Nothing Then
         Throw New ArgumentNullException("destinationType")
       End If
       If TypeOf value Is Edges Then
         Dim edgeValue As Edges = DirectCast(value, Edges)
         Dim isAll As Boolean = (edgeValue.TopLeft = edgeValue.TopRight _
          AndAlso edgeValue.TopLeft = edgeValue.BottomLeft _
          AndAlso edgeValue.TopLeft = edgeValue.BottomRight)
         Dim isDual As Boolean = (edgeValue.TopLeft = edgeValue.TopRight _
          AndAlso edgeValue.BottomLeft = edgeValue.BottomRight)
    
    
         If destinationType Is GetType(String) Then
          If (culture Is Nothing) Then
            culture = CultureInfo.CurrentCulture
          End If
          Dim separator As String = culture.TextInfo.ListSeparator(0) & " "
          Dim integerConverter As TypeConverter = TypeDescriptor.GetConverter(GetType(Integer))
    
          Dim stringArray As String() = New String() { _
           integerConverter.ConvertToString(context, culture, edgeValue.TopLeft), _
           integerConverter.ConvertToString(context, culture, edgeValue.TopRight), _
           integerConverter.ConvertToString(context, culture, edgeValue.BottomLeft), _
           integerConverter.ConvertToString(context, culture, edgeValue.BottomRight)}
    
          If isAll Then
            Return stringArray(0)
          ElseIf isDual Then
            Return String.Join(separator, {stringArray(0), stringArray(2)})
          Else
            Return String.Join(separator, stringArray)
          End If
         ElseIf destinationType Is GetType(InstanceDescriptor) Then
          If isAll Then
            Return New InstanceDescriptor(GetType(Edges).GetConstructor( _
              New Type() {GetType(Integer)}), New Object() {edgeValue.TopLeft})
          Else
            Return New InstanceDescriptor(GetType(Edges).GetConstructor( _
             New Type() {GetType(Integer), GetType(Integer), GetType(Integer), GetType(Integer)}), _
             New Object() {edgeValue.TopLeft, edgeValue.TopRight, edgeValue.BottomLeft, edgeValue.BottomRight})
          End If
         End If
       End If
       Return MyBase.ConvertTo(context, culture, value, destinationType)
      End Function
    
      ''' <summary>
      ''' GetCreateInstanceSupported
      ''' </summary>
      ''' <param name="context"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function GetCreateInstanceSupported(ByVal context As ITypeDescriptorContext) As Boolean
       Return True
      End Function
    
      ''' <summary>
      ''' CreateInstance
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="propertyValues"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function CreateInstance(ByVal context As ITypeDescriptorContext, _
       ByVal propertyValues As IDictionary) As Object
       If context Is Nothing Then
         Throw New ArgumentNullException("context")
       End If
       If propertyValues Is Nothing Then
         Throw New ArgumentNullException("propertyValues")
       End If
       Dim value As Edges = DirectCast(context.PropertyDescriptor.GetValue(context.Instance), Edges)
       Return New Edges( _
        CInt(propertyValues.Item("TopLeft")), _
        CInt(propertyValues.Item("TopRight")), _
        CInt(propertyValues.Item("BottomLeft")), _
        CInt(propertyValues.Item("BottomRight")))
      End Function
    
      ''' <summary>
      ''' GetPropertiesSupported
      ''' </summary>
      ''' <param name="context"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function GetPropertiesSupported(ByVal context As ITypeDescriptorContext) As Boolean
       Return True
      End Function
    
      ''' <summary>
      ''' GetProperties
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="value"></param>
      ''' <param name="attributes"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function GetProperties(ByVal context As ITypeDescriptorContext, _
       ByVal value As Object, ByVal attributes As Attribute()) As PropertyDescriptorCollection
       Return TypeDescriptor.GetProperties(GetType(Edges), attributes).Sort( _
        New String() {"TopLeft", "TopRight", "BottomLeft", "BottomRight"})
      End Function
    
    #End Region
    
    End Class
    
    

     

    Gruß Frank

    • Als Antwort markiert NachtAktiv Sonntag, 19. September 2010 21:31
    Sonntag, 19. September 2010 20:57

Alle Antworten

  • Hallo,

    In einfachen Fällen kann man über das DefaultValue Attribut einen Wert mitgeben.
    Für komplexere Fällen unterstützt der Designer weitere Methoden,
    zum einen Reset<EigenschaftsName> für das Zurücksetzen
    und zum anderen ShouldSerialize<Eigenschaftsname>, um zu bestimmen,
    ob eine Eigenschaft serialisiert werden muß, weil sie vom Standard abweicht
    (und wird auch vom Eigenschaftseditor ausgewertet)

    Eine mögliche Implementation in einem Steuerlement wäre:

      ' Vermutlich sinnfreie Werte nur zur Demo
      Private _radius As Edges = New Edges(1, 2, 3, 4)
      <Category("Layout")> _
      <RefreshProperties(RefreshProperties.Repaint)> _
      Public Property Radius() As Edges
        Get
          Return _Radius
        End Get
        Set(ByVal value As Edges)
          _radius = value
          RaiseEvent RadiusChanged(Me, EventArgs.Empty)
        End Set
      End Property
    
      Private Sub ResetRadius()
        Me.Radius = New Edges(1, 2, 3, 4)
      End Sub
    
      Private Function ShouldSerializeRadius() As Boolean
        'Return Not Me.Radius.IsEmpty
        Return Not (Me.Radius.TopLeft = 1 AndAlso Me.Radius.TopRight = 2 AndAlso Me.Radius.BottomLeft = 3 AndAlso Me.Radius.BottomRight = 4)
      End Function
    
      <Category("PropertyChanged")> _
      Public Event RadiusChanged As EventHandler
    
    
    

    Das vermutlich sinnvfrei (1, 2, 3, 4) habe ich nur zur Verdeutlichung verwendet.
    Verwendet man Standardwerte wie Leer sollte man sich zur Vereinfachung eine Eigenschaft dafür erstellen, siehe IsEmpty (unten).
    Auch sollte man für Windows Forms explizite Ereignisse in der Form <EigenschaftsName>Changed verwenden,
    anstatt INotifyPropertyChanged, damit die Ereignisse später im Eigenschaftsfenster auftauchen.

    Das von Dir verwendete NotifyParentProperty Attribut wirkt nur auf eine Eigenschaft
    gleichen Namens im übergeordneten Element hat.

    Ich empfehle die Lektüre von Erweitern der Entwurfszeitunterstützung wo einiges mehr erläutert wird.

     

    Ein TypeConverter sollte für die Entwicklungszeit das Erzeugen eines InstanceDescriptor unterstützen,
    und unabhängig von der eingestellten Kultur funktonieren  - schon englisch, deutsch haben unterschiedliche Listentrennzeichen.
    Ich habe Deinen Konverter mal umgeschrieben (auf Basis einer meiner Konverter und nicht intensiv getestet):

    Option Explicit On
    Option Strict On
    Option Infer On
    
    Imports System.ComponentModel
    Imports System.ComponentModel.Design.Serialization
    Imports System.Globalization
    
    <TypeConverter(GetType(EdgesConverter)), Serializable()> _
    Public Class Edges
      Implements System.ComponentModel.INotifyPropertyChanged
    
      Public Sub New()
      End Sub
    
      ''' <summary>Konstruktor für gleiche Werte.</summary>
      ''' <remarks>auch vom EdgesConverter verwendet.</remarks>
      Public Sub New(ByVal all As Integer)
        Me.TopLeft = all
        Me.TopRight = all
        Me.BottomLeft = all
        Me.BottomRight = all
      End Sub
    
      ''' <summary>Konstruktor für unterschiedliche Werte.</summary>
      ''' <remarks>auch vom EdgesConverter verwendet.</remarks>
      Public Sub New(ByVal topLeft As Integer, ByVal topRight As Integer, ByVal bottomLeft As Integer, ByVal bottomRight As Integer)
        Me.TopLeft = topLeft
        Me.TopRight = topRight
        Me.BottomLeft = bottomLeft
        Me.BottomRight = bottomRight
      End Sub
    
      ''' <summary>
      ''' Hilfsfunktion für einfache Abfragen
      ''' </summary>
      <Browsable(False)> _
      Public ReadOnly Property IsEmpty As Boolean
        Get
          Return (Me._TopLeft = 0 AndAlso Me._TopRight = 0 AndAlso Me._BottomLeft = 0 AndAlso Me._BottomRight = 0)
        End Get
      End Property
    
    
    #Region "Properties"
    
      Private _TopLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken oberen Ecke an."), _
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property TopLeft() As Integer
        Get
          Return _TopLeft
        End Get
        Set(ByVal value As Integer)
          _TopLeft = value
          OnPropertyChanged("TopLeft")
        End Set
      End Property
    
      Private _TopRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten oberen Ecke an."), _
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property TopRight() As Integer
        Get
          Return _TopRight
        End Get
        Set(ByVal value As Integer)
          _TopRight = value
          OnPropertyChanged("TopRight")
        End Set
      End Property
    
      Private _BottomLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken unteren Ecke an."),
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property BottomLeft() As Integer
        Get
          Return _BottomLeft
        End Get
        Set(ByVal value As Integer)
          _BottomLeft = value
          OnPropertyChanged("BottomLeft")
        End Set
      End Property
    
      Private _BottomRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten unteren Ecke an."),
      NotifyParentProperty(True), DefaultValue(0)> _
      Public Property BottomRight() As Integer
        Get
          Return _BottomRight
        End Get
        Set(ByVal value As Integer)
          _BottomRight = value
          OnPropertyChanged("BottomRight")
        End Set
      End Property
    
    #End Region
    
    #Region "Events / Eventhandler"
    
      Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
      End Sub
    
      <NonSerialized()> _
      Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
       Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    
    #End Region
    
    End Class
    
    Public Class EdgesConverter
      Inherits TypeConverter
    
      Public Overrides Function CanConvertFrom(ByVal context As ITypeDescriptorContext, ByVal sourceType As Type) As Boolean
        Return sourceType Is GetType(String) OrElse MyBase.CanConvertFrom(context, sourceType)
      End Function
    
      Public Overrides Function CanConvertTo(ByVal context As ITypeDescriptorContext, ByVal destinationType As Type) As Boolean
        Return destinationType Is GetType(InstanceDescriptor) OrElse MyBase.CanConvertTo(context, destinationType)
      End Function
    
      Public Overrides Function ConvertFrom( _
        ByVal context As ITypeDescriptorContext, _
        ByVal culture As CultureInfo, _
        ByVal value As Object) As Object
    
        ' Kein String dann Standardbehandlung
        Dim stringValue As String = TryCast(value, String)
        If stringValue IsNot Nothing Then
          stringValue = stringValue.Trim()
        End If
        If String.IsNullOrEmpty(stringValue) Then
          Return MyBase.ConvertFrom(context, culture, value)
        End If
    
        If culture Is Nothing Then
          ' aktuelle Kultur als Vorgabe
          culture = CultureInfo.CurrentCulture
        End If
        ' Trennzeichen lt. Cultur
        Dim separatorChar As Char = culture.TextInfo.ListSeparator.Chars(0)
        ' Für Konvertierung von Integer 
        Dim integerConverter As TypeConverter = TypeDescriptor.GetConverter(GetType(Integer))
    
        Dim stringArray = stringValue.Split(New Char() {separatorChar})
    
        Dim integerValues As Integer() = New Integer(stringArray.Length - 1) {}
        For index = 0 To integerValues.Length - 1
          integerValues(index) = CInt(integerConverter.ConvertFromString(context, culture, stringArray(index)))
        Next
        If integerValues.Length = 1 Then
          Return New Edges(integerValues(0))
        ElseIf integerValues.Length = 2 Then
          ' Top und Bottom gleich
          Return New Edges(integerValues(0), integerValues(0), integerValues(1), integerValues(1))
        ElseIf integerValues.Length = 4 Then
          Return New Edges(integerValues(0), integerValues(1), integerValues(2), integerValues(3))
        Else
          Throw New ArgumentException("Ungültige Angabe")
        End If
      End Function
    
      Public Overrides Function ConvertTo( _
        ByVal context As ITypeDescriptorContext, _
        ByVal culture As CultureInfo, _
        ByVal value As Object, _
        ByVal destinationType As Type) As Object
    
        If destinationType Is Nothing Then
          Throw New ArgumentNullException("destinationType")
        End If
        If TypeOf value Is Edges Then
          Dim edgeValue = DirectCast(value, Edges)
          Dim isAll = (edgeValue.TopLeft = edgeValue.TopRight _
            AndAlso edgeValue.TopLeft = edgeValue.BottomLeft _
            AndAlso edgeValue.TopLeft = edgeValue.BottomRight)
          Dim isDual = (edgeValue.TopLeft = edgeValue.TopRight _
            AndAlso edgeValue.BottomLeft = edgeValue.BottomRight)
    
    
          If destinationType Is GetType(String) Then
            If (culture Is Nothing) Then
              culture = CultureInfo.CurrentCulture
            End If
            Dim separator As String = culture.TextInfo.ListSeparator(0) & " "
            Dim integerConverter As TypeConverter = TypeDescriptor.GetConverter(GetType(Integer))
    
            Dim stringArray As String() = New String() { _
              integerConverter.ConvertToString(context, culture, edgeValue.TopLeft), _
              integerConverter.ConvertToString(context, culture, edgeValue.TopRight), _
              integerConverter.ConvertToString(context, culture, edgeValue.BottomLeft), _
              integerConverter.ConvertToString(context, culture, edgeValue.BottomRight)}
    
            If isAll Then
              Return stringArray(0)
            ElseIf isDual Then
              Return String.Join(separator, stringArray(0), stringArray(2))
            Else
              Return String.Join(separator, stringArray)
            End If
          ElseIf destinationType Is GetType(InstanceDescriptor) Then
            If isAll Then
              Return New InstanceDescriptor(GetType(Edges).GetConstructor( _
                  New Type() {GetType(Integer)}), New Object() {edgeValue.TopLeft})
            Else
              Return New InstanceDescriptor(GetType(Edges).GetConstructor( _
                New Type() {GetType(Integer), GetType(Integer), GetType(Integer), GetType(Integer)}), _
                New Object() {edgeValue.TopLeft, edgeValue.TopRight, edgeValue.BottomLeft, edgeValue.BottomRight})
            End If
          End If
        End If
        Return MyBase.ConvertTo(context, culture, value, destinationType)
      End Function
    
      Public Overrides Function GetCreateInstanceSupported(ByVal context As ITypeDescriptorContext) As Boolean
        Return True
      End Function
    
      Public Overrides Function CreateInstance(ByVal context As ITypeDescriptorContext, _
        ByVal propertyValues As IDictionary) As Object
        If context Is Nothing Then
          Throw New ArgumentNullException("context")
        End If
        If propertyValues Is Nothing Then
          Throw New ArgumentNullException("propertyValues")
        End If
        Dim value As Edges = DirectCast(context.PropertyDescriptor.GetValue(context.Instance), Edges)
        Return New Edges( _
          CInt(propertyValues.Item("TopLeft")), _
          CInt(propertyValues.Item("TopRight")), _
          CInt(propertyValues.Item("BottomLeft")), _
          CInt(propertyValues.Item("BottomRight")))
      End Function
    
      Public Overrides Function GetPropertiesSupported(ByVal context As ITypeDescriptorContext) As Boolean
        Return True
      End Function
    
      Public Overrides Function GetProperties(ByVal context As ITypeDescriptorContext, _
        ByVal value As Object, ByVal attributes As Attribute()) As PropertyDescriptorCollection
        Return TypeDescriptor.GetProperties(GetType(Edges), attributes).Sort( _
          New String() {"TopLeft", "TopRight", "BottomLeft", "BottomRight"})
      End Function
    
    End Class
    
    
    

    Darin u. a. gezeigt wie man 1, 2 oder 4 Werte anzeigen/konvertieren
    und den Designer bei der Anzeige von Eigenschaften beeinflussen kann.

    Gruß Elmar

     

    Sonntag, 19. September 2010 10:49
    Beantworter
  • Hallo Elmar,

    ich danke Dir erstmal recht herzlich für die Mühe die Du Dir mit meinem CodeSchnipsel gemacht hast und natürlich auch die sehr ausführlichen Erklärungen. Die Lösung mit ShouldSerializeRadius() wende ich auch im Augenblick an. Allerdings dachte ich, das es doch noch einen anderen Weg für DefaultValue() geben müßte. Denn beispielsweise folgendes Beispiel kommt hier ohne die Definition von ShouldSerializeXXXX aus.

     Private _Font As New System.Drawing.Font("Microsoft Sans Serif", 8.25)
    <Localizable(True), Category("Text"), DefaultValue(GetType(Font), "Microsoft Sans Serif, 8.25pt"), XmlIgnore()> _
      Public Property Font() As Font
       Get
         Return _Font
       End Get
       Set(ByVal Value As Font)
         _Font = Value
         MyBase.OnPropertyChanged("Font")
       End Set
      End Property
    
    Gruß Frank
    Sonntag, 19. September 2010 12:35
  • Hallo Frank,

    kann man zwar machen, hier wäre das:

    <DefaultValue(GetType(Edges), "1, 2, 3, 4")> _
    
    da der Wert vom TypeConverter mit InvariantCulture ausgewertet wird -
    Listentrenner ist dort ein Komma.

    Meiner Erfahrung nach klappt es jedoch mit Anzeige und Serialisierung nicht mmer zuverlässig.
    Gerade noch kurz für VS2010 getestet und es scheint sich nichts geändert zu haben:
    Verzichtet man auf die zusätzlichen Methoden und verwendet nur DefaultValue,
    wird der Wert fett angezeigt.
    Führt man ein Reset (via Eigenschaftsfenster) aus, ist der Wert kurzzeitig "normal",
    nach dem nächsten Laden aber wieder fett.

    Auch das Nicht-Serialisieren des Standardwertes klappt dabei nicht immer zuverlässig,
    was dann den Code (in InitalizeComponent etc) aufbläht.

    Deswegen verwende ich durchweg die zwei Methoden mehr, wenn es mehr als
    ein einfacher Wert ist.

    Gruß Elmar

    Sonntag, 19. September 2010 13:58
    Beantworter
  • Hallo Elmar,

    die ChangedNotification funktioniert jetzt völlig einwandfrei. Allerdings grübel ich noch an dem DefaultValue Problem. Könnte es sein, das ich zum diesem Zweck die Function Equals überschreiben müßte. Ich denke mal für die Auswertung von DefaultValue erzeugt das System doch sicherlich eine weitere Instanz von Edges und vergleicht diese dann mit der vorhandenen Instanz. Somit sind diese ja dann immer unterschiedlich. Wenn ich jetzt Equals() überschreibe wie kann ich dann die Wandlungsroutinen im TypeConverter nutzen. Sorry wenn ich vieleicht nerve, aber Du besitzt so ein enormes Wissen und das nutze ich natürlich gern. Falls Du die Pappen dick hast einfach nicht mehr darauf Antworten. Ansonsten nochmals Dank.

    Gruß Frank

     

    Sonntag, 19. September 2010 18:41
  • Hallo Frank,

    kann man schon, nur ist das Frage der Abwägung, wo man die zusätzlichen Zeilen Code schreibt,
    braucht man den Typ nur einige Male, macht es eher mehr Arbeit.
    Zudem missfallen mir persönlich Attribute mit String-Angaben, da sich leichter Fehler einschleichen.

    Macht man daraus ein Structure (vermeidet damit Nothing Prüfungen)
    und packt  Equals, GetHashCode und Vergleiche hinzu, wird man es los:

    <TypeConverter(GetType(EdgesConverter)), Serializable()> _
    Public Structure Edges
    ' ... Rest wie oben
    
    
      Public Overrides Function Equals(ByVal other As Object) As Boolean
        Return TypeOf other Is Edges AndAlso Me = CType(other, Edges)
      End Function
    
      Public Overrides Function GetHashCode() As Integer
        Return (Me._TopLeft Xor Me._TopRight) << 16 Or (Me._BottomLeft Xor Me._BottomRight)
      End Function
    
      Public Shared Operator =(ByVal left As Edges, ByVal right As Edges) As Boolean
        Return (left._TopLeft = right._TopLeft AndAlso _
          left._TopRight = right._TopRight AndAlso _
          left._BottomLeft = right._BottomLeft AndAlso _
          left._BottomRight = right._BottomRight)
      End Operator
    
      Public Shared Operator <>(ByVal left As Edges, ByVal right As Edges) As Boolean
        Return Not (left = right)
      End Operator
    End Structure
    Allerdings habe ich den Designer (getestet in einem Panel) trotzdem dabei "erwischt",
    dass er die Anzeige mal fett darstellt (bisher war es nach Neuladen weg).

    Umgekehrt: Will man Entwicklungszeit sparen, könnte man z. B. die Padding Struktur verwenden,
    die erfüllt einen ähnlichen Zweck (und kann sogar ein, zwei Dinge menr).
    Zumindest sofern die Verwendung für Radius so zu gestalten ist,
    das die anderen Namen keine geistigen Bocksprünge erfordern.

    Gruß Elmar

    • Bearbeitet Elmar BoyeEditor Sonntag, 19. September 2010 19:50 Code Formatierung
    Sonntag, 19. September 2010 19:47
    Beantworter
  • Hallo Elmar,

    ich danke Dir wirklich recht herzlich. Jetzt spielt alles so wie ich mir das gewünscht habe. Ich weis das Beispiel war ein bischen banal, aber es ging mir hier eher um Verständnisfragen in Verbindung mit dem ExpandableObjectConverter bzw. dem Einsatz von TypeConvertern. Ich stimme Dir zu, das drei Zeilen ShouldSerializeXXX das bestimmt schnell gelöst hätten. Ich kann nur sagen "Klassen sollten für die Ewigkeit definiert werden, und wenn nicht dann aber flexibel anpassbar sein". :-)

    Und mit diesem Satz möchte ich mich gern verabschieden.

    Gruß Frank

     

    • Bearbeitet NachtAktiv Sonntag, 19. September 2010 21:08
    Sonntag, 19. September 2010 20:39
  •  

    Hallo Community

    hier nun das Endergebnis für Alle. Eine Menge Text um 4 Werte zu verwalten :-)

    Imports System.ComponentModel
    Imports System.ComponentModel.Design.Serialization
    Imports System.Globalization
    
    
    <TypeConverter(GetType(EdgesConverter)), Serializable()> _
    Public Class Edges
      Implements System.ComponentModel.INotifyPropertyChanged
    
    #Region "Konstruktor"
    
      ''' <summary>Default Kostruktor</summary>
      ''' <remarks></remarks>
      Public Sub New()
      End Sub
    
      ''' <summary>Konstruktor für gleiche Werte.</summary>
      ''' <remarks>auch vom EdgesConverter verwendet.</remarks>
      Public Sub New(ByVal all As Integer)
       Me.TopLeft = all
       Me.TopRight = all
       Me.BottomLeft = all
       Me.BottomRight = all
      End Sub
    
      ''' <summary>Konstruktor für unterschiedliche Werte.</summary>
      ''' <remarks>auch vom EdgesConverter verwendet.</remarks>
      Public Sub New(ByVal topLeft As Integer, ByVal topRight As Integer, ByVal bottomLeft As Integer, ByVal bottomRight As Integer)
       Me.TopLeft = topLeft
       Me.TopRight = topRight
       Me.BottomLeft = bottomLeft
       Me.BottomRight = bottomRight
      End Sub
    
      ''' <summary>
      ''' Hilfsfunktion für einfache Abfragen
      ''' </summary>
      <Browsable(False)> _
      Public ReadOnly Property IsEmpty As Boolean
       Get
         Return (Me._TopLeft = 0 AndAlso Me._TopRight = 0 AndAlso Me._BottomLeft = 0 AndAlso Me._BottomRight = 0)
       End Get
      End Property
    
    #End Region
    
    #Region "Properties"
    
      Private _TopLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken oberen Ecke an."), _
      DefaultValue(0)> _
      Public Property TopLeft() As Integer
       Get
         Return _TopLeft
       End Get
       Set(ByVal value As Integer)
         _TopLeft = value
         OnPropertyChanged("TopLeft")
       End Set
      End Property
    
      Private _TopRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten oberen Ecke an."), _
      DefaultValue(0)> _
      Public Property TopRight() As Integer
       Get
         Return _TopRight
       End Get
       Set(ByVal value As Integer)
         _TopRight = value
         OnPropertyChanged("TopRight")
       End Set
      End Property
    
      Private _BottomLeft As Integer
      <DescriptionAttribute("Gibt den Radius der linken unteren Ecke an."),
      DefaultValue(0)> _
      Public Property BottomLeft() As Integer
       Get
         Return _BottomLeft
       End Get
       Set(ByVal value As Integer)
         _BottomLeft = value
         OnPropertyChanged("BottomLeft")
       End Set
      End Property
    
      Private _BottomRight As Integer
      <DescriptionAttribute("Gibt den Radius der rechten unteren Ecke an."),
      DefaultValue(0)> _
      Public Property BottomRight() As Integer
       Get
         Return _BottomRight
       End Get
       Set(ByVal value As Integer)
         _BottomRight = value
         OnPropertyChanged("BottomRight")
       End Set
      End Property
    
    #End Region
    
    #Region "Events / Eventhandler"
    
      Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
       RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
      End Sub
    
      <NonSerialized()> _
      Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
      Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    
    #End Region
    
    #Region "Methoden"
    
      Public Overrides Function Equals(ByVal other As Object) As Boolean
       Return TypeOf other Is Edges AndAlso Me = CType(other, Edges)
      End Function
    
      Public Overrides Function GetHashCode() As Integer
       Return (Me._TopLeft Xor Me._TopRight) << 16 Or (Me._BottomLeft Xor Me._BottomRight)
      End Function
    
      Public Shared Operator =(ByVal left As Edges, ByVal right As Edges) As Boolean
       Return (left._TopLeft = right._TopLeft AndAlso _
        left._TopRight = right._TopRight AndAlso _
        left._BottomLeft = right._BottomLeft AndAlso _
        left._BottomRight = right._BottomRight)
      End Operator
    
      Public Shared Operator <>(ByVal left As Edges, ByVal right As Edges) As Boolean
       Return Not (left = right)
      End Operator
    
    #End Region
    
    End Class
    
    Public Class EdgesConverter
      Inherits TypeConverter
    
    #Region "Methoden"
    
      ''' <summary>
      ''' CanConvertFrom
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="sourceType"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function CanConvertFrom(ByVal context As ITypeDescriptorContext, ByVal sourceType As Type) As Boolean
       Return sourceType Is GetType(String) OrElse MyBase.CanConvertFrom(context, sourceType)
      End Function
    
      ''' <summary>
      ''' CanConvertTo
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="destinationType"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function CanConvertTo(ByVal context As ITypeDescriptorContext, _
                         ByVal destinationType As Type) As Boolean
       Return destinationType Is GetType(InstanceDescriptor) OrElse MyBase.CanConvertTo(context, destinationType)
      End Function
    
      ''' <summary>
      ''' ConvertFrom
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="culture"></param>
      ''' <param name="value"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function ConvertFrom(ByVal context As ITypeDescriptorContext, _
                         ByVal culture As CultureInfo, _
                         ByVal value As Object) As Object
    
       ' Kein String dann Standardbehandlung
       Dim stringValue As String = TryCast(value, String)
       If stringValue IsNot Nothing Then
         stringValue = stringValue.Trim()
       End If
       If String.IsNullOrEmpty(stringValue) Then
         Return MyBase.ConvertFrom(context, culture, value)
       End If
    
       If culture Is Nothing Then
         ' aktuelle Kultur als Vorgabe
         culture = CultureInfo.CurrentCulture
       End If
       ' Trennzeichen lt. Cultur
       Dim separatorChar As Char = culture.TextInfo.ListSeparator.Chars(0)
       ' Für Konvertierung von Integer 
       Dim integerConverter As TypeConverter = TypeDescriptor.GetConverter(GetType(Integer))
    
       Dim stringArray As String() = stringValue.Split(New Char() {separatorChar})
    
       Dim integerValues As Integer() = New Integer(stringArray.Length - 1) {}
       For index As Integer = 0 To integerValues.Length - 1
    
         integerValues(index) = CInt(integerConverter.ConvertFromString(context, culture, stringArray(index)))
       Next
       If integerValues.Length = 1 Then
         Return New Edges(integerValues(0))
       ElseIf integerValues.Length = 2 Then
         ' Top und Bottom gleich
         Return New Edges(integerValues(0), integerValues(0), integerValues(1), integerValues(1))
       ElseIf integerValues.Length = 4 Then
         Return New Edges(integerValues(0), integerValues(1), integerValues(2), integerValues(3))
       Else
         Throw New ArgumentException("Ungültige Angabe")
       End If
      End Function
    
      ''' <summary>
      ''' ConvertTo
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="culture"></param>
      ''' <param name="value"></param>
      ''' <param name="destinationType"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function ConvertTo( _
       ByVal context As ITypeDescriptorContext, _
       ByVal culture As CultureInfo, _
       ByVal value As Object, _
       ByVal destinationType As Type) As Object
    
       If destinationType Is Nothing Then
         Throw New ArgumentNullException("destinationType")
       End If
       If TypeOf value Is Edges Then
         Dim edgeValue As Edges = DirectCast(value, Edges)
         Dim isAll As Boolean = (edgeValue.TopLeft = edgeValue.TopRight _
          AndAlso edgeValue.TopLeft = edgeValue.BottomLeft _
          AndAlso edgeValue.TopLeft = edgeValue.BottomRight)
         Dim isDual As Boolean = (edgeValue.TopLeft = edgeValue.TopRight _
          AndAlso edgeValue.BottomLeft = edgeValue.BottomRight)
    
    
         If destinationType Is GetType(String) Then
          If (culture Is Nothing) Then
            culture = CultureInfo.CurrentCulture
          End If
          Dim separator As String = culture.TextInfo.ListSeparator(0) & " "
          Dim integerConverter As TypeConverter = TypeDescriptor.GetConverter(GetType(Integer))
    
          Dim stringArray As String() = New String() { _
           integerConverter.ConvertToString(context, culture, edgeValue.TopLeft), _
           integerConverter.ConvertToString(context, culture, edgeValue.TopRight), _
           integerConverter.ConvertToString(context, culture, edgeValue.BottomLeft), _
           integerConverter.ConvertToString(context, culture, edgeValue.BottomRight)}
    
          If isAll Then
            Return stringArray(0)
          ElseIf isDual Then
            Return String.Join(separator, {stringArray(0), stringArray(2)})
          Else
            Return String.Join(separator, stringArray)
          End If
         ElseIf destinationType Is GetType(InstanceDescriptor) Then
          If isAll Then
            Return New InstanceDescriptor(GetType(Edges).GetConstructor( _
              New Type() {GetType(Integer)}), New Object() {edgeValue.TopLeft})
          Else
            Return New InstanceDescriptor(GetType(Edges).GetConstructor( _
             New Type() {GetType(Integer), GetType(Integer), GetType(Integer), GetType(Integer)}), _
             New Object() {edgeValue.TopLeft, edgeValue.TopRight, edgeValue.BottomLeft, edgeValue.BottomRight})
          End If
         End If
       End If
       Return MyBase.ConvertTo(context, culture, value, destinationType)
      End Function
    
      ''' <summary>
      ''' GetCreateInstanceSupported
      ''' </summary>
      ''' <param name="context"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function GetCreateInstanceSupported(ByVal context As ITypeDescriptorContext) As Boolean
       Return True
      End Function
    
      ''' <summary>
      ''' CreateInstance
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="propertyValues"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function CreateInstance(ByVal context As ITypeDescriptorContext, _
       ByVal propertyValues As IDictionary) As Object
       If context Is Nothing Then
         Throw New ArgumentNullException("context")
       End If
       If propertyValues Is Nothing Then
         Throw New ArgumentNullException("propertyValues")
       End If
       Dim value As Edges = DirectCast(context.PropertyDescriptor.GetValue(context.Instance), Edges)
       Return New Edges( _
        CInt(propertyValues.Item("TopLeft")), _
        CInt(propertyValues.Item("TopRight")), _
        CInt(propertyValues.Item("BottomLeft")), _
        CInt(propertyValues.Item("BottomRight")))
      End Function
    
      ''' <summary>
      ''' GetPropertiesSupported
      ''' </summary>
      ''' <param name="context"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function GetPropertiesSupported(ByVal context As ITypeDescriptorContext) As Boolean
       Return True
      End Function
    
      ''' <summary>
      ''' GetProperties
      ''' </summary>
      ''' <param name="context"></param>
      ''' <param name="value"></param>
      ''' <param name="attributes"></param>
      ''' <returns></returns>
      ''' <remarks></remarks>
      Public Overrides Function GetProperties(ByVal context As ITypeDescriptorContext, _
       ByVal value As Object, ByVal attributes As Attribute()) As PropertyDescriptorCollection
       Return TypeDescriptor.GetProperties(GetType(Edges), attributes).Sort( _
        New String() {"TopLeft", "TopRight", "BottomLeft", "BottomRight"})
      End Function
    
    #End Region
    
    End Class
    
    

     

    Gruß Frank

    • Als Antwort markiert NachtAktiv Sonntag, 19. September 2010 21:31
    Sonntag, 19. September 2010 20:57
  • Hallo Frank,

    die letzten Erweiterungen setzen voraus, dass es sich um einen Werttyp (Structure vs Class ) handelt,
    sonst muß Equals, = und <> anders implementiert werden, da auch Nothing möglich ist.

    Gruß Elmar

    Montag, 20. September 2010 09:17
    Beantworter
  • Danke Elmar,

    Dann haben wir doch aus dem Würfel eine Kugel gefeilt.

    Gruß Frank

     

     Public Shared Operator =(ByVal left As Edges, ByVal right As Edges) As Boolean
       If (left Is Nothing) And (right Is Nothing) Then
         Return True
       Else
         If (left Is Nothing) Or (right Is Nothing) Then
          Return False
         Else
          Return (left._TopLeft = right._TopLeft AndAlso _
           left._TopRight = right._TopRight AndAlso _
           left._BottomLeft = right._BottomLeft AndAlso _
           left._BottomRight = right._BottomRight)
         End If
       End If
      End Operator
    

     

    Montag, 20. September 2010 14:38