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:
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?