Can anyone show me a simple working example for a WPF MVVM application to set the ItemsSource of combobox B based on the SelectedItem of ComboBox A?
It seems from what I've found on this site that it gets all too complicated all too quickly.
What's the "right" MVVM way to get it done?
Thank you.
EDIT
I updated using Didier's example.
An extract of my XAML:
<ComboBox Name="BrowserStackDesktopOS" ItemsSource="Binding Platforms.AvailableBrowserStackDesktopOSes}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopOSVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopOSVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSVersionSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowser" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowsers}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowserVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowserVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserVersionSelectedValue, Mode=TwoWay}"/>
And an example of my code behind:
public string BrowserStackDesktopOSSelectedValue {
get { return (string)GetValue(BrowserStackDesktopOSSelectedValueProperty); }
set { SetValue(BrowserStackDesktopOSSelectedValueProperty, value);
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
NotifyPropertyChanged("BrowserStackDesktopOSSelectedValue");
}
}
However when I select a value for the first ComboBox nothing happens. I am wanting the Itemsource of the next ComboBox to by populated.
What have I done wrong?
Basically you need to expose in your MVVM 2 collections of values for combo-box choices and two properties for selected values.
In the beginning only the first collection if filled with values. When the first selected value changes the second collection will be filled in with appropriate values. Here is an example implementation:
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Set the data context of the window
DataContext = new TestVM();
}
}
public class TestVM : INotifyPropertyChanged
{
#region Class attributes
protected static string[] firstComboValues = new string[] { "Choice_1", "Choice_2" };
protected static string[][] secondComboValues =
new string[][] {
new string[] { "value_1_1", "value_1_2", "value_1_3" },
new string[] { "value_2_1", "value_2_2", "value_2_3" }
};
#endregion
#region Public Properties
#region FirstSelectedValue
protected string m_FirstSelectedValue;
/// <summary>
///
/// </summary>
public string FirstSelectedValue
{
get { return m_FirstSelectedValue; }
set
{
if (m_FirstSelectedValue != value)
{
m_FirstSelectedValue = value;
UpdateSecondComboValues();
NotifyPropertyChanged("FirstSelectedValue");
}
}
}
#endregion
#region SecondSelectedValue
protected string m_SecondSelectedValue;
/// <summary>
///
/// </summary>
public string SecondSelectedValue
{
get { return m_SecondSelectedValue; }
set
{
if (m_SecondSelectedValue != value)
{
m_SecondSelectedValue = value;
NotifyPropertyChanged("SecondSelectedValue");
}
}
}
#endregion
#region FirstComboValues
protected ObservableCollection<string> m_FirstComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> FirstComboValues
{
get { return m_FirstComboValues; }
set
{
if (m_FirstComboValues != value)
{
m_FirstComboValues = value;
NotifyPropertyChanged("FirstComboValues");
}
}
}
#endregion
#region SecondComboValues
protected ObservableCollection<string> m_SecondComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> SecondComboValues
{
get { return m_SecondComboValues; }
set
{
if (m_SecondComboValues != value)
{
m_SecondComboValues = value;
NotifyPropertyChanged("SecondComboValues");
}
}
}
#endregion
#endregion
public TestVM()
{
FirstComboValues = new ObservableCollection<string>(firstComboValues);
}
/// <summary>
/// Update the collection of values for the second combo box
/// </summary>
protected void UpdateSecondComboValues()
{
int firstComboChoice;
for (firstComboChoice = 0; firstComboChoice < firstComboValues.Length; firstComboChoice++)
{
if (firstComboValues[firstComboChoice] == FirstSelectedValue)
break;
}
if (firstComboChoice == firstComboValues.Length)// just in case of a bug
SecondComboValues = null;
else
SecondComboValues = new ObservableCollection<string>(secondComboValues[firstComboChoice]);
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the associated XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="window" x:Class="Testing1.MainWindow">
<Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width=" 300">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="10"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox x:Name="FirstOne" ItemsSource="{Binding FirstComboValues}" SelectedItem="{Binding FirstSelectedValue, Mode=TwoWay}"/>
<ComboBox x:Name="SecondOne" ItemsSource="{Binding SecondComboValues}" SelectedItem="{Binding SecondSelectedValue, Mode=TwoWay}" Grid.Row="2"/>
</Grid>
</Grid>
</Window>
As you can see the SelectedValue
properties of combo boxes are binded in TwoWay mode so when SelectedValue
property of the combo box changes it changes the value on the VM side. And in FirstSelectedValue
property setter UpdateSecondComboValues()
method is called to update values for the second combo box.
EDIT:
It happens because you mixed both INotifPropertyChanged and DependencyObject. You should choose one of them. Usually you implement INotifyPropertyChanged in your VM and the code in the property setter will work.
If you inherit from DependencyObject however, you should not write any code in the setter/getter. It will never be called by the TwoWay binding. It will just call GetValue(...) internally. To be able to execute an action on DependencyProperty change you should declare it differently with a property changed handler:
#region BrowserStackDesktopOSSelectedValue
/// <summary>
/// BrowserStackDesktopOSSelectedValue Dependency Property
/// </summary>
public static readonly DependencyProperty BrowserStackDesktopOSSelectedValue Property =
DependencyProperty.Register("BrowserStackDesktopOSSelectedValue ", typeof(string), typeof(YourVM),
new FrameworkPropertyMetadata((string)null,
new PropertyChangedCallback(OnBrowserStackDesktopOSSelectedValue Changed)));
/// <summary>
/// Gets or sets the BrowserStackDesktopOSSelectedValue property. This dependency property
/// indicates ....
/// </summary>
public string BrowserStackDesktopOSSelectedValue
{
get { return (string)GetValue(BrowserStackDesktopOSSelectedValue Property); }
set { SetValue(BrowserStackDesktopOSSelectedValue Property, value); }
}
/// <summary>
/// Handles changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
private static void OnBrowserStackDesktopOSSelectedValue Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
YourVM target = (YourVM)d;
string oldBrowserStackDesktopOSSelectedValue = (string)e.OldValue;
string newBrowserStackDesktopOSSelectedValue = target.BrowserStackDesktopOSSelectedValue ;
target.OnBrowserStackDesktopOSSelectedValue Changed(oldBrowserStackDesktopOSSelectedValue , newBrowserStackDesktopOSSelectedValue );
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
protected virtual void OnBrowserStackDesktopOSSelectedValue Changed(string oldBrowserStackDesktopOSSelectedValue , string newBrowserStackDesktopOSSelectedValue )
{
//Here write some code to update your second ComboBox content.
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
}
#endregion
By the way I always use Dr WPF snippets to write DPs so it goes much faster.