CSV file reading and writing with DataGridView

I found an issue of a beginner developer on a forum about importing a CSV phonebook file into DataGridView and having some search option.

First of all, I have created 2 forms:

  • one for the main form
  • one for adding new phonebook entry

The main form has a DataGridView, a MenuStrip, an OpenFileDialog, a SaveFileDialog, a Label, a TextBox and a Button. The MenuStrip has 2 items: File and Phonebook. File menu has 4 elements: Open CSV, Save…, a separator and Exit. The Phonebook menu has only 1 menu: Add…

Now create our Phonebook class and implement the IComparable interface (so the csv file items will be sorted alphabetically. Let’s say we have 6 fields (by request of the guy at the forum):

  • Station
  • Section
  • Infaco
  • Location
  • DirectDial
  • AutoNumber

(if I were him, I would have an ID field too…)

We also need 3 constructors of the class: one empty, one with the 6 arguments mentioned above as fields and one with string array for the CSV import). Also need a public method for CSV export, called ToCsv().

Finally the most interesting part of the class: implementing the interface with the method of public int CompareTo(object obj)

The full class looks like this:

public class Phonebook : IComparable
    {
        public string Station { get; set; }
        public string Section { get; set; }
        public string Infaco { get; set; }
        public string Location { get; set; }
        public string DirectDial { get; set; }
        public int AutoNumber { get; set; }

        public Phonebook()
        {

        }

        public Phonebook(string station, string section, string infaco, string location, string directDial, int autoNumber)
        {
            Station = station;
            Section = section;
            Infaco = infaco;
            Location = location;
            DirectDial = directDial;
            AutoNumber = autoNumber;
        }

        public static Phonebook FromCsv(string csvLine)
        {
            string[] values = csvLine.Split(',');
            Phonebook phonebook = new Phonebook();
            phonebook.Station = values[0];
            phonebook.Section = values[1];
            phonebook.Infaco = values[2];
            phonebook.Location = values[3];
            phonebook.DirectDial = values[4];
            phonebook.AutoNumber = Convert.ToInt32(values[5]);

            return phonebook;
        }

        public string ToCsv()
        {
            return Station + "," + Section + "," + Infaco + "," + Location + "," + DirectDial + "," + AutoNumber;
        }

        public int CompareTo(object obj)
        {
            string s1 = this.Station as string;
            if (s1 == null) return 0;
            string s2 = (obj as Phonebook).Station as string;
            if (s2 == null) return 0;
            int len1 = s1.Length;
            int len2 = s2.Length;
            int marker1 = 0;
            int marker2 = 0;

            while (marker1 < len1 && marker2 < len2)
            {
                char ch1 = s1[marker1];
                char ch2 = s2[marker2];

                char[] space1 = new char[len1];
                int loc1 = 0;
                char[] space2 = new char[len2];
                int loc2 = 0;

                do
                {
                    space1[loc1++] = ch1;
                    marker1++;

                    if (marker1 < len1)
                    {
                        ch1 = s1[marker1];
                    }
                    else
                    {
                        break;
                    }
                } while (char.IsDigit(ch1) == char.IsDigit(space1[0]));

                do
                {
                    space2[loc2++] = ch2;
                    marker2++;

                    if (marker2 < len2)
                    {
                        ch2 = s2[marker2];
                    }
                    else
                    {
                        break;
                    }
                } while (char.IsDigit(ch2) == char.IsDigit(space2[0]));

                string str1 = new string(space1);
                string str2 = new string(space2);

                int result;

                if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
                {
                    int thisNumericChunk = int.Parse(str1);
                    int thatNumericChunk = int.Parse(str2);
                    result = thisNumericChunk.CompareTo(thatNumericChunk);
                }
                else
                {
                    result = str1.CompareTo(str2);
                }

                if (result != 0)
                {
                    return result;
                }
            }

            return len1 - len2;
        }
    }

On the form create a class level List<> variable of our phonebook type:

List<Phonebook> phonebookFromCsv;

This will keep our phonebook entries.

Let’s create the CSV opening event on the form:

private void openCSVToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (ofdCsv.ShowDialog() == DialogResult.OK)
            {
                if (phonebookFromCsv == null) phonebookFromCsv = File.ReadAllLines(ofdCsv.FileName)
                        .Skip(1)
                        .Select(x => Phonebook.FromCsv(x))
                        //.Where(x => x.Station != "")//optional conditions here
                        .ToList();
                else
                {
                    phonebookFromCsv.AddRange(File.ReadAllLines(ofdCsv.FileName)
                    .Skip(1)
                    .Select(x => Phonebook.FromCsv(x))
                    //.Where(x => x.Station != "")//optional conditions here
                    .ToList());
                }

                phonebookFromCsv.Sort();

                BindData();
            }
        }

The BindData() method only sets the datasource of the DataGridView:

private void BindData()
        {
            dataGridView1.DataSource = null;
            dataGridView1.DataSource = phonebookFromCsv;
            addToolStripMenuItem.Enabled = true;
            dataGridView1.ClearSelection();
        }

If we successfully read our CSV file, we should save it:

private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (sfdCsv.ShowDialog() == DialogResult.OK)
            {
                StreamWriter writer = new StreamWriter(sfdCsv.FileName);
                writer.WriteLine("Station ,Section,Infaco,Location ,Direct Dial,Auto No.");

                for (int i = 0; i < dataGridView1.Rows.Count; i++)
                {
                    Phonebook entry = (Phonebook)dataGridView1.Rows[i].DataBoundItem;
                    writer.WriteLine(entry.ToCsv());
                }
                writer.Close();
            }
        }

Create a TextChanged event for the TextBox. This will select every row where our search condition fits:

private void txbSearch_TextChanged(object sender, EventArgs e)
        {
            try
            {
                dataGridView1.ClearSelection();
                if (txbSearch.Text.Length > 0)
                {
                    foreach (DataGridViewRow row in dataGridView1.Rows)
                    {
                        for (int i = 0; i < row.Cells.Count; i++)
                        {
                            if (row.Cells[i].Value != null && row.Cells[i].Value.ToString().ToLower().Contains(txbSearch.Text.ToLower()))
                            {
                                int rowIndex = row.Index;
                                dataGridView1.Rows[rowIndex].Selected = true;
                                break;
                            }
                        }

                    }
                }
                else
                {
                    dataGridView1.ClearSelection();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

Finally create the window opening event for the NewEntry form:

private void addToolStripMenuItem_Click(object sender, EventArgs e)
        {
            NewEntry dialog = new NewEntry();
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                if (phonebookFromCsv != null) phonebookFromCsv.Add(dialog.Entry);
                else
                {
                    phonebookFromCsv = new List();
                    phonebookFromCsv.Add(dialog.Entry);
                }
                BindData();
            }
        }

The main form should look something similar to this:

The new form should have only 6 labels with 6 textboxes (or 5 textboxes with 1 NumericUpDown for the AutoNumber field), a public field for the new entry and one button. This button should have DialogResult set to OK. The form code looks like this:

public partial class NewEntry : Form
    {
        public Phonebook Entry { get; set; }

        public NewEntry()
        {
            InitializeComponent();
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            try
            {
                Entry = new Phonebook(txbStation.Text, txbSection.Text, txbInfaco.Text, txbLocation.Text, txbDirectDial.Text, (int)nudAutoNumber.Value);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                DialogResult = DialogResult.None;
            }
        }
    }

This form design should look like this:

If we start typing something in the search textbox, the row that fits the criteria will be selected. Let’s type: ne

Finally let’s save our new phonebook to a new CSV.