Question

Below is code to an inherited ComboBox. The issue is that the ComboBox is being populated (PopulateComboBox()) multiple times.

Edit: I've taken the advice of Amit Mittal (find his answer below) and implemented ISupportInitialize. Now PopulateComboBox() is only called at run-time, like it should be.

By this implementation, the items should be populated during run-time, and destroyed on exit. However, the designer itself is storing these values when they're created run-time, and not destroying after run-time.

Is there an elegant solution to the implementation of this code?

Public Class ComboBoxExColors
    Inherits ComboBox
    Implements ISupportInitialize

    Public Sub New()
        MyBase.New()
        Me.Size = New Size(146, 23)
        Me.DropDownStyle = ComboBoxStyle.DropDownList
        Me.MaxDropDownItems = 16
    End Sub

    Public Sub BeginInit() Implements System.ComponentModel.ISupportInitialize.BeginInit
        ' Do nothing?
    End Sub

    Public Sub EndInit() Implements System.ComponentModel.ISupportInitialize.EndInit
        Me.DrawMode = DrawMode.OwnerDrawVariable ' fixed or variable?
        Me.PopulateComboBox()
    End Sub

    Public Sub PopulateComboBox()
        'Me.Items.Clear() ' rather than forcing items to be cleared, is there a more elegant solution to the implementation of this code, rather than forcing an item clear that shouldn't exist to begin with?
        Me.Items.Add("Default")
        Me.Items.Add("Custom")
        Dim KnownColors() As String = System.Enum.GetNames(GetType(System.Drawing.KnownColor)) ' get all colors
        For Each c As String In KnownColors ' add non system colors
            If Not Color.FromName(c).IsSystemColor Then
                Me.Items.Add(c)
            End If
        Next c
    End Sub

    Protected Overrides Sub OnDrawItem(ByVal e As DrawItemEventArgs)
        ' this draws each item onto the control
        If e.Index > -1 Then
            Dim item As String = Me.Items(e.Index).ToString

            e.DrawBackground()
            e.Graphics.DrawString(item, e.Font, SystemBrushes.WindowText, e.Bounds.X, e.Bounds.Y)
            e.DrawFocusRectangle()
        End If
    End Sub

End Class
Was it helpful?

Solution

The problem is PopulateComboBox is being invoked from the constructor.

When you drop your combobox on designer surface designer internally invokes its constructor to get default values of its properties (including Item property). And then, the code behind calls the constructor and after that explicitly specifies the design time properties as well (which in your case seems to be the 'default' values only). Hence duplicating of Items.

Proper way to initialize a custom control is by implementing ISupportInitialize interface.

EndInit will be called exactly once after designer's code has applied all the design time properties (if you are not using a designer then it will be your responsibility to call it).

So rule of thumb is

  1. For default values, specify in constructor
  2. To 'initialize' use ISupportInitialize.

For instance in addition to calling PopulateComboBox from it, you can also use it to force DrawMode property to OwnerDrawFixed as this is crucial for your inherited control and you would not want to let the user of the control to specify anything else at design time (though this will not exactly prevent design time 'specification' of a different value but regardless of that at run time you will have always forced the correct value.)

Note:

  1. After implementing this interface You may need to re-build the project and then again do a 'drag-drop' of the inherited control on the designer surface. You can inspect the automagically created designer code to verify that EndInit is indeed being invoked.
  2. As mentioned earlier as well, ISupportInitialize is used by the designer. If you do not use designer, it is your responsibility to call its methods.

OTHER TIPS

It should be in the Sub New unless you plan on changing the values dynamically later on.

Also, I don't know why you need to clear the Array. The GC will take care of it.

In looking into your problem I originally thought that using the DesignMode property would prevent the ItemArray from being created when the control was added to a Form... But it did not work.

I then attached another instance of Visual Studio to the project and noticed that DesignMode was set to false the whole time. I was not able to find another property that would be unique to keep your control from creating the ItemArray when it is added to your form (this is what is causing the first loading of your data).

What you are trying to do is unique in the fact you are building your datasource into your Control and running it everytime you create it, Whereas the normal ComboBox would just load in an array from the properties or an attached datasource.

My only suggestion at this time is to take your PopulateComboBox Method out of your Control's Constructor and call it in the parent Form's Constructor instead or create it dynamically and add it to your Form.

i.e.

Public Class Form1
    Public Sub New()
        InitializeComponent()
        ComboBoxExColors1.PopulateComboBox()
    End Sub
End Class
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top