Strange behaviour (or bug?) with ComboBox in WPF when changing DataContext and having bound ItemsSource and SelectedItem

大兔子大兔子 提交于 2021-01-27 06:53:01

问题


I'm trying to debug a strange error in a combobox bound to an itemssource and selecteditem. It's driving me crazy.

The problem arise when changing selected tabitem in which the combobox exists. (Actually it's only when changing the DataContext of the ComboBox). The SelectedItem-binding has a custom validationrule to give error if value is null.

The problem is that wpf calls my custom rule when switching tabitems (DataContext) and tries to validate a value of null, even though the selecteditem-source never is null. This is a problem.

This is a simplified case I made that shows the same error:

Set a breakpoint in NotNullValidationRule.Validate and see how WPF tries to validate SelectedItem as null even though it is not present in any of the view model instances.

UPDATE

After some more experimenting I've discovered that the TabControl actually is irrelevant. Even with a simple ComboBox and a button to toggle it's DataContext I get the exact same problem. I'm replacing the code example with a new version.

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace ComboBoxValidationBugTest
{
  public partial class MainWindow : Window
  {
    private Test t1, t2;

    public MainWindow()
    {
      InitializeComponent();

      t1 = new Test();
      t1.Items.Add("A");
      t1.Items.Add("B");
      t1.Items.Add("C");
      t1.SelectedItem = "A";

      t2 = new Test();
      t2.Items.Add("B");
      t2.Items.Add("C");
      t2.Items.Add("D");
      t2.SelectedItem = "B";

      ComboBox1.DataContext = t1;
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
      ComboBox1.DataContext = ComboBox1.DataContext == t1 ? t2 : t1;
    }
  }

  public class Test : INotifyPropertyChanged
  {
    private string _selectedItem;

    private ObservableCollection<string> _items = new ObservableCollection<string>();

    public ObservableCollection<string> Items
    {
      get
      {
        return _items;
      }
    }

    public string SelectedItem
    {
      get
      {
        return _selectedItem;
      }
      set
      {
        _selectedItem = value;
        OnPropertyChanged("SelectedItem");
      }
    }

    public override string ToString()
    {
      return _selectedItem;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
      PropertyChangedEventHandler handler = PropertyChanged;
      if (handler != null)
      {
        handler(this, new PropertyChangedEventArgs(propertyName));
      }
    }
  }

  public class NotNullValidationRule : ValidationRule
  {
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
      if (value == null)
      {
        return new ValidationResult(false, "Value was null");
      }

      return new ValidationResult(true, null);
    }
  }
}

And the XAML:

<Window x:Class="ComboBoxValidationBugTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:comboBoxValidationBugTest="clr-namespace:ComboBoxValidationBugTest"
            Title="MainWindow" Height="350" Width="525">
      <Grid>
        <DockPanel>
          <Button Content="Toggle DataContext" DockPanel.Dock="Top" Click="ButtonBase_OnClick" />
          <ComboBox ItemsSource="{Binding Items}" VerticalAlignment="Top" x:Name="ComboBox1">
            <ComboBox.SelectedItem>
              <Binding Path="SelectedItem">
                <Binding.ValidationRules>
                  <comboBoxValidationBugTest:NotNullValidationRule />
                </Binding.ValidationRules>
              </Binding>
            </ComboBox.SelectedItem>
          </ComboBox>
        </DockPanel>
      </Grid>
    </Window>

回答1:


The issue you are getting is due the fact TabControl in wpf is virtualized. Only the visible tab actually exists and is rendered with the selected item. So on switching tabitem, control invalidates the previous tab and renders the tab with newly selected item and hence triggers dependency property change and in turn your ValidationRule.

So basically you will have to turn off the Tab virtualization to fix this. There are some workarounds to solve this. But a good solution is provided in the article below:

http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization




回答2:


Okai, so I have had many bugs with comboBox, and found that order matters. Try this:

    <ComboBox  
    SelectedValue="{Binding Aldersgrense, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}" 
    ItemsSource="{Binding ElementName=UserControl, Path=DataContext.AldersgrenseTyper, Mode=OneTime}"
    DisplayMemberPath="Beskrivelse" SelectedValuePath="Verdi"
    Style="{StaticResource ErrorStyle}"></ComboBox>

Set x:Name=UserControl. Some bugs will be introduced using SelectedItem, order of SelectedValue and ItemSource, Mode!=OneTime in Items, and ofc the binding itself. Hope this helps someone out there.




回答3:


Apparently it's a problem with the order of how bindings are updated when a new DataContext is set. When the ItemsSource-binding gets a new DataContext it notices (in some cases) that the selected item is not present in the new list, which then goes about setting SelectedItem to null and also validates this. Then the SelectedItem-binding gets the same DataContext as ItemsSource, is updated to it's correct value but without any validation to clear out previously failed rules.

The moment I changed the order of the bindings it worked! (in xaml that is)



来源:https://stackoverflow.com/questions/18867389/strange-behaviour-or-bug-with-combobox-in-wpf-when-changing-datacontext-and-h

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!