收录日期:2020/06/02 03:43:03 时间:2014-01-31 12:49:01 标签:c#,.net,winforms,data-binding

I created a simple example with data binding (unfortunately we have a similar case in our system). I created a funky combo box:

public class FunkyComboBox : ComboBox
{
    private object currentValue = null;

    public FunkyComboBox()
    {
        if (LicenseManager.UsageMode == LicenseUsageMode.Runtime)
            this.Items.Add("Other...");
    }

    protected override void OnSelectedIndexChanged(EventArgs e)
    {
        if (!this.Text.StartsWith("Other") && currentValue != this.SelectedItem)
        {
            currentValue = this.SelectedItem;
            BindingManagerBase bindingManager = DataManager;
            base.OnSelectedIndexChanged(e);
        }
    }

    protected override void OnSelectionChangeCommitted(EventArgs e)
    {
        string itemAsStr = this.SelectedItem != null ? SelectedItem.ToString() : "";
        if (itemAsStr.StartsWith("Other"))
        {
            string newItem = "item" + this.Items.Count;
            if (!Items.Contains(newItem))
            {
                Items.Add(newItem);
            }
            SelectedItem = newItem;
        }
        else
        {
            OnSelectedIndexChanged(e); //forces a selectedIndexChanged event to be thrown
            base.OnSelectionChangeCommitted(e);
        }
    }
}

Which adds new items when you click Other (in our system it opens a form where you can query the database, etc). Then I have a simple data object:

public class MyClass
{
    private string value;
    public string MyData
    {
        get{ return value;}
        set{ this.value = value;}
    }
}

And a test form with two controls bound to this object (some designer code removed):

public partial class Form1 : Form
{
    MyClass myObj = new MyClass();
    public Form1()
    {
        InitializeComponent();
        myObj.MyData = "Nothing";
        myClassBindingSource.DataSource = myObj;
    }

    private void InitializeComponent()
    {
        this.components = new System.ComponentModel.Container();
        this.textBox1 = new System.Windows.Forms.TextBox();
        this.myClassBindingSource = new System.Windows.Forms.BindingSource(this.components);
        this.funkyComboBox1 = new DataBindingTests.FunkyComboBox();
        ((System.ComponentModel.ISupportInitialize)(this.myClassBindingSource)).BeginInit();
        this.SuspendLayout();
        //
        // textBox1
        //
        this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.myClassBindingSource, "MyData", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));

        //
        // myClassBindingSource
        //
        this.myClassBindingSource.DataSource = typeof(DataBindingTests.MyClass);
        //
        // funkyComboBox1
        //
        this.funkyComboBox1.DataBindings.Add(new System.Windows.Forms.Binding("SelectedItem", this.myClassBindingSource, "MyData", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
        this.funkyComboBox1.DataBindings.Add(new System.Windows.Forms.Binding("SelectedValue", this.myClassBindingSource, "MyData", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
        //
        // Form1
        //
        this.Controls.Add(this.textBox1);
        this.Controls.Add(this.funkyComboBox1);
        ((System.ComponentModel.ISupportInitialize)(this.myClassBindingSource)).EndInit();
        this.ResumeLayout(false);
        this.PerformLayout();
    }

    private FunkyComboBox funkyComboBox1;
    private System.Windows.Forms.BindingSource myClassBindingSource;
    private System.Windows.Forms.TextBox textBox1;
}

If you run this code and start playing with the combo box, you will notice that the edit box changes only if you click on it. After every change a null value is set to my object and the text box is cleared. How can I make it set the correct value after every change?

I'm not really sure why the ComboBox data binding behaves this way, but I have found a workaround. It seems as though the databinding doesn't work correctly if you don't use a datasource for your ComboBox's values.

I made a few minor changes to FunkyComboBox, and it now works as expected.

public class FunkyComboBox : ComboBox
{
    private object currentValue = null;
    private List<string> innerItems = new List<string>();

    public FunkyComboBox()
    {
        if (LicenseManager.UsageMode == LicenseUsageMode.Runtime)
            innerItems.Add("Other...");

        this.DataSource = innerItems;
    }

    protected override void OnSelectedIndexChanged(EventArgs e)
    {
        if (!this.Text.StartsWith("Other") && currentValue != this.SelectedItem)
        {
            currentValue = this.SelectedItem;
            BindingManagerBase bindingManager = DataManager;
            base.OnSelectedIndexChanged(e);
        }
    }

    protected override void OnSelectionChangeCommitted(EventArgs e)
    {
        string itemAsStr = this.SelectedItem != null ? SelectedItem.ToString() : "";
        if (itemAsStr.StartsWith("Other"))
        {
            string newItem = "item" + this.Items.Count;                
            if(!innerItems.Contains(newItem))
            {
                innerItems.Add(newItem);
                this.RefreshItems();
            } SelectedItem = newItem;
        }
        else
        {
            OnSelectedIndexChanged(e);
            //forces a selectedIndexChanged event to be thrown              
            base.OnSelectionChangeCommitted(e);
        }
    }
}

It seems to be a bug with the base ComboBox as well. It is not possible to get this binding source craziness to work correctly.