简体   繁体   English

使用MVVM C#将WPF ListBox项源控件的UpdateSourceTrigger设置为Explicit

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

I'm having a Collection of type Employee Model Class. 我有一个Employee Model Class类型的Collection。 It has two properties empName and isChecked. 它具有两个属性empName和isChecked。

Expectation : I need to update the property isChecked from the Checkbox, while on Clicking the Apply Button. 期望 :我需要在单击“应用”按钮时,从复选框更新属性isChecked。 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. 我使用MVVM方法将此集合绑定到WPF列表框中。 The empName is Binded with TextBlock and isChecked is Binded with Checkbox. empName与TextBlock绑定,isChecked与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 . 注意:请使用MVVM方法

There is my suggestion: 我的建议是:
At first, you should define EmployeesViewModel 首先,您应该定义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 . 此视图模型包含itemsSource和ListBox选定项目。 The ApplyChanges(object par) method is called when the ApplyChangesCommand is invoked and the Employee.IsChecked is updated. 调用ApplyChangesCommand并更新Employee.IsChecked时,将调用ApplyChanges(object par)方法。

You should also wrap the Employee into the view model. 您还应该将Employee包装到视图模型中。

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 . 假设ListBoxDataContextEmployeesViewModel

<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>

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. VisualTreeHelper获取VisualTree中的所有CheckBox项。 (Credit: Used THIS solution.) (来源:使用过的THIS解决方案。)
  • 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: 使用FindVisualChildren方法创建一个帮助器类:

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. 此方法将帮助我们获取ListBox控件下的所有CheckBox。

THE BEHAVIOR 行为

ApplyAllCheckBoxBindingsInListBoxBehavior . 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. 自恕我直言,由于命令是从ViewModel初始化的,所以我已将其从Command切换为行为,该命令不应具有对视觉的引用(控件等),并且解决方案基于访问视觉控件。

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: 该行为将在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. 请注意,我已经为ListBox添加了名称,并用行为替换了按钮上的Command。

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. 如果您想要更一般的行为,则需要对行为,XAML和帮助程序进行一些修改。

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! 编码愉快! :) :)

Your real problem is UpdateSourceTrigger=Explicit now needs the UpdateSource() method of BindingExpression to be called. 真正的问题是UpdateSourceTrigger=Explicit现在需要调用BindingExpressionUpdateSource()方法。 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. 而且您需要MVVM解决方案,因此您也不需要ViewModel中的任何UI对象。

So the question becomes how will you get access to UpdateSource() of all bindings expression in ViewModel? 因此,问题就变成了如何获取ViewModel中所有绑定表达式的UpdateSource()

Below is, how I've done it. 以下是我的操作方法。

Create a CustomControl of Button which will hold your BindingExpression , which we can later pass in Command as a CommandParameter : 创建一个ButtonCustomControl ,它将保存您的BindingExpression ,稍后我们可以将Command作为CommandParameter传递给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 CheckBox bindings to Button's Binding collection. 最后,棘手的部分(我们可以/将要辩论)将您所有的CheckBox绑定与Button's Binding集合相关联。 I've created a Converter to do so(not really to bind any command to Checkboxes ), you can use any other event/behaviour etc: 我已经创建了一个Converter来做到这一点(并不是将任何command绑定到Checkboxes ),您可以使用任何其他事件/行为等:

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. 而且一切正常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM