1

I am successfully binding to a list of objects and setting this as the DataGridViews datasource. Definiting columns, at run time, which include the appropriate DataPropertyNames.

However I now need to add a list to my object class. The size of this list is dynamic, but always the same size for all instances of my object.

So my question is how can I create my DataGridViewTextBoxColumn to create a column for each items within this list?

Below is my object code, which has been simplified for this question. Within the languages Dictionary will be something like:

  • "English", "Hello"
  • "German", "Hallo"
  • "Spanish", "Hola"

Ideally the Key would appear as the column name.

Looking like this (each row is a StringItem):

Example

public class StringItem
{
    #region Attributes ########################################################################
    string name;    // String name, used to generate the enum for referencing
    string comment; // Helpful description of the string item.
    Dictionary<string, string> languages = new Dictionary<string, string>(); // For language strings.
    #endregion

    #region Public Functions ##################################################################
    public string Name
    {
        get { return name; }
    }

    public string Comment
    {
        get { return comment; }
        set { comment = value; }
    }

    public Dictionary<string, string> Languages
    {
        get { return languages; }
        set { languages = value; }
    }
    #endregion
}

Update: I believe the suggested link in the comments isn't trying to achieve quite the same thing, however it is useful.

I can see that by adding the follow code to my StringItem I can directly access the language dictionary doing myObj["English"]

    public string this[string key]
    {
        get
        {
            return languages[key];
        }
        set
        {
            languages[key] = value;
        }
    }

However the DataPropertyName, for each column, doesn't quite work liek this. I assume it uses reflections? Can anyone confirm this and tell me if I can implement my own reflection, or whatever DataPropertyName is using, to get my dictionary item.

This is how I set up the columns:

        DataGridViewColumn column = new DataGridViewTextBoxColumn();
        column.DataPropertyName = "Name";
        column.Name = "Name";
        dgvStrings.Columns.Add(column);

        foreach (string lang in ProjectSettings.Languages)
        {
            column = new DataGridViewTextBoxColumn();
            column.DataPropertyName = lang; // <<<< THIS ISN'T WORKING.
            column.Name = lang;
            dgvStrings.Columns.Add(column);
        }
Reza Aghaei
  • 103,774
  • 12
  • 145
  • 300
TheGrovesy
  • 131
  • 2
  • 12
  • Possible duplicate of [Binding DataGrid to ObservableCollection](http://stackoverflow.com/questions/14171098/binding-datagrid-to-observablecollectiondictionary) – lokusking Mar 08 '17 at 12:57
  • Yeah this looks interesting. I have now implemented in the StringItem class an accessor which uses the key 'public string this[string key]' and then when creating the columns put a foreach loop to create a column for each language and setting the DataPropertyName to the language key. But this isn't actually getting, or setting the values! Would be because I have implemented the TryGetMember & TrySetMember functions? – TheGrovesy Mar 08 '17 at 14:09
  • @lokusking As the linked code suggests I have updated my StringItem to inherit from DynamicObject and then override the `TryGetMember` and `TrySetMember` so that when the row requests a language, as defined by DataPropertyName of the column I can return the Dictionary entry. However putting a break point in the TryGetMember function it never gets hit! Is that what DataPropertyName should trigger? – TheGrovesy Mar 08 '17 at 16:10
  • @TheGrovesy I know the question is old, but just as a record for future readers and also for your future projects, you can use shape your class to a `DataTable` at run-time and after editing, revert it back to `List`. I posted the code to perform such conversion. – Reza Aghaei Sep 09 '17 at 20:24

2 Answers2

0

You could iterate through your list and add a ColumnHeader for each item.

Example:

foreach ( var item in list )
{
    someDataGridView.Columns.Add(item + "ColumnHeader", item);
}

Explanation:
You can add columns programmatically. The first argument being the Column name, item + "ColumnHeader" in this case and the 2nd is Column text eg. displayed text, in this case item so if it was German that would be the header.

EpicKip
  • 3,645
  • 1
  • 17
  • 35
0

You can create a DataTable containing columns for your class properties and languages, then after editing the data table in DataGridView, revert it back to List<StringItem>.

To do so, I suppose you have StringItem. I just refactored your code to make it more clean:

public class StringItem
{
    public static string[] LanguageNames
    {
        get { return new[] { "English", "German", "Spanish" }; }
    }
    public StringItem(string name)
    {
        Name = name;
        Languages = new Dictionary<string, string>();
        LanguageNames.ToList().ForEach(x => Languages.Add(x, null));
    }
    public string Name { get; private set; }
    public string Comment { get; set; }
    public Dictionary<string, string> Languages { get; set; }
}

Then create methods to convert List<StringItem> to a DataTable and vice versa:

public static class StringItemExtensions
{
    public static DataTable ToDataTable(this List<StringItem> list)
    {
        var dt = new DataTable();
        dt.Columns.Add("Name").ReadOnly = true;
        dt.PrimaryKey = new DataColumn[] { dt.Columns["Name"] };
        dt.Columns.Add("Comment");
        StringItem.LanguageNames.ToList().ForEach(x => dt.Columns.Add(x));
        list.ForEach(item =>
        {
            var values = new List<object>();
            values.Add(item.Name);
            values.Add(item.Comment);
            values.AddRange(item.Languages.Values.Cast<string>());
            dt.Rows.Add(values.ToArray());
        });
        return dt;
    }
    public static List<StringItem> ToStringItemList(this DataTable table)
    {
        return table.AsEnumerable().Select(row =>
        {
            var item = new StringItem(row.Field<string>("Name"));
            foreach (var lang in StringItem.LanguageNames)
                item.Languages[lang] = row.Field<string>(lang);
            return item;
        }).ToList();
    }
}

Now you can edit a List<StringItem> in DataGridView:

var list = new List<StringItem>();
list.Add(new StringItem("Key1"));
list.Add(new StringItem("Key2"));
list.Add(new StringItem("Key3"));
this.dataGridView1.DataSource = list.ToDataTable();

After finish editing, it's enough to export the data table to a List<StringItem>:

var list = ((DataTable)this.dataGridView1.DataSource).ToStringItemList();
Reza Aghaei
  • 103,774
  • 12
  • 145
  • 300