Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind a CheckBox to a bool typed DbColumn that is nullable?

In Windows Forms (.NET 2.0, Visual Studio 2005 SP1) : I have a typed DataSet, with a column which type is System.Boolean, which is nullable and which default value is DBNull. I have a Form, containing a CheckBox control that I want to bind to the prior column value.

  • I have tried to bind the Checked property to the column via the designer : it works great, only if the default value for the column is set to either True or False.
  • I have tried to bind the CheckState property to the column via the designer, and attaching my own Format and Parse event handlers but they never get called :

    b.Format+=delegate(object sender, ConvertEventArgs cevent) {
        cevent.Value=DoFormat((CheckState)cevent.Value); // cf. end of the question
    };
    b.Parse+=delegate(object sender, ConvertEventArgs cevent) {
        cevent.Value=DoParse(cevent.Value); // cf. end of the question
    };
    
  • I have tried to create a custom Binding instance in the code, attach my event handlers and add it to the CheckBox bindings : the event handlers are still never get called...

    Binding b=new Binding("CheckState", _BindingSource, "MyColumn", false, DataSourceUpdateMode.OnPropertyChanged, DBNull.Value);
    

As a note : a DBNull value is acceptable only when coming from the DataSet (it means the value has never been set). But the user should only be able to set the value to True or False via the CheckBox.

For reference, here is the code of the parsing and formatting methods :

internal static CheckState DoParse(object value)
{
    if ((value==null) || (value is DBNull))
        return CheckState.Indeterminate;

    bool v=Convert.ToBoolean(value);
    return (v ? CheckState.Checked : CheckState.Unchecked);
}

internal static object DoFormat(CheckState value)
{
    switch (value)
    {
    case CheckState.Checked:
        return true;
    case CheckState.Indeterminate:
        return DBNull.Value;
    case CheckState.Unchecked:
        return false;
    }

    return null;
}
like image 807
Mac Avatar asked Jan 23 '23 08:01

Mac


2 Answers

Have you tried binding CheckBox.CheckState to the DataColumn without attaching to Parse and Format events or messing with the Binding?

Unfortunately I don’t have an instance of Visual Studio 2005 available but I assembled a quick form in Visual Studio 2008 and it did exactly what you specified:

As a note : a DBNull value is acceptable only when coming from the DataSet (it means the value has never been set). But the user should only be able to set the value to True or False via the CheckBox.

I may be the Parse, Format or Binding getting in your way or it may be that Windows Forms behaves differently in 2008 than in 2005


UPDATE Aug 18: It works on Visual Studio 2005 too both through the designer and through code. Here's the code that demonstrates it working:


using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApplication1 {
    public partial class Form1 : Form {
        DataTable table = new DataTable();
        public Form1() {
            InitializeComponent();

            //Creates the table structure
            table.Columns.Add("Name", typeof(string));
            table.Columns.Add("MyColumn", typeof(bool));

            //Populates the table with some stuff
            for (int i = 0; i &lt 5; i++) {
                table.Rows.Add(i.ToString());
            }

            //Creates the controls and puts them on the form.
            TextBox textBox = new TextBox();
            textBox.Location = new Point(10, 10);
            textBox.DataBindings.Add("Text", table, "Name");

            CheckBox checkBox = new CheckBox();
            checkBox.Left = textBox.Left;
            checkBox.Top = textBox.Bottom + 10;

            //Without true on the last argument, it will not work properly.
            checkBox.DataBindings.Add("CheckState", table, "MyColumn", true);

            Button previous = new Button();
            previous.Text = "";
            next.Top = previous.Top;
            next.Left = previous.Right + 5;
            next.Click += new EventHandler(next_Click);

            this.Controls.AddRange(new Control[] { textBox, checkBox, previous, next });
        }

        void next_Click(object sender, EventArgs e) {
            this.BindingContext[this.table].Position++;
        }

        void previous_Click(object sender, EventArgs e) {
            this.BindingContext[this.table].Position--;
        }
    }
}


UPDATE Aug 23:

Why it works

Binding has a private method called FormatObject which is responsible for obtaining a representation of the value coming from the data source that is appropriate for being shown on the control.

When formatting is enabled, Binding.FormatObject() will run through a code path that will call eventual handlers you have for the Binding.Format event. If any handler changes the value being propagated from the data source to the control through ConvertEventArgs.Value, that value will be used. Otherwise, it will call a default formatter called FormatObject on an internal class called System.Windows.Forms.Formatter.

The comments on the source code state:

“The real conversion work happens inside FormatObjectInternal()”

The comments for FormatObjectInternal state:

“Performs some special-case conversions (eg. Boolean to CheckState)”

Inside of FormatObjectInternal it checks to see if the value coming from the data source is null or DBNull and if that is the case, it checks to see if the type of the property being bound is of CheckState. If that is the case, it returns CheckState.Indeterminate.

As you can see, this is such a common case that it is a surprise it didn't work on Windows Forms 1.x. Luckily, the fixed it on 2.0 and beyond.

like image 112
Alfred Myers Avatar answered Jan 25 '23 23:01

Alfred Myers


The easist way I know, is derive from CheckBox class, add "DataValue" property which can handle DBNull values and bind the data to "DataValue" property:

public class DataCheckBox : CheckBox {
    public virtual object DataValue {
        get { return this.Checked; }
        set {
            if ( value == null || value is DBNull ) {
                this.CheckState = CheckState.Indeterminate;
            }
            else {
                this.Checked = (bool)value;
            }
        }
    }
}
like image 38
TcKs Avatar answered Jan 26 '23 00:01

TcKs