代码之家  ›  专栏  ›  技术社区  ›  Red_Phoenix

如何将选中的复选框列表绑定到数据库wpf mvvm ef

  •  0
  • Red_Phoenix  · 技术社区  · 6 年前

    我正试图使用MVVM让一个用户控件在WPF中创建一个复选框列表。此外,实体框架正被用于部署数据。鉴于以下情况:

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

    如何让用户控件加载整个运动类列表,然后选择运动员可以玩的列表?

    2 回复  |  直到 6 年前
        1
  •  1
  •   Red_Phoenix    6 年前

    我找到了解决我问题的办法。我找到了它 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用户控件.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
        }
    }
    

    多值转换器

    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"/>
    
        2
  •  0
  •   Bizhan    6 年前

    这个问题很宽泛,很模糊,但我尽量解释清楚。你可能需要至少读两遍整本书。还可以阅读 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 . 将所有运动添加到 Sports 在里面 运动员 和设置 IsSelected 对那些必要的。

    见施工单位: public SportSelectionVM(Model.Sport model, bool isSelected)

    应使用类似的策略来创建 运动员 或者在父级中填充athletevm列表。

    EF和UOW:

    正如我们所知,MVVM背后的理念是:

    [模型]<---[vm]<--twoway绑定-->[视图]

    当ef添加到这个模式中时,通常也建议遵循uow模式。

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

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

    所以你不用担心 模型 相反,你将关注 数据服务 吃点东西 like this .

    型号<->VM

    考虑到所有这些信息, 视图模型 只使用 数据服务 从数据库中获取数据并将其放入可绑定属性和可观察集合中,以维护 双面装订 .

    但是 虚拟机 模型 没有 两个星期 关系,意味着任何改变 视图模型 应该反映在 模型 然后保存到数据库中 手动 .

    我最喜欢的解决方案是充分利用 PropertyChangedCallback 的功能 DependencyProperty 告诉 数据服务 为了反映变化:

        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 .