I am new to C# and MVVM, and I've spent all day trying to get the value of a ComboBox
to my ViewModel on SelectionChanged
. I have managed to figure it out using either CallMethodAction
or InvokeCommandAction
with the resources:
System.Windows.Interactivity.dll
Microsoft.Expression.Interactions.dll
My problem is that both these methods return the value of the ComboBox
before it has changed. Could anyone explain how to get the value after the change?
I have spent hours searching through SO and Google for a solution, so I wonder if others are too. Any advice will be appreciated!
My code is below:
MainWindow.xaml
<Window x:Class="SelectionChange.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:si="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
xmlns:vm="clr-namespace:SelectionChange"
Title="MainWindow" Width="300" Height="300">
<Window.DataContext>
<vm:ViewModel />
</Window.DataContext>
<Grid>
<ComboBox Name="SelectBox" VerticalAlignment="Top" SelectedIndex="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<si:CallMethodAction MethodName="SelectionChanged" TargetObject="{Binding}" />
<!--<i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=SelectBox, Path=Text}" />-->
</i:EventTrigger>
</i:Interaction.Triggers>
<ComboBoxItem Content="Item 1" />
<ComboBoxItem Content="Item 2" />
<ComboBoxItem Content="Item 3" />
</ComboBox>
</Grid>
</Window>
ViewModel.cs
namespace SelectionChange
{
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
public class ViewModel
{
public ViewModel()
{
SelectionChangedCommand = new SelectionChangedCommand();
}
public ICommand SelectionChangedCommand
{
get;
set;
}
public void SelectionChanged(object sender, EventArgs e)
{
ComboBox SelectBox = (ComboBox)sender;
MessageBox.Show("Called SelectionChanged: " + SelectBox.Text);
}
}
}
SelectionChangedCommand.cs
namespace SelectionChange
{
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
public class SelectionChangedCommand : ICommand
{
public SelectionChangedCommand()
{
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
MessageBox.Show("Executed SelectionChangedCommand: " + parameter);
}
}
}
Edit: My Solution
It turns out I didn't understand Binding
well enough and instead was trying to implement something simple in a rather complicated way! Instead of using dependencies, I have now achieved what I needed using regular bindings. As an example, I've bound a TextBox
to the SelectedIndex
of the ComboBox
, which gets updated using INotifyPropertyChanged
.
MainWindow.xaml
<Window x:Class="SelectionChange.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SelectionChange"
Title="MainWindow" Width="300" Height="300">
<Window.DataContext>
<vm:ViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ComboBox SelectedItem="{Binding SelectedItem}" SelectedIndex="0" Grid.Column="0" VerticalAlignment="Top">
<ComboBoxItem Content="Item 1" />
<ComboBoxItem Content="Item 2" />
<ComboBoxItem Content="Item 3" />
</ComboBox>
<!-- TextBox to display the ComboBox's SelectedIndex -->
<TextBox Text="{Binding SelectedIndex}" Grid.Column="1" VerticalAlignment="Top" />
</Grid>
</Window>
ViewModel.cs
namespace SelectionChange
{
using System;
using System.ComponentModel;
using System.Windows.Controls;
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
}
// Property to store / retrieve ComboBox's SelectedIndex
private int _SelectedIndex;
public int SelectedIndex { get; set; }
// Property to bind to ComboBox's SelectedItem
private ComboBoxItem _SelectedItem;
public ComboBoxItem SelectedItem
{
get { return _SelectedItem; }
set
{
_SelectedItem = value;
// SelectedItem's Content
string Content = (string)value.Content;
// SelectedItem's parent (i.e. the ComboBox)
ComboBox SelectBox = (ComboBox)value.Parent;
// ComboBox's SelectedIndex
int Index = SelectBox.SelectedIndex;
// Store the SelectedIndex in the property
SelectedIndex = Index;
// Raise PropertyChanged with the name of the stored property
RaisePropertyChanged("SelectedIndex");
}
}
// INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
Why not do it the simpler way
<ComboBox MaxHeight="25"
ItemsSource="{Binding Source}"
SelectedItem="{Binding TheSelectedItem, Mode=TwoWay}" />
In your ViewModel declare the combo box items and use a property "Source" to return it to the view
List<string> _source = new List<string>{"Item 1", "Item 2", "Item 3"};
public List<string> Source
{
get { return _source; }
}
Then define one property which holds the selected item
string _theSelectedItem = null;
public string TheSelectedItem
{
get { return _theSelectedItem; }
set { _theSelectedItem = value; } // NotifyPropertyChanged
}
Also don't forget to implement the INotifyPropertyChanged interface while setting the _source
If you just want to get notified when your current item changes, why not use tools that are already part of WPF instead of all these dependencies.
First get the underlying view to your collection and use the CurrentChanged event on it.
ComboBoxList = new ObservableCollection<string>();
var view = CollectionViewSource.GetDefaultView(ComboBoxList);
view.MoveCurrentTo(ComboBoxList[0]);
view.CurrentChanged += new EventHandler(ComboBoxCurrentChanged);
In your xaml you just bind your collection and set IsSynchronizedWithCurrentItem
to true.
<ComboBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding ComboBoxList}"/>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With