Tse Tse - 5 months ago 52
C# Question

Convert a dataGridView colum to checkbox column

I am making a program to check how many courses I have left in my univercity.
I load info from a

csv
put it's data to a dataset and the display it with a datagrid.

I want some columns(lab and theory) to have checkbox cells so that I can check the courses I passed and then save them back to the
csv
to load them again later (when I pass somethnig :P).
But I have problem converting those (string) columns to checkboxes as I am not so experienced in c#

That's my code :

string delimiter = ";";
string tablename = "paTable";
filename = "aname";
DataSet dataset = new DataSet();
StreamReader sr = new StreamReader(filename);
DataGridViewColumn column = new DataGridViewColumn();
dataset.Tables.Add(tablename);

dataset.Tables[tablename].Columns.Add("A/A");
dataset.Tables[tablename].Columns.Add("Course");
dataset.Tables[tablename].Columns.Add("Semester");
dataset.Tables[tablename].Columns.Add("Theory");
dataset.Tables[tablename].Columns.Add("Lab");
dataset.Tables[tablename].Columns.Add("Passed");

string alldata = sr.ReadLine();
while (sr.Peek() != -1)
{
alldata = sr.ReadLine();
string[] rows;
rows = alldata.Split("\r".ToCharArray());

foreach (string r in rows)
{
string[] items = r.Split(delimiter.ToCharArray());
dataset.Tables[tablename].Rows.Add(items);
}

this.dataGridView1.DataSource = dataset.Tables[0].DefaultView;
}


Any help would be appreciated !

Answer Source

If you manually created and added the columns to your DataGridView, I think you could keep your DataSet-loading process as is.

Turn off AutoGenerateColumns and add them manually:

dataGridView1.AutoGenerateColumns = false;
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn { Name="Course", ... } );
dataGridView1.Columns.Add(new DataGridViewCheckBoxColumn { Name="Passed", ... } );
...

The grid view should populate after you bind it with your DataView like you're doing:

dataGridView1.DataSource = dataset.Tables[0].DefaultView;

EDIT

Because it relies on strong types and proper binding, a perhaps better alternative would be to create a class to represent a CSV record (types assumed). If this class implements the INotifyPropertyChanged interface the values can be edited in the DataGridView and reflected in the class with minimal effort.

public class CsvDataRow : INotifyPropertyChanged
{
    private bool _aa;
    private string _course;
    private int _semester;
    private double _theory;
    private double _lab;
    private bool _passed;

    public bool AA { get { return _aa; } set { if (value == _aa) return; _aa = value; NotifyPropertyChanged("AA"); } }
    public string Course { get { return _course; } set { if (value == _course) return; _course = value; NotifyPropertyChanged("Course"); } }
    public int Semester { get { return _semester; } set { if (value == _semester) return; _semester = value; NotifyPropertyChanged("Semester"); } }
    public double Theory { get { return _theory; } set { if (value == _theory) return; _theory = value; NotifyPropertyChanged("Theory"); } }
    public double Lab { get { return _lab; } set { if (value == _lab) return; _lab = value; NotifyPropertyChanged("Lab"); } }
    public bool Passed { get { return _passed; } set { if (value == _passed) return; _passed = value; NotifyPropertyChanged("Passed"); } }

    char _delimiter;

    // static factory method creates object from CSV row
    public static CsvDataRow Create(string row, char delimiter)
    {
        return new CsvDataRow(row, delimiter);
    }

    // private constructor initializes property values
    private CsvDataRow(string row, char delimiter)
    {
        _delimiter = delimiter;

        var values = row.Split(_delimiter);
        AA = (values[0].ToString().Equals("1"));
        Course = Convert.ToString(values[1]);
        Semester = Convert.ToInt32(values[2]);
        Theory = Convert.ToDouble(values[3]);
        Lab = Convert.ToDouble(values[4]);
        Passed = (values[5].ToString().Equals("1"));
    }

    // a method to convert back into a CSV row
    public string ToCsvString()
    {
        var values = new string[] { (AA ? 1 : 0).ToString(), Course, Semester.ToString(), Theory.ToString(), Lab.ToString(), (Passed ? 1: 0).ToString() };
        return string.Join(_delimiter.ToString(), values);
    }

    // INotifyPropertyChanged interface requires this event
    public event PropertyChangedEventHandler PropertyChanged;

    // helper method to raise PropertyChanged event
    private void NotifyPropertyChanged(string name)
    {
        if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}

The CSV file itself could be represented by a class:

public class CsvDataSource
{
    public char Delimiter { get; private set; }
    public CsvDataSource()
        : this(',')
    { }

    public string[] Columns { get; private set; }

    public ObservableCollection<CsvDataRow> Rows { get; private set; }

    public CsvDataSource(char delimiter)
    {
        Delimiter = delimiter;
    }

    public void LoadCsv(string csvFileName)
    {
        string header;
        string data;

        using (var reader = new StreamReader(csvFileName))
        {
            header = reader.ReadLine(); // assumes 1st row is column headers
            data = reader.ReadToEnd();
        }

        Columns = header.Split(Delimiter);
        var rows = Regex.Split(data, Environment.NewLine);

        if (!rows.Select(row => row.Split(Delimiter)).All(row => row.Length == Columns.Length)) throw new FormatException("Inconsistent data format.");
        Rows = new ObservableCollection<CsvDataRow>(rows.Select(row => CsvDataRow.Create(row, Delimiter)));
    }
}

The DataGridView can then be bound to the Rows property of a CsvDataSource instance, so the form's code can look like this:

public partial class Form1 : Form
{
    private CsvDataSource _data;
    private ObservableCollection<CsvDataRow> _rows;

    public Form1()
    {
        InitializeComponent();
    }

    public void LoadCsv(CsvDataSource data)
    {
        _data = data;
        _rows = _data.Rows;
        dataGridView1.DataSource = _rows;
    }

    public void SaveCsv(string path)
    {
        using (var writer = new StreamWriter(path))
        {
            writer.WriteLine(string.Join(_data.Delimiter.ToString(), _data.Columns));
            foreach (var row in _rows)
            {
                writer.WriteLine(row.ToCsvString());
            }
        }
    }
}

Of course you'd have a button calling the SaveCsv method, and you'll want another button to add/remove rows to/from your DataGridView. Also you'll want to wrap file I/O operations in a try/cath block.

This is a quickly summed-up implementation that does require a bit of additional work to be fully functional, but it gives an idea, and works out of the box.

This way, the "defining the columns" part is completely taken care of in the CsvDataRow class which provides a strong type for each record. No DataSet, no DataColumn, no DataGridViewColumn either. Just the data, and some data binding.