views:

500

answers:

2

I'm trying to use DataGridView with a business object. Here's a simplified scenario: - call the object "Recipe", and it has a BindingList of a few Ingredient objects in the Recipe - I also have a "database", which is a BindingList of all available Ingredient objects

I would like to display a DataGridView of all the Ingredients in the Recipe, and allow the user to select new Ingredients from a combobox in the Name field.

When I try to set this up the way I think it should, the name of the Ingredients in the Recipe are not displayed, though I can select from the IngredientDB, and selecting a new Ingredient does update the list in the Recipe.

How do I get the name to display correctly?

Here's what I have so far:

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;

namespace WindowsFormsApplication1 {
    public class Ingredient {

        private String name;
        public String Name {
            get { return name; }
            set { name = value; }
        }

        private string description;
        public String Description {
            get { return description; }
            set { description = value; }
        }

        private int amount;
        public int Amount {
            get { return amount; }
            set { amount = value; }
        }

        public Ingredient(String n, String d, int a) {
            Name = n;
            Description = d;
            Amount = a;
        }

        public Ingredient() { }

    }

    public class Data {
        BindingList<Ingredient> ingredientDB = null;
        public BindingList<Ingredient> IngredientDB {
            get { return ingredientDB; }
        }

        public Data() {
            ingredientDB = new BindingList<Ingredient>();
            ingredientDB.Add(new Ingredient("rice", "a grain", 2));
            ingredientDB.Add(new Ingredient("whole wheat flour", "for baking", 1));
            ingredientDB.Add(new Ingredient("butter", "fatty", 3));
        }

    }

    public class Recipe : INotifyPropertyChanged {

        public String Name {
            get;
            set;
        }

        BindingList<Ingredient> ingredients = null;
        public BindingList<Ingredient> Ingredients {
            get { return ingredients; }
            set {
                ingredients = value;
                NotifyPropertyChanged("Ingredients");
            }
        }

        public Recipe() {
            Ingredients = new BindingList<Ingredient>();
            Ingredients.Add(new Ingredient("Water", "Wet", 2));
            Ingredients.Add(new Ingredient("Gin", "Yummy", 2));
        }


        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info) {
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion
    }

    public class myForm : Form {

        private DataGridView dgv;
        private BindingSource recipeBS;
        private BindingSource dataBS;
        public myForm() {
            this.Height = 200;
            this.Width = 400;
            Recipe myRecipe = new Recipe();
            Data myData = new Data();
            recipeBS = new BindingSource(myRecipe, null);
            dataBS = new BindingSource(myData, "IngredientDB");
            dgv = new DataGridView();
            dgv.Width = 400;
            dgv.DataError += new
            DataGridViewDataErrorEventHandler(DataGridView1_DataError);
            dgv.DataSource = myRecipe.Ingredients;
            // dgv.Columns.Remove("Name");
            DataGridViewComboBoxColumn comboboxColumn = new DataGridViewComboBoxColumn();
            comboboxColumn.DataPropertyName = "Name";
            comboboxColumn.HeaderText = "Name";
            comboboxColumn.DataSource = dataBS;
            comboboxColumn.DisplayMember = "Name";
            comboboxColumn.ValueMember = "Name";
            dgv.Columns.Insert(0, comboboxColumn);
            this.Controls.Add(dgv);
        }
        private void DataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs anError) { }

        [STAThreadAttribute()]
        static void Main() {
            Application.EnableVisualStyles();
            Application.Run(new myForm());
        }
    }


}
+1  A: 

The problem is that the DataGridViewComboBoxColumn is not editable, so it will only accept value from its data source.

So, you have 2 options here:

  1. The easy solution: make sure all possible ingredients for the recipe are set before creating the recipe and choosing them from the DataGridViewComboBoxColumn.

  2. The more difficult solution: when the user edits one ingredient of the recipe, handle this event and change the style of the underlying Control of the DataGridViewComboBoxColumn to DropDown. When the user is done editing, check to see if he entered a new value (not in the data source) and effectively add it to the data source (create a new ingredient). You can find information about implementing this approach here.

Julien Poulin
+1  A: 

You do not have the Water or Gin ingredients in your "ingredientDB" list. The DataGridView's data source is set to the ingredients list. When the form is loaded, it creates 3 columns automatically (Name, Description, Amount) based on the properties of your business object (i.e. Ingredient). Then the rows are added based on your Ingredients list ("Water" and "Gin"). So the net result is that you have 3 columns and 2 rows in your DataGridView and it looks like this:

Name  | Description | Amount
Water | Wet         | 2
Gin   | Yummy       | 2

However, you are inserting a DataGridViewComboBoxColumn into column 0 (i.e. the first column) and that's where the issue is.

The DataGridVidwComboBoxColumn's data source is set to your dataBs BindingSource which in turn is set to the "ingredientDB". The "ingredientDB" only contains entries for "rice", "whole wheat flour", and "butter". When the column is displayed for the first row, it looks for an Ingredient with Name == "Water" within its DataSource (the "ingredientDB"). This "ingredientDB" doesn't contain an Ingredient for Water and would normally raise a DataGridViewDataErrorEvent. However, you are handling the event yourself and not doing anything with it. Therefore, the ComboBox has nothing in it. The same thing happens for the "Gin" Ingredient. No "Gin" Ingredient can be found within your "ingredientDB" so it cannot be selected.

To fix the problem, you need to add both the "Water" and "Gin" to your "ingredientDB":

public Data()
{
    ingredientDB = new BindingList<Ingredient>();
    ingredientDB.Add(new Ingredient("rice", "a grain", 2));
    ingredientDB.Add(new Ingredient("whole wheat flour", "for baking", 1));
    ingredientDB.Add(new Ingredient("butter", "fatty", 3));
    ingredientDB.Add(new Ingredient("Water", "Wet", 2));
    ingredientDB.Add(new Ingredient("Gin", "Yummy", 2));
}
smoak