WPF ComboBox:将SelectedItem设置为不在ItemsSource中的项目 – >绑定奇怪

我想实现以下目标:我想要一个显示可用COM端口的ComboBox。 在启动时(并单击“刷新”按钮)我想获得可用的COM端口并将选择设置为最后选择的值(来自应用程序设置)。

如果设置(最后一个COM端口)中的值不在值列表(可用COM端口)中,则会发生以下情况:

虽然ComboBox没有显示任何内容(知道新的SelectedItem不在ItemsSource中“足够聪明”),但ViewModel更新为“无效值”。 我实际上期望Binding具有与ComboBox显示的值相同的值。

用于演示目的的代码:

MainWindow.xaml:

         

MainWindow.xaml.cs:

  // usings removed namespace DemoComboBinding { public partial class MainWindow : Window { //... private void Button_Click(object sender, RoutedEventArgs e) { combo.SelectedItem = "COM4"; // would be setting from Properties } } } 

视图模型:

  namespace DemoComboBinding { class DemoViewModel : INotifyPropertyChanged { string selected; string[] source = { "COM1", "COM2", "COM3" }; public string[] Source { get { return source; } set { source = value; } } public string Selected { get { return selected; } set { if(selected != value) { selected = value; OnpropertyChanged("Selected"); } } } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; void OnpropertyChanged(string propertyname) { var handler = PropertyChanged; if(handler != null) { handler(this, new PropertyChangedEventArgs(propertyname)); } } #endregion } } 

我最初提出的一个解决方案是检查Selected setter内是否要设置的值在可用COM端口列表内(如果没有,设置为空字符串并发送OPC)。

我想知道:为什么会这样? 还有其他我没有看到的解决方案吗?

简而言之,您不能将SelectedItem设置为值,而不是ItemsSource的值。 AFAIK,这是所有Selector后代的默认行为,这是相当明显的:设置SelectedItem不仅是一个数据更改,这也应该导致一些视觉后果,如生成一个项目容器和重新绘制项目(所有这些东西操纵ItemsSource )。 你在这里做的最好的是这样的代码:

 public DemoViewModel() { selected = Source.FirstOrDefault(s => s == yourValueFromSettings); } 

另一个选项是允许用户通过使其可编辑在ComboBox输入任意值。

我意识到这对你有所帮助,但我希望它至少可以帮到某些人。 对不起,如果有拼写错误,我必须在记事本中键入:

ComboBoxAdaptor.cs:

  using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Markup; namespace Adaptors { [ContentProperty("ComboBox")] public class ComboBoxAdaptor : ContentControl { #region Protected Properties protected bool IsChangingSelection { get; set; } protected ICollectionView CollectionView { get; set; } #endregion #region Dependency Properties public static readonly DependencyProperty ComboBoxProperty = DependencyProperty.Register("ComboBox", typeof(ComboBox), typeof(ComboBoxAdaptor), new FrameworkPropertyMetadata(new PropertyChangedCallback(ComboBox_Changed))); private static void ComboBox_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { var theComboBoxAdaptor = (ComboBoxAdaptor)d; theComboBoxAdaptor.ComboBox.SelectionChanged += theComboBoxAdaptor.ComboBox_SelectionChanged; } public ComboBox ComboBox { get { return (ComboBox)GetValue(ComboBoxProperty); } set { SetValue(ComboBoxProperty, value); } } public static readonly DependencyProperty NullItemProperty = DependencyProperty.Register("NullItem", typeof(object), typeof(ComboBoxAdaptor), new PropertyMetadata("(None)")); public object NullItem { get { return GetValue(NullItemProperty); } set { SetValue(NullItemProperty, value); } } public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ComboBoxAdaptor), new FrameworkPropertyMetadata(new PropertyChangedCallback(ItemsSource_Changed))); public IEnumerable ItemsSource { get { return (IEnumerable)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(ComboBoxAdaptor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(SelectedItem_Changed))); public object SelectedItem { get { return GetValue(SelectedItemProperty); } set { SetValue(SelectedItemProperty, value); } } public static readonly DependencyProperty AllowNullProperty = DependencyProperty.Register("AllowNull", typeof(bool), typeof(ComboBoxAdaptor), new PropertyMetadata(true, AllowNull_Changed)); public bool AllowNull { get { return (bool)GetValue(AllowNullProperty); } set { SetValue(AllowNullProperty, value); } } #endregion #region static PropertyChangedCallbacks static void ItemsSource_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { ComboBoxAdaptor adapter = (ComboBoxAdaptor)d; adapter.Adapt(); } static void AllowNull_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { ComboBoxAdaptor adapter = (ComboBoxAdaptor)d; adapter.Adapt(); } static void SelectedItem_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { ComboBoxAdaptor adapter = (ComboBoxAdaptor)d; if (adapter.ItemsSource != null) { //If SelectedItem is changing from the Source (which we can tell by checking if the //ComboBox.SelectedItem is already set to the new value), trigger Adapt() so that we //throw out any items that are not in ItemsSource. object adapterValue = (e.NewValue ?? adapter.NullItem); object comboboxValue = (adapter.ComboBox.SelectedItem ?? adapter.NullItem); if (!object.Equals(adapterValue, comboboxValue)) { adapter.Adapt(); adapter.ComboBox.SelectedItem = e.NewValue; } //If the NewValue is not in the CollectionView (and therefore not in the ComboBox) //trigger an Adapt so that it will be added. else if (e.NewValue != null && !adapter.CollectionView.Contains(e.NewValue)) { adapter.Adapt(); } } } #endregion #region Misc Callbacks void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (ComboBox.SelectedItem == NullItem) { if (!IsChangingSelection) { IsChangingSelection = true; try { int selectedIndex = ComboBox.SelectedIndex; ComboBox.SelectedItem = null; ComboBox.SelectedIndex = -1; ComboBox.SelectedIndex = selectedIndex; } finally { IsChangingSelection = false; } } } object newVal = (ComboBox.SelectedItem == null ? null : ComboBox.SelectedItem); if (!object.Equals(SelectedItem, newVal)) { SelectedItem = newVal; } } void CollectionView_CurrentChanged(object sender, EventArgs e) { if (AllowNull && (ComboBox != null) && (((ICollectionView)sender).CurrentItem == null) && (ComboBox.Items.Count > 0)) { ComboBox.SelectedIndex = 0; } } #endregion #region Methods protected void Adapt() { if (CollectionView != null) { CollectionView.CurrentChanged -= CollectionView_CurrentChanged; CollectionView = null; } if (ComboBox != null && ItemsSource != null) { CompositeCollection comp = new CompositeCollection(); //If AllowNull == true, add a "NullItem" as the first item in the ComboBox. if (AllowNull) { comp.Add(NullItem); } //Now Add the ItemsSource. comp.Add(new CollectionContainer { Collection = ItemsSource }); //Lastly, If Selected item is not null and does not already exist in the ItemsSource, //Add it as the last item in the ComboBox if (SelectedItem != null) { List items = ItemsSource.Cast().ToList(); if (!items.Contains(SelectedItem)) { comp.Add(SelectedItem); } } CollectionView = CollectionViewSource.GetDefaultView(comp); if (CollectionView != null) { CollectionView.CurrentChanged += CollectionView_CurrentChanged; } ComboBox.ItemsSource = comp; } } #endregion } } 

如何在Xaml中使用它

    

一些笔记

如果SelectedItem更改为不在ComboBox的值,则它将添加到ComboBox (但不是ItemsSource)。 下次通过Binding更改SelectedItem ,任何不在ItemsSource项目都将从ComboBox删除。

此外, ComboBoxAdaptor允许您将Null项插入ComboBox 。 这是一个可选function,您可以通过在xaml中设置AllowNull="False"来关闭它。