Very strange ComboBox databinding problem

Hi,
this problem really drives me nuts. I have a custom ComboBox, which derives from the normal ComboBox control. I'm setting the DataSource of the ComboBox in my code to a DataTable and also set DisplayMember and ValueMember. So, the binding in general works fine, but here's the odd part:
Depending on what is selected I color the background of the ComboBox differently. To achieve this effect when the DataSource changed i overwrite the OnDataSourceChanged method:


protectedoverridevoid OnDataSourceChanged(EventArgs e)
{
SelectionChanged();
base.OnDataSourceChanged(e);
}


The selction changed method does all the coloring and here is what really buzzes me. When the method is triggered, I evalute this.Text and this.SelectedValue. However, both of them are empty after OnDataSourceChanged is entered. Anyways, when I set a debug point at the code that checks if this.Text is empty, this.Text is indeed "", however, when I now explore all the properties and fields of "this" in the debugger, this.Text suddenly changes to the expected value. If I continue to run the code the value and coloring shows up just fine. However, when I let it run through without setting the debug point, this.Text stays empty and it gets colored incorrectly since the text actually shows up in the control afterwards.
Any ideas?
[1682 byte] By [TomFrey] at [2007-12-16]
# 1
Without replicating your specific problem, I'm guessing...but, when you change the DataSource for a combobox, the selection gets wiped out. You need to reset it.

Probably you are seeing the odd behaviour because SelectionChanged() is called before calling the base method. Try moving SelectionChanged() after the base.OnDataSourceChanged call.

durstin at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 2
OnDataSourceChanged() is called after the data source is set but before the ComboBox is filled with the data source items. As Durstin has pointed out, by calling the base implementation first, you will ensure the ComboBox is filled prior to your code being called. Note that the values of this.Text and this.SelectedValue will be invalid until after you step past base.OnDataSourceChanged(). Possibly the issue you are seeing is you are stepping past base.OnDataSourceChanged() causing the values to then appear.

Note that calls to this.Text and this.SelectedValue prior to base.OnDataSourceChanged() will result in exceptions that are caught and handled by ComboBox.

Joe

JoeStegman at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 3
hm, now I'm calling base.OnDataSourceChanged first but still have the very same problem? What else could be wrong?
TomFrey at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 4

Can you post a repro of the issue? The sample below works fine on Beta 2 bits so there may be more to it than what you've described.

Joe


public class Form1 : Form
{
private Button button1;
private MyComboBox myComboBox1;

public Form1()
{
this.button1 = new Button();
this.myComboBox1 = new MyComboBox();

this.button1.Location = new System.Drawing.Point(171, 10);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.Text = "Set DS";
this.button1.Click += delegate
{
ChangeDataSource();
};

this.myComboBox1.FormattingEnabled = true;
this.myComboBox1.Location = new System.Drawing.Point(12, 12);
this.myComboBox1.Name = "myComboBox1";
this.myComboBox1.Size = new System.Drawing.Size(153, 21);

this.Controls.Add(this.myComboBox1);
this.Controls.Add(this.button1);

this.Load += delegate
{
ChangeDataSource();
};
}

private void ChangeDataSource()
{
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
dt.Columns.Add("Name");

int seconds = DateTime.Now.Second + 1;

for (int idx = seconds; idx >= 0; idx--)
{
dt.Rows.Add(idx, idx.ToString());
}

this.myComboBox1.DisplayMember = "Name";
this.myComboBox1.ValueMember = "ID";
this.myComboBox1.DataSource = dt;
}
}

class MyComboBox : ComboBox
{
Random _rand = new Random();

protected override void OnDataSourceChanged(EventArgs e)
{
base.OnDataSourceChanged(e);

Color color = Color.FromArgb(_rand.Next(255), _rand.Next(255), _rand.Next(255));
this.BackColor = color;

System.Diagnostics.Debug.WriteLine("Text: " + this.Text);
System.Diagnostics.Debug.WriteLine("SelectedValue: " + ((null == this.SelectedValue) ? "(NULL)" : this.SelectedValue.ToString()));
}
}

JoeStegman at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 5
hmm... now that's interesting .. your sample works for me as well ... here's my ComboBox class:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
namespace Vms.Sam.Client.CommonControls
{
class CustomComboBox : ComboBox
{
#region Fields
private Color _goodBackColor = Color.LightGreen;
private Color _badBackColor = Color.LightPink;
private Color _badNotRequiredColor = Color.LightBlue;
private Color _blankBackColor = System.Drawing.SystemColors.Window;
private bool _isGood;
private bool _isRequired;
#endregion
#region Properties
[Browsable(false)]
public override Color BackColor
{
get { return base.BackColor; }
set { }
}
[Browsable(false)]
public override Color ForeColor
{
get { return base.ForeColor; }
set { }
}
public bool IsRequired
{
get { return _isRequired; }
set
{
_isRequired = value;
SetColors();
}
}
[Browsable(false)]
public bool IsGood
{
get { return _isGood; }
}
#endregion
public CustomComboBox()
{
SetColors();
}
private void SetColors()
{
if (_isRequired == true && this.Enabled == true && _isGood == false)
{
base.BackColor = _badBackColor;
}
else if (_isRequired == true && this.Enabled == true && _isGood == true)
{
base.BackColor = _goodBackColor;
}
else
{
base.BackColor = SystemColors.Window;
}
base.Invalidate();
}
private void SelectionChanged()
{
if (_isRequired == true)
{
if (this.Text.ToString().Length == 0 && this.Enabled == true)
{
base.BackColor = _badBackColor;
_isGood = false;
}
else if (this.Enabled)
{
base.BackColor = _goodBackColor;
_isGood = true;
}
else if (!this.Enabled)
{
base.BackColor = SystemColors.Control;
_isGood = true;
}
}
}

protected override void OnSelectedIndexChanged(EventArgs e)
{
base.OnSelectedIndexChanged(e);
SelectionChanged();
}
protected override void OnSelectedValueChanged(EventArgs e)
{
base.OnSelectedValueChanged(e);
SelectionChanged();
}
protected override void OnEnabledChanged(EventArgs e)
{
base.OnEnabledChanged(e);
SelectionChanged();
}
protected override void OnDataSourceChanged(EventArgs e)
{
base.OnDataSourceChanged(e);
SelectionChanged();
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
SelectionChanged();
}
}
}


Here I set the DataSource of the ComboBox:

cbCoop.DisplayMember = "CoopName";
cbCoop.ValueMember = "CoopId";
cbCoop.DataSource = ObjectStore.Account.Coops.Tables[0];


TomFrey at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 6
ahhh ... I found my error ...
I changed the DataSource after the InitializeComponent() of the hosting form. It works when I do it in the Load method of the form. Can someone tell me what the difference is?
Anyways, thanks a lot for all your help
TomFrey at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 7

The ComboBox Text does not get set until it's handle is created and this doesn't happen until the Form is made visible (after the Form constructor) - so any checks on the ComboBox.Text prior to Form.Load will return string.Empty.

Also, is your data source filled in InitializeComponent()? If not, then this will also cause problems as there will be no SelectedValue or Text value.

Joe

JoeStegman at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 8
Nope, as I indicated, now I fill it in the Form Load method and everything works.. so, problem solved ;)
Thanks a lot for the explanation and your help.
TomFrey at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...