【问题标题】:How to bind the selection of CheckBox List to database WPF MVVM EF如何将复选框列表的选择绑定到数据库 WPF MVVM EF
【发布时间】:2018-12-31 02:58:17
【问题描述】:

我正在尝试使用 MVVM 在 WPF 中创建 UserControl 以创建 CheckBoxList。此外,实体框架也被用于部署数据。鉴于以下情况:

WPF(用户控件)

<Grid>
    <ListBox Name="ListBox" ItemsSource="{Binding TheList}" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <CheckBox Content="{Binding Sport}" 
                          Tag="{Binding SportsId}"
                          IsChecked="{Binding IsChecked}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

public class Athlete
{
    public int AthleteId { get; set; }
    public string Name { get; set; }
    public ICollection<Sports> Sports { get; set; }

}

public class Sports {
    public int SportsId { get; set; }
    public string Sport { get; set; }
}

如何让 UserControl 加载 Sports 类的整个列表,然后选择 Athlete 可以玩的那些?

【问题讨论】:

  • 请用谷歌搜索 MVVM,学习一些基础知识,尝试解决方案,如果遇到问题,请在此处提出问题的详细信息。
  • 我浏览了看起来相关的 Google 结果。但是,我找不到任何显示绑定到数据库的内容。我找到的那些向您展示了如何从 ObservableCollection 创建一个列表,然后获取检查的结果。我还没有找到任何显示如何进行双向绑定的东西。
  • 这是因为直接将视图绑定到数据库模型,除了问题太大之外,在 MVVM 以及许多其他设计模式中都被认为是反模式。您可能会找到解决方法,但根本不推荐。
  • 我没有将视图绑定到数据库。我正在尝试创建一个 UserControl,它将从 VM 中获取 2 个属性。一个是可用项目的完整列表(运动),另一个是集合(运动员可以玩的运动)。然后我希望 UserControl 检查运动员所玩的框。因此,如果运动员可以参加 5 项运动中的 2 项,那么这 2 项已经被选中。所有谷歌结果显示创建一个带有点击事件的框列表来构建一个返回列表(单向数据)。我需要双向数据。

标签: wpf mvvm data-binding user-controls checkboxlist


【解决方案1】:

这个问题非常广泛和模糊,但我会尽力解释。您可能需要至少阅读整本书两次。并阅读the external link 到它的末尾,或者至少仔细阅读其中的代码。

先看最终解决方案:

public class AthleteVM : DependencyObject
{
    public int AthleteId { get; set; }
    public string Name { get; set; }

    private ObservableCollection<SportSelectionVM> _sports = new ObservableCollection<SportSelectionVM>();
    public ObservableCollection<SportSelectionVM> Sports { get { return _sports; } }

}

public class SportSelectionVM : DependencyObject
{
    public int SportsId { get; set; }
    public string Name { get; set; }

    private Model.Sport _model;
    public SportSelectionVM(Model.Sport model, bool isSelected)
    {
        _model = model;
        SportsId = model.Id;
        Name = model.Name;
        IsSelected = isSelected;
    }

    /// <summary>
    /// Gets or Sets IsSelected Dependency Property
    /// </summary>
    public bool IsSelected
    {
        get { return (bool)GetValue(IsSelectedProperty); }
        set { SetValue(IsSelectedProperty, value); }
    }
    public static readonly DependencyProperty IsSelectedProperty =
        DependencyProperty.Register("IsSelected", typeof(bool), typeof(AthleteVM), new PropertyMetadata(false, (d, e) =>
        {
            //  PropertyChangedCallback
            var vm = d as SportSelectionVM;
            var val = (bool)e.NewValue;
            AthleteDataService.UpdateModel(vm._model, val);//database changes here
        }));
}

XAML:

    <ListBox Name="ListBox" ItemsSource="{Binding Sports}" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <CheckBox Content="{Binding Name}" 
                      Tag="{Binding SportsId}"
                      IsChecked="{Binding IsSelected}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

此视图的DataContext 是AthleteVM 的一个实例。将所有运动添加到AthleteVM 中的Sports,并在必要时设置IsSelected

查看构造函数:public SportSelectionVM(Model.Sport model, bool isSelected)

应该使用类似的策略来创建AthleteVM 或在其父级中填充 AthleteVM 列表。

EF 和 UOW:

我们知道这是 MVVM 背后的想法:

[模型] [视图]

当EF加入这个模式时,通常建议也遵循UOW模式。

通常,UOW (UnitOfWork) 是一个负责一个 数据库事务(我不是指 SQLTransaction)的对象,建议始终在 using 语句中创建一个 UOW以便以后处理。使用这种方法,您应该会遇到这个问题:不同的 UOW 如何相互交互。答案是:他们没有。

每个 UOW 创建数据库的惰性副本并开始修改它,直到您告诉它丢弃或保存。如果在此过程的中间创建另一个 UOW,则它不包含对先前 UOW 所做的任何更改,除非先前的 UOW 被保存。

因此您不必担心 Model,而是将注意力集中在 DataService 上以获取 like this 的内容。

模型虚拟机

考虑到所有这些信息,ViewModel 只需使用 DataService 的实例从数据库中获取数据,并将它们放入可绑定的属性和可观察的集合中以维护 双向绑定

但是 VMModel 没有 TwoWay 关系,这意味着对 ViewModel 的任何更改都应该是反映在 Model 上,然后手动保存在数据库中。

我最喜欢的解决方案是充分利用DependencyPropertyPropertyChangedCallback 功能来告诉DataService 反映更改:

    public int MyProperty
    {
        get { return (int)GetValue(MyPropertyProperty); }
        set { SetValue(MyPropertyProperty, value); }
    }
    public static readonly DependencyProperty MyPropertyProperty =
        DependencyProperty.Register("MyProperty", typeof(int), typeof(MyViewModel), 
            new PropertyMetadata(0, (d,e)=>
            {
                var vm = d as MyViewModel;
                var val = (int)e.NewValue;//check conditions here
                vm._model.MyProperty = val;//update model
                vm._dataService.Update(vm._model);//update database
            }));

在上面的示例中,MyViewModel 类有一个 _model_dataService 的实例。

【讨论】:

    【解决方案2】:

    我找到了解决问题的方法。我能够找到它here。它是这样的:

    WPF 用户控件.xaml

    <UserControl x:Class="YourNamespace.CheckBoxList"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:YourNamespace"
                 mc:Ignorable="d" 
                 x:Name="ThisCheckBoxList"
                 d:DesignHeight="450" d:DesignWidth="800">
        <ScrollViewer  VerticalScrollBarVisibility="Auto">
            <StackPanel>
                <ItemsControl x:Name="host"
                              ItemsSource="{Binding ElementName=ThisCheckBoxList, Path=ItemsSource}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <local:MyCheckBox x:Name="theCheckbox"
                                              DisplayMemberPath="{Binding ElementName=ThisCheckBoxList, Path=DisplayPropertyPath}" 
                                              Unchecked="MyCheckBox_Checked"   
                                              Checked="MyCheckBox_Checked" 
                                              Tag="{Binding Path=.}">
                                <local:MyCheckBox.IsChecked >
                                    <MultiBinding Mode="OneWay" >
                                        <MultiBinding.Converter>
                                            <local:IsCheckedValueConverter />
                                        </MultiBinding.Converter>
                                        <Binding Path="."></Binding>
                                        <Binding ElementName="ThisCheckBoxList" Path="SelectedItems"></Binding>
                                        <Binding ElementName="ThisCheckBoxList" Path="DisplayPropertyPath"></Binding>
                                    </MultiBinding>
                                </local:MyCheckBox.IsChecked>
                            </local:MyCheckBox>
    
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </StackPanel>
        </ScrollViewer>
    </UserControl>
    

    WPF UserControl.xaml.cs

    using System.Collections;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Media;
    
    namespace Eden
    {
        /// <summary>
        /// Interaction logic for CheckBoxList.xaml
        /// </summary>
        public partial class CheckBoxList : UserControl
        {
            public CheckBoxList()
            {
                InitializeComponent();
            }
    
            public object ItemsSource
            {
                get => GetValue(ItemsSourceProperty);
                set => SetValue(ItemsSourceProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for ItemSource.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty ItemsSourceProperty =
                DependencyProperty.Register("ItemsSource", typeof(object), typeof(CheckBoxList),
                                            new UIPropertyMetadata(null, (sender, args) => Debug.WriteLine(args)));
    
            public IList SelectedItems
            {
                get => (IList)GetValue(SelectedItemsProperty);
                set => SetValue(SelectedItemsProperty, value);
            }
    
    
    
            // Using a DependencyProperty as the backing store for SelectedItems.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty SelectedItemsProperty =
                DependencyProperty.Register("SelectedItems", typeof(IList), typeof(CheckBoxList),
                                            new UIPropertyMetadata(null, SelectedChanged));
    
            /// <summary>
            /// This is called when selected property changed.
            /// </summary>
            /// <param name="obj"></param>
            /// <param name="args"></param>
            private static void SelectedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
            {
                if (args.NewValue is INotifyCollectionChanged ncc)
                {
                    ncc.CollectionChanged += (sender, e) =>
                    {
                        CheckBoxList thiscontrol = (CheckBoxList)obj;
                        RebindAllCheckbox(thiscontrol.host);
                    };
                }
            }
    
            private static void RebindAllCheckbox(DependencyObject de)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(de); i++)
                {
                    DependencyObject dobj = VisualTreeHelper.GetChild(de, i);
                    if (dobj is CheckBox cb)
                    {
                        var bexpression = BindingOperations.GetMultiBindingExpression(cb, MyCheckBox.IsCheckedProperty);
                        if (bexpression != null) bexpression.UpdateTarget();
                    }
                    RebindAllCheckbox(dobj);
                }
            }
    
    
    
            public string DisplayPropertyPath
            {
                get => (string)GetValue(DisplayPropertyPathProperty);
                set => SetValue(DisplayPropertyPathProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for DisplayPropertyPath.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty DisplayPropertyPathProperty =
                DependencyProperty.Register("DisplayPropertyPath", typeof(string), typeof(CheckBoxList),
                                            new UIPropertyMetadata("", (sender, args) => Debug.WriteLine(args)));
    
            private PropertyInfo mDisplayPropertyPathPropertyInfo;
    
            private void MyCheckBox_Checked(object sender, RoutedEventArgs e)
            {
                if (SelectedItems == null)
                    return;
    
                MyCheckBox chb = (MyCheckBox)sender;
                object related = chb.Tag;
                if (mDisplayPropertyPathPropertyInfo == null)
                {
    
                    mDisplayPropertyPathPropertyInfo =
                        related.GetType().GetProperty(
                            DisplayPropertyPath, BindingFlags.Instance | BindingFlags.Public);
                }
    
                object propertyValue;
                if (DisplayPropertyPath == ".")
                    propertyValue = related;
                else
                    propertyValue = mDisplayPropertyPathPropertyInfo.GetValue(related, null);
    
                if (chb.IsChecked == true)
                {
                    if (!SelectedItems.Cast<object>()
                             .Any(o => propertyValue.Equals(
                                           DisplayPropertyPath == "." ? o : mDisplayPropertyPathPropertyInfo.GetValue(o, null))))
                    {
                        SelectedItems.Add(related);
                    }
                }
                else
                {
                    object toDeselect = SelectedItems.Cast<object>()
                        .Where(o => propertyValue.Equals(DisplayPropertyPath == "." ? o : mDisplayPropertyPathPropertyInfo.GetValue(o, null)))
                        .FirstOrDefault();
                    if (toDeselect != null)
                    {
                        SelectedItems.Remove(toDeselect);
                    }
                }
            }
        }
    
        public class MyCheckBox : CheckBox
        {
            public string DisplayMemberPath
            {
                get => (string)GetValue(DisplayMemberPathProperty);
                set => SetValue(DisplayMemberPathProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for DisplayMemberPath.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty DisplayMemberPathProperty =
                 DependencyProperty.Register("DisplayMemberPath",
                 typeof(string),
                 typeof(MyCheckBox),
                 new UIPropertyMetadata(string.Empty, (sender, args) =>
                 {
                     MyCheckBox item = (MyCheckBox)sender;
                     Binding contentBinding = new Binding((string)args.NewValue);
                     item.SetBinding(ContentProperty, contentBinding);
                 }));
        }
    }
    

    基础多值转换器

    using System;
    using System.Globalization;
    using System.Windows.Data;
    using System.Windows.Markup;
    
    namespace Eden
    {
        /// <summary>
        /// A base value converter that allows direct XAML usage
        /// </summary>
        /// <typeparam name="T">The type of this value converter</typeparam>
        public abstract class BaseMultiValueConverter<T> : MarkupExtension, IMultiValueConverter
            where T : class, new()
        {
    
            #region Private Variables
    
            /// <summary>
            /// A single static instance of this value converter
            /// </summary>
            private static T Coverter = null;
    
            #endregion
    
            #region Markup Extension Methods
            /// <summary>
            /// Provides a static instance of the value converter
            /// </summary>
            /// <param name="serviceProvider">The service provider</param>
            /// <returns></returns>
            public override object ProvideValue(IServiceProvider serviceProvider)
            {
                return Coverter ?? (Coverter = new T());
            }
    
            #endregion
    
            #region Value Converter Methods
    
            /// <summary>
            /// The method to convert on type to another
            /// </summary>
            /// <param name="value"></param>
            /// <param name="targetType"></param>
            /// <param name="parameter"></param>
            /// <param name="culture"></param>
            /// <returns></returns>
            public abstract object Convert(object[] value, Type targetType, object parameter, CultureInfo culture);
    
            /// <summary>
            /// The method to convert a value back to it's source type
            /// </summary>
            /// <param name="value"></param>
            /// <param name="targetType"></param>
            /// <param name="parameter"></param>
            /// <param name="culture"></param>
            /// <returns></returns>
            public abstract object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture);
    
            #endregion
        }
    }
    

    IMultiValueConverter

    using EcoDev.Data;
    using System;
    using System.Collections;
    using System.Globalization;
    using System.Reflection;
    using System.Windows.Data;
    
    namespace Eden
    {
        /// <summary>
        /// 
        /// </summary>
        public class IsCheckedValueConverter : BaseMultiValueConverter<IsCheckedValueConverter>
        {
            private PropertyInfo PropertyInfo { get; set; }
            private Type ObjectType { get; set; }
    
            public override object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (values[1] == null) return false; // IF I do not have no value for selected simply return false
    
                if (!(values[2] is string PropertyName)) return false;
    
                if (string.IsNullOrEmpty(PropertyName)) return false;
                if (!targetType.IsAssignableFrom(typeof(bool))) throw new NotSupportedException("Can convert only to boolean");
                IEnumerable collection = values[1] as IEnumerable;
                object value = values[0];
                if (value.GetType() != ObjectType)
                {
                    PropertyInfo = value.GetType().GetProperty(PropertyName, BindingFlags.Instance | BindingFlags.Public);
                    ObjectType = value.GetType();
                }
                foreach (var obj in collection)
                {
                    if (PropertyName == ".")
                    {
                        if (value.Equals(obj)) return true;
                    }
                    else
                    {
                        if (PropertyInfo.GetValue(value, null).Equals(PropertyInfo.GetValue(obj, null))) return true;
                    }
    
                }
                return false;
            }
    
    
            public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }
    

    然后你所要做的就是在任何你想使用它的窗口/页面中使用这个代码:

    <local:CheckBoxList Height="Auto"
                        SelectedItems="{Binding SelectedItems}"
                        ItemsSource="{Binding ItemsSource}"
                        DisplayPropertyPath="Text"/>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-07
      • 2018-01-03
      • 1970-01-01
      • 2017-04-14
      • 2017-05-07
      • 2011-05-05
      • 1970-01-01
      相关资源
      最近更新 更多