【问题标题】:Color ComboBox Custom Control designer problem颜色组合框自定义控件设计器问题
【发布时间】:2021-02-10 18:16:36
【问题描述】:

我有一个自定义控件,它在下拉列表中显示颜色选择,效果很好。
我发现同一表单上的多个控件的性能很差,因此我将其更改为将 Color 索引存储在 Items 集合中。
这很好用,但是设计器会填充大量值,这会导致控件中的项目为空。

如何阻止设计师存储项目?

这是我不想要的设计器代码:

Me.cboCWarcColor.Items.AddRange(New Object() 
    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 
     19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 
     36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 
     53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 
     70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 
     87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 
     103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 
     116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 
     129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140}
)

这是自定义控件代码:

Imports System.Collections.Generic

Public Class ColorCombo
    Inherits System.Windows.Forms.ComboBox
    Private mSelectedColor As Color = Nothing
    Private Shared myColors As New List(Of Color)
    Private Shared myColorsIndices As New List(Of Object)

    Private Sub ColorCombo_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles Me.DrawItem
        Try
            If e.Index < 0 Or e.Index >= myColors.Count Then
                e.DrawBackground()
                e.DrawFocusRectangle()
                Exit Try
            End If
            ' Get the Color object from the Items list
            Dim aColor As Color = myColors.Item(e.Index) 'myColors.Item(e.Index)

            ' get a square using the bounds height
            Dim rect As Rectangle = New Rectangle(4, e.Bounds.Top + 2, CInt(e.Bounds.Height * 1.5), e.Bounds.Height - 4)


            ' call these methods first
            e.DrawBackground()
            e.DrawFocusRectangle()

            Dim textBrush As Brush
            ' change brush color if item is selected
            If e.State = DrawItemState.Selected Then
                textBrush = Brushes.White
            Else
                textBrush = Brushes.Black
            End If

            ' draw a rectangle and fill it
            Dim p As New Pen(aColor)
            Dim br As New SolidBrush(aColor)
            e.Graphics.DrawRectangle(p, rect)
            e.Graphics.FillRectangle(br, rect)

            ' draw a border
            rect.Inflate(1, 1)
            e.Graphics.DrawRectangle(Pens.Black, rect)
            ' draw the Color name
            e.Graphics.TextRenderingHint = Drawing.Text.TextRenderingHint.ClearTypeGridFit
            e.Graphics.DrawString(aColor.Name, Me.Font, textBrush, rect.Width + 5, ((e.Bounds.Height - Me.Font.Height) \ 2) + e.Bounds.Top)

            p.Dispose()
            br.Dispose()

        Catch ex As Exception
            e.DrawBackground()
            e.DrawFocusRectangle()
        End Try
    End Sub

    Public Sub New()
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        Try
            Dim aColorName As String
            Me.BeginUpdate()
            Items.Clear()
            SelectedItem = Nothing
            If myColors.Count = 0 Then
                Dim names() As String = System.Enum.GetNames(GetType(System.Drawing.KnownColor))
                For Each aColorName In names
                    If aColorName.StartsWith("Active") _
                    Or aColorName.StartsWith("Button") _
                    Or aColorName.StartsWith("Window") _
                    Or aColorName.StartsWith("Inactive") _
                    Or aColorName.StartsWith("HighlightText") _
                    Or aColorName.StartsWith("Control") _
                    Or aColorName.StartsWith("Scroll") _
                    Or aColorName.StartsWith("Menu") _
                    Or aColorName.StartsWith("Gradient") _
                    Or aColorName.StartsWith("App") _
                    Or aColorName.StartsWith("Desktop") _
                    Or aColorName.StartsWith("GrayText") _
                    Or aColorName.StartsWith("HotTrack") _
                    Or aColorName.StartsWith("Transparent") _
                    Or aColorName.StartsWith("Info") Then
                    Else
                        AddColor(Color.FromName(aColorName))
                    End If
                Next

            Else
                Me.Items.AddRange(myColorsIndices.ToArray)
            End If

        Catch
        Finally
            Me.EndUpdate()
        End Try
        ' Add any initialization after the InitializeComponent() call.

    End Sub

    Public Function AddColor(clr As Color) As Integer
        myColors.Add(clr)
        Dim idx As Integer = myColors.Count - 1
        myColorsIndices.Add(idx)
        Me.Items.Add(idx)
        Return idx
    End Function

    ''' <summary>
    ''' Returns a named color if one matches else it returns the passed color
    ''' </summary>
    Public Function GetKnownColor(ByVal c As Color, Optional ByVal tolerance As Double = 0) As Color
        For Each clr As Color In myColors
            If ColorDistance(c, clr) <= tolerance Then
                Return clr
            End If
        Next
        Return c
    End Function

    ''' <summary>
    ''' Returns index if one matches
    ''' </summary>
    Public Function ContainsColor(ByVal c As Color) As Integer
        Dim idx As Integer = 0
        For Each clr As Color In myColors
            If c.ToArgb = clr.ToArgb Then
                Return idx
            End If
            idx += 1
        Next
        Return -1
    End Function

    Sub ColorCombo_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.SelectedIndexChanged
        If SelectedIndex >= 0 Then
            mSelectedColor = myColors.Item(SelectedIndex)
        End If
    End Sub
    Public Property SelectedColor() As Color
        Get
            'If mSelectedColor.Name = "Transparent" Then
            '    Return Color.Black
            'End If
            Return mSelectedColor
        End Get

        Set(ByVal value As Color)
            Try
                Dim smallestDist As Double = 255
                Dim currentDist As Double = 0
                Dim bestMatch As Integer = 0
                Dim idx As Integer = -1
                For Each c As Color In myColors
                    idx += 1
                    currentDist = ColorDistance(c, value)
                    If currentDist < smallestDist Then
                        smallestDist = currentDist
                        bestMatch = idx
                    End If
                Next
                If Me.Items.Count >= bestMatch Then
                    Me.SelectedIndex = bestMatch
                End If
            Catch ex As Exception
                Debug.Print(ex.Message)
            End Try
        End Set
    End Property

    Private Function ColorDistance(ByRef clrA As Color, ByRef clrB As Color) As Double
        Dim r As Long, g As Long, b As Long
        r = CShort(clrA.R) - CShort(clrB.R)
        g = CShort(clrA.G) - CShort(clrB.G)
        b = CShort(clrA.B) - CShort(clrB.B)
        Return Math.Sqrt(r * r + g * g + b * b)
    End Function
End Class

【问题讨论】:

    标签: vb.net winforms graphics combobox user-controls


    【解决方案1】:

    由于您将颜色选择添加到 ComboBox.Items 集合中,因此表单设计器会序列化此集合,将所有项目添加到 Form.Designer.vb 文件中。当您使用设计器中的“属性”窗格将项目添加到组合框时也会发生这种情况:相同的 效果

    您可以改为设置 ComboBox 的 DataSource:它更快,并且您添加的对象没有序列化。我还建议不要在 Control Constructor 中添加这些值,而是在 OnHandleCreated() 覆盖中添加:这些值仅在创建控制句柄时加载,在运行时,所以你不加载(不是那么有用)集合设计器中的项目。
    由于可以在运行时多次重新创建句柄,因此需要对其进行检查(以避免多次构建集合)。

    在这里,我使用ColorConverterGetStandardValues() 方法来构建已知颜色的集合,不包括具有IsSystemColor 属性集的枚举颜色。
    该集合存储在一个 Color 对象数组中,这里命名为 supportedColors

    你也可以过滤[Enum].GetValues()返回的集合,得到同样的结果,例如:

    Dim colors As Color() = [Enum].GetValues(GetType(KnownColor)).OfType(Of KnownColor)().
        Where(Function(kc) kc > 26 AndAlso kc < 168).
        Select(function(kc) Color.FromKnownColor(kc)).ToArray()
    

    SystemColors 的索引 167(我建议不要依赖这些值)。

    我对自定义控件做了一些更改:

    • 当控件派生自现有类时,我们不订阅事件(例如,DrawItem),我们覆盖引发事件的方法(例如,OnDrawItem()),然后调用base (MyBase) 来增加事件(最终,如果有必要,我们也不能这样做)。在这方面,我们始终领先一步。
    • 绘图部分需要一些重构:
      • 项目的背景实际上被绘制了 3 次
      • 应使用Using 语句声明可处置对象,因此我们不会忘记处置它们:对于图形对象非常重要。
      • Graphics.DrawString() 替换为TextRenderer.DrawText,以尊重原图。
      • 简化了计算:在这里尽可能快很重要。
      • 因此也删除所有Try/Catch 块:成本高且并非真正需要(绘制时不要使用Try/Catch 块,一些If 条件和一些约束 - 例如Math.Min(Math.Max()) - 更好)。
      • 还覆盖 OnMeasureItem() 以更改项目的高度,设置为 Font.Height + 4(非常标准)。
      • 您可以在源代码中看到的其他内容。

    我已将 SelectedColor 自定义属性更改为更可靠,并使其与OnSelectedIndexChanged()OnSelectionChangeCommitted() 一起使用。
    所有项目都代表一种颜色,因此您可以将颜色选择为,例如:

    Private Sub ColorCombo1_SelectionChangeCommitted(sender As Object, e As EventArgs) Handles ColorCombo1.SelectionChangeCommitted
        SomeControl.BackColor = DirectCast(ColorCombo1.SelectedItem, Color)
        ' Or
        SomeControl.BackColor = ColorCombo1.SelectedColor
    End Sub
    

    修改了 ComboBox 自定义控件:

    • 删除 Public Sub NewInitializeComponent() 中的内容,不再需要。
    Imports System.Collections.Generic
    Imports System.ComponentModel
    Imports System.Drawing
    Imports System.Windows.Forms
    
    Public Class ColorCombo
        Inherits ComboBox
    
        Private mSelectedColor As Color = Color.Empty
        Private supportedColors As Color() = Nothing
    
        Public Sub New()
            DropDownStyle = ComboBoxStyle.DropDownList
            DrawMode = DrawMode.OwnerDrawVariable
            FlatStyle = FlatStyle.Flat
            FormattingEnabled = False
            ' Set these just to show that the background color is important here
            ForeColor = Color.White
            BackColor = Color.FromArgb(32, 32, 32)
        End Sub
    
        Protected Overrides Sub OnHandleCreated(e As EventArgs)
            MyBase.OnHandleCreated(e)
            If DesignMode OrElse Me.Items.Count > 0 Then Return
    
            supportedColors = New ColorConverter().GetStandardValues().OfType(Of Color)().
                Where(Function(c) Not c.IsSystemColor).ToArray()
    
            ' Preserves a previous selection if any
            Dim tmpCurrentColor = mSelectedColor
            Me.DisplayMember = "Name"
            Me.DataSource = supportedColors
            If Not tmpCurrentColor.Equals(Color.Empty) Then
                mSelectedColor = tmpCurrentColor
                SelectedColor = mSelectedColor
            End If
        End Sub
    
        Private flags As TextFormatFlags = TextFormatFlags.NoPadding Or TextFormatFlags.VerticalCenter
        Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
            e.DrawBackground()
            If e.Index < 0 Then Return
    
            Dim itemColor = supportedColors(e.Index)
            Dim colorRect = New Rectangle(e.Bounds.X + 1, e.Bounds.Y + 1, e.Bounds.Height - 2, e.Bounds.Height - 2)
    
            Using colorBrush As New SolidBrush(itemColor)
                e.Graphics.FillRectangle(colorBrush, colorRect)
    
                Dim textRect = New Rectangle(New Point(colorRect.Right + 6, e.Bounds.Y), e.Bounds.Size)
                TextRenderer.DrawText(e.Graphics, itemColor.Name, e.Font, textRect, e.ForeColor, Color.Transparent, flags)
            End Using
    
            e.DrawFocusRectangle()
            MyBase.OnDrawItem(e)
        End Sub
    
        Protected Overrides Sub OnMeasureItem(e As MeasureItemEventArgs)
            e.ItemHeight = Font.Height + 4
            MyBase.OnMeasureItem(e)
        End Sub
    
        Protected Overrides Sub OnSelectedIndexChanged(e As EventArgs)
            If SelectedIndex >= 0 Then mSelectedColor = supportedColors(SelectedIndex)
            MyBase.OnSelectedIndexChanged(e)
        End Sub
    
        Protected Overrides Sub OnSelectionChangeCommitted(e As EventArgs)
            mSelectedColor = supportedColors(SelectedIndex)
            MyBase.OnSelectionChangeCommitted(e)
        End Sub
    
        Public Property SelectedColor As Color
            Get
                Return mSelectedColor
            End Get
            Set
                mSelectedColor = Value
                If Not IsHandleCreated Then Return
    
                If mSelectedColor.IsKnownColor Then
                    SelectedItem = mSelectedColor
                Else
                    If supportedColors Is Nothing Then Return
                    Dim smallestDist As Double = 255
                    Dim currentDist As Double = 0
                    Dim bestMatch As Integer = 0
                    Dim idx As Integer = -1
    
                    For Each c As Color In supportedColors
                        idx += 1
                        currentDist = ColorDistance(c, Value)
                        If currentDist < smallestDist Then
                            smallestDist = currentDist
                            bestMatch = idx
                        End If
                    Next
                    If supportedColors.Count >= bestMatch Then
                        mSelectedColor = supportedColors(bestMatch)
                        SelectedItem = mSelectedColor
                    End If
                End If
            End Set
        End Property
    
        Private Function ColorDistance(clrA As Color, clrB As Color) As Double
            Dim r As Integer = CInt(clrA.R) - clrB.R
            Dim g As Integer = CInt(clrA.G) - clrB.G
            Dim b As Integer = CInt(clrA.B) - clrB.B
            Return Math.Sqrt(r * r + g * g + b * b)
        End Function
    
        Public Function GetKnownColor(c As Color, Optional ByVal tolerance As Double = 0) As Color
            For Each clr As Color In supportedColors
                If ColorDistance(c, clr) <= tolerance Then Return clr
            Next
            Return c
        End Function
    
        Public Function ContainsColor(c As Color) As Integer
            Dim idx As Integer = 0
            For Each clr As Color In Me.Items
                If c.ToArgb = clr.ToArgb Then Return idx
                idx += 1
            Next
            Return -1
        End Function
    End Class
    

    这就是它的工作原理:

    【讨论】:

      猜你喜欢
      • 2021-10-24
      • 1970-01-01
      • 2010-12-22
      • 2017-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多