Question

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.

Was it helpful?

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>

OTHER TIPS

Your properties need to inherit from INotifyPropertyChanged, so they can update the UI, and so does your ViewModel too. Have your view model (or gasp! code behind) inherit from INotifyPropertyChanged. Implement the interface then you can do this for your properties.

    private string _keyIndex= string.Empty;
    public string KeyIndex
    {
        get { return this._keyIndex; }

       set
       {
            this._keyIndex= value;
            this.RaisePropertyChangedEvent("KeyIndex");
        }
   }

@Andrew's answer is correct, but I like simplicity and ease of use so I implemented a way for the columns to be automatically set up and bound to the datagrid with the names I wanted without have to use the datagrid columns.

Declare a column name class that inherits from attribute like so:

public class ColumnNameAttribute : Attribute
{
    public ColumnNameAttribute(string Name) { this.Name = Name; }
    public string Name { get; set; }

}

Decorate your model with the Column Name like so:

public class Data
  {
        [ColumnName("Name")]
        public string Name{ get; set; }
        [ColumnName("Customer")]
        public string Client { get; set; }
        [ColumnName("Activity Type")]
        public string Activity_Type { get; set; }
    }

Set AutoGenerateColumns to True and then add this event handler:

 private void gen_AutoGridColumns(object sender, DataGridAutoGeneratingColumnEventArgs e)
    {           
        var desc = e.PropertyDescriptor as PropertyDescriptor;
        if (desc.Attributes[typeof(ColumnNameAttribute)] is ColumnNameAttribute att) e.Column.Header = att.Name;

        //set the width for each column           
        e.Column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);            
    }

You now can skip adding the DataGridColumns definitions into the DataGrid and still have them named whatever you want.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top