Search code examples
c#wpfdatagriditemssource

How to Bind ObservableCollection Class to DataGrin in WPF


All, I am creating a data set which is 'bound' to a DataGrid at run-time. I pull in some data that is used to build a class which is inheriting from ObservableCollection<T>. The class in question is

public class ResourceData : ObservableCollection<ResourceRow>
{
   public ResourceData(List<ResourceRow> resourceRowList) : base()
   {
      foreach (ResourceRow row in resourceRowList)
         Add(row);
   }
}

public class ResourceRow
{
   private string keyIndex;
   private string fileName;
   private string resourceName;
   private List<string> resourceStringList;

   public string KeyIndex
   {
      get { return keyIndex; }
      set { keyIndex = value; }
   }

   public string FileName
   {
      get { return fileName; }
      set { fileName = value; }
   }

   public string ResourceName
   {
      get { return resourceName; }
      set { resourceName = value; }
   }

   public List<string> ResourceStringList
   {
      get { return resourceStringList; }
      set { resourceStringList = value; }
   }
}

I return a ResourceData object from a method called BuildDataGrid(), defined as

private ResourceData BuildDataGrid(Dictionary<string, string> cultureDict)
{
    // ...
}

I then set the DataGrid's ItemSource to this ResourceData object via

dataGrid.ItemSource = BuiltDataGrid(cultureDictionary);

however, this is not correctly expanding the ResourceStringList within ResourceRow. I get displayed:

The DataGrid

My question is: How can I amend my ResourceRow class to allow the DataGrid to automatically read and display the contents of a ResourceData object? I want to display each item in the ResourceStringList in a separate column.

Thanks for your time.


Solution

  • Here's my solution - I changed up the control, rather than the ResourceRow but I think it achieves what you're looking for.

    We just create a DataGrid with design-time columns in the xaml, and then add the run-time columns dynamically in the control's constructor. A couple things to keep in mind - we're assuming the first row of the ResourceData is a good model for all of the rows since this is the one we use to determine the columns to add to the datagrid in the constructor. Second, Since ResourceRow does not implement INotifyPropertyChanged, we won't get updated values for the columns that come from the ResourceStringList - but that can easily be changed.

    The Code-behind:

    namespace WpfApplication
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                ResourceData data = GetData();
                _dataGrid.ItemsSource = data;
    
                for (int i = 0; i < data[0].ResourceStringList.Count; i++)
                {
                    DataGridTextColumn column = new DataGridTextColumn();
                    column.Binding = new Binding(string.Format("ResourceStringList[{0}]", i));
                    column.Header = string.Format("dynamic column {0}", i);
                    _dataGrid.Columns.Add(column);
                }
            }
    
            public ResourceData GetData()
            {
                List<ResourceRow> rows = new List<ResourceRow>();
    
                for (int i = 0; i < 5; i++)
                {
                    rows.Add(new ResourceRow() { KeyIndex = i.ToString(), FileName = string.Format("File {0}", i), ResourceName = string.Format("Name {0}", i), ResourceStringList = new List<string>() { "first", "second", "third" } });
                }
                return new ResourceData(rows);
            }
        }
    
        public class ResourceData : ObservableCollection<ResourceRow>
        {
            public ResourceData(List<ResourceRow> resourceRowList)
                : base()
            {
                foreach (ResourceRow row in resourceRowList)
                    Add(row);
            }
        }
    
        public class ResourceRow
        {
            private string keyIndex;
            private string fileName;
            private string resourceName;
            private List<string> resourceStringList;
    
            public string KeyIndex
            {
                get { return keyIndex; }
                set { keyIndex = value; }
            }
    
            public string FileName
            {
                get { return fileName; }
                set { fileName = value; }
            }
    
            public string ResourceName
            {
                get { return resourceName; }
                set { resourceName = value; }
            }
    
            public List<string> ResourceStringList
            {
                get { return resourceStringList; }
                set { resourceStringList = value; }
            }
        }
    }
    

    The xaml:

    <Window x:Class="WpfApplication.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <DataGrid x:Name="_dataGrid" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding KeyIndex}" Header="Key Index"/>
                    <DataGridTextColumn Binding="{Binding FileName}" Header="File Name"/>
                    <DataGridTextColumn Binding="{Binding ResourceName}" Header="Resource Name"/>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>