我的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 我该如何解决这个问题?
答案 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}}在MultiBinding
到Id
而不是.
。
<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
等。 - 我们退出比赛,直到筹码溢出。