选择项目时列表中的数据网格崩溃

Datagrid from list crashes when item selected

BindingList 是正确答案,它解决了所有问题,包括能够格式化列。这是我必须更改的唯一几行,现在一切正常。

//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();

我将其他所有内容保持原样,以便人们可以看到问题以及如何解决这些问题。

Picture of UI with some data in second grid (bottom) and an ingredient ready to be added 我有一个 gridview,我正在将我的成分列表放入其中,它工作得很好。 我有第二个 gridvew,我希望能够从第一个 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;
            }
        }   
    }
}

成分正在从 LINQ 提取到 SQL tables。 Ingredient其实是一个抽象class,有HealthyIngredient和RegularIngredient子class,但是两个子class都存储在同一个SQLtable里,有null其他子class有其特定值的地方的值。

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)

这似乎也工作得很好,包括一堆相当复杂的数学运算,涉及乘以 table 的插入字段并将它们添加到表单其他地方的文本框,并更改第一个网格之间的一些信息第二个,所以数据都被正确传递到第二个数据网格。

但是,如果我尝试 select 来自第二个数据网格的任何内容,我会收到一个错误,导致整个程序崩溃并将其一路发送回 Program.cs。

奇怪的是,每隔一段时间,如果我将数据网格设置为 RowHeadderSelect 并且我只单击行 header,它会正常工作,包括让我使用编辑按钮并正确地进行数学计算再次,然后它在 session 期间工作,无论我点击哪里,但是当我重新启动程序时它再次给我同样的错误。

我已经尝试过 List、IList、ICollection 和 IEnumerable,以防万一是我的列表类型导致了问题,但似乎并非如此。有人对我做错了什么有什么建议吗?

我添加了更多相关代码。这些是目前影响任何事情的仅有的三种方法 - 加载、添加,然后尝试单击数据网格视图。

我不知道把调试代码放在哪里,因为它在进入 CellContentClick 方法之前就崩溃了。我在那里有一个断点,但它永远不会到达它。我在想这是我加载数据的内容或方式,但它在页面上的数据网格中看起来都是正确的。是否有其他方法可以将数据设置到数据网格?感谢您的帮助。

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

经过多次测试,我将其分解并可以 post 一个非常简单的示例来重现您描述的内容。但是对于笑容...

在表单“加载”事件中,有一行设置配方成分网格的数据源……

this.dgvRecipeIngredients.DataSource = selectedIngredients;

// Comment out that line.


此时我们不需要设置网格数据源,因为列表是空的。单击添加按钮时,它将被设置。

请试试这个,如果有帮助请告诉我。

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