I have a datagrid from SCADA software that I am recreating in WPF by creating a custom user control that can be used for more use cases. I have an observable collection of type SettingRow that contains the data of one cell. The problem is that it is not containing the data of one cell.
If I have 10 items in my collection than I cannot see an seperate item in for example Row 1, Column 3. I see the same item in for example Row 1, Column 2. So in short: I see the same SettingRow object no matter what column I select in a row.
Every cell is an seperate setting object that is bound to an DataTemplateSelector to select the appropiate datatemplate to display the value (boolean as checkbox, string and value as textbox etc.)
I have created a DataGrid that has a templateselector based on the value in the SettingRow class. The datagrid's itemssource is currently bound to the ObservableCollection in my viewmodel. It works, I see every SettingRow in each cell per row, but I am quite sure that I am seeing the same SettingRow in each column which is not an seperate object that is independent of the object in the column before that one.
I've tried some solutions on StackOverflow with an observable collection inside an observable collection but I do not really get it to work.
Underneath is the class that is displayed in each row. Does this object get copied over to the next column? Or is the same object re-used in every column?
public class SettingRow : ObservableObject
{
public int ID { get; set; }
public object Value { get; set; }
public string Name { get; set; }
public string Unit { get; set; }
}
My datagrid in XAML is declared and only has the Name property. The rest are added through the code.
<DataGrid x:Name="OverviewDataGridView" ItemsSource="{Binding SettingsCollection, Mode=TwoWay}" AutoGenerateColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}">
</DataGridTextColumn>
</DataGrid.Columns>
Below I am creating a column per phase. The 4 phases are simulated since I do not have actual data. Now, every SettingRow should be individually editable and I need to retrieve it from the ViewModel.
This is in the code behind.
public void GenerateColumns()
{
//Simulate 4 phases and adding a column per phase!
for(int i = 0; i < 4; i++)
{
DataGridTemplateColumn templateCol = new DataGridTemplateColumn();
templateCol.Header = "Phase " + i;
TemplateSelector templateSel = new TemplateSelector();
templateSel.BooleanTemplate = this.Resources["BooleanTemplate"] as DataTemplate;
templateSel.TextTemplate = this.Resources["TextTemplate"] as DataTemplate;
templateCol.CellTemplateSelector = templateSel;
this.OverviewDataGridView.Columns.Add(templateCol);
}
}
The ViewModel is quite simple it creates some test data. Every SettingRow that is created is displayed in every column, but I think I see the same object in each
public class DynamicDataGridViewModel : ObservableObject
{
private ObservableCollection<SettingRow> settingsCollection;
public ObservableCollection<SettingRow> SettingsCollection
{
get { return settingsCollection; }
set
{
settingsCollection = value;
RaisePropertyChangedEvent("SettingsCollection");
}
}
public DynamicDataGridViewModel()
{
SettingsCollection = new ObservableCollection<SettingRow>();
SettingsCollection.Add(new SettingRow() { ID = 0, Name = "Phase Active", Value = true, Unit = "" });
SettingsCollection.Add(new SettingRow() { ID = 1, Name = "Minimum phase duration", Value = +900, Unit = "s" });
SettingsCollection.Add(new SettingRow() { ID = 2, Name = "Min Supply air temperature", Value = 50.0302, Unit = "C" });
}
}
And lastly, my DataTemplateSelector. It only handles the value of the SettingRow and display the correct symbol (boolean = checkbox etc.)
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate TextTemplate { get; set; }
public SettingRow Content { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ContentPresenter presenter = container as ContentPresenter;
DataGridCell cell = presenter.Parent as DataGridCell;
SettingRow row = (cell.DataContext as SettingRow);
if(row != null && row.Value != null)
{
this.Content = row;
switch (Type.GetTypeCode(row.Value.GetType()))
{
case TypeCode.Boolean:
return BooleanTemplate;
default:
return TextTemplate;
}
}
else
{
return null;
}
}
}
EDIT:
Templates for the columns below
<DataTemplate x:Key="BooleanTemplate">
<StackPanel Orientation="Horizontal" Margin="2">
<CheckBox IsChecked="{Binding Value}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="TextTemplate">
<StackPanel Orientation="Horizontal" Margin="2">
<TextBox Height="25" MinWidth="70" FontSize="14">
<TextBox.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="Value" Mode="TwoWay"/>
<Binding Path="Unit" Mode="TwoWay"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
</DataTemplate>
What I need is a DataGrid bound to an observable collection. Which has dynamic amount of columns (represents an phase) and I need one SettingRow in every column and it to be editable per object. So that I can for example set the visibility of the Phase Duration setting in the Levelling phase to invisible.
EDIT 2:
What I have in mind is to use a observable collection inside an observable collection like the snippet below this way I can represent my data as the columns in each row. This way I can access every row and column individual object.:
private ObservableCollection<ObservableCollection<SettingRow>> items;
public ObservableCollection<ObservableCollection<SettingRow>> ItemsSourceOfDataGrid
{
get { return items; }
set
{
items = value;
RaisePropertyChangedEvent("ItemsSourceOfDataGrid")
}
}
EDIT: So after looking over the comments and what you want, the EASIEST thing to do is to define all of your row names and then have the ObservableCollection
be a collection of columns instead of rows. If you want to go the crazy route of both dynamic rows and columns, well these can get you started:
https://svitla.com/blog/grid-with-dynamic-number-of-rows-and-columns-part-1 https://svitla.com/blog/grid-with-dynamic-number-of-rows-and-columns-part-2
The source Github: https://github.com/IReznykov/Blog
So, the way to do it with dynamic columns instead is pretty straight forward.
Just to reiterate:
Make sure your RaisePropertyChangedEvent
is added to the items in your model. This will allow them to be changed dynamically inside your SettingsCollection
. Also, the XAML portion of your DataGrid
needs to be setup in a certain way to work with this.
public class SettingColumn : ObservableObject
{
public int _PhaseNumber;
public int PhaseNumber
{
get { return _PhaseNumber; }
set
{
PhaseNumber = value;
RaisePropertyChangedEvent("PhaseNumber");
}
}
public object _MinPhaseDuration;
public object MinPhaseDuration
{
get { return _Value; }
set
{
Value = value;
RaisePropertyChangedEvent("Value");
}
}
public string _MinSupplyAirTemp;
public string MinSupplyAirTemp
{
get { return _MinSupplyAirTemp; }
set
{
MinSupplyAirTemp = value;
RaisePropertyChangedEvent("MinSupplyAirTemp");
}
}
// The rest of your row labels here.
.
.
.
Hope this edit helps get you going. Again, dynamic rows AND columns is a pain. Save yourself some headache and define either all your row labels or your column labels.