这是我的第一篇文章...所以我希望我做对了。我正在使用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”源项目,以便用户无法在其他组合框中选择此项目
以下是我认为需要完成的事项:
向我指出正确方向的任何帮助将不胜感激。
下面的代码文件-您将在下面找到我迄今为止在使用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
}
}
答案 0 :(得分:0)
在经过反复试验和更多在线研究之后,我将为我的问题提供部分解决方案。也许将来会对别人有帮助。
该解决方案与对原始问题的质疑不同,我想如何实现它;但是,它确实提供了可行的解决方案。我看到的这种实现方式的主要问题是,如果添加更多的组合框,它需要我认为是多余的代码。
实施更改:
我敢肯定,有一种更好的,更多的 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));
}
}
}