wpfdata-bindingdatatabledatagrid

Binding DataTable with CustomClass in Cell to the DataGrid WPF


I have DataGrid.

    <DataGrid ItemsSource="{Binding DataView}" x:Name="AttributeGrid"
              AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" EnableColumnVirtualization="False" EnableRowVirtualization="False"  
              DataContextChanged="AttributeGrid_DataContextChanged">
    </DataGrid>

For the Cells of that DataGrid I have Custom Style

    <Style x:Key="DataGridDataViewCustomCellStyle" TargetType="{x:Type DataGridCell}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="DataGridCell">
                    <Grid>
                        <ComboBox x:Name="ComboBoxCondition" IsEditable="True" ItemsSource="{Binding Values}" Visibility="Collapsed"
                                  SelectedValue="{Binding SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                  SelectedValuePath="ValueID" 
                                  DisplayMemberPath="ValueStr"/>
                        <TextBox x:Name="TextBoxCondition" Text="{Binding TextValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="True" Visibility="Collapsed"/>
                        <DatePicker x:Name="DateCondition" 
                                    Text="{Binding DateValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                    IsEnabled="True" Visibility="Collapsed"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <DataTrigger Binding="{Binding ValueType}" Value="Text">
                            <Setter Property="Visibility" TargetName="TextBoxCondition" Value="Visible"/>
                            <Setter Property="Visibility" TargetName="ComboBoxCondition" Value="Hidden"/>
                            <Setter Property="Visibility" TargetName="DateCondition" Value="Hidden"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding ValueType}" Value="Combobox">
                            <Setter Property="Visibility" TargetName="TextBoxCondition" Value="Hidden"/>
                            <Setter Property="Visibility" TargetName="ComboBoxCondition" Value="Visible"/>
                            <Setter Property="Visibility" TargetName="DateCondition" Value="Hidden"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding ValueType}" Value="Date">
                            <Setter Property="Visibility" TargetName="TextBoxCondition" Value="Hidden"/>
                            <Setter Property="Visibility" TargetName="ComboBoxCondition" Value="Hidden"/>
                            <Setter Property="Visibility" TargetName="DateCondition" Value="Visible"/>
                        </DataTrigger>

                        <DataTrigger Binding="{Binding IsModified, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
                            <Setter Property="Background" TargetName="TextBoxCondition" Value="Cornsilk"/>
                            <Setter Property="Background" TargetName="ComboBoxCondition" Value="Cornsilk"/>
                            <Setter Property="Background" TargetName="DateCondition" Value="Cornsilk"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding IsModified, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Value="False">
                            <Setter Property="Background" TargetName="TextBoxCondition" Value="White"/>
                            <Setter Property="Background" TargetName="ComboBoxCondition" Value="White"/>
                            <Setter Property="Background" TargetName="DateCondition" Value="White"/>
                        </DataTrigger>

                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

if DataView is based on List<object>

gridAttributes = new List<AttributeItem>();

gridAttributes add some data .....

DataView = CollectionViewSource.GetDefaultView(gridAttributes);

everything works as expected. In DataGridCell I have objects of AttributeIem and can Style it like I need.

But, when DataView is based on DataTable, then in DataGridCell, for one reason or another I am getting DataRowView, and then Style not working and everything goes wrong.

RunDataTable.Rows.Clear();
RunDataTable.Columns.Clear();

RunDataTable.Columns.Add("SectionName");
RunDataTable.Columns.Add("RunName");

foreach (AttributeItem attr in gridAttributes)
{
   RunDataTable.Columns.Add(attr.AttributeName, typeof(AttributeItem));
}
DataRow row = RunDataTable.NewRow();
row["SectionName"] = section;
row["RunName"] = name;
foreach (AttributeItem item in gridAttributes)
{
  row[item.AttributeName] = item;
}
RunDataTable.Rows.Add(row);

DataView = new DataView(RunDataTable);

Here is the piece of Error binding lists that I am getting

System.Windows.Data Error: 40 : BindingExpression path error: 'IsModified' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=IsModified; DataItem='DataRowView' (HashCode=47477033); target element is 'DataGridCell' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Error: 40 : BindingExpression path error: 'Values' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=Values; DataItem='DataRowView' (HashCode=47477033); target element is 'ComboBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedValue' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=SelectedValue; DataItem='DataRowView' (HashCode=47477033); target element is 'ComboBox' (Name=''); target property is 'SelectedValue' (type 'Object')
System.Windows.Data Error: 40 : BindingExpression path error: 'TextValue' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=TextValue; DataItem='DataRowView' (HashCode=47477033); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'DateValue' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=DateValue; DataItem='DataRowView' (HashCode=47477033); target element is 'DatePicker' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'ValueType' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=ValueType; DataItem='DataRowView' (HashCode=47477033); target element is 'DataGridCell' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Error: 40 : BindingExpression path error: 'ValueType' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=ValueType; DataItem='DataRowView' (HashCode=47477033); target element is 'DataGridCell' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Error: 40 : BindingExpression path error: 'ValueType' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=ValueType; DataItem='DataRowView' (HashCode=47477033); target element is 'DataGridCell' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Error: 40 : BindingExpression path error: 'IsModified' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=IsModified; DataItem='DataRowView' (HashCode=47477033); target element is 'DataGridCell' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Error: 40 : BindingExpression path error: 'IsModified' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=IsModified; DataItem='DataRowView' (HashCode=47477033); target element is 'DataGridCell' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Error: 40 : BindingExpression path error: 'Values' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=Values; DataItem='DataRowView' (HashCode=47477033); target element is 'ComboBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedValue' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=SelectedValue; DataItem='DataRowView' (HashCode=47477033); target element is 'ComboBox' (Name=''); target property is 'SelectedValue' (type 'Object')
System.Windows.Data Error: 40 : BindingExpression path error: 'TextValue' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=TextValue; DataItem='DataRowView' (HashCode=47477033); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'DateValue' property not found on 'object' ''DataRowView' (HashCode=47477033)'. BindingExpression:Path=DateValue; DataItem='DataRowView' (HashCode=47477033); target element is 'DatePicker' (Name=''); target property is 'Text' (type 'String')

Any suggestions how can I either adapt my Style or code behind to get it working. Thanks.

I tried different ways of DataSources. But unfortunatly each time I am getting the same error

Adding Columns:

    public static void RefreshColumnsFromModel(this DataGrid datagrid, DataGridRuns gridModel)
    {
        var myResourceDictionary = new ResourceDictionary();
        myResourceDictionary.Source = new Uri("/SomeSource;component/Resources/SomeSource.xaml", UriKind.RelativeOrAbsolute);

        datagrid.Columns.Clear();

        foreach (ColumnWithFilter column in gridModel.Columns.OrderBy(c => c.Index))
        {
            if (column.IsReadOnly)
            {
                datagrid.Columns.Add(
                        new DataGridTextColumn()
                        {
                            Header = column.ColumnHeader,
                            IsReadOnly = column.IsReadOnly,
                            Binding = new Binding(column.ColumnBinding),
                            CellStyle = myResourceDictionary["DataGridReadOnlyCellStyle"] as Style,
                            HeaderTemplate = datagrid.Resources["HeaderTemplate"] as DataTemplate
                        }
                    );
            }
            else
            {
                datagrid.Columns.Add(
                        new DataGridTemplateColumn()
                        {
                            Header = column.ColumnHeader,
                            IsReadOnly = column.IsReadOnly,
                            //Binding = new Binding(column.ColumnBinding),
                            //HeaderTemplate = myResourceDictionary["HeaderTemplate"] as DataTemplate
                            HeaderTemplate = datagrid.Resources["HeaderTemplate"] as DataTemplate,
                            CellStyle = myResourceDictionary["DataGridDataViewCustomCellStyle"] as Style
                        }
                    );
            }
        }
    }

DataGridRuns represents the Model for DataGrid, some custom behavior for filtering and sorting

   public class DataGridRuns : BaseClass
   {
       private List<AttributeItem> gridAttributes = new List<AttributeItem>();
       public ObservableCollection<AttributeItem> GridAttributes
       {
           get { return new ObservableCollection<AttributeItem>(gridAttributes); }
           set { gridAttributes = value.ToList(); RaisePropertyChanged(); }
       }

       public DataTable RunDataTable { get; set; } = new DataTable("RunData");

       private DataView dataView = null;
       public DataView DataView
       {
           get { return dataView; }
       }

       private ColumnWithFilter selectedColumn = null;
       public ColumnWithFilter SelectedColumn
       {
           get { return selectedColumn; }
           set { selectedColumn = value; RaisePropertyChanged(); }
       }

       public Action RefreshColumns;

       private List<ColumnWithFilter> columns = new List<ColumnWithFilter>();
       public ObservableCollection<ColumnWithFilter> Columns
       {
           get { return new ObservableCollection<ColumnWithFilter>(columns); }
           set { columns = value.ToList(); RaisePropertyChanged(); }
       }

       public DataGridRuns()
       {

       }

       private List<AttributeItem> CreateAttributesFromInfo(IList<AttributeInfo> _attributes)
       {

           List<AttributeItem> result = new List<AttributeItem>();

           try
           {
               foreach (var attr in _attributes)
               {
                   result.Add(new AttributeItem(attr));
               }
           }
           catch (Exception ex)
           {

           }

           return result;

       }
       public void AddRun(string section, string name, IList<AttributeInfo> _attributes, bool template = false)
       {
           var attr = CreateAttributesFromInfo(_attributes);
           if (gridAttributes == null || gridAttributes.Count() == 0)
           {
               gridAttributes = new List<AttributeItem>(attr);
               CreateColumns();
           }

           DataRow row = RunDataTable.NewRow();
           row["SectionName"] = section;
           row["RunName"] = name;
           foreach (AttributeItem item in gridAttributes)
           {
               row[item.AttributeName] = item;
           }
           RunDataTable.Rows.Add(row);

           dataView = new DataView(RunDataTable);
           //dataView = CollectionViewSource.GetDefaultView(RunDataTable);

       }

       public void ClearData()
       {
           gridAttributes.Clear();
           gridAttributes = new List<AttributeItem>();
       }

       public void CreateColumns()
       {
           RunDataTable.Rows.Clear();
           RunDataTable.Columns.Clear();

           columns = new List<ColumnWithFilter>();
           ColumnWithFilter col = null;

           col = new ColumnWithFilter("Section Name", nameof(AttributeItem.SectionName))
           {
               Index = 0,
               IsReadOnly = true,
           };
           columns.Add(col);
           RunDataTable.Columns.Add("SectionName");

           col = new ColumnWithFilter("Run Name", nameof(AttributeItem.RunName))
           {
               Index = 1,
               IsReadOnly = true,
           };
           columns.Add(col);
           RunDataTable.Columns.Add("RunName");

           int colIndex = 1;
           foreach (AttributeItem attr in gridAttributes)
           {
               col = new ColumnWithFilter(attr.AttributeName, nameof(attr))
               {
                   Index = ++colIndex,
                   IsReadOnly = false,
               };
               columns.Add(col);
               RunDataTable.Columns.Add(attr.AttributeName, typeof(AttributeItem));
           }
       }

       public void RefreshDataView()
       {
           foreach (DataRow item in RunDataTable.Rows)
           {
               item.ItemArray.ToList().ForEach(a => (a as AttributeItem)?.StartMarkingModifications());
           }

           RaisePropertyChanged(nameof(DataView));
       }

       public void RefreshPopupContent(string header = "")
       {
           selectedColumn = columns.Where(c => c.ColumnHeader.Equals(header)).FirstOrDefault();

           if (selectedColumn == null) return;

           selectedColumn.RefreshFilterValues(gridAttributes
               .Where(a => !string.IsNullOrEmpty((string)GetPropertyValue(a, selectedColumn.ColumnBinding)))
               .Select(a => (string)GetPropertyValue(a, selectedColumn.ColumnBinding)).Distinct().ToList());

           RaisePropertyChanged(nameof(SelectedColumn));
       }

       public void Filtering()
       {
           UpdateDataViewRowFilter();
       }


       private void UpdateDataViewRowFilter()
       {
           string rowFilter = string.Empty;
           foreach (ColumnWithFilter column in columns.Where(c => c.FilterIndex > 0))
           {
               string andStr = !string.IsNullOrEmpty(rowFilter) ? " AND " : "";
               switch (column.FilterIndex)
               {
                   case 1:
                       rowFilter += andStr + $"{column.ColumnBinding} IS NULL";
                       break;
                   default:
                       rowFilter += andStr + $"{column.ColumnBinding} = '{column.FilteredValue}'";
                       //filterResult = ((string)GetPropertyValue(attribute, column.ColumnBinding)).Equals(column.FilteredValue);
                       break;
               }
           }
           (dataView as DataView).RowFilter = rowFilter;
       }

       public object GetPropertyValue(object obj, string propertyName)
       {
           return obj.GetType().GetProperty(propertyName)?.GetValue(obj, null);
       }
   }

This is what I see in DataTable and I expect the same to be in DataGrid Screenshot from DataTable Visualizer

Add this is what I see in UI Custom Grid with Column Filtering


Solution

  • I'm not sure I understood all your code correctly. I will offer a solution based on what I could understand.
    In the DataTable cells you have the type "AttributeItem". A binding with path "column.ColumnBinding" will return an instance of that type from the corresponding cell. The ValueType and IsModified properties (used in the ControlTemplate) belong to this instance.

    In this case, in my opinion, you are using the DataGridCell style change incorrectly. Instead, you need to create a data template for the cell's contents.

    <DataTemplate x:Key="DataGridDataViewCustomCellDataTemplate">
        <Grid>
            <ComboBox x:Name="ComboBoxCondition" IsEditable="True" ItemsSource="{Binding Values}" Visibility="Collapsed"
                                    SelectedValue="{Binding SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                    SelectedValuePath="ValueID" 
                                    DisplayMemberPath="ValueStr"/>
            <TextBox x:Name="TextBoxCondition" Text="{Binding TextValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="True" Visibility="Collapsed"/>
            <DatePicker x:Name="DateCondition" 
                                    Text="{Binding DateValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                    IsEnabled="True" Visibility="Collapsed"/>
        </Grid>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding ValueType}" Value="Text">
                <Setter Property="Visibility" TargetName="TextBoxCondition" Value="Visible"/>
                <Setter Property="Visibility" TargetName="ComboBoxCondition" Value="Hidden"/>
                <Setter Property="Visibility" TargetName="DateCondition" Value="Hidden"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding ValueType}" Value="Combobox">
                <Setter Property="Visibility" TargetName="TextBoxCondition" Value="Hidden"/>
                <Setter Property="Visibility" TargetName="ComboBoxCondition" Value="Visible"/>
                <Setter Property="Visibility" TargetName="DateCondition" Value="Hidden"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding ValueType}" Value="Date">
                <Setter Property="Visibility" TargetName="TextBoxCondition" Value="Hidden"/>
                <Setter Property="Visibility" TargetName="ComboBoxCondition" Value="Hidden"/>
                <Setter Property="Visibility" TargetName="DateCondition" Value="Visible"/>
            </DataTrigger>
    
            <DataTrigger Binding="{Binding IsModified, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
                <Setter Property="Background" TargetName="TextBoxCondition" Value="Cornsilk"/>
                <Setter Property="Background" TargetName="ComboBoxCondition" Value="Cornsilk"/>
                <Setter Property="Background" TargetName="DateCondition" Value="Cornsilk"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding IsModified, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Value="False">
                <Setter Property="Background" TargetName="TextBoxCondition" Value="White"/>
                <Setter Property="Background" TargetName="ComboBoxCondition" Value="White"/>
                <Setter Property="Background" TargetName="DateCondition" Value="White"/>
            </DataTrigger>
    
        </DataTemplate.Triggers>
    </DataTemplate>
    

    In Template DataGridTemplateColumn you need to create a template with a ContentControl that will accept a binding with the path "column.ColumnBinding" in the Content property and the template described above in the ContentTemplate property.

            private const string templateString = @"
        <DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
            <ContentControl
                Content=""{{Binding {0}}}""
                ContentTemplate=""{{DynamicResource DataGridDataViewCustomCellDataTemplate}}""/>
        </DataTemplate>
    ";
            public static void RefreshColumnsFromModel(this DataGrid datagrid, DataGridRuns gridModel)
            {
                var myResourceDictionary = new ResourceDictionary();
                myResourceDictionary.Source = new Uri("/SomeSource;component/Resources/SomeSource.xaml", UriKind.RelativeOrAbsolute);
    
                datagrid.Columns.Clear();
    
                foreach (ColumnWithFilter column in gridModel.Columns.OrderBy(c => c.Index))
                {
                    if (column.IsReadOnly)
                    {
                        datagrid.Columns.Add(
                                new DataGridTextColumn()
                                {
                                    Header = column.ColumnHeader,
                                    IsReadOnly = column.IsReadOnly,
                                    Binding = new Binding(column.ColumnBinding),
                                    CellStyle = myResourceDictionary["DataGridReadOnlyCellStyle"] as Style,
                                    HeaderTemplate = datagrid.Resources["HeaderTemplate"] as DataTemplate
                                }
                            );
                    }
                    else
                    {
                        string templateSource = string.Format(templateString, column.ColumnBinding);
                        DataTemplate template = (DataTemplate) XamlReader.Parse(templateSource);
                        datagrid.Columns.Add(
                                new DataGridTemplateColumn()
                                {
                                    Header = column.ColumnHeader,
                                    IsReadOnly = column.IsReadOnly,
                                    //Binding = new Binding(column.ColumnBinding),
                                    //HeaderTemplate = myResourceDictionary["HeaderTemplate"] as DataTemplate
                                    HeaderTemplate = datagrid.Resources["HeaderTemplate"] as DataTemplate,
                                    CellTemplate = template
                                }
                            );
                    }
                }
            }
    

    Unfortunately, I cannot test this code. But if you provide minimal code that reproduces these circumstances, I can conduct a test and prepare a guaranteed working version.