我的 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();
}
}
所以我想要的是在我在组合框中选择一种语言后通知我的 Languages 属性,但目前当我这样做时,它根本没有显示任何值(见图):组合框错误我该如何解决这个问题?
必须将所选项目视为等于集合中的项目。However, you always return a collection of new objects from Languages
, so when the selection changes, you set ComboBox.ItemsSource
to a new collection which does not contain the object that was just selected. 因此ComboBox
将所选项目值丢弃为无效。SelectedValuePath
正如您发现的那样,使用无法解决此问题。
你可以通过重写解决这一问题Equals(Language)
上Language
保持你的,或(更好)Languages
围绕永久和重用他们。
如果您需要翻译该Name
属性,您可以使用 value converter 来完成。
我建议不要覆盖的原因Equals()
是,在 C# 中,您习惯于假设比较引用类型的两个实例意味着Object.ReferenceEquals()
,而打破该假设可能会导致错误。我根据经验说话。
由于我们不再使用该Name
属性,我们可以更改Languages
为一个List<int>
Language Ids —— 在这种情况下,我们可以将其设为只读,而不必费心重新创建列表。我在这里做过。
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
,只需SelectedValuePath="Id"
在 ComboBox 上恢复,并将Path
第一个Binding
中的 设置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
更改为一个新的对象实例,即 update SelectedValue
,这将导致LanguageId
再次设置——与它已经拥有的值相同。如果省略了递归后卫,LanguageId
的set
块将提高PropertyChanged
对Languages
再次将更新SelectedItem
,等等-我们是去比赛,直到堆栈溢出。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句