Search code examples
wpfmvvmdata-bindinguser-controlscheckboxlist

How to bind the selection of CheckBox List to database WPF MVVM EF


I am attempting to make a UserControl to create a CheckBoxList in WPF using MVVM. Also, Entity Framework is being used to deploy the data. Given the following:

WPF (UserControl)

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

Classes

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

How can I get the UserControl to load the entire list of the Sports class and then select the ones that the Athlete can play?


Solution

  • I found the solution to my issue. I was able to find it here. It goes like this:

    WPF UserControl.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);
                 }));
        }
    }
    

    BaseMultiValueConverter

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

    And then all you have to do in whatever window/page you want to use it in is use this code:

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