组合框的WPF SelectedValue不从viewmodel属性更新

时间:2017-07-06 13:31:21

标签: c# wpf mvvm combobox

我的MainWindow中有以下组合框:

<ComboBox SelectedValue="{Binding LanguageId}" SelectedValuePath="Id" DisplayMemberPath="Name" ItemsSource="{Binding Languages}"/>

在我的ViewModel中,我设置了以下两个属性:

public List<Language> Languages
{
  get
  {
    return new List<Language>()
    {
     new Language { Id = 0, Name = ml.ml_string(100, "Language1") },
     new Language { Id = 1, Name = ml.ml_string(101, "Language2") },
     new Language { Id = 2, Name = ml.ml_string(102, "Language3") },
     new Language { Id = 3, Name = ml.ml_string(103, "Language4") }
    };
  }
}

public int LanguageId
{
  get
  {
    return _languageId;
  }
  set
  {
    _languageId = value;
    NotifyPropertyChanged("Languages");
    NotifyPropertyChanged();
  }
}

所以我想要的是在我在组合框中选择一种语言后通知我的语言属性,但是当我这样做时,它根本没有显示任何值(见图片): combobox error 我该如何解决这个问题?

1 个答案:

答案 0 :(得分:0)

必须将所选项目视为与集合中的项目相同。但是,您总是从Languages返回一组新对象,因此当选择更改时,您将ComboBox.ItemsSource设置为不包含刚刚选择的对象的新集合。因此ComboBox会将所选项目值丢弃为无效。正如您所发现的,使用SelectedValuePath无法解决此问题。

您可以通过覆盖Equals(Language)上的Language来解决此问题,或者通过永久保留Languages并重新使用它们来更好地解决此问题。

如果您需要翻译Name属性,则可以执行with a value converter

我建议不要覆盖Equals()的原因是,在C#中,您习惯于假设比较引用类型的两个实例意味着Object.ReferenceEquals(),并且打破该假设可能会导致错误。我是根据经验说的。

由于我们不再使用Name属性,我们可以将Languages更改为List<int>语言ID - 在这种情况下,我们可以将其设为只读和从来不打算重新创建列表。我在这里做过。

public class LanguageNameConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[0] is int)
        {
            var itemLanguageId = (int)values[0];
            var displayLanguageId = (int)values[1];

            //  Do stuff here to get the translated name of the item language
            var itemLanguageNameTranslated = $"Name of language {itemLanguageId} in language {displayLanguageId}";

            return itemLanguageNameTranslated;
        }
        else return values[0]?.GetType();
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

XAML:

<ComboBox 
    SelectedValue="{Binding LanguageId}" 
    ItemsSource="{Binding Languages}"
    >
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <Label>
                <Label.Content>
                    <MultiBinding 
                        Converter="{StaticResource LanguageNameConverter}"
                        >
                        <Binding Path="." />
                        <Binding 
                            Path="DataContext.LanguageId" 
                            RelativeSource="{RelativeSource AncestorType=ComboBox}" 
                            />
                    </MultiBinding>
                </Label.Content>
            </Label>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

如果您希望将Language课程保留在Languages中,只需在ComboBox上恢复SelectedValuePath="Id",并设置第一个Path Binding 1}}在MultiBindingId而不是.

<ComboBox 
    SelectedValue="{Binding LanguageId}" 
    SelectedValuePath="Id"
    ...

...

<MultiBinding 
    Converter="{StaticResource LanguageNameConverter}"
    >
    <Binding Path="Id" />
    <Binding 
        Path="DataContext.LanguageId" 
        RelativeSource="{RelativeSource AncestorType=ComboBox}" 
        />
</MultiBinding>

等于覆盖解决方案

这是Equals()解决方案;我们尝试了但是在选择更改后,保留旧的SelectedItem,其旧名称是以前选择的语言时出现问题。那是因为Equals()覆盖的事情。所以我们选择了多值转换器,它不需要任何变通办法或有趣的业务。

按照WPF想要的方式做事,没有人受伤。这是规则。

public class Language
{
    public int Id { get; set; }
    public String Name { get; set; }

    public override bool Equals(object obj)
    {
        if (obj is Language)
        {
            return ((Language)obj).Id == Id;
        }
        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

如果您选择此解决方案,则还需要向LanuageId属性添加递归防护:

private int _languageId;
public int LanguageId
{
    get
    {
        return _languageId;
    }
    set
    {
        if (_languageId != value)
        {
            _languageId = value;
            OnPropertyChanged("Languages");
            OnPropertyChanged();
        }
    }
}

现在替换ComboBox.ItemsSource会导致实际SelectedItem更改为新的对象实例,更新SelectedValue会导致LanguageId再次设置 - 到它已经具有相同的价值。如果省略递归防护,LanguageId的{​​{1}}阻止将再次为set引发PropertyChanged,这将更新Languages等。 - 我们退出比赛,直到筹码溢出。