Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Index was out of range only on last record

Status
Not open for further replies.

PRMiller2

Technical User
Jul 30, 2010
123
0
0
I'm very new to C#, so hoping someone can help me out. I've been banging my head against the wall on this one. I'm receiving the following error: "Index was out of range. Must be non-negative and less than the size of the collection."

In Main.cs, when a user has a customer selected in a combo box, they can select a command that will open Clients.cs. In Clients.cs, a DataGridView is populated with all Customer records. The program will select the row representing the customer chosen in Main.cs. Here's the code from Main.cs:

Code:
        private void editClient()
        {
            Clients clientForm = new Clients();

            if (clientComboBox.SelectedValue != null)
            {                
               // int clientVal = (int)clientComboBox.SelectedValue;  
                int clientVal = Convert.ToInt32(clientComboBox.SelectedValue);
                clientForm.ClientID = clientVal;
            }
            else
            {
                clientForm.ClientID = 0;
            }

            DialogResult result = clientForm.ShowDialog();
            clientForm = null;
        }


Here's the Load event in Clients.cs:
Code:
        private void Clients_Load(object sender, EventArgs e)
        {
            this.customerTableAdapter.Fill(this.cs_dbDataSet.Customer);

            if (clientID == 0)
            {
                addsaveButton.Text = "Add";
            }
            else
            {
                addsaveButton.Text = "Save";

                int rowCount = clientsDataGridView.Rows.Count;

                DataGridViewRow row = new DataGridViewRow();
                for (int i = 0; i < rowCount; i++)
                {
                    row = clientsDataGridView.Rows[i];
                    if (row.Cells["lngCustomerID_pk"].Value.Equals(clientID))
                    {
                        clientsDataGridView.FirstDisplayedScrollingRowIndex = i;
                        clientsDataGridView.CurrentCell = row.Cells[clientID];
                        row.Selected = true;
                        clientabbbrevTextBox.Text = row.Cells["clientabbrevDGVTextBoxColumn"].Value.ToString();
                        clientnameTextBox.Text = row.Cells["clientnameDGVTextBoxColumn"].Value.ToString();
                        break;
                    }
                    row = null;
                }
            }
            
        }

This works for all records except the last one in the Customer table. When that one is selected, I receive the error listed above. If the database table contains three tables, how can I make the index less than the size of the collection? In my mind, that means that the user will never be able to select the last record in a database.

Of course, that's a foolish interpretation on my part, but it does leave me scratching my head. Any recommendations?

Thanks,
Paul
 
indexes are zero-based. a collection containing 3 items are indexed 0, 1, 2.
Typically when you see this type of error an "off by one" index is the problem.

something else that would help is the complete stack trace. when an exception is thrown you get the exception type, message and stack trace. all three of these pieces together provides a wealth of information on how to get started debugging the error.

*****************************************************************
some other things to consider. not directly related to you issue.
1. you can move the row declaration to
var row = clientsDataGridView.Rows;
no need to new up a row just to replace it.
2. drop row = null; this is just noise
3. I would alter the entire else block to use the data rather than the UI controls. consider the UI a one-way concept. once the data is loaded into the UI it shouldn't be used to logically process data. Another way to look at it. all data processing should be done before sending information to the UI controls. this keeps the UI layer "dumb". This is good because the UI is the most complicated part of any system.

Jason Meckley
Programmer

faq855-7190
faq732-7259
 
Thanks Jason. The Customer table -- and, by extension, the DataGridView -- contains 3 records. Since the loop statement accounts for that at the outset, I know the problem lies further down. Heeding your advance, I took a look at all three debug items.

It appears that this line is problematic:

Code:
clientsDataGridView.CurrentCell = row.Cells[clientID];

My intent was to have the DataGridView select the row where lngCustomerID_pk -- one of the columns defined in the DataGridView Collection -- equals the clientID. Would it be a fair conclusion to say that it is instead trying to highlight the row where the index = clientID? If so, how do I change to my intended behavior?
 
By the way, thank you for the advice on data controls. I come from an Access/VBA world where I like to set my data sources programatically, rather than with wizards, etc. I picked up the UI controls from a tutorial I followed, planning on making the change "eventually." You prompted me to take a stab at switching things up.

Here's my new Clients_Load event:

Code:
        private void Clients_Load(object sender, EventArgs e)
        {
            fillClientDataGridView();

            if (clientID == 0)
            {
                addsaveButton.Text = "Add";
            }
            else
            {
                addsaveButton.Text = "Save";

                int rowCount = clientsDataGridView.Rows.Count;

                var row = new DataGridViewRow();
                for (int i = 0; i < rowCount; i++)
                {
                    row = clientsDataGridView.Rows[i];
                    if (row.Cells["lngCustomerID_pk"].Value.Equals(clientID))
                    {
                        clientsDataGridView.FirstDisplayedScrollingRowIndex = i;
                        clientsDataGridView.CurrentCell = row.Cells[clientID];
                        row.Selected = true;
                        clientabbbrevTextBox.Text = row.Cells["Abbreviation"].Value.ToString();
                        clientnameTextBox.Text = row.Cells["Full Name"].Value.ToString();
                        break;
                    }
                }
            }            
        }

And here's fillClientDataGridView:

Code:
        private void fillClientDataGridView()
        {
            string conn = ComplianceManager.Properties.Settings.Default.CS_DBConnectionString;
            string sql = "SELECT lngCustomerID_pk, txtCustomerAbbrev, txtCustomerName FROM Customer;";

            OleDbConnection connection = new OleDbConnection(conn);
            OleDbDataAdapter dataAdapter = new OleDbDataAdapter(sql, connection);
            DataSet ds = new DataSet();
            
            connection.Open();
            dataAdapter.Fill(ds, "tableCustomers");
            connection.Close();
            clientsDataGridView.DataSource = ds;
            clientsDataGridView.DataMember = "tableCustomers"; 
            clientsDataGridView.Columns[0].Name = "lngCustomerID_pk";
            clientsDataGridView.Columns[0].Visible = false;
            clientsDataGridView.Columns[1].Name = "Abbreviation";
            clientsDataGridView.Columns[1].Width = 100;
            clientsDataGridView.Columns[2].Name = "Full Name";
            clientsDataGridView.Columns[2].Width = 285;            
        }

I'd love to hear your feedback on this approach.

Thanks,
Paul
 
OK, I think I see it now. you are using the client id as the indexer. assuming the client id is the PK value in the db it won't also be the index. (and if it does it's only by coincidence).

the second code fragment is cleaner, but you're still using the UI components to preform logic. the data that is bound to the grid and populating UI will be the same, but you don't want to use one UI control to populate the other.

it would look something like this. lets assume a data table is the source of data.
Code:
var table = LoadDataTable(...);
gridview.datasource = table;
if(client id != 0)
{
   foreach(DataRow row in table.Rows)
   {
      set active gridview row from the datarow;
      load text controls from the datarow;
   }
}

Jason Meckley
Programmer

faq855-7190
faq732-7259
 
Ah, the joy of being a newb. Learning can be very rewarding... and somewhat frustrating. I think I take your point now on using the data controls, but mayhap I'm still a bit thick. Here's the latest:

Code:
        private void fillClientDataGridView()
        {
            DataTable table = ClientTable();
            clientsDataGridView.DataSource = table;
            clientsDataGridView.Columns[0].Name = "lngCustomerID_pk";
            clientsDataGridView.Columns[0].Visible = false;
            clientsDataGridView.Columns[1].HeaderText = "Abbreviation";
            clientsDataGridView.Columns[1].Name = "Abbreviation";
            clientsDataGridView.Columns[1].Width = 100;
            clientsDataGridView.Columns[2].HeaderText = "Full Name";
            clientsDataGridView.Columns[2].Name = "Full Name";
            clientsDataGridView.Columns[2].Width = 285;
        }

        private DataTable ClientTable()
        {
            string conn = ComplianceManager.Properties.Settings.Default.CS_DBConnectionString;
            string sql = "SELECT lngCustomerID_pk, txtCustomerAbbrev, txtCustomerName FROM Customer;";

            OleDbConnection connection = new OleDbConnection(conn);
            OleDbDataAdapter dataAdapter = new OleDbDataAdapter(sql, connection);
            DataSet ds = new DataSet();

            connection.Open();
            dataAdapter.Fill(ds);
            connection.Close();

            System.Data.DataTable datTable = ds.Tables[0];
            return datTable;
        }

I wasn't able to find the right way to use your hints to produce the desired behavior (selecting the appropriate DataGridView row). That will come with time, I think. In the mean time, here's how I adapted my first version of code to select the row:

Code:
        private void Clients_Load(object sender, EventArgs e)
        {
            fillClientDataGridView();

            int rowCount = clientsDataGridView.RowCount;
            DataGridViewRow row = new DataGridViewRow();
            for (int i = 0; i < rowCount; i++)
            {
                row = clientsDataGridView.Rows[i];
                if (row.Cells["lngCustomerID_pk"].Value.Equals(clientID))
                {
                    row.Selected = true;
                }
            }
            // TODO:  Add code to populate text box controls from ClientTable()... perhaps an If statement
            // in the function if clientID != 0...?
        }

This does run without throwing an exception. However, even if the last row in in the DataGridView is highlighted, the first cell also remains highlighted. Not sure why that is.

Am I on the right track, or moving backwards? And your thoughts on the first cell highlight?

Thanks for your patience!
 
that's looking much better :)

Jason Meckley
Programmer

faq855-7190
faq732-7259
 
Thanks Jason! Any thoughts on that single cell remaining highlighted while all cells in the correct row are also highlighted?
 
Got it! Added this line:

Code:
clientsDataGridView.FirstDisplayedScrollingRowIndex = clientsDataGridView.Rows.IndexOf(row);

So the block now reads as follows:

Code:
        private void Clients_Load(object sender, EventArgs e)
        {
            FillClientDataGridView();

            if (clientID != 0)
            {
                addsaveButton.Text = "Save";
            }
            else
            {
                addsaveButton.Text = "Add";
            }

            int rowCount = clientsDataGridView.RowCount;
            DataGridViewRow row = new DataGridViewRow();
            for (int i = 0; i < rowCount; i++)
            {
                row = clientsDataGridView.Rows[i];
                if (row.Cells["lngCustomerID_pk"].Value.Equals(clientID))
                {
                    row.Selected = true;                    
                    clientsDataGridView.FirstDisplayedScrollingRowIndex = clientsDataGridView.Rows.IndexOf(row);
                }
            }
            this.dataChanged = false;
        }

Thanks again!



 
excellent!

Jason Meckley
Programmer

faq855-7190
faq732-7259
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top