更改自定义子项 属性 不会更新父项 class 的控件

Changing custom child property doesn't update parent class's control

我不知道我做错了什么,但我创建了一个带有 属性 的自定义控件,该控件使用继承 ExpandableObjectConverter.[=21 的类型转换器具有子属性=]

似乎我已经正确设置了所有内容,但是当我尝试更改父项的任何子属性时 属性,设计器中的显示不会改变,直到我单击另一个 属性(例如,如果我更改使用我的自定义 Gradient class 颜色的对象的 Color1 属性,则设计器中的颜色将不会更改,直到我单击那个 属性 或对象)。

代码包含在下面。


IndicatorBar控件class:

Imports System.ComponentModel

Public Class IndicatorBar
    Private _Percentage As Double
    Private ReadOnly _BackGradient, _BarGradient As Gradient
    Private _Side As SourceSide

    ...

    <Description("Expand to set the colors of the background gradient."), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
    Public ReadOnly Property BackGradient As Gradient
        Get
            Return _BackGradient
        End Get
    End Property

    <Description("Expand to set the colors of the bar gradient."), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
    Public ReadOnly Property BarGradient As Gradient
        Get
            Return _BarGradient
        End Get
    End Property

    ...

    Private Sub IndicatorBar_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
        Dim backRect As New Rectangle(0, 0, Width, Height)
        Dim maskRect As RectangleF
        Dim bar As New Rectangle(0, 0, Width, Height)

        Select Case Side
            Case SourceSide.Left
                maskRect = New RectangleF(0, 0, Width * Percentage, Height)
            Case SourceSide.Top
                maskRect = New RectangleF(0, 0, Width, Height * Percentage)
            Case SourceSide.Right
                maskRect = New RectangleF(Width * (1.0 - Percentage), 0, Width * Percentage, Height)
            Case SourceSide.Bottom
                maskRect = New RectangleF(0, Height * (1.0 - Percentage), Width, Height * Percentage)
        End Select
        Using backGrad As New Drawing2D.LinearGradientBrush(backRect, BackGradient.Color1, BackGradient.Color2, BackGradient.Angle)
        e.Graphics.FillRectangle(backGrad, backRect)
        End Using
        e.Graphics.SetClip(maskRect)
        Using barGrad As New Drawing2D.LinearGradientBrush(bar, BarGradient.Color1, BarGradient.Color2, BarGradient.Angle)
            e.Graphics.FillRectangle(barGrad, bar)
        End Using
        e.Graphics.ResetClip()
    End Sub
End Class

Gradient class:

Imports System.ComponentModel

<TypeConverter(GetType(GradientConverter))>
Public Class Gradient

    Private _Angle As UShort = 0
    Private _Color1, _Color2 As Color

    Public Sub New()
        Color1 = SystemColors.ControlLight
        Color2 = SystemColors.ControlLightLight
    End Sub

    Public Sub New(ByVal c1 As Color, ByVal c2 As Color)
        Color1 = c1
        Color2 = c2
    End Sub

    Public Sub New(ByVal c1 As Color, ByVal c2 As Color, ByVal ang As UShort)
        Color1 = c1
        Color2 = c2
        Angle = ang
    End Sub

    <Browsable(True), NotifyParentProperty(True), RefreshProperties(RefreshProperties.Repaint), EditorBrowsable(EditorBrowsableState.Always)>
    Public Property Color1 As Color
        Get
            Return _Color1
        End Get
        Set(value As Color)
            _Color1 = value
        End Set
    End Property

    <Browsable(True), NotifyParentProperty(True), RefreshProperties(RefreshProperties.Repaint), EditorBrowsable(EditorBrowsableState.Always)>
    Public Property Color2 As Color
        Get
            Return _Color2
        End Get
        Set(value As Color)
            _Color2 = value
        End Set
    End Property

    <Browsable(True), NotifyParentProperty(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(0)>
    Public Property Angle As UShort
        Get
            Return _Angle
        End Get
        Set(value As UShort)
            _Angle = value Mod 360
        End Set
    End Property
End Class

GradientConverter class:

Public Class GradientConverter
    Inherits ExpandableObjectConverter

    Public Overrides Function ConvertTo(context As ITypeDescriptorContext, culture As Globalization.CultureInfo, value As Object, destinationType As Type) As Object
        If destinationType Is GetType(String) Then
            Return ""
        End If
        Return MyBase.ConvertTo(context, culture, value, destinationType)
    End Function
End Class

首先,打开 Option Strict 您的代码中有 8 个左右的隐式转换。我还会更改 Angle 类型。 LinearGradientBrush 需要一个单曲,但你有 UShort.

第 1 部分

由于Gradient属性是本身一个类型(class),所以需要在子元素改变时通知父元素.换句话说,在 IndicartorBar.BarGradient.ColorX 中 - 您希望通知冒泡 2 级 IndicatorBar 执行绘画的人。

我怀疑你试图用 NotifyParentPropertyRefreshProperties 做什么。问题是对 Color1 的更改只会通知 Gradient。此外,属性不会自动与它们修饰的 Class 或 属性 交互。大多数情况下,它们是对设计器或序列化器等其他东西的指令。

解决方案是实现 INotifyPropertyChanged,这很简单并且不会对代码进行太多更改:

Public Class Gradient
    Implements INotifyPropertyChanged

    ...
    ' VS will add this when you press ENTER on the Implements line
    Public Event PropertyChanged(sender As Object,
         e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged

    ...

    Public Property Color2 As Color
        Get
            Return _Color2
        End Get
        Set(value As Color)
            If value <> _Color2 Then
                _Color2 = value

                RaiseEvent PropertyChanged(Me, 
                   New PropertyChangedEventArgs("Gradient"))
            End If
        End Set
    End Property

同时修改 Color1Color2 setter 以触发事件。然后在 IndicatorBar 我们需要订阅事件并响应:

Public Class IndicatorBar
    ...
    ' I have no idea why these were ReadOnly
    Private WithEvents _BackGradient, _BarGradient As Gradient
    ...

    Private Sub _BackGradient_PropertyChanged(sender As Object,
        e As PropertyChangedEventArgs) Handles BackGradient.PropertyChanged, 
               _BarGradient.PropertyChanged

        Me.Invalidate()

    End Sub

现在,当颜色选择器下拉列表关闭后 Color#Gradient 上发生变化时,将通知 UserControl 并且应立即重绘控件Invalidate().

要为 Angle 添加它,只需如上所述在 setter 中引发事件。还要确保在测试更改之前清理并重建项目。

第 2 部分

另一个问题是您的 TypeConverter 没有做任何事情。它继承自ExpandableObjectConverter,所以子属性崩溃了。但是当折叠时,TypeConverter 应该提供一个摘要,如 FontLocation.

如果子属性,您的 ConvertTo 应该提供该摘要。

Public Class GradientConverter
    Inherits ExpandableObjectConverter

    ' when the designer asks if we can convert to string,
    ' reply "Yes, I can!"
    Public Overrides Function CanConvertTo(context As ITypeDescriptorContext, 
                           destinationType As Type) As Boolean

        If destinationType Is GetType(String) Then
            Return True
        End If

        Return MyBase.CanConvertTo(context, destinationType)
    End Function

    Public Overrides Function ConvertTo(context As ITypeDescriptorContext,
                                        culture As Globalization.CultureInfo,
                                        value As Object,
                                        destinationType As Type) As Object
        If destinationType Is GetType(String) Then
            ' cast value to our Type
            Dim grad As Gradient = CType(value, Gradient)

            ' return the prop summary
            Return String.Format("{0}, {1}, {2}", grad.Angle.ToString,
                                    grad.Color1.ToString,
                                    grad.Color2.ToString)
        End If
        Return MyBase.ConvertTo(context, culture, value, destinationType)
    End Function
End Class

现在,您的 2 个 Gradient 属性将汇总 3 个子属性,并在您更改它们时更新:

注:
如果您想允许用户编辑 3 个子属性的字符串形式(例如,让他们在其中一种颜色 上键入“蓝色”而不 扩展或打开 Gradient 属性,您必须实施 CanConvertFrom / ConvertFrom 才能将文本转换为有效的 属性 值。

在这种情况下,请注意如何装饰或分隔各个值(例如,在文本中使用 2 个逗号)。您的 ConvertFrom 代码将必须解析格式化字符串以提取这些值(还要记住用户可能已删除或更改分隔符)。