Search code examples
c#wpfxamlwpf-controlswpfdatagrid

Binding additional column with any control in datagrid WPF


I have one requirement to bind additional column either in “listview” or “datagrid” inside datagrid using WPF C#.

Basic datagrid I have as follows

<table border="1" width="100%">
    <tr>
    	<td>Item Id</td>
    	<td>Item Name</td>
    	<td>Quantity</td>
    	<td>Price</td>
    </tr>
    <tr>
    	<td>500</td>
    	<td>Sanwich</td>
    	<td>2</td>
    	<td>10</td>
    </tr>
</table>

For the above datagrid my XAML code as follows.

<DataGrid Grid.Column="1" Margin="8,60,8,16"AutoGenerateColumns="False" Height="350"
     HorizontalAlignment="Left" Name="gridOrderDetails" VerticalAlignment="Top" Width="450" >
    <DataGrid.Columns>
        <DataGridTextColumn Header="Item ID" Binding="{Binding itemId}" />                                        

        <DataGridTextColumn Header="Quantity" Binding="{Binding quantity}" />
        <DataGridTextColumn Header="Item Name" Binding="{Binding comments}" />
        <DataGridTextColumn Header="Item Price" Binding="{Binding ItemPrice}" />
    </DataGrid.Columns>
</DataGrid>

My code behind code as follows in button click event:

private void Button_Click(object sender, RoutedEventArgs e)
{
    try
    {
        DBDataContext dbContext = new DBDataContext();
        SP_Get_OrderItemResult orderRow = gridOrder.SelectedItem as SP_Get_OrderItemResult;

        gridOrderDetails.ItemsSource = dbContext.SP_Get_ItemList(orderRow.orderid);
    }
    catch (Exception Ex)
    {
        MessageBox.Show(Ex.Message);
        return;
    }
}

My desired output is as follows

<table border="1" width="100%">
    <tr>
    	<td>Item Id</td>
    	<td>Item Name</td>
    	<td>Additionals</td>
    	<td>Quantity</td>
    	<td>Price</td>
    </tr>
    <tr>
    	<td>500</td>
    	<td>Sanwich</td>
    	<td>Egg<p class="MsoNormal" style="margin-left:0in">Ketchup</p>
    	<p>
    	<span style="font-size: 11.0pt; line-height: 115%; font-family: Calibri,sans-serif">
    	Salad</span></td>
    	<td>2</td>
    	<td>10</td>
    </tr>
</table>

To achieve this datagrid I wrote my XAML code as follows by using DataGridTemplateColumn.

<DataGrid Grid.Column="1" Margin="8,60,8,16"AutoGenerateColumns="False" Height="350"
    HorizontalAlignment="Left" Name="gridOrderDetails" VerticalAlignment="Top" Width="450" LoadingRow="gridOrderDetails_LoadingRow">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Item ID" Binding="{Binding itemId}" />
        <DataGridTextColumn Header="Item Name" Binding="{Binding ItemName}" />
        <DataGridTemplateColumn Header="Addtionals">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <DataGrid AutoGenerateColumns="False" Name="gridAdditionals" >
                        <DataGridTextColumn Header="Additional" Binding="Additionals"></DataGridTextColumn>
                    </DataGrid>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>         
    <DataGridTextColumn Header="Quantity" Binding="{Binding quantity}" />
        <DataGridTextColumn Header="Comments" Binding="{Binding comments}" />
        <DataGridTextColumn Header="Item Price" Binding="{Binding ItemPrice}" />
    </DataGrid.Columns>
</DataGrid>

In code behind I tried with LoadingRow event but unable to pass corresponding ItemID from cell value.

private void gridOrderDetails_LoadingRow(object sender, DataGridRowEventArgs e)
{
    DCAPPDBDataContext dbContext = new DCAPPDBDataContext();

    DataGrid grid = FindChild<DataGrid>(gridOrderDetails, "gridAdditionals");
    grid.ItemsSource = dbContext.SP_Get_AdditionalsList(500);
} 

I guess the logic I am using is wrong. This can be achieved using ItemDataBound in Asp.net C#. Can anyone help me out how to do this in WPF C#.


Solution

  • When using WPF it's appropriate to follow the MVVM pattern. Either way, you should define a proper class (i.e. view-model) for your Order (containing properties such as Id, Name, Quantity, etc ...). This class should also has a property for your Aditionals (e.g. a collection of strings). Then by using proper DataBindings you'll achieve your desired functionality.

    This is a working example to give you an idea:

    Order class:

    public class Order : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
    
        void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    
        public Order(int id)
        {
            DBDataContext dbContext = new DBDataContext();
            DCAPPDBDataContext dbContext2 = new DCAPPDBDataContext();
    
            var itemsSource = dbContext.SP_Get_ItemList(id);
            var additionals = dbContext2.SP_Get_AdditionalsList(id);
    
            Id = id;
            Name = itemsSource.name;
            Quantity = itemsSource.quantity;
            // other properties ...
    
            Additionals = new ObservableCollection<string>();
            foreach (var item in additionals)
            {
                Additionals.Add(item);
            }
        }
    
        private int _id;
        public int Id
        {
            get
            {
                return _id;
            }
            set
            {
                _id = value;
                OnPropertyChanged("Id");
            }
        }
    
        // define other properties e.g. Name, Quantity, Price, etc.
        // the same way as Id.
    
        public ObservableCollection<string> Additionals {get;set;}
    }
    

    Xaml:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <DataGrid Grid.Row="0" AutoGenerateColumns="False"
              ItemsSource="{Binding Orders}" 
              SelectedItem="{Binding SelectedOrder}">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Item ID" Binding="{Binding Id}" />
            <DataGridTextColumn Header="Item Name" Binding="{Binding Name}" />
            <DataGridTemplateColumn Header="Addtionals">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                            <ItemsControl ItemsSource="{Binding Additionals}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <TextBlock Text="{Binding}"/>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}" />
            <DataGridTextColumn Header="Item Price" Binding="{Binding Price}" />
        </DataGrid.Columns>
    </DataGrid>
    <Button  Grid.Row="1" Content="New Order" Click="Order_Click"/>
    </Grid>
    

    MainWindow.cs:

        public ObservableCollection<Order> Orders { get; set; }
    
        public Order SelectedOrder { get; set; }
    
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
    
            Orders = new ObservableCollection<Order>();
    
        }
    
        private void Order_Click(object sender, RoutedEventArgs e)
        {
            SP_Get_OrderItemResult orderRow = gridOrder.SelectedItem as SP_Get_OrderItemResult;
            Orders.Add(new Order(orderRow.orderid));
        }