颜色组合框自定义控件设计器问题
Color ComboBox Custom Control designer problem
我有一个自定义控件,可以在下拉菜单中显示颜色选择,效果很好。
我发现同一个表单上有多个控件时性能很差,所以我将其更改为将颜色索引存储在 Items 集合中。
这很好用,但 Designer 会填充大量值,这会导致控件中出现空项。
如何阻止设计器存储项目?
这是我不想要的设计器代码:
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
由于您要将颜色选择添加到 ComboBox.Items 集合中,表单设计器会序列化此集合,将所有项目添加到 Form.Designer.vb 文件中。当您使用设计器中的“属性”窗格将项目添加到 ComboBox 时也会发生这种情况:相同 effect.
您可以改为设置 ComboBox 的数据源:速度更快,并且您添加的对象不会被序列化。我还建议不要在 Control Constructor 中添加这些值,而是在 OnHandleCreated() override 中:这些值仅在创建 Control Handle 时加载,位于 run-time,因此您不加载(不是很有用)设计器中的项目集合。
由于可以在 run-time 重新创建句柄不止一次,因此需要对其进行检查(以避免多次构建集合)。
这里,我使用的是 ColorConverter's GetStandardValues() method to build a collection of known colors, excluding from the enumeration colors that have the 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 的索引 < 27 和 > 167(我建议不要依赖这些值)。
我对自定义控件做了一些更改:
- 当控件派生自现有 class 时,我们不订阅事件(例如,
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() and 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
修改了组合框自定义控件:
- 删除
Public Sub New
和 InitializeComponent()
中的内容,不再需要了。
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
这是它的工作原理:
我有一个自定义控件,可以在下拉菜单中显示颜色选择,效果很好。
我发现同一个表单上有多个控件时性能很差,所以我将其更改为将颜色索引存储在 Items 集合中。
这很好用,但 Designer 会填充大量值,这会导致控件中出现空项。
如何阻止设计器存储项目?
这是我不想要的设计器代码:
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
由于您要将颜色选择添加到 ComboBox.Items 集合中,表单设计器会序列化此集合,将所有项目添加到 Form.Designer.vb 文件中。当您使用设计器中的“属性”窗格将项目添加到 ComboBox 时也会发生这种情况:相同 effect.
您可以改为设置 ComboBox 的数据源:速度更快,并且您添加的对象不会被序列化。我还建议不要在 Control Constructor 中添加这些值,而是在 OnHandleCreated() override 中:这些值仅在创建 Control Handle 时加载,位于 run-time,因此您不加载(不是很有用)设计器中的项目集合。
由于可以在 run-time 重新创建句柄不止一次,因此需要对其进行检查(以避免多次构建集合)。
这里,我使用的是 ColorConverter's GetStandardValues() method to build a collection of known colors, excluding from the enumeration colors that have the 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 的索引 < 27 和 > 167(我建议不要依赖这些值)。
我对自定义控件做了一些更改:
- 当控件派生自现有 class 时,我们不订阅事件(例如,
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() and 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
修改了组合框自定义控件:
- 删除
Public Sub New
和InitializeComponent()
中的内容,不再需要了。
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
这是它的工作原理: