×
INTELLIGENT WORK FORUMS
FOR COMPUTER PROFESSIONALS

Contact US

Log In

Come Join Us!

Are you a
Computer / IT professional?
Join Tek-Tips Forums!
  • Talk With Other Members
  • Be Notified Of Responses
    To Your Posts
  • Keyword Search
  • One-Click Access To Your
    Favorite Forums
  • Automated Signatures
    On Your Posts
  • Best Of All, It's Free!

*Tek-Tips's functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.

Posting Guidelines

Promoting, selling, recruiting, coursework and thesis posting is forbidden.

Students Click Here

Index was out of range only on last record

Index was out of range only on last record

Index was out of range only on last record

(OP)
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

RE: Index was out of range only on last record

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[i];
   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: Database Connection Management
FAQ732-7259: Keeping the UI responsive

RE: Index was out of range only on last record

(OP)
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?

RE: Index was out of range only on last record

(OP)
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

RE: Index was out of range only on last record

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: Database Connection Management
FAQ732-7259: Keeping the UI responsive

RE: Index was out of range only on last record

(OP)
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!

RE: Index was out of range only on last record

(OP)
Thanks Jason!  Any thoughts on that single cell remaining highlighted while all cells in the correct row are also highlighted?

RE: Index was out of range only on last record

(OP)
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!



 

Red Flag This Post

Please let us know here why this post is inappropriate. Reasons such as off-topic, duplicates, flames, illegal, vulgar, or students posting their homework.

Red Flag Submitted

Thank you for helping keep Tek-Tips Forums free from inappropriate posts.
The Tek-Tips staff will check this out and take appropriate action.

Reply To This Thread

Posting in the Tek-Tips forums is a member-only feature.

Click Here to join Tek-Tips and talk with other members! Already a Member? Login


Close Box

Join Tek-Tips® Today!

Join your peers on the Internet's largest technical computer professional community.
It's easy to join and it's free.

Here's Why Members Love Tek-Tips Forums:

Register now while it's still free!

Already a member? Close this window and log in.

Join Us             Close