I have a WPF user control that sits in an ElementHost so it can be used in a WinForms app. On this WPF user control is a ComboBox, to which I'm trying to data-bind an ObservableCollection of a rather simple custom class. This ObservableCollection is a public property of my "view model" class (the MVVM methodology is not perfect) which I bind to the user control (view), and I have a mock subclass to which I bind at design time.
The ComboBox renders exactly as expected in the XAML designer, but at runtime it's completely empty, with the drop-down area about 3 rows high, no matter how many items I add to it (the number of items never changes during the app's lifetime). There are other controls like TextBlocks and a homemade NumericUpDown that bind to other properties and operate just fine at runtime, it's the ComboBox that won't cooperate.
One other thing, should it matter--the view model instance is deserialized from a file. I have yet to include the properties I've added to it in the (de)serialization, but this matters because the instance will be created, then I have to call a function to finish the initialization since the constructor is bypassed, and this finish-up includes initializing the databound collection of items. I don't know if this is related to the problem, but I thought I'd mention it in case it is.
The item class. DescriptionText may change if the user selects a new language:
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;
}
}
}
}
}
Abridged XAML of the user control. The code-behind calls InitializeComponent inside the constructor and implements IProcessOrder. Otherwise, it doesn't touch anything, including JuiceChoicesText, JuiceFlavorOptions, ChosenJuice, or the controls here:
<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>
Relevant "view model" code:
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;
}
}
}
}
Setting up the user control in its parent control(s). No options are added to or removed from the ComboBox('s DataContext) after the data-bind:
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;
The WinForms UserControl, which has on it just a 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.
}
}
The mock:
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;
}
}
}
So, what am I missing? Thanks...