1

In my current WPF application, it's required to generate columns of a DataGrid dynamically in my ViewModel. For that I have used the approach with System.Windows.Interactivity and behaviors. Therefore I created ColumnsBehavior exactly like in this post:

Is it possible to get dynamic columns on wpf datagrid in mvvm pattern?

Everything worked fine, when I initialized the bounded list and generated the columns with the bindings. When I switched to ListBox to show a DataGrid in every ListBoxItem, everything worked great again. Now I want to add the information (model) to the bounded list at run time. When a ListBoxItem is selected, the corresponding DataGrid should be displayed. With a Button above the DataGrid I can pass sample data to the bounded list objects. When I hit the Button, the right amount of columns with the right headers are generated, but the rows only appears the next time, in which the ListBoxItem is visible again. This means the data is appearing as recently as I refresh the grid.

To bind the Botton Command property to the ViewModel I used the ActionCommand implementation:

RelayCommand

I am using the MVVM pattern.

This is my ViewModel:

    public class ViewModel
    {
        public ViewModel()
        {
            ListItems = new ObservableCollection<ListItemModel>();
            ListItems.Add(new ListItemModel()
            {
                IsSelected = true
            });
            ListItems.Add(new ListItemModel()
            {
                IsSelected = false
            });

            FillCommand = new RelayCommand(FillAction);
        }

        public ObservableCollection<ListItemModel> ListItems { get; set; }

        public ListItemModel SelectedListItem { get; set; }

        public ICommand FillCommand { get; set; }

        public void FillAction(object sender)
        {
            SelectedListItem.Entries = new ObservableCollection<Entry>()
            {
                new Entry()
                {
                    Cells = new ObservableCollection<Cell>()
                    {
                        new Cell() { Cond1 = 11, Value = 99 },
                        new Cell() { Cond1 = 22, Value = 99 },
                        new Cell() { Cond1 = 33, Value = 99 }
                    }
                },
                new Entry()
                {
                    Cells = new ObservableCollection<Cell>()
                    {
                        new Cell() { Cond1 = 11, Value = 99 },
                        new Cell() { Cond1 = 22, Value = 99 },
                        new Cell() { Cond1 = 33, Value = 99 }
                    },
                },
                new Entry()
                {
                    Cells = new ObservableCollection<Cell>()
                    {
                        new Cell() { Cond1 = 11, Value = 99 },
                        new Cell() { Cond1 = 22, Value = 99 },
                        new Cell() { Cond1 = 33, Value = 99 }
                    },
                }
            };

            SelectedListItem.GenerateGrid();
        }
    }

The property for holding the columns is in the ListItemModel. The object structure behind the ItemsSource of the DataGrid and the columns property you can see in the next code:

public class ListItemModel
    {
        public ListItemModel()
        {
            Entries = new ObservableCollection<Entry>();
            DataColumns = new ObservableCollection<DataGridColumn>();
        }

        public ObservableCollection<Entry> Entries { get; set; }

        public ObservableCollection<DataGridColumn> DataColumns { get; set; }

        public bool IsSelected { get; set; }

        public void GenerateGrid()
        {
            if (Entries.Count != 0)
            {
                var columns = Entries[0]?.Cells;

                for (int i = 0; i < columns.Count; i++)
                {
                    Binding b = new Binding(string.Format("Cells[{0}].Value", i));
                    DataGridTextColumn text = new DataGridTextColumn();
                    text.Header = columns[i].Cond1.ToString();
                    text.Binding = b;
                    DataColumns.Add(text);
                }
            }
        }
    }

    public class Entry
    {
        public ObservableCollection<Cell> Cells { get; set; }

        public Entry() { Cells = new ObservableCollection<Cell>(); }
    }

    public class Cell
    {
        public Cell() { }

        public int Cond1 { get; set; }

        public int Value { get; set; }
    }
}

For the view you need the namespace:

http://schemas.microsoft.com/expression/2010/interactivity

The following code sample shows the view.

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <ListBox ItemsSource="{Binding ListItems}" SelectedItem="{Binding SelectedListItem}">
            <ListBox.Resources>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                </Style>
            </ListBox.Resources>
        </ListBox>
        <DockPanel Grid.Column="1">
            <Button DockPanel.Dock="Top" Command="{Binding FillCommand}">Fill Data</Button>
            <DataGrid x:Name="ToleranceGrid" ColumnWidth="*" ItemsSource="{Binding Path=SelectedListItem.Entries}"
                      CanUserAddRows="False" SelectionMode="Single" SelectionUnit="Cell" AutoGenerateColumns="False" local:ColumnsBindingBehaviour.BindableColumns="{Binding Path=SelectedListItem.DataColumns}">
            </DataGrid>
        </DockPanel>
    </Grid>

The ViewModel is set to the DataContext of the view in code behind.

The result should look like this: WPF Application

The first ListItemModel is selected and I pressed the Button above the DataGrid. As you can see, the columns were generated, but the rows aren't displayed. I debugged the code, an everything is correctly set in my ViewModel. When I would select the second ListItemModel and would go back to the first one, the content will be shown correctly.

Do you have any ideas?

dodo978
  • 27
  • 4

1 Answers1

0

Your ListItemModel should implement the INotifyPropertyChanged interface and raise the PropertyChanged event when the Entries property is set to a new collection:

public class ListItemModel : INotifyPropertyChanged
{
    public ListItemModel()
    {
        Entries = new ObservableCollection<Entry>();
        DataColumns = new ObservableCollection<DataGridColumn>();
    }

    private ObservableCollection<Entry> _entries;
    public ObservableCollection<Entry> Entries
    {
        get { return _entries; }
        set { _entries = value; NotifyPropertyChanged(); }
    }

    public ObservableCollection<DataGridColumn> DataColumns { get; set; }

    public bool IsSelected { get; set; }

    public void GenerateGrid()
    {
        if (Entries.Count != 0)
        {
            var columns = Entries[0]?.Cells;

            for (int i = 0; i < columns.Count; i++)
            {
                Binding b = new Binding(string.Format("Cells[{0}].Value", i));
                DataGridTextColumn text = new DataGridTextColumn();
                text.Header = columns[i].Cond1.ToString();
                text.Binding = b;
                DataColumns.Add(text);
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • I provided a very small example, in my real code, the properties are far more nested. I had to fire PropertyChanged inside every Setter in the chain to my row representing property. Thank you so much, this took me almost one week :) – dodo978 Mar 28 '19 at 13:51