我有一个位于 ElementHost 中的 WPF 用户控件,因此可以在 WinForms 应用程序中使用它。在这个 WPF 用户控件上有一个 ComboBox,我试图将一个相当简单的自定义类的 ObservableCollection 数据绑定到它。这个 ObservableCollection 是我的“视图模型”类(MVVM 方法并不完美)的公共属性,我将其绑定到用户控件(视图),并且我有一个在设计时绑定的模拟子类。
ComboBox 在 XAML 设计器中完全按照预期呈现,但在运行时它完全是空的,下拉区域大约 3 行高,无论我向其中添加多少项目(项目数量在应用程序的生命周期中永远不会改变) )。还有其他控件,如 TextBlocks 和自制的 NumericUpDown,可以绑定到其他属性并在运行时运行良好,但 ComboBox 不会配合。
另一件事,如果重要的话——视图模型实例是从文件反序列化的。我还没有在(反)序列化中包含我添加到它的属性,但这很重要,因为将创建实例,然后我必须调用一个函数来完成初始化,因为构造函数被绕过,并且完成-up 包括初始化项目的数据绑定集合。我不知道这是否与问题有关,但我想我会提到它,以防万一。
项目类别。如果用户选择新语言,描述文本可能会更改:
namespace Company._DataBinding {
public class FlavorOption {
public event PropertyChangedEventHandler PropertyChanged;
private string _descriptionText;
public EnumFlavor Flavor { get; set; }
public string DescriptionText {
get { return _descriptionText; }
set {
if (_descriptionText != value) {
_descriptionText = value;
try {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("DescriptionText"));
} catch (Exception anException) {
throw anException;
}
}
}
}
}
精简了用户控件的 XAML。代码隐藏在构造函数内调用InitializeComponent并实现IProcessOrder。否则,它不会触及任何内容,包括 JuiceChoicesText、JuiceFlavorOptions、ChosenJuice 或此处的控件:
<UserControl x:Class="Company._Specifics.JuiceOrderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Company"
xmlns:bound="clr-namespace:Company._DataBinding"
mc:Ignorable="d"
d:DesignHeight="405" d:DesignWidth="612">
<Grid Background="White" d:DataContext="{d:DesignInstance Type=bound:MockProtoVM, IsDesignTimeCreatable=True}">
<StackPanel>
<TextBlock Text="{Binding JuiceChoicesText}" Grid.Column="2" />
<ComboBox Margin="5" ItemsSource="{Binding JuiceFlavorOptions}"
SelectedValuePath="Flavor" DisplayMemberPath="DescriptionText"
SelectedValue="{Binding ChosenJuice, Mode=TwoWay}" />
</StackPanel>
</Grid>
相关“视图模型”代码:
namespace Maf._Specifics {
public class ProtoVM : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string _juiceChoicesText;
private EnumFlavor _chosenJuice;
public ObservableCollection<FlavorOption> JuiceFlavorOptions { get; set; }
public string JuiceChoicesText {
get { return _juiceChoicesText; }
set {
if (_juiceChoicesText != value) {
_juiceChoicesText = value;
NotifyListeners();
}
}
}
public EnumFlavor ChosenJuice {
get { return _chosenJuice; }
set {
if (_chosenJuice != value) {
_chosenJuice = value;
NotifyListeners();
}
}
}
public ProtoVM() {
FinishInitialization();
}
public void FinishInitialization() {
JuiceChoicesText = "Choose your juice flavor:";
if (JuiceFlavorOptions == null) {
JuiceFlavorOptions = new ObservableCollection<FlavorOption> {
new FlavorOption { Flavor = EnumFlavor.CHERRY, DescriptionText = "Cherry" },
new FlavorOption { Flavor = EnumFlavor.ORANGE, DescriptionText = "Orange" },
new FlavorOption { Flavor = EnumFlavor.GRAPE, DescriptionText = "Grape" }
};
ChosenJuice = EnumFlavor.CHERRY;
}
protected void NotifyListeners([CallerMemberName] string propertyName = "") {
try {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} catch (Exception anException) {
throw anException;
}
}
}
}
在其父控件中设置用户控件。数据绑定后,不会向 ComboBox(的 DataContext)添加或删除任何选项:
var juiceOrderHost = new WPFWrapperUC();
var orderJuiceTab = new TabPage(tabName);
_juiceOrderView = new JuiceOrderView();
juiceOrderHost.SetChild(_juiceOrderView);
orderJuiceTab.Controls.Add((UserControl)juiceOrderHost);
tbcThingsToOrder.TabPages.Add(orderJuiceTab);
...
_juiceOrderKindaVM.FinishInitialization(); // _juiceOrderKindaVM is a ProtoVM
_juiceOrderView.DataContext = _juiceOrderKindaVM;
WinForms UserControl,其上只有一个 System.Windows.Forms.Integration.ElementHost:
namespace Company {
public partial class WPFWrapperUC : UserControl, IProcessOrder {
public WPFWrapperUC(UIElement hosting = null) {
InitializeComponent();
if (hosting != null)
SetChild(hosting);
}
public void SetChild(UIElement hosting) {
elhHostForWPFControl.Child = hosting;
}
// ...various methods that forward calls to
// IProcessOrder methods to elhHostForWPFControl.Child,
// should it be a IProcessOrder, which JuiceOrderView is.
}
}
模拟:
namespace Company._DataBinding {
public class MockProtoVM : ProtoVM {
public MockProtoVM() : base() {
JuiceChoicesText = "User sees different text at runtime, like 'Choose your juice flavor:'";
ChosenJuice = EnumFlavor.ORANGE;
}
}
}
那么,我错过了什么?谢谢...
经过更多研究/吐槽,事实证明,在执行上面所示的 DataContext 分配之前,在调用 FinishInitialization 之前,DataContext 先前已分配给 ProtoVM 实例,因此 JuiceFlavorOptions 为 null。然后它在同一个实例上再次调用 FinishInitialization,但显然设置 JuiceFlavorOptions 还不够?然后,它将实例分配给 DataContext,这修复了 TextBlock,但没有修复 ComboBox。至少,这是我对发生的事情的最好猜测。
此外,我对 FlavorOption.DescriptionText 的翻译/交换需要的不仅仅是迭代 JuiceFlavorOptions 和更新每个 FlavorOption 的 DescriptionText 值。当选择新选项时,这将显示正确的文本,但下拉列表仍将包含所有旧选项的文本。您必须清除 JuiceFlavorOptions 并使用新的 FlavorOption 实例及其新的 DescriptionText 值重新填充它。为了防止 ComboBox 看起来未选中,您必须调用 ChosenJuice 上的属性更改委托来完全更新绑定:
JuiceFlavorOptions.Clear();
// French
JuiceFlavorOptions.Add(new FlavorOption { Flavor = EnumFlavor.CHERRY, DescriptionText = "Cerise" });
JuiceFlavorOptions.Add(new FlavorOption { Flavor = EnumFlavor.ORANGE, DescriptionText = "Orange" });
JuiceFlavorOptions.Add(new FlavorOption { Flavor = EnumFlavor.GRAPE, DescriptionText = "Raisin" });
NotifyListeners("ChosenJuice");
希望这可以帮助那些设法进入我的奇怪情况的人。