ComboBox bug?

  • Thread starter Thread starter Darren
  • Start date Start date
D

Darren

Could someone explain why the items property is not valid unless the control is added to a form?
In the sample below the before == 0 and after == 2.

DataTable table = new DataTable("Bool");
table.Columns.Add( "id" );
table.Columns.Add( "val" );
table.Rows.Add( new object[]{ "0", "False" } );
table.Rows.Add( new object[]{ "1", "True" } );

ComboBox comboBox = new ComboBox();

comboBox.DataSource = table;
comboBox.DisplayMember = "val";
comboBox.ValueMember = "id";

int before = comboBox.Items.Count;
Controls.Add( comboBox );
int after = comboBox.Items.Count ;
 
Hi,

Darren said:
Could someone explain why the items property is not valid unless the
control is added to a form?
In the sample below the before == 0 and after == 2.

When you set ComboBox.DataSource to a list, then the control needs to create
or get a CurrencyManager which manages a position in the list. A
CurrencyManager lives in a BindingContext and a Control by default uses its
parent BindingContext.

Before adding it to the ControlCollection it has no parent and therefore no
BindingContext. DataBinding is postponed until a BindingContext is
available. When the Control is added to the ControlCollection, it will have
a parent and it will use the parent's (Form) BindingContext.

That's the default, you can however _explicitly_ set the BindingContext to
the Form's BindingContext, eg:

ComboBox comboBox = new ComboBox();
comboBox.BindingContext = this.BindingContext;
comboBox.DataSource = table;
comboBox.DisplayMember = "val";
comboBox.ValueMember = "id";
// comboBox.Items.Count should be 2


Though it's possible, i would not rely on DataBinding of invisble Controls
and prefer to get the count from the underlying DataSource ( eg.
table.Rows.Count ).

HTH,
Greetings

DataTable table = new DataTable("Bool");
table.Columns.Add( "id" ); table.Columns.Add( "val" );
table.Rows.Add( new object[]{ "0", "False" } );
table.Rows.Add( new object[]{ "1", "True" } );

ComboBox comboBox = new ComboBox();

comboBox.DataSource = table;
comboBox.DisplayMember = "val";
comboBox.ValueMember = "id";

int before = comboBox.Items.Count;
Controls.Add( comboBox );
int after = comboBox.Items.Count ;
 
Thanks for the help.

I tried to set the binding context by creating a new BindingContext object but that didn't help.
My problem is that I want to set the selected value before the control is given a parent. (It may be attached to any number of parent controls and needs to be populated before being displayed)

I don't know why the SelectedValue needs a binding context.
It be nice if the SelectedValue behaved as a property unless a binding context was available and the control bound to it.



Bart said:
Hi,

Could someone explain why the items property is not valid unless the
control is added to a form?
In the sample below the before == 0 and after == 2.


When you set ComboBox.DataSource to a list, then the control needs to create
or get a CurrencyManager which manages a position in the list. A
CurrencyManager lives in a BindingContext and a Control by default uses its
parent BindingContext.

Before adding it to the ControlCollection it has no parent and therefore no
BindingContext. DataBinding is postponed until a BindingContext is
available. When the Control is added to the ControlCollection, it will have
a parent and it will use the parent's (Form) BindingContext.

That's the default, you can however _explicitly_ set the BindingContext to
the Form's BindingContext, eg:

ComboBox comboBox = new ComboBox();
comboBox.BindingContext = this.BindingContext;
comboBox.DataSource = table;
comboBox.DisplayMember = "val";
comboBox.ValueMember = "id";
// comboBox.Items.Count should be 2


Though it's possible, i would not rely on DataBinding of invisble Controls
and prefer to get the count from the underlying DataSource ( eg.
table.Rows.Count ).

HTH,
Greetings


DataTable table = new DataTable("Bool");
table.Columns.Add( "id" ); table.Columns.Add( "val" );
table.Rows.Add( new object[]{ "0", "False" } );
table.Rows.Add( new object[]{ "1", "True" } );

ComboBox comboBox = new ComboBox();

comboBox.DataSource = table;
comboBox.DisplayMember = "val";
comboBox.ValueMember = "id";

int before = comboBox.Items.Count;
Controls.Add( comboBox );
int after = comboBox.Items.Count ;
 
there are multiple known combobox bugs :P

not sure if this is the right spot for this, Darren - just the most
recent topic...

all i was trying to accomplish was dynamically allocating combo
elements from a list while preventing the selected item from also being
displayed; unfortunately, setting to the list to null and then an
updated list goofs up the ability to alter the combo.text property :(

2 tricks to accomplish:
(1) create a new thread specifically for the purpose of setting the
text value
(2) similarly, initial setting of the combo.text using form.onLoad()

here's the code - good luck:

Imports System
Imports System.Windows.Forms
Imports System.Collections
Imports System.Threading


Public Class ExcludePull
inherits Form


Dim WithEvents comboMain As ComboBox = new ComboBox()

Dim strElements As String() = {"blue", "green", "yellow", "red",
"orange"}

Dim selectedMainIndex As String = "green"




<System.STAThread()> _
Public Shared Sub Main()

System.Windows.Forms.Application.Run(New ExcludePull())

End Sub 'Main




Public Sub New()


comboMain.DataSource = listExcludingElement(selectedMainIndex)


'
' hack: each of these (BindingContext/SelectedIndex) must be
set as
' below in order to specify which value should be displayed
' upon control creation (known .NET BUG(s))
'
' warning: setting the BindingContext in this manner may cause grief
' under more complex bound data circumstances
'
' note: alternate thread trick doesn't work in the form constructor
'
'comboMain.BindingContext = New BindingContext()
'comboMain.SelectedIndex = -1
'comboMain.Text = "green"


' some folks maintain that setting this helps - i'm not convinced...
' comboMain.Sorted = False

comboMain.DropDownStyle = ComboBoxStyle.DropDown
comboMain.Location = New System.Drawing.Point(10, 10)
comboMain.MaxDropDownItems = 10
comboMain.Size = New System.Drawing.Size(150, 25)
comboMain.Font = New System.Drawing.Font _
("Courier New", 10.2!, _
System.Drawing.FontStyle.Regular, _
System.Drawing.GraphicsUnit.Point, _
CType(0, Byte))


me.Text = "dynamic combo ops (exclusion)"

me.controls.add(comboMain)


'
' prevent user from resizing the form window
'
me.Height = 100
me.Width = 300
me.MinimumSize = New System.Drawing.Size(300, 100)
me.MaximumSize = New System.Drawing.Size(300, 100)


End Sub




'
' performs actions whenever the form is loaded
'
' note: prevents the combo.text bug workaround nonsense...
'
Private Sub Form_Load(sender As Object, e As System.EventArgs) _
Handles MyBase.Load

me.comboMain.Text = selectedMainIndex

End Sub




'
' returns a subset of the original list (missing "excluded")
'
Public Function listExcludingElement(excluded As String) As
ArrayList

Dim tempList As ArrayList = new ArrayList()


tempList.AddRange(strElements)

tempList.remove(excluded)


Return templist

End Function ' listExcludingElement



'
' sub called only from embedded thread to change combo.text
'
Public Sub SetTxt()

me.comboMain.Text = selectedMainIndex

End Sub ' SetTxt




Private Sub comboMain_ChngCommit(sender as Object, e as EventArgs) _
Handles comboMain.SelectionChangeCommitted

Dim thdSetTxt as Thread = New Thread(AddressOf me.SetTxt)


Dim tempList As ArrayList = new ArrayList()


selectedMainIndex = me.comboMain.SelectedItem.toString()


me.comboMain.Datasource = Nothing

me.comboMain.DataSource = listExcludingElement(selectedMainIndex)


'
' kludge: combo.text cannot be modified in this thread -
' must initiate a different one (.NET BUG?)
'
thdSetTxt.Start()


End Sub ' comboMain_ChngCommit


End Class 'excludepull
 
Back
Top