Search code examples
.netwpfxamlmvvmwpfdatagrid

One ObservableCollection per DataGridColumn in WPF DataGrid


Developing a WPF app using MVVMLight.

I'm trying to bind each DataGridColumn of a DataGrid(with AutoGenerateColumns set to False) to one ObservableCollection<string> per column. I've already asked something similar and received a very good answer but the problem with that approach is that items get added in the DataGrid one row at a time. That approach also introduces a new Model class which makes it a problem for TwoWay binding with the actual Model class I need to keep in sync with my View.

My question is this: Assuming that I have several ObservableCollection<string> of the exact same count/length, is it possible to bind one ObservableCollection<string> per column? My approach so far has been this:

  • create an ObservableCollection<ObservableCollection<string>> (let's name it GridColumns) property on my ViewModel containing each ObservableCollections<string> I need
  • bind the ItemsSource of my DataGrid to GridColumns
  • set the binding of each DataGridColumn to each ObservableCollection<string> in GridColumns

I can't seem to get it to work though. Am I totally off with this approach?

EDIT:

Here is what I came up with:

1) Created a Model class which exposes ObservableCollection properties

 public class AttributeListItems : ObservableObject
    {
        private ObservableCollection<string> category;
        public ObservableCollection<string> Category
        {
            get { return category; }
            set
            {
                if (category == value)
                {
                    return;
                }
                category = value;
                RaisePropertyChanged(() => Category);
            }
        }
        private ObservableCollection<string> fileValue;
        public ObservableCollection<string> FileValue
        {
            get { return fileValue; }
            set
            {
                if (fileValue == value)
                {
                    return;
            }
                fileValue = value;
                RaisePropertyChanged(() => FileValue);
            }
        }
    }

2) Created a property of ObservableCollection<AttributeListItems> on my ViewModel:

    private ObservableCollection<AttributeListItems> gridItems;
    public ObservableCollection<AttributeListItems> GridItems
    {
        get { return gridItems;  }
        set
        {
            if (gridItems == value)
            { return; }
            gridItems = value;
            RaisePropertyChanged(() => GridItems);
        }
    }

3) On the setter of my SelectedAttribute property of my ViewModel (which is when I want GridItems to be repopulated) I added this:

            gridItems.Clear();
            gridItems.Add(new AttributeListItems { 
                Category = selectedAttribute.Categories, 
                FileValue = selectedAttribute.FileValues } 
                );
            RaisePropertyChanged(() => GridItems);

4) And on my View I set the ItemsSource of the DataGrid to GridItems and the binding of it's column to Category, as such:

<DataGrid Grid.Row="0" Grid.ColumnSpan="2" Margin="5" AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" EnableColumnVirtualization="True" CanUserReorderColumns="False" CanUserSortColumns="False" VerticalScrollBarVisibility="Visible" >
                <DataGrid.Columns>
                    <DataGridTextColumn Header="categories" Width="auto" Binding="{Binding Category}" />
                    <!--<DataGridTextColumn Header="file values" Width="auto" Binding="{Binding FileValue}" />-->
                </DataGrid.Columns>
</DataGrid>

When I run this, the DataGrid only shows one row in the column, it's text being "(Collection)". I tried changing the binding of the column from Category to Category/, which partially works: it only shows the first value of the Category collection instead of all of them, which is where I'm stuck at the moment.


Solution

  • Firstly, you need a

    class RowItem
    {
        bool IsChecked;
        string Category;
        string FileValue;
    }
    

    Then you populate a Collection of RowItem s where ever you need it, by something like

    GridData = new ObservableCollection<RowItem>();
    

    And finally in the XAML,

    <DataGrid Grid.Row="0" Grid.ColumnSpan="2" Margin="5" AutoGenerateColumns="False" ItemsSource="{Binding GridData}" EnableColumnVirtualization="True" CanUserReorderColumns="False" CanUserSortColumns="False" VerticalScrollBarVisibility="Visible" >
                    <DataGrid.Columns>
                        <DataGridCheckBoxColumn Header="Is Checked" Width="auto" IsChecked="{Binding IsChecked}" />
                        <DataGridTextColumn Header="Categories" Width="auto" Binding="{Binding Category}" />
                        <DataGridTextColumn Header="File values" Width="auto" Binding="{Binding FileValue}" />
                    </DataGrid.Columns>
    </DataGrid>
    

    Also, it seems like you have selectedAttribute.Categories and selectedAttribute.FileValues collections as the data source. Assuming that they have the same length the GridData can be populated by:

    var rowItems = new List<RowItem>();
    
    for( int i = 0; i < selectedAttribute.Categories.Count; i++ )
    {
        var row = new RowItem 
        { 
            Category =  selectedAttribute.Categories[i],
            FileValues =  selectedAttribute.FileValues[i],
        };
        rowItems.Add(row);
    }
    
    GridData = new ObservableCollection<RowItem>(rowItems);