Donate

How To Apply Checkbox Check All To WPF DataGrid Using MVVM Pattern

Good day Gents!

In this post, I will demonstrate on how to create a WPF DataGrid application that has a check all checkbox behavior for selecting all rows using the MVVM Pattern. First is we need to create a .NET Core WPF project that targets the .NET 7 framework with a project structure similar to the screenshot below.
How To Apply Checkbox Check All To WPF DataGrid Using MVVM Pattern
App.config
Update your App.config's connection string with your local database connection.
<configuration>
  <connectionStrings>
    <add name="products"
         connectionString="data source=.;Initial Catalog=TestDatabase;Integrated Security=true;"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
</configuration>
DBUtil.cs
The DBUtil class will retrieve records from the database and update the discontinued field of a particular product.
public static class DBUtil
{
   public static DataTable GetProduct()
   {
      DataSet ds = new DataSet();
      string query = "Select * from Products;";

      try
      {
         using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["products"].ConnectionString.ToString()))
         {
            SqlCommand cmd = new SqlCommand(query, conn);
            conn.Open();
            SqlDataAdapter da = new SqlDataAdapter(cmd);
            da.Fill(ds);
            conn.Close();
         }
      }
      catch (Exception ex)
      {
         throw ex;
      }

      return ds.Tables[0];
   }

   public static int UpdateProductDiscontinue(bool value, int productID)
   {
      int result = 0;
      string query = String.Format("Update Products set discontinue = '{0}' where productID = '{1}' ;", value, productID);

      try
      {
         using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["products"].ConnectionString.ToString()))
         {
            SqlCommand cmd = new SqlCommand(query, conn);
            conn.Open();
            result = cmd.ExecuteNonQuery();
            conn.Close();
         }
      }
      catch (Exception ex)
      {
         throw ex;
      }

      return result;
   }
}
ViewModelBase.cs
The ViewModelBase class implements the INotifyPropertyChanged interface. This is useful for any property changes used in databinding.
public class ViewModelBase : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;

   protected void OnPropertyChanged(string propertyName)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}
RelayCommand.cs
The RelayCommand class that implements the ICommand interface. Commands are used for handling events in WPF with regards to the MVVM Architectural Pattern. The sole purpose of a command is to relay or distribute its functionality to other objects by invoking delegates. The default return value for a CanExecute method is true.
public class RelayCommand : ICommand
{
   private readonly Action<object> _execute;
   private readonly Predicate<object> _canExecute;

   public RelayCommand(Action<object> execute)
       : this(execute, null)
   {
   }

   public RelayCommand(Action<object> execute, Predicate<object> canExecute)
   {
      if (execute == null)
         throw new ArgumentNullException("execute");
      _execute = execute;
      _canExecute = canExecute;
   }

   public bool CanExecute(object parameter)
   {
      return _canExecute == null ? true : _canExecute(parameter);
   }

   public event EventHandler CanExecuteChanged
   {
      add { CommandManager.RequerySuggested += value; }
      remove { CommandManager.RequerySuggested -= value; }
   }

   public void Execute(object parameter)
   {
      _execute(parameter);
   }
}
MultiParamConverter.cs
This class will clone the values of all items in the DataGrid. This is useful when the DataGrid's header check all checkbox is checked, the parameters will then be passed to the UpdateAllProducts() in the ViewModel and process each record to verify if the product item will be discontinued.
public class MultiParamConverter : IMultiValueConverter
{
   public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
   {
      return values.Clone();
   }

   public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
   {
      throw new NotImplementedException();
   }
}
Products.cs
This class inherits the ViewModelBase class and contains the properties of a product that is bound to the DataGrid. Any changes made by the user will reflect in any of these properties since they implement the OnPropertyChanged() method.
public class Products : ViewModelBase
{
   private int _id;
   public int Id
   {
      get
      {
         return _id;
      }
      set
      {
         _id = value;
         OnPropertyChanged("Id");
      }
   }

   private string _name;
   public string ProductName
   {
      get
      {
         return _name;
      }
      set
      {
         _name = value;
         OnPropertyChanged("ProductName");
      }
   }

   private decimal _price;
   public decimal UnitPrice
   {
      get
      {
         return _price;
      }
      set
      {
         _price = value;
         OnPropertyChanged("UnitPrice");
      }
   }

   private string _quantity;
   public string QuantityPerUnit
   {
      get
      {
         return _quantity;
      }
      set
      {
         _quantity = value;
         OnPropertyChanged("QuantityPerUnit");
      }
   }

   private bool _discontinue;
   public bool Discontinue
   {
      get
      {
         return _discontinue;
      }
      set
      {
         _discontinue = value;
         OnPropertyChanged("Discontinue");
      }
   }
}
ProductsViewModel.cs
The View Model class is the main piece of the project. This is were all the processing is executed. From firing of check event handler, populating the datagrid from the database, updating a single product discontinued property and modifying all product item's discontinued property. This is the DataContext object for the MainWindow class.
public class ProductsViewModel : ViewModelBase
{
   #region Commands

   private ICommand _updateItemCommand;
   private ICommand _updateAllCommand;
   public ICommand UpdateItemCommand
   {
      get
      {
         if (_updateItemCommand == null)
         {
            _updateItemCommand = new RelayCommand(param => UpdateItem(param), null);
         }

         return _updateItemCommand;
      }
   }

   public ICommand UpdateAllCommand
   {
      get
      {
         if (_updateAllCommand == null)
         {
            _updateAllCommand = new RelayCommand(param => UpdateAllProducts(param), null);
         }

         return _updateAllCommand;
      }
   }

   #endregion

   #region Members

   private ObservableCollection<Products> _productList;
   public ObservableCollection<Products> ProductList
   {
      get
      {
         return _productList;
      }
      set
      {
         _productList = value;
         OnPropertyChanged("ProductList");
      }
   }

   private bool _checkAll;
   public bool CheckAll
   {
      get
      {
         return _checkAll;
      }
      set
      {
         _checkAll = value;
         OnPropertyChanged("CheckAll");
      }
   }

   public MultiParamConverter MultiParamConverter
   {
      get; set;
   }

   #endregion

   #region ViewModelMethods

   public ProductsViewModel()
   {
      MultiParamConverter = new MultiParamConverter();
      GetAllProducts();
   }

   private void UpdateItem(object param)
   {
      var product = (Products)param;
      DBUtil.UpdateProductDiscontinue(product.Discontinue, product.Id);
      GetAllProducts();
   }

   private void UpdateAllProducts(object param)
   {
      var values = (object[])param;
      var check = (bool)values[0];
      var productsItemCollection = (ItemCollection)values[1];

      if (productsItemCollection.Count > 0)
      {
         foreach (var item in productsItemCollection)
         {
            if (item != null)
            {
               DBUtil.UpdateProductDiscontinue(check, ((Products)item).Id);
            }
         }
      }

      GetAllProducts();
   }

   private void GetAllProducts()
   {
      bool flag = true;

      ProductList = new ObservableCollection<Products>(Products(DBUtil.GetProduct()));

      foreach (var product in ProductList)
      {
         if (!product.Discontinue)
         {
            flag = false;
            CheckAll = false;
         }
      }

      if (flag)
         CheckAll = true;
   }

   private ObservableCollection<Products> Products(DataTable dt)
   {
      var convertedList = (from rw in dt.AsEnumerable()
                           select new Products()
                           {
                              Id = Convert.ToInt32(rw["productID"]),
                              ProductName = rw["ProductName"].ToString(),
                              UnitPrice = Convert.ToDecimal(rw["UnitPrice"]),
                              QuantityPerUnit = (rw["QuantityPerUnit"]).ToString(),
                              Discontinue = (bool)rw["Discontinue"]
                           }).ToList();

      return new ObservableCollection<Products>(convertedList);
   }

   #endregion
}
MainWindow.xaml
The UI has two window resources specifically the ProductsViewModel classwhich is the window's datacontext object and the MultiParamConverter class which is the header checkbox's MultiBinding converter. The UI's main component is a DataGrid control that shows the products and has an discontintued checkbox for each record. The DataGrid's header has a check all checkbox that will set all of the product's discontinued property either true or false when checked.
<Window x:Class="MVVMCheckboxAlIDataGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MVVMCheckboxAlIDataGrid.ViewModel"
        xmlns:converter="clr-namespace:MVVMCheckboxAlIDataGrid.ViewModel.Converters"
        mc:Ignorable="d" WindowStartupLocation="CenterScreen"
        Title="MainWindow" Height="550" Width="600">
   <Window.Resources>
      <local:ProductsViewModel x:Key="ProductsViewModel" />
      <converter:MultiParamConverter x:Key="MultiParamConverter"/>
   </Window.Resources>
   <Grid Width="580">
      <Grid.RowDefinitions>
         <RowDefinition Height="20" />
         <RowDefinition Height="100" />
         <RowDefinition Height="50" />
         <RowDefinition Height="*" />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="260" />
         <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
      <StackPanel Grid.Row="1" Grid.RowSpan="3" Grid.ColumnSpan="2" DataContext="{Binding Source={StaticResource ProductsViewModel}}">
         <DataGrid Name="DgProducts" AutoGenerateColumns="False" CanUserAddRows="False"
                      ItemsSource="{Binding ProductList}">
            <DataGrid.Columns>
               <DataGridTextColumn Header="Name" Binding="{Binding Path=Id}" Visibility="Hidden" Width="215"/>
               <DataGridTextColumn Header="Name" Binding="{Binding Path=ProductName}" Width="215"/>
               <DataGridTextColumn Header="Price" Binding="{Binding Path=UnitPrice}"/>
               <DataGridTextColumn Header="Quantity Per Unit" Binding="{Binding Path=QuantityPerUnit}" Width="180"/>
               <DataGridTemplateColumn>
                  <DataGridTemplateColumn.HeaderStyle>
                     <Style TargetType="{x:Type DataGridColumnHeader}">
                        <Setter Property="HorizontalContentAlignment" Value="Center"/>
                     </Style>
                  </DataGridTemplateColumn.HeaderStyle>
                  <DataGridTemplateColumn.Header>
                     <CheckBox Name="chkCheckAll" HorizontalAlignment="Center" 
                                      IsChecked="{Binding Path=DataContext.CheckAll, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor,
                                            AncestorType={x:Type StackPanel}}}" 
                                      Command="{Binding DataContext.UpdateAllCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}}">
                        <CheckBox.CommandParameter>
                           <MultiBinding Converter="{StaticResource MultiParamConverter}">
                              <Binding ElementName="chkCheckAll" Path="IsChecked"/>
                              <Binding ElementName="DgProducts" Path="Items"/>
                           </MultiBinding>
                        </CheckBox.CommandParameter>
                     </CheckBox>
                  </DataGridTemplateColumn.Header>
                  <DataGridTemplateColumn.CellTemplate>
                     <DataTemplate>
                        <CheckBox Name="chkDiscontinue" IsChecked="{Binding Discontinue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                    Command="{Binding DataContext.UpdateItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}}" 
                                    CommandParameter="{Binding}" Margin="45 2 0 0" />
                     </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
               </DataGridTemplateColumn>
            </DataGrid.Columns>
         </DataGrid>
      </StackPanel>
   </Grid>
</Window>
Output
How To Apply Checkbox Check All To WPF DataGrid Using MVVM Pattern
How To Apply Checkbox Check All To WPF DataGrid Using MVVM Pattern


Cheers!

Comments

Donate

Popular Posts From This Blog

WPF CRUD Application Using DataGrid, MVVM Pattern, Entity Framework, And C#.NET

TypeScript Error Or Bug: The term 'tsc' is not recognized as the name of a cmdlet, function, script file, or operable program.

Invalid nested tag div found, expected closing tag input