动态绑定多个组合框以禁用选定的源项目

时间:2018-08-08 02:57:06

标签: c# wpf mvvm combobox binding

这是我的第一篇文章...所以我希望我做对了。我正在使用C#,WPF,并尝试遵循MVVM模式。诚然,我有很多东西要学,但是在去年取得了进步。我将对发布的代码中的其他注释表示感谢,以帮助我也学习。

目标:将多个(3至5个)组合框的源项目绑定到同一个可观察集合,并要求可观察集合中的任何项目只能使用一次。如果在一个组合框中选择了一项,则所有其他组合框都将禁用该项目

研究:在我的搜索过程中,我遇到了两篇文章:

  

C# multiple combobox with the same itemssource

     

您应该看一下MVVM模式和ICollectionView。当您至少不了解MVVM时,很容易达到,但很难解释。 – Mighty Badaboom 17年7月3日在12:42

     

Multiple Combo Boxes with shared Binding - Display error after first selection from box

     

或者您可以使用更多的MVVM:如果是我,我将在一个模板化ItemsItem中创建一系列ComboBoxes,它们绑定到某个具有SelectedPort属性的类的集合中,并且我将为该类使用自定义类以及使用String PortName和bool IsPortEnabled进行的简单操作。我将IsPortEnabled绑定到XAML中的ComboBoxItem.IsEnabled。那里的代码要少得多,但是从概念上来说,这与您现在所在的位置相比有很大的提高。如果您有兴趣,我们可以去那里-16年7月12日,13:27 Ed Plunkett

发布问题的个人未概述或要求对MVVM的上述答案之一。我对“从概念上进行重大突破”感兴趣,以学习如何优雅地做到这一点。

UI功能:我正在编写的应用程序从用户那里获取输入,以分析不同类型的地形特征断线。有2种分析模式(两个单选按钮)需要不同的输入(启用/禁用功能选择的组框)。

现在,我最多有5个组合框绑定到源项目的相同可观察集合。分析模式1或模式2需要“功能0”组合框,其中具有功能1到功能3组合框以及可选的功能4组合框(用于模式1)。

现在,在这种背景下,要求可观察的集合中的任何项目只能用于Feature0-4组合框或Feature0和Feature5-6组合框一次(请参见图片WPF UI Example)。我不确定如何选择已启用的组合框,当其中任何一个更改时,都将获取所选的项目,并从其他组合框中禁用该源项目。

例如,如果在“ Feature0”组合框中选择了“ Layer_0”,则应该禁用Feature1-4组合框中的“ Layer_0”源项目,以便用户无法在其他组合框中选择此项目

以下是我认为需要完成的事项:

  1. combobox_SelectedIndexChanged的事件处理程序-绑定到FeaturesView.xaml和FeaturesViewModel.cs中的组合框。 可以使用一个事件事件处理程序(仍在研究中)吗?
  2. 如何维护已启用控件的选定项目的源项目列表,并禁用源项目列表中的所有选定项目?

向我指出正确方向的任何帮助将不胜感激。

下面的代码文件-您将在下面找到我迄今为止在使用UI时所拥有的不同项目文件。显然,不起作用的部分是禁用选定的功能名称。

MainWindow.xaml

<Window
    x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:views="clr-namespace:WpfApp1.Views"
    Title="MainWindow"
    Width="300"
    Height="450"
    mc:Ignorable="d">

    <Grid>
        <views:FeatureView />
    </Grid>
</Window>

FeatureView.xaml

