Refining the Form’s Data Grid View

We had already set some general options for the data grid view, but now that we’ve dropped a data source into the data grid view and have some columns, let’s edit the columns.

The Data Grid View Column Editor

Let’s start with the first column, ID. We don’t need to change the ReadOnly status because the data grid view’s overall ReadOnly status has already been set to True, but we want the ID to be formatted to four digits with 0 as a placeholder, thus ID 1 would display as 0001. We figure we’ll never have 10,000 clients, and if we do, we’ll be using different software.

For the ID column, set the Width to 32 or some value lower than the default 100. I usually use eight pixels per character as a rule of thumb when using Segoe UI at 9 pt. Then pop open the cell style builder and set the Format property to 0000 — without quote marks.

The Cell Style Builder

Now let’s do the same for the date. Well, not the same, but similar. We want the date to look like this: MM/DD HH:MM, that is, two-digit month, followed by two-digit day of the month, then two-digit hour, followed by two-digit minutes. I know this is Americas-centric (not just the US but the whole of the Americas), and we’ll figure out how to make it work automatically in locales that place the day before the month later.

I’ve set the Date column to a width of 80. In a flash of inspiration, I have renamed the header text from “Date” to “Since,” that is, the date since the person has been our client. Makes more sense, right? Then, in the cell style builder, I set the Format property to MM/dd hh:mm, again without quote marks. I know the width of 80 is a little loose, so I also see the Alignment property to MiddleCenter, so it always looks aligned.

Ok, this done, we run the application, and oops, we get this.

Unexpected date formatting or lack thereof

The date is formatted incorrectly! Or wait… it’s actually not formatted at all. Check with SQLite Studio and you’ll see that the Date column is a Text column. What the heck? Then you remember that was a design choice made when the database was setup. SQLite doesn’t have a specific date type. You can store dates as text, real numbers, or integer numbers. Whatever choice you make, you must use SQL functions to retrieve and store values, and I don’t know how that’s going to work with the queries that Visual Studio generates automatically. (“I told you we should have gone with Access!”)

It turns out there’s a very useful data grid view event called CellFormatting. As the data grid view is being displayed, this event fires for every cell. It has an argument e that contains the row number, the column number, the cell value, etc. We can then change the cell value to display what we want. With the data grid selected, change the view in the Properties tab from properties to events, and double-click on CellFormatting. This will create the event method and open the editor. It’s empty, so we’ll have to add the necessary code..

private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    var dgv = sender as DataGridView;
    if (e.ColumnIndex == dgv.Columns["Since"].Index)
    {
        DateTime dt = Convert.ToDateTime(e.Value);
        e.Value = dt.ToString("MM/dd hh:mm");
    }
}

In the code above, we start by creating a reference to the data grid view from the sender. The sender object is the object that triggered the event. You might ask, why not just refer to dataGridView1? This way is more foolproof. What if you had several data grid views, and referred to the wrong one? Ok, I’m sure you’d notice that right away, but still, it’s good practice. In general, the fewer references to the “outside,” the better.

Every time the event fires, we check to see if the current column is the “Since” column. You might ask, why not just refer to 1, which we know is the index number of the “Since” column. The reason we don’t is that the column might change. This way, it finds the column no matter where it is. In general, hard-code as little as you can. Numbers entered directly into code are called “magic numbers” and are generally not good practice.

Once the current cell is the cell with the date, we create a DateTime object with the value converted from the string value in e. Then we convert the value back into string, this time with the format we want. It would have been possible to do it in one line, like this:

e.Value = Convert.ToDateTime(e.Value).ToString("MM/dd hh:mm");

And sure, it works, but you’re saving a line and windup up with a cryptic line of code the workings of which you won’t remember in six months. It’s just so much clearer when it’s like this:

 DateTime dt = Convert.ToDateTime(e.Value);
 e.Value = dt.ToString("MM/dd hh:mm");

Now check this out. In the code above, we’re converting the string from SQLite to a DateTime type and then converting it back to a string in a certain date format. Remember how, in the form designer, you could edit the data grid view’s columns, and for any column, pop open the cell style builder, within which you could set the format? Remember how we set the ID column’s format to 0000 to create a zero-padded four-digit number? Well, instead of converting from string to date and back to string, we could do this:

e.Value = Convert.ToDateTime(e.Value);

And then we could pop open the cell style builder for the Since column and set the format to [MM/dd hh:mm]. It would work just as well. The thing is that e.Value is of object type, so it can accept a string or a DateTime type. However, I thought this might be less clear, though even as I type this, I thought it might be better to make things as agnostic as possible. If we later switch from SQLite to a different database, a database that does handle DateTime types natively, which way leads to less change?

Finally, let’s remove some columns. As you might remember, we decided not to have US-centric address fields, and instead merely created four address lines and four contact lines. This way, people from all over the world could use the application, as long as they could enter their address in four lines. Just so everything fits, I’ve decided to remove columns Address2 through Address4, and Contact3 through Contact4, Now the form looks like this, and after expanding the Name field to 180 pixels, everything fits just right.

And here we have the code. The code to fill the table adapter and the call to InitializeComponent are inserted behind the scenes by Visual Studio. The only code we had to add was the CellFormatting handler, and only because of SQLite’s handling of dates.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TheBooks
{
    public partial class ClientList : Form
    {
        public ClientList()
        {
            InitializeComponent();
        }

        private void ClientList_Load(object sender, EventArgs e)
        {
            // TODO: This line of code loads data into the 'dSClients.Clients' table.
            // You can move, or remove it, as needed.
            this.clientsTableAdapter.Fill(this.dSClients.Clients);
        }

        private void dataGridView1_CellFormatting(
            object sender, DataGridViewCellFormattingEventArgs e)
        {
            var dgv = sender as DataGridView;
            if (e.ColumnIndex == dgv.Columns["Since"].Index)
            {
                DateTime dt = Convert.ToDateTime(e.Value);
                e.Value = dt.ToString("MM/dd hh:mm");
            }
        }
    }
}

What is InitializeComponent? If you expand the ClientList form in SolutionExplorer, you’ll see that under ClientList.cs we have ClientList.Designer.cs. There’s also a ClientList.resx file, but we can ignore that for now. The ClientList.Designer.cs file has the code that creates the form that we created in the form designer. You can open this file to inspect it. If we didn’t have the form designer, we’d have to write all that code ourselves!

When you open a form in designer view, Visual Studio looks at this code to determine how to show you the form. When you make changes to the form and save it, Visual Studio writes all the code back. This is why it’s almost always pointless to edit the designer file directly — your changes will be overwritten. It’s also why, if you do edit the designer file and introduce an error, Visual Studio will refuse to show you the form.

Now we come to the purpose of InitializeComponent. It calls the code in the designer file and initializes the form. That’s why Visual Studio places the call in the constructor. The constructor is the method with the same name as the class, and executes automatically to initialize the class. So the constructor initializes the class, and the call to InitializeComponent initializes the form.

Leave a Reply

Your email address will not be published. Required fields are marked *