I want to add a single row DataGrid
inside another DataGrid
s row details template.
This is my XAML:
<DataGrid x:Name="dataGrid" Margin="10,100,10,96" PreviewKeyDown="dataGrid_PreviewKeyDown" AutoGenerateColumns="False" ItemsSource="{Binding }" AlternatingRowBackground="AliceBlue" CellEditEnding="onCellEditEnding" SelectedItem="{Binding SelectedItem}" >
<DataGrid.Columns>
<DataGridTextColumn Width="*" Header="No" Binding= "{Binding Path=Id, Mode=TwoWay}" IsReadOnly="True" />
<DataGridTextColumn Width="*" Header="Barcode" Binding= "{Binding Path=Barcode, Mode=TwoWay}" />
<DataGridTextColumn Width="*" Header="Item" Binding= "{Binding Path=Item, Mode=TwoWay}" IsReadOnly="True"/>
<DataGridTextColumn Width="*" Header="Qty" Binding= "{Binding Path=Qty, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTextColumn Width="*" Header="Mrp" Binding= "{Binding Path=Mrp, Mode=TwoWay}" IsReadOnly="True"/>
<DataGridTextColumn Width="*" Header="Discount" Binding= "{Binding Path=Discount, Mode=TwoWay}" IsReadOnly="True"/>
<DataGridTextColumn Width="*" Header="Cgst" Binding= "{Binding Path=Cgst, Mode=TwoWay}" IsReadOnly="True"/>
<DataGridTextColumn Width="*" Header="Sgst" Binding= "{Binding Path=Sgst, Mode=TwoWay}" IsReadOnly="True"/>
<DataGridTextColumn Width="*" Header="Amount" Binding= "{Binding Path=Amount, Mode=OneWay ,UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True"/>
<DataGridTextColumn Width="*" Header="Salesman" Binding= "{Binding Path=Salesman, Mode=TwoWay}" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="X" Click="Button_Click"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate >
<DataTemplate>
<DataGrid x:Name="RowDetailsDataGrid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Width="*" Binding="{Binding Details}" />
<DataGridTextColumn Width="*" Binding="{Binding CgstDetails}" />
<DataGridTextColumn Width="*" Binding="{Binding SgstDetails}" />
<DataGridTextColumn Width="*" Binding="{Binding DiscountDetails}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
This is (roughly) my code-behind:
ObservableCollection<TableData> tableData = new ObservableCollection<TableData>();
public MainWindow()
{
InitializeComponent();
dataGrid.DataContext = tableData;
}
public class TableData : INotifyPropertyChanged
{
public int Id { get; set; }
public int No { get; set; }
public string Details { get; set; }
public string CgstDetails { get; set; }
public string SgstDetails { get; set; }
public string DiscountDetails { get; set; }
public string Barcode { get; set; }
public string Item { get; set; }
private double qty;
public double Qty {
get { return qty; }
set
{
qty = value;
OnPropertyChanged("Qty");
}
}
public double Mrp { get; set; }
public double Discount { get; set; }
public double Cgst { get; set; }
public double Sgst { get; set; }
private double amount; // field
public double Amount
{
get { return amount; }
set {
amount = (Qty * Mrp) ((Cgst / 100) * (Mrp * Qty)) ((Sgst / 100) * (Mrp * Qty));
OnPropertyChanged("Amount");
}
}
public int Salesman { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public bool RowAdded{ get; set; }
}
private void onCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
var rowFetched = new TableData()
{
Id = currentRowIndex 1,
No = currentRowIndex 1,
Barcode = item.Barcode,
Qty = item.Qty,
Mrp = item.Mrp,
Item = item.Item,
Discount = item.Discount,
Cgst = item.Cgst,
Sgst = item.Cgst,
Amount = item.Amount,
Salesman = item.Salesman,
Details = item.Details,
DiscountDetails = item.DiscountDetails,
CgstDetails = item.CgstDetails,
SgstDetails = item.SgstDetails,
RowAdded = true
};
tableData.Insert(currentRowIndex, rowFetched);
}
This is not working. Instead of a DataGrid
I have just put a TextBlock
inside WrapPanel
(see the code below) and the data-binding is working perfectly and the data is shown in the row details, but I want it to be a row (DataGrid
) instead of a WrapPanel
.
<DataGrid.RowDetailsTemplate >
<DataTemplate>
<WrapPanel Background="LightGray">
<Border BorderBrush="Black" BorderThickness="1" Width="355" Padding="40 0 0 0" >
<TextBlock FontSize="16" Foreground="MidnightBlue" Text="{Binding Details}" VerticalAlignment="Center" />
</Border>
<Border BorderBrush="Black" BorderThickness="1" Width="71">
<TextBlock FontSize="16" Foreground="MidnightBlue" Text="{Binding CgstDetails}" VerticalAlignment="Center" />
</Border>
<Border BorderBrush="Black" BorderThickness="1" Width="71">
<TextBlock FontSize="16" Foreground="MidnightBlue" Text="{Binding SgstDetails}" VerticalAlignment="Center" />
</Border>
<Border BorderBrush="Black" BorderThickness="1" Width="71">
<TextBlock FontSize="16" Foreground="MidnightBlue" Text="{Binding DiscountDetails}" VerticalAlignment="Center" />
</Border>
<Border BorderBrush="Black" BorderThickness="1" Width="188">
<TextBlock FontSize="16" Foreground="MidnightBlue" VerticalAlignment="Center" />
</Border>
</WrapPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
If anyone could help?
CodePudding user response:
I do not think that it is a good idea to put a nested DataGrid
into the row details template just to display a single row, but if that is your only option, you can do it as follows.
A DataGrid
expects an ItemsSource
of a collection type, more specifically an IEnumerable
. What you tried to do is bind properties on the DataContext
itself directly, which does not work. There are at least two options that you have to make it work, both of which essentially provide a collection of a single item as ItemsSource
to the nested DataGrid
.
Create a value converter that wraps your
TableData
item in a collection (here an array).public class ItemToCollectionConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value is null ? null : new[] { value }; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new InvalidOperationException(); } }
Create an instance of the converter in XAML (e.g. in your
DataGrid.Resources
) and simply bind the data context using the converter to theItemsSource
of the nestedDataGrid
. This way, yourTableData
item gets wrapped in a collection and is treated as a single item collection, so the column bindings will work correctly.<DataGrid x:Name="dataGrid" Margin="10,100,10,96" PreviewKeyDown="dataGrid_PreviewKeyDown" AutoGenerateColumns="False" ItemsSource="{Binding}" AlternatingRowBackground="AliceBlue" CellEditEnding="onCellEditEnding" SelectedItem="{Binding SelectedItem}" > <DataGrid.Resources> <local:ItemToCollectionConverter x:Key="ItemToCollectionConverter"/> </DataGrid.Resources> <DataGrid.Columns> <DataGridTextColumn Width="*" Header="No" Binding= "{Binding Path=Id, Mode=TwoWay}" IsReadOnly="True" /> <DataGridTextColumn Width="*" Header="Barcode" Binding= "{Binding Path=Barcode, Mode=TwoWay}" /> <DataGridTextColumn Width="*" Header="Item" Binding= "{Binding Path=Item, Mode=TwoWay}" IsReadOnly="True"/> <DataGridTextColumn Width="*" Header="Qty" Binding= "{Binding Path=Qty, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> <DataGridTextColumn Width="*" Header="Mrp" Binding= "{Binding Path=Mrp, Mode=TwoWay}" IsReadOnly="True"/> <DataGridTextColumn Width="*" Header="Discount" Binding= "{Binding Path=Discount, Mode=TwoWay}" IsReadOnly="True"/> <DataGridTextColumn Width="*" Header="Cgst" Binding= "{Binding Path=Cgst, Mode=TwoWay}" IsReadOnly="True"/> <DataGridTextColumn Width="*" Header="Sgst" Binding= "{Binding Path=Sgst, Mode=TwoWay}" IsReadOnly="True"/> <DataGridTextColumn Width="*" Header="Amount" Binding= "{Binding Path=Amount, Mode=OneWay ,UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True"/> <DataGridTextColumn Width="*" Header="Salesman" Binding= "{Binding Path=Salesman, Mode=TwoWay}" IsReadOnly="True"/> <DataGridTemplateColumn Header="Delete"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Button Content="X" Click="Button_Click"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> <DataGrid.RowDetailsTemplate> <DataTemplate> <DataGrid x:Name="RowDetailsDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding Converter={StaticResource ItemToCollectionConverter}}"> <DataGrid.Columns> <DataGridTextColumn Width="*" Binding="{Binding Details}" /> <DataGridTextColumn Width="*" Binding="{Binding CgstDetails}" /> <DataGridTextColumn Width="*" Binding="{Binding SgstDetails}" /> <DataGridTextColumn Width="*" Binding="{Binding DiscountDetails}"/> </DataGrid.Columns> </DataGrid> </DataTemplate> </DataGrid.RowDetailsTemplate> </DataGrid>
Expose a collection property from your
TableData
type that contains a single item of a sub type or helper type that only contains the details properties.public class TableDetailsData { public string Details { get; set; } public string CgstDetails { get; set; } public string SgstDetails { get; set; } public string DiscountDetails { get; set; } }
public class TableData : INotifyPropertyChanged { private TableDetailsData _detailsData; public TableDetailsData DetailsData { get => _detailsData; set { _detailsData = value; DetailsDataCollection = new List<TableDetailsData> { _detailsData }; OnPropertyChanged(nameof(DetailsDataCollection)); } } public IEnumerable<TableDetailsData> DetailsDataCollection { get; private set; } // ...other code. }
The
DetailsDataCollection
is always updated when theDetailsData
changes. Whether you create the collection like this and expose theDetailsData
as object or as properties is up to you, there are many possibilities and this is only to get an idea what to do. Now you can bind this collection asItemsSource
in the nestedDataGrid
.<DataGrid x:Name="RowDetailsDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding DetailsDataCollection}" CanUserAddRows="False">
Personally, I prefer the value converter solution as you do not have to expose a redundant collection property and pollute your code, as it is not a pretty clean solution. I would recommend you to create a proper details type and think about a better way to represent the data than a single row DataGrid
, which seems out of place and does not provide a good user experience in this scenario.