<UserControl
    x:Class="WpfApp1.Views.FeatureView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfApp1.Views"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <UserControl.Resources>
        <Style x:Key="MyRadioButtonTemplate" TargetType="RadioButton">
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="Margin" Value="5" />
        </Style>
        <ControlTemplate x:Key="ComboWithHeader" TargetType="ContentControl">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Label
                    Grid.Column="0"
                    Margin="2"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Center"
                    Content="{TemplateBinding Content}"
                    IsTabStop="False"
                    Target="comboBox" />

                <ComboBox
                    x:Name="comboBox"
                    Grid.Column="1"
                    Margin="2"
                    IsEnabled="True"
                    IsTabStop="True"
                    ItemsSource="{Binding Path=Features, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

            </Grid>
        </ControlTemplate>
        <BooleanToVisibilityConverter x:Key="BoolToVis" />
    </UserControl.Resources>

    <Grid>
        <StackPanel Margin="5" Orientation="Vertical">

            <GroupBox Header="Analysis Mode">
                <Grid Margin="2" HorizontalAlignment="Stretch">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <RadioButton
                        x:Name="Mode1"
                        Grid.Column="0"
                        Content="Mode 1"
                        IsChecked="True"
                        Style="{StaticResource MyRadioButtonTemplate}" />

                    <RadioButton
                        x:Name="Mode2"
                        Grid.Column="1"
                        Content="Mode2"
                        Style="{StaticResource MyRadioButtonTemplate}" />

                </Grid>
            </GroupBox>

            <GroupBox Header="Line Selection">
                <StackPanel>
                    <ContentControl
                        Name="Feature0"
                        Content="Feature 0"
                        Template="{StaticResource ComboWithHeader}" />
                    <Button
                        Width="60"
                        Margin="5"
                        HorizontalAlignment="Right"
                        Content="Select" />

                </StackPanel>
            </GroupBox>

            <GroupBox
                Header="Mode1 Layer Names"
                IsEnabled="{Binding ElementName=Mode1, Path=IsChecked}"
                Visibility="{Binding ElementName=Mode1, Path=IsChecked, Converter={StaticResource BoolToVis}}">
                <StackPanel>
                    <ContentControl
                        Name="Feature1"
                        Content="Feature 1"
                        Template="{StaticResource ComboWithHeader}" />
                    <ContentControl
                        Name="Feature2"
                        Content="Feature 2"
                        Template="{StaticResource ComboWithHeader}" />
                    <ContentControl
                        Name="Feature3"
                        Content="Feature 3"
                        Template="{StaticResource ComboWithHeader}" />
                    <ContentControl
                        Name="Feature4"
                        Content="Feature 4"
                        Template="{StaticResource ComboWithHeader}"
                        Visibility="{Binding ElementName=IncludeFeature4, Path=IsChecked, Converter={StaticResource BoolToVis}}" />
                    <CheckBox
                        Name="IncludeFeature4"
                        Margin="2"
                        Content="Include Feature 4" />
                </StackPanel>
            </GroupBox>

            <GroupBox
                Header="Mode2 Layer Names"
                IsEnabled="{Binding ElementName=Mode2, Path=IsChecked}"
                Visibility="{Binding ElementName=Mode2, Path=IsChecked, Converter={StaticResource BoolToVis}}">
                <StackPanel>
                    <ContentControl
                        Name="Feature5"
                        Content="Feature 5"
                        Template="{StaticResource ComboWithHeader}" />
                    <ContentControl
                        Name="Feature6"
                        Content="Feature 6"
                        Template="{StaticResource ComboWithHeader}" />

                </StackPanel>
            </GroupBox>

        </StackPanel>

    </Grid>
</UserControl>

FeatureView.xaml.cs

namespace WpfApp1.Views
{
    using System.Windows.Controls;
    using WpfApp1.ViewModels;

    /// <summary>
    /// Interaction logic for MultiComboBoxes.xaml
    /// </summary>
    public partial class FeatureView : UserControl
    {
        public FeatureView()
        {
            InitializeComponent();
            this.DataContext = new FeatureViewModel();
        }
    }
}

FeatureModel.cs

namespace WpfApp1.Models
{
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;

    public class FeatureModel : INotifyPropertyChanged
    {
        private enum FeatureList
        {
            Layer_0,
            Layer_1,
            Layer_2,
            Layer_3,
            Layer_4,
            Layer_5,
            Layer_6
        }

        public ObservableCollection<string> Features = new ObservableCollection<string>(Enum.GetNames(typeof(FeatureList)));

        private string featureName;

        public string FeatureName
        {
            get
            {
                return featureName;
            }

            set
            {
                if (featureName != value)
                {
                    featureName = value;
                    RaisePropertyChanged("FeatureName");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}

FeatureViewModel.cs

namespace WpfApp1.ViewModels
{
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using WpfApp1.Models;

    public class FeatureViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<string> AllFeatures { get; set; }
        public ObservableCollection<string> Features { get; set; }

        public FeatureViewModel()
        {
            FeatureModel featureModel = new FeatureModel();
            Features = featureModel.Features;
            AllFeatures = Features;
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Need to implement this interface in order to get data binding
        /// to work properly.
        /// </summary>
        /// <param name="propertyName"></param>
        private void NotifyPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion INotifyPropertyChanged Members
    }
}

1 个答案:

答案 0 :(得分:0)

在经过反复试验和更多在线研究之后,我将为我的问题提供部分解决方案。也许将来会对别人有帮助。

该解决方案与对原始问题的质疑不同,我想如何实现它;但是,它确实提供了可行的解决方案。我看到的这种实现方式的主要问题是,如果添加更多的组合框,它需要我认为是多余的代码。

实施更改:

  1. 用于选择分析模式的单选按钮-与绑定的基本问题无关
  2. 禁用项目-本来不错,但现在从列表中删除项目更加容易。
  3. 将多个组合框绑定到一个可观察的集合,已替换为每个组合框的IEnumerable源项。
  4. 在每个组合框旁边添加了一个按钮,以“清除”所选项目。

我敢肯定,有一种更好的,更多的 MVVM'ish 方式(这是我希望通过发布此问题来找到的方式),但是经过大量的研究和在线阅读了许多主题为了解决这个问题,由于缺乏专业知识,我没有成功。也许有专业知识的人可以适当地修改此解决方案。

下面是与四个组合框一起使用的代码。有一个项目无法正常运行。可以使用“空列表”项目,但是当从每个列表中删除选定的项目时,应该忽略该空项目(仍在寻找解决方案)。

MainWindow.xaml:

<Window
    x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:prop="clr-namespace:WpfApp1.Properties"
    xmlns:views="clr-namespace:WpfApp1.Views"
    Title="MainWindow"
    Width="350"
    Height="700"
    mc:Ignorable="d">

    <Grid>
        <StackPanel>
            <views:FeatureView />
        </StackPanel>

    </Grid>

</Window>

MainWindow.xaml.cs:

using System;
using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

FeatureView.xaml:

<UserControl
    x:Class="WpfApp1.Views.FeatureView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="clr-namespace:WpfApp1.ViewModels"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <UserControl.Resources>

        <!--  MyComboBoxStyle  -->
        <Style x:Key="MyComboBoxStyle" TargetType="{x:Type ComboBox}">
            <Setter Property="Tag" Value="" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ComboBox}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="120" />
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>

                            <Label
                                Grid.Column="0"
                                Width="Auto"
                                Margin="2"
                                HorizontalAlignment="Left"
                                VerticalAlignment="Center"
                                Content="{TemplateBinding Tag}"
                                Foreground="Black"
                                IsTabStop="False" />

                            <ComboBox
                                Name="cmbBox"
                                Grid.Column="1"
                                Margin="2"
                                Foreground="Black"
                                ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ItemsSource}"
                                SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem}" />

                            <Button
                                Grid.Column="2"
                                Padding="2"
                                Command="{Binding OnComboBox_ClearSelection}"
                                CommandParameter="{Binding ElementName=cmbBox}"
                                Style="{DynamicResource MyButtonTemplate}" />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!--  MyButtonTemplate  -->
        <Style x:Key="MyButtonTemplate" TargetType="{x:Type Button}">
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="Margin" Value="5" />
            <Setter Property="Height" Value="15" />
            <Setter Property="Width" Value="15" />
            <Setter Property="IsTabStop" Value="True" />
            <Setter Property="IsEnabled" Value="True" />
            <Setter Property="HorizontalAlignment" Value="Right" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Border
                                Name="border"
                                Padding="4,2"
                                Background="{TemplateBinding Background}"
                                BorderBrush="DarkGray"
                                BorderThickness="1"
                                CornerRadius="3">
                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
                            </Border>
                            <TextBlock Text="X"
                                       HorizontalAlignment="Center"
                                       VerticalAlignment="Center"/>

                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="border" Property="BorderBrush" Value="Black" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>

        </Style>

        <!--  MyLabelTemplate  -->
        <Style x:Key="MyLabelTemplate" TargetType="{x:Type Label}">
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="Margin" Value="2" />
            <Setter Property="Width" Value="Auto" />
            <Setter Property="HorizontalAlignment" Value="Right" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="IsTabStop" Value="False" />
        </Style>

        <!--  MyTextBlockTemplate  -->
        <Style x:Key="MyTextBlockTemplate" TargetType="{x:Type TextBlock}">
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="Margin" Value="2" />
            <Setter Property="IsEnabled" Value="True" />
            <Setter Property="HorizontalAlignment" Value="Center" />
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>

    </UserControl.Resources>


    <!--  Main Layout  -->
    <StackPanel Margin="5" Orientation="Vertical">

        <!--  Feature 1  -->
        <ComboBox
            Name="Feature1View"
            ItemsSource="{Binding Feature1Items}"
            SelectedItem="{Binding Feature1SelectedValue}"
            Style="{StaticResource MyComboBoxStyle}"
            Tag="Feature 1" />

        <!--  Feature 2  -->
        <ComboBox
            Name="Feature2View"
            ItemsSource="{Binding Feature2Items}"
            SelectedItem="{Binding Feature2SelectedValue}"
            Style="{StaticResource MyComboBoxStyle}"
            Tag="Feature 2" />


        <!--  Feature 3  -->
        <ComboBox
            Name="Feature3View"
            ItemsSource="{Binding Feature3Items}"
            SelectedItem="{Binding Feature3SelectedValue}"
            Style="{StaticResource MyComboBoxStyle}"
            Tag="Feature 3" />

        <!--  Feature 4  -->
        <ComboBox
            Name="Feature4View"
            ItemsSource="{Binding Feature4Items}"
            SelectedItem="{Binding Feature4SelectedValue}"
            Style="{StaticResource MyComboBoxStyle}"
            Tag="Feature 4" />

        <Rectangle
            Width="Auto"
            Height="5"
            Fill="#FFF4F4F5"
            Stroke="Black" />

        <!--  Feature 1 Selected  -->
        <DockPanel>
            <Label Content="Feature 1 Selected" Style="{StaticResource MyLabelTemplate}" />
            <TextBlock
                x:Name="Feature1Selected"
                Style="{StaticResource MyTextBlockTemplate}"
                Text="{Binding Feature1SelectedValue, Mode=OneWay}" />
        </DockPanel>

        <!--  Feature 2 Selected  -->
        <DockPanel>
            <Label Content="Feature 2 Selected" Style="{StaticResource MyLabelTemplate}" />

            <TextBlock
                x:Name="Feature2Selected"
                Style="{StaticResource MyTextBlockTemplate}"
                Text="{Binding Feature2SelectedValue, Mode=OneWay}" />
        </DockPanel>

        <!--  Feature 3 Selected  -->
        <DockPanel>
            <Label Content="Feature 3 Selected" Style="{StaticResource MyLabelTemplate}" />

            <TextBlock
                x:Name="Feature3Selected"
                Style="{StaticResource MyTextBlockTemplate}"
                Text="{Binding Feature3SelectedValue, Mode=OneWay}" />
        </DockPanel>

        <!--  Feature 4 Selected  -->
        <DockPanel>
            <Label Content="Feature 4 Selected" Style="{StaticResource MyLabelTemplate}" />

            <TextBlock
                x:Name="Feature4Selected"
                Style="{StaticResource MyTextBlockTemplate}"
                Text="{Binding Feature4SelectedValue, Mode=OneWay}" />
        </DockPanel>

    </StackPanel>
</UserControl>

FeatureView.xaml.cs:

namespace WpfApp1.Views
{
    using System.Windows.Controls;
    using WpfApp1.ViewModels;

    /// <summary>
    /// Interaction logic for MultiComboBoxes.xaml
    /// </summary>
    public partial class FeatureView : UserControl
    {
        public FeatureView()
        {
            InitializeComponent();
            this.DataContext = new FeatureViewModel();
        }
    }
}

FeatureViewModel.cs:

namespace WpfApp1.ViewModels
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Windows.Controls;
    using System.Windows.Input;
    using WpfApp1.Models;

    public class FeatureViewModel : INotifyPropertyChanged
    {
        #region Definitions

        #region enum

        private enum MyFeatureList
        {
            Layer_1,
            Layer_2,
            Layer_3,
            Layer_4
        }

        #endregion enum

        #region PrivateProperties

        private readonly List<string> features = new List<string>();

        #region AddFeatures

        private ObservableCollection<Feature> AddFeatures
        {
            get
            {
                var features = new ObservableCollection<Feature>
            {
                // add blank feature
                new Feature("", true)
            };
                foreach (var feature in Enum.GetNames(typeof(MyFeatureList)))
                {
                    features.Add(new Feature(feature, true));
                }
                return features;
            }
        }

        #endregion AddFeatures

        #endregion PrivateProperties

        #region PublicProperties

        public IEnumerable<string> Feature1Items => features.Where(o => o != Feature2SelectedValue && o != Feature3SelectedValue && o != Feature4SelectedValue);

        public IEnumerable<string> Feature2Items => features.Where(o => o != Feature1SelectedValue && o != Feature3SelectedValue && o != Feature4SelectedValue);

        public IEnumerable<string> Feature3Items => features.Where(o => o != Feature1SelectedValue && o != Feature2SelectedValue && o != Feature4SelectedValue);

        public IEnumerable<string> Feature4Items => features.Where(o => o != Feature1SelectedValue && o != Feature2SelectedValue && o != Feature3SelectedValue);

        #endregion PublicProperties

        #region Constructor

        public FeatureViewModel()
        {
            features = AddFeatures.Select(c => c.FeatureName).ToList();
            OnComboBox_ClearSelection = new BaseCommand(ClearSelection);
        }

        #endregion Constructor

        #region Feature1SelectedValue

        protected string feature1SelectedValue;

        /// <summary>
        /// Feature 1 selected value
        /// </summary>
        public string Feature1SelectedValue
        {
            get { return feature1SelectedValue; }
            set
            {
                if (feature1SelectedValue != value)
                {
                    feature1SelectedValue = value;
                    OnPropertyChanged();
                    OnPropertyChanged(nameof(Feature2Items));
                    OnPropertyChanged(nameof(Feature3Items));
                    OnPropertyChanged(nameof(Feature4Items));
                }
            }
        }

        #endregion Feature1SelectedValue

        #region Feature2SelectedValue

        protected string feature2SelectedValue;

        /// <summary>
        /// Feature 1 selected value
        /// </summary>
        public string Feature2SelectedValue
        {
            get { return feature2SelectedValue; }
            set
            {
                if (feature2SelectedValue != value)
                {
                    feature2SelectedValue = value;
                    OnPropertyChanged();
                    OnPropertyChanged(nameof(Feature1Items));
                    OnPropertyChanged(nameof(Feature3Items));
                    OnPropertyChanged(nameof(Feature4Items));
                }
            }
        }

        #endregion Feature2SelectedValue

        #region Feature3SelectedValue

        protected string feature3SelectedValue;

        /// <summary>
        /// Feature 1 selected value
        /// </summary>
        public string Feature3SelectedValue
        {
            get { return feature3SelectedValue; }
            set
            {
                if (feature3SelectedValue != value)
                {
                    feature3SelectedValue = value;
                    OnPropertyChanged();
                    OnPropertyChanged(nameof(Feature1Items));
                    OnPropertyChanged(nameof(Feature2Items));
                    OnPropertyChanged(nameof(Feature4Items));
                }
            }
        }

        #endregion Feature3SelectedValue

        #region Feature4SelectedValue

        protected string feature4SelectedValue;

        /// <summary>
        /// Feature 1 selected value
        /// </summary>
        public string Feature4SelectedValue
        {
            get { return feature4SelectedValue; }
            set
            {
                if (feature4SelectedValue != value)
                {
                    feature4SelectedValue = value;
                    OnPropertyChanged();
                    OnPropertyChanged(nameof(Feature1Items));
                    OnPropertyChanged(nameof(Feature2Items));
                    OnPropertyChanged(nameof(Feature3Items));
                }
            }
        }

        #endregion Feature4SelectedValue

        #endregion Definitions

        #region Methods

        public ICommand OnComboBox_ClearSelection { get; }

        public void ClearSelection(object sender)
        {
            ComboBox combobox = sender as ComboBox;
            combobox.SelectedItem = null;
        }

        #endregion Methods

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Need to implement this interface in order to get data binding
        /// to work properly.
        /// </summary>
        /// <param name="propertyName"></param>
        private void NotifyPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion INotifyPropertyChanged Members
    }

    #region BaseCommand

    public class BaseCommand : ICommand
    {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _method;

        public event EventHandler CanExecuteChanged;

        public BaseCommand(Action<object> method)
            : this(method, null)
        {
        }

        public BaseCommand(Action<object> method, Predicate<object> canExecute)
        {
            _method = method;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
            {
                return true;
            }

            return _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _method.Invoke(parameter);
        }

        #endregion BaseCommand
    }
}

Features.cs:

namespace WpfApp1.Models
{
    using System.ComponentModel;

    public class Feature : INotifyPropertyChanged
    {
        public Feature()
        {
        }

        public Feature(string featureName, bool isSelected)
        {
            this.featureName = featureName;
            this.isSelected = isSelected;
        }

        private string featureName;

        public string FeatureName
        {
            get
            {
                return featureName;
            }

            set
            {
                if (featureName != value)
                {
                    featureName = value;
                    RaisePropertyChanged("FeatureName");
                }
            }
        }

        private bool isSelected;

        public bool IsSelected
        {
            get
            {
                return isSelected;
            }

            set
            {
                if (isSelected != value)
                {
                    isSelected = value;
                    RaisePropertyChanged("IsSelected");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string property)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }
}