Datagrid from list crashes when item selected

1

BindingList was the correct answer, it fixed all of the problems, including being able to format the columns. Here are the only lines I had to change, and everything is working perfectly now.

//This went at the top:
BindingList<Ingredient> selectedIngredients = new BindingList<Ingredient>();

//This went in the page Load method:
dgvRecipeIngredients.DataSource = new BindingSource() { DataSource = selectedIngredients};

//These three went as appropriate in methods that added or removed items from the list. 
//The ResetBindings removed items from the list, which wasn’t happening before without resetting the page. 

selectedIngredients.Add(ingr);
selectedIngredients.RemoveAt(idxSelectedIngr);
selectedIngredients.ResetBindings();

I have left everything else as it was, so people can see the problems, and how this fixed them.

Picture of UI with some data in second grid (bottom) and an ingredient ready to be added I have one gridview that I am bringing my list of ingredients into that is working just fine. I have a second gridvew that I want to be able to add ingredients to from the first gridview.

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 HealthierRecipes
{
    public partial class AddRecipe : Form
    {
        public static int ingrId = 0;
        public static int availIngrId = -1;
        private static int idxSelectedIngr = 0;
        private static int idxSelectedAvailIngr = -1;
        private List<Ingredient> selectedIngredients = new List<Ingredient>();
        
        public AddRecipe()
        {
            InitializeComponent();
        }

        private void AddRecipe_Load(object sender, EventArgs e)
        {
            
            using (RecipeClassesDataContext dbContext = new RecipeClassesDataContext())
            {
                dgvAvailIngredients.DataSource = dbContext.Ingredients.OfType<Ingredient>().ToList();
                this.dgvRecipeIngredients.DataSource = selectedIngredients;
                   
                formatPage();
                txtTotCals.Text = "0";
                txtTotCarbs.Text = "0";
                txtTotProtein.Text = "0";
                txtTotFat.Text = "0";   
            }    
        }

        private void dgvRecipeIngredients_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            using (RecipeClassesDataContext dbContext = new RecipeClassesDataContext())
            {
                idxSelectedIngr = e.RowIndex;
                ingrId = Convert.ToInt32(dgvRecipeIngredients.Rows[e.RowIndex].Cells[0].Value);

                //var ingr = (from r in dbContext.Ingredients where r.IngredientId == ingrId select r).First();
                //txtAddIngredient.Text = ingr.Name;
                //txtAddUnit.Text = ingr.Units;
                //txtAddAmount.Text = ingr.Amount.ToString();
            } 
        }

        private void dgvAvailIngredients_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            using (RecipeClassesDataContext dbContext = new RecipeClassesDataContext())
            {
                idxSelectedAvailIngr = e.RowIndex;
                availIngrId = Convert.ToInt32(dgvAvailIngredients.Rows[e.RowIndex].Cells[0].Value);

                var ingr = (from r in dbContext.Ingredients where r.IngredientId == availIngrId select r).First();
                txtAddIngredient.Text = ingr.Name;
                txtAddUnit.Text = ingr.Units;
                txtAddAmount.Text = ingr.Amount.ToString();
            }    
        }
        private void bnAddIngredient_Click(object sender, EventArgs e)
        {
            using (RecipeClassesDataContext dbContext = new RecipeClassesDataContext())
            {
                var ingr = (from r in dbContext.Ingredients where r.IngredientId == availIngrId select r).First();

                if (availIngrId > 0)
                {
                    float amountNumber = 0;
                    bool amountValid = float.TryParse(txtAddAmount.Text, out amountNumber);
                    if (amountValid == false)
                    {
                        MessageBox.Show("Please only use numbers in the amount box.");
                        return;
                    }

                    ingr.Amount = amountNumber;
                    selectedIngredients.Add(ingr);
                    
                    txtAddIngredient.Text = "";
                    txtAddUnit.Text = "";
                    txtAddAmount.Text = "";

                    dgvRecipeIngredients.DataSource = null;
                    dgvRecipeIngredients.DataSource = selectedIngredients;


                    //TODO This is where the code to multiply the amount happens, then add the ingredient to the recipe. 
                    //Includes updating recipe nutrients.

                    float ingrcals = ingr.Calories * ingr.Amount;
                    float ingrcarbs = ingr.Calories * ingr.Amount;
                    float ingrpro = ingr.Protein * ingr.Amount;
                    float ingrfat = ingr.Fat * ingr.Amount;

                    float totcals = float.Parse(txtTotCals.Text);
                    float totcarbs = float.Parse(txtTotCarbs.Text);
                    float totpro = float.Parse(txtTotProtein.Text);
                    float totfat = float.Parse(txtTotFat.Text);

                    txtTotCals.Text = (totcals + ingrcals).ToString();
                    txtTotCarbs.Text = (totcarbs + ingrcarbs).ToString();
                    txtTotProtein.Text = (totpro + ingrpro).ToString();
                    txtTotFat.Text = (totfat + ingrfat).ToString();

                }
                else { MessageBox.Show("Please select an ingredient to add."); }

            }
        }


        private void bnSaveRecipe_Click_1(object sender, EventArgs e)
        {
            //TODO save recipe
            using (RecipeClassesDataContext dbContext = new RecipeClassesDataContext())
            {

                Recipe r = new Recipe();

                if (ValidateForm())
                {
                    r.Name = txtRecName.Text;
                    r.Dish = cbDish.SelectedItem.ToString();
                    r.Servings = Int32.Parse(txtServings.Text);
                    r.TotalCalories = Int32.Parse(txtTotCals.Text);
                    r.TotalFat = Int32.Parse(txtTotFat.Text);
                    r.TotalCarbs = Int32.Parse(txtTotCarbs.Text);
                    r.TotalProtein = Int32.Parse(txtTotProtein.Text);
                    r.Instructions = rtxtInstructions.Text;

                    dbContext.Recipes.InsertOnSubmit(r);
                    dbContext.SubmitChanges();
                    MessageBox.Show("Record is saved", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);

                    int id = r.RecipeId;

                    // r.IngredientList = selectedIngredients;

                    IngredientList il = new IngredientList();
                    foreach (Ingredient ingr in selectedIngredients)
                    {
                        il.RecipeId = id;
                        il.IngredientId = ingr.IngredientId;
                        il.IngredientAmount = ingr.Amount;
                    }


                    txtRecName.Text = "";
                    txtServings.Text = "";
                    txtTotCals.Text = "0";
                    txtTotFat.Text = "0";
                    txtTotCarbs.Text = "0";
                    txtTotProtein.Text = "0";
                    rtxtInstructions.Text = "";
                    dgvRecipeIngredients.DataSource = null;
                    dgvRecipeIngredients.DataSource = selectedIngredients;
                }
                else
                {
                    MessageBox.Show("Please fill in all boxes on form with valid values.");
                }
            }


        }

        private bool ValidateForm()
        {
            bool output = true;

            if (txtRecName.Text.Length == 0)
            {
                output = false;
            }

            if (cbDish.SelectedItem == null)
            {
                output = false;
            }


            int servingsNumber = 0;
            bool servingsValid = int.TryParse(txtServings.Text, out servingsNumber);
            if (servingsValid == false)
            {
                output = false;
            }

            int caloriesNumber = 0;
            bool caloriesValid = int.TryParse(txtTotCals.Text, out caloriesNumber);
            if (caloriesValid == false)
            {
                output = false;
            }

            int fatNumber = 0;
            bool fatValid = int.TryParse(txtTotFat.Text, out fatNumber);
            if (fatValid == false)
            {
                output = false;
            }

            int carbsNumber = 0;
            bool carbsValid = int.TryParse(txtTotCarbs.Text, out carbsNumber);
            if (carbsValid == false)
            {
                output = false;
            }

            int proteinNumber = 0;
            bool proteinValid = int.TryParse(txtTotProtein.Text, out proteinNumber);
            if (proteinValid == false)
            {
                output = false;
            }

            return output;
        }

        private void btnAddCancel_Click(object sender, EventArgs e)
        {
            //TODO Change from exit to cancel
            Application.Exit();
        }

        private void bnDelIngr_Click(object sender, EventArgs e)
        {
            //TODO - Add code to remove Nutrition info for removed ingredient - this is done, but it doesn't reomove the item from the list yet.

            using (RecipeClassesDataContext dbContext = new RecipeClassesDataContext())
            {
                var ingr = (from r in dbContext.Ingredients where r.IngredientId == ingrId select r).First();

                if (ingrId > 0)
                {

                    float ingrcals = ingr.Calories * ingr.Amount;
                    float ingrcarbs = ingr.Calories * ingr.Amount;
                    float ingrpro = ingr.Protein * ingr.Amount;
                    float ingrfat = ingr.Fat * ingr.Amount;

                    float totcals = float.Parse(txtTotCals.Text);
                    float totcarbs = float.Parse(txtTotCarbs.Text);
                    float totpro = float.Parse(txtTotProtein.Text);
                    float totfat = float.Parse(txtTotFat.Text);

                    txtTotCals.Text = (totcals - ingrcals).ToString();
                    txtTotCarbs.Text = (totcarbs - ingrcarbs).ToString();
                    txtTotProtein.Text = (totpro - ingrpro).ToString();
                    txtTotFat.Text = (totfat - ingrfat).ToString();

                    ingr.Amount = 1;
                    selectedIngredients.Remove(ingr);

                    dgvRecipeIngredients.DataSource = null;
                    dgvRecipeIngredients.DataSource = selectedIngredients;
                }
                else { MessageBox.Show("Please select an ingredient to delete."); }

            }
        }

        private void bnEdit_Click(object sender, EventArgs e)
        {
            using (RecipeClassesDataContext dbContext = new RecipeClassesDataContext())
            {
                //TODO - Add code to remove Nutrition info for Edited ingredient - this is done, but it doesn't reomove the item from the list yet.
                var ingr = (from r in dbContext.Ingredients where r.IngredientId == ingrId select r).First();

                if (ingrId >0)
                {
                    txtAddIngredient.Text = ingr.Name;
                    txtAddUnit.Text = ingr.Units;
                    txtAddAmount.Text = ingr.Amount.ToString();

                    float ingrcals = ingr.Calories * ingr.Amount;
                    float ingrcarbs = ingr.Calories * ingr.Amount;
                    float ingrpro = ingr.Protein * ingr.Amount;
                    float ingrfat = ingr.Fat * ingr.Amount;

                    float totcals = float.Parse(txtTotCals.Text);
                    float totcarbs = float.Parse(txtTotCarbs.Text);
                    float totpro = float.Parse(txtTotProtein.Text);
                    float totfat = float.Parse(txtTotFat.Text);

                    txtTotCals.Text = (totcals - ingrcals).ToString();
                    txtTotCarbs.Text = (totcarbs - ingrcarbs).ToString();
                    txtTotProtein.Text = (totpro - ingrpro).ToString();
                    txtTotFat.Text = (totfat - ingrfat).ToString();
                    
                    selectedIngredients.Remove(ingr);

                    dgvRecipeIngredients.DataSource = null;
                    dgvRecipeIngredients.DataSource = selectedIngredients;
                }
                else { MessageBox.Show("Please select an ingredient to edit."); }
            }
                
        }

        private void formatPage()
        {
            dgvAvailIngredients.Columns[0].Width = 25;
            dgvAvailIngredients.Columns[1].Width = 150;
            dgvAvailIngredients.Columns[2].Width = 75;
            dgvAvailIngredients.Columns[3].Width = 25;
            dgvAvailIngredients.Columns[4].Width = 50;
            dgvAvailIngredients.Columns[5].Width = 50;
            dgvAvailIngredients.Columns[6].Width = 50;
            dgvAvailIngredients.Columns[7].Width = 50;

            dgvRecipeIngredients.Columns[0].Width = 25;
            dgvRecipeIngredients.Columns[1].Width = 150;
            dgvRecipeIngredients.Columns[2].Width = 75;
            dgvRecipeIngredients.Columns[3].Width = 25;
            dgvRecipeIngredients.Columns[4].Width = 50;
            dgvRecipeIngredients.Columns[5].Width = 50;
            dgvRecipeIngredients.Columns[6].Width = 50;
            dgvRecipeIngredients.Columns[7].Width = 50;

        }

        private void btnSearchIngr_Click(object sender, EventArgs e)
        {
            string srch = txtSearchIngr.Text;
            using (RecipeClassesDataContext dbContext = new RecipeClassesDataContext())
            {
                var ingrlist = from r in dbContext.Ingredients where r.Name.Contains(srch) select r;
                dgvAvailIngredients.DataSource = ingrlist;
            }
        }   
    }
}

Ingredient is being pulled from LINQ to SQL tables. Ingredient is actually an abstract class, with HealthyIngredient and RegularIngredient subclasses, but both subclasses are stored in the same SQL table, with null values in the places where the other subclass has its specific values.

CREATE TABLE [dbo].[Ingredient] (
[IngredientId]    INT           IDENTITY (1, 1) NOT NULL,
[Name]            VARCHAR (50)  NOT NULL,
[Units]           NCHAR (10)    NOT NULL,
[Amount]          FLOAT           DEFAULT ((1)) NOT NULL,
[Calories]        INT           NOT NULL,
[Fat]             INT           NOT NULL,
[Carbs]           INT           NOT NULL,
[Protein]         INT           NOT NULL,
[Discriminator]   NVARCHAR (50) NOT NULL,
[HealthyVariant1] NVARCHAR (50) NULL,
[HealthyVariant2] NVARCHAR (50) NULL,
[HealthyType]     NVARCHAR (50) NULL,
[RegularVariant]  NVARCHAR (50) NULL,
[HealthyVar1Id]   INT           NULL,
[HealthyVar2Id]   INT           NULL,
[RegVarId]        INT           NULL,
PRIMARY KEY CLUSTERED ([IngredientId] ASC)

This also seems to work just fine, including a bunch of fairly complicated math involving multiplying inserted fields of the table and adding them to text boxes elsewhere on the form, and changing some info between the first grid and the second, so the data is all being passed correctly to the second datagrid.

However, if I try to select anything from the second datagrid, I get an error that crashes the whole program and sends it all the way back to the Program.cs.

Strangely enough, every once in a while if I have the datagrid set to RowHeadderSelect and I only click on the row header, it works correctly, including letting me use the edit button and doing the math correctly again, and then it works during that session no matter where I click, but then when I restart the program it gives me the same error again.

I have tried List, IList, ICollection, and IEnumerable, just in case it was my list type causing the issue, but it doesn’t seem to be. Anyone have some suggestions about what I am doing wrong?

I have added more of the relevant code. These are the only three methods that affect anything at the moment - the Load, the Add, and then trying to click on the datagridview.

I don't know where to put any debugging code, since it crashes before it enters the CellContentClick method. I have a breakpoint there but it never reaches it. I am thinking that it is what or how I am loading the data, but it all looks right in the datagrid on the page. Is there a different way I should be setting up the data to the datagrid? Thanks for helping.

System.IndexOutOfRangeException
  HResult=0x80131508
  Message=Index -1 does not have a value.
  Source=System.Windows.Forms
   at System.Windows.Forms.CurrencyManager.get_Item(Int32 index)
   at System.Windows.Forms.CurrencyManager.get_Current()
   at System.Windows.Forms.DataGridView.DataGridViewDataConnection.OnRowEnter(DataGridViewCellEventArgs e)
   at System.Windows.Forms.DataGridView.OnRowEnter(DataGridViewCell& dataGridViewCell, Int32 columnIndex, Int32 rowIndex, Boolean canCreateNewRow, Boolean validationFailureOccurred)
   at System.Windows.Forms.DataGridView.SetCurrentCellAddressCore(Int32 columnIndex, Int32 rowIndex, Boolean setAnchorCellAddress, Boolean validateCurrentCell, Boolean throughMouseClick)
   at System.Windows.Forms.DataGridView.OnRowHeaderMouseDown(HitTestInfo hti, Boolean isShiftDown, Boolean isControlDown)
   at System.Windows.Forms.DataGridView.OnCellMouseDown(DataGridViewCellMouseEventArgs e)
   at System.Windows.Forms.DataGridView.OnMouseDown(MouseEventArgs e)
   at System.Windows.Forms.Control.WmMouseDown(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.DataGridView.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
   at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.Run(Form mainForm)
   at HealthierRecipes.Program.Main() in C:\Users\Tania\source\repos\Old\HealthierRecipes\HealthierRecipes\Program.cs:line 25
c#
list
datagridview
linq-to-sql
visual-studio-2019
asked on Stack Overflow Feb 8, 2021 by TBaildon • edited Feb 10, 2021 by TBaildon

1 Answer

0

After quite a few tests, I broke this down and can post a very simple example to reproduce what you describe. But for grins...

In the forms “Load” event there is a line that sets the recipe ingredient grid’s data source…

this.dgvRecipeIngredients.DataSource = selectedIngredients;

// Comment out that line.


We do not need to set grids data source at this time since the list is empty. It will get set when the add button is clicked.

Please try this and let me know if this helps.

This looks like another good example where a BindingList<T> is a better choice.

answered on Stack Overflow Feb 9, 2021 by JohnG • edited Feb 9, 2021 by JohnG

User contributions licensed under CC BY-SA 3.0