Sunday, April 17, 2016

Set UpdateSourceTrigger to Explicit for WPF ListBox Item Source Controls using MVVM C#

Leave a Comment

I'm having a Collection of type Employee Model Class. It has two properties empName and isChecked.

Expectation : I need to update the property isChecked from the Checkbox, while on Clicking the Apply Button. Otherwise it don't need to update the Property.

void Main() {     Dictionary<int, List<Employee>> empList = new Dictionary<int, List<Employee>>()     {         {1, new List<Employee>() { new Employee() {empName = "Raj"}, new Employee() {empName = "Kumar"}}},         {2, new List<Employee>() { new Employee() {empName = "Bala"}}},          {3, new List<Employee>() { new Employee() {empName = "Manigandan"}}},          {4, new List<Employee>() { new Employee() {empName = "Prayag"}, new Employee() {empName = "Pavithran"}}},          {5, new List<Employee>() { new Employee() {empName = "Selva"}}},     };      empList.Dump(); }  public class Employee {     public string empName { get; set; }     public bool isChecked { get; set; } } 

I Binded this Collection into a WPF ListBox using MVVM approach. The empName is Binded with TextBlock and isChecked is Binded with Checkbox.

<ListBox ItemsSource="{Binding empList.Values, IsAsync=True, UpdateSourceTrigger=Explicit}">     <cust:BListBox.ItemTemplate>         <DataTemplate>              <CheckBox IsChecked="{Binding isChecked, UpdateSourceTrigger=Explicit}">                  <CheckBox.Content>                      <StackPanel Orientation="Horizontal">                          <TextBlock Text="{Binding empName, IsAsync=True}" Visibility="Visible" />                       </StackPanel>                   </CheckBox.Content>               </CheckBox>         </DataTemplate>     </ListBox.ItemTemplate> </ListBox>  <Button Content="Apply" Command="{Binding ApplyChangesCommand}"/> 

The Command for the Apply Button is

public ICommand ApplyChangesCommand         {             get             {                 return new DelegatingCommand((object param) =>                 {                     /// Logical Code.                  });             }         } 

Note: Kindly use MVVM approach.

3 Answers

Answers 1

There is my suggestion:
At first, you should define EmployeesViewModel

public class EmployeesViewModel : INotifyPropertyChanged {     public ObservableCollection<EmployeeViewModel> EmployeeList { get; set; }      public ICommand ApplyChangesCommand;      public EmployeesViewModel()     {         EmployeeList = new ObservableCollection<EmployeeViewModel>         {              new EmployeeViewModel(new Employee {EmpName = "Raj"}),             new EmployeeViewModel(new Employee {EmpName = "Kumar"}),             new EmployeeViewModel(new Employee {EmpName = "Bala"}),             new EmployeeViewModel(new Employee {EmpName = "Manigandan"}),             new EmployeeViewModel(new Employee {EmpName = "Prayag"}),             new EmployeeViewModel(new Employee {EmpName = "Pavithran"}),             new EmployeeViewModel(new Employee {EmpName = "Selva"})         };          ApplyChangesCommand = new DelegatingCommand(ApplyChanges);     }      private void ApplyChanges(object param)     {         foreach(var item in EmployeeList)         {             item.Model.IsChecked = item.IsChecked;         }     }     .... } 

This view model contains the itemssource and selected item of the ListBox. The ApplyChanges(object par) method is called when the ApplyChangesCommand is invoked and the Employee.IsChecked is updated.

You should also wrap the Employee into the view model.

public class EmployeeViewModel : INotifyPropertyChanged {     public Employee Model { get; set; }      private string _empName;      public string EmpName     {         get { return _empName;}         set         {             _empName = value;             OnPropertyChanged();         }     }      private bool _isChecked;      public bool IsChecked     {         get { return _isChecked;}         set         {             _isChecked = value;             OnPropertyChanged();         }     }      public EmployeeViewModel(Employee model)     {         Model = model;         IsChecked = model.IsChecked;         EmpName = model.EmpName;     }      public event PropertyChangedEventHandler PropertyChanged;      [NotifyPropertyChangedInvocator]     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)     {         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));     } } 

Suppose that the DataContext of the ListBox is EmployeesViewModel.

<ListBox ItemsSource="{Binding EmployeeList, IsAsync=True, UpdateSourceTrigger=Explicit}" SelectedItem="{Binding SelectedItem}"> <cust:BListBox.ItemTemplate>     <DataTemplate>          <CheckBox IsChecked="{Binding IsChecked, UpdateSourceTrigger=Explicit}">              <CheckBox.Content>                  <StackPanel Orientation="Horizontal">                      <TextBlock Text="{Binding EmpName, IsAsync=True}" Visibility="Visible" />                   </StackPanel>               </CheckBox.Content>           </CheckBox>     </DataTemplate> </ListBox.ItemTemplate> 

Answers 2

Ok, so I've found a solution. This solution isn't too generic, but it can be made generic via some simple steps. Tell me if it's important to you.

WARNING! It's going to be a bit long...

For this solution, we will use the following:

  • VisualTreeHelper to get all CheckBox items in VisualTree. (Credit: Used THIS solution.)
  • A Behavior to update all check boxes in the ListBox.

So, let's begin!

THE HELPER

As I've states, I've took the solution from THIS answer.

Create a helper class with a FindVisualChildren method:

public static class VisualHelper {     public static IEnumerable<T> FindVisualChildren<T>(DependencyObject dependencyObject) where T: DependencyObject     {         if (dependencyObject == null)             yield break;          int totalChildrenAmount = VisualTreeHelper.GetChildrenCount(dependencyObject);         for (int childIndex = 0; childIndex < totalChildrenAmount; childIndex++)         {             DependencyObject child = VisualTreeHelper.GetChild(dependencyObject, childIndex);             if (child is T)             {                 yield return (T)child;             }              foreach (T deeperChild in FindVisualChildren<T>(child))             {                 yield return deeperChild;             }         }     } } 

This method will help us to get all the CheckBoxes that are under the ListBox control.

THE BEHAVIOR

ApplyAllCheckBoxBindingsInListBoxBehavior. I know this name is long, but since we need something very specific, I strongly suggest to use a long name, to make it clear what the behavior does.

I've switched it from Command to a behavior, since IMHO, since commands are initialized from the ViewModel, the command shouldn't have any references to visuals (controls and such) and the solution is based on accessing visual controls.

Enough talk, here is the behavior:

public class ApplyAllCheckBoxBindingsInListBoxBehavior {     public static ListBox GetListBox(DependencyObject obj)     {         return (ListBox)obj.GetValue(ListBoxProperty);     }      public static void SetListBox(DependencyObject obj, ListBox value)     {         obj.SetValue(ListBoxProperty, value);     }      // Using a DependencyProperty as the backing store for ListBox.  This enables animation, styling, binding, etc...     public static readonly DependencyProperty ListBoxProperty =         DependencyProperty.RegisterAttached("ListBox", typeof(ListBox), typeof(ApplyBindingsBehavior), new PropertyMetadata(null, ListBoxChanged));      private static void ListBoxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)     {         Button button = d as Button;         if (button == null)             return;          button.Click -= OnClick;         button.Click += OnClick;     }      private static void OnClick(object sender, RoutedEventArgs routedEventArgs)     {         ListBox lb = GetListBox(sender as Button);         IEnumerable<CheckBox> allCBs = VisualHelper.FindVisualChildren<CheckBox>(lb);          foreach (CheckBox checkBox in allCBs)         {             checkBox.GetBindingExpression(CheckBox.IsCheckedProperty).UpdateSource();         }     } } 

The behavior will be set on the button in the XAML:

<ListBox Name="EmpList" ItemsSource="{Binding empList.Values, IsAsync=True, UpdateSourceTrigger=Explicit}">     <cust:BListBox.ItemTemplate>         <DataTemplate>              <CheckBox IsChecked="{Binding isChecked, UpdateSourceTrigger=Explicit}">                  <CheckBox.Content>                      <StackPanel Orientation="Horizontal">                          <TextBlock Text="{Binding empName, IsAsync=True}" Visibility="Visible" />                       </StackPanel>                   </CheckBox.Content>               </CheckBox>         </DataTemplate>     </ListBox.ItemTemplate> </ListBox>  <Button Content="Apply" behaviors:ApplyAllCheckBoxBindingsInListBoxBehavior.ListBox="{Binding ElementName=EmpList}"/> 

Note that I've added a name to the ListBox and replaced the Command on the button with the behavior.

As I've stated before, the behavior is very specific. This is to make the solution simpler. If you want a more generic behavior, the behavior, the XAML and the helper needs to be modified a bit.

First of all, make this solution work. If it works, and you still want to make it more generic, let me know, I'll be glad to help!

Happy Coding! :)

Answers 3

Your real problem is UpdateSourceTrigger=Explicit now needs the UpdateSource() method of BindingExpression to be called. So there is no way it can be achieved by only binding. And you want MVVM solution so you also don't want any UI objects in ViewModel.

So the question becomes how will you get access to UpdateSource() of all bindings in ViewModel?

here is how I've done it.

Create a CustomControl of Button which will hold your BindingExpression which we can later pass in Command:

public class MyButton : Button {     private List<BindingExpression> bindings;     public List<BindingExpression> Bindings     {         get          {             if (bindings == null)                 bindings = new List<BindingExpression>();             return bindings;          }         set { bindings = value; }     } } 

Command:

public RelayCommand ApplyChangesCommand { get; set; }     public void ApplyChangesCommandAction(object param)     {         foreach (var item in (param as List<BindingExpression>))         {             item.UpdateSource();         }     } 

Command Binding:

 <local:MyButton x:Name="MyButton" Content="Apply" Command="{Binding ApplyChangesCommand}"                      CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=Bindings}"/> 

At last the tricky part(which we can/will debate) to associate your all bindings to Button's collection. I've created a Converter to do so(not really to bind any command), you can use any other event/behaviour etc:

Command binding for Check Box:

<CheckBox.Command>     <MultiBinding Converter="{StaticResource Converter}">                                        <Binding RelativeSource="{RelativeSource Self}" Path="." />        <Binding ElementName="MyButton" Path="Bindings" />     </MultiBinding> </CheckBox.Command> 

Converter:

 public class Converter : IMultiValueConverter {     public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)     {         CheckBox FE = values[0] as CheckBox;         List<BindingExpression> bindings = values[1] as List<BindingExpression>;         bindings.Add(FE.GetBindingExpression(CheckBox.IsCheckedProperty));         return null;     }      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)     {         return null;     } } 

And Everything Works Fine.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment