I want to do data validation on combo-box selected value. it's default value is 0 so i want it should be selected with any value not 0.
I have binded it's selected value with int dependency property in view model.
i know we can do data validation by dependency property so any help much appreciated.
<ComboBox x:Name="cmbBox" Width="100" Margin="10,0,20,0" ItemsSource="{Binding Path=TotalPeople}"
SelectedItem="{Binding Mode=TwoWay, Path=SelectedPeopleNumber}" Height="20"/>
here is viewmodel dependency property code.
public ExpenseViewModel()
{
totalPeople = new List<int>();
populate();
}
private void populate()
{
totalPeople.Add(2);
totalPeople.Add(3);
totalPeople.Add(4);
totalPeople.Add(5);
}
private List<int> totalPeople;
public List<int> TotalPeople
{
get { return totalPeople; }
set
{
if (totalPeople != value)
{
totalPeople = value;
NotifyPropertyChanged("TotalPeople");
}
}
}
public int SelectedPeopleNumber
{
get { return (int)GetValue(SelectedPeopleNumberProperty); }
set { SetValue(SelectedPeopleNumberProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedPeopleNumber. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedPeopleNumberProperty =
DependencyProperty.Register("SelectedPeopleNumber", typeof(int), typeof(ExpenseViewModel), new PropertyMetadata(0));
i want combobox should be red bordered when default value is 0.
You seem to have got confused between DependencyProperty and Databinding Property.
Databinding property are the ones which has source items used for displaying the data in the controls and also to store the selected values.
DependencyProperty(DP) are the ones to which you bind the above databinding properties.
In your Xaml Code SelectedItem is a DP and you have bound it to another DP(SelectedPeopleNumber) which I guess you did not intend to do.
Below is the fixed code
<ComboBox x:Name="cmbBox" Width="100" Margin="10,0,20,0" ItemsSource="{Binding Path=TotalPeople}"
SelectedItem="{Binding Path=SelectedIndex, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Height="20">
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent,
RelativeSource={x:Static RelativeSource.Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
</ComboBox>
The ViewModel
class ExpenseViewModel : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
var pc = PropertyChanged;
if (pc != null)
{
pc(this, new PropertyChangedEventArgs(propName));
}
}
private void populate()
{
_totalPeople.Add(2);
_totalPeople.Add(3);
_totalPeople.Add(4);
_totalPeople.Add(5);
NotifyPropertyChanged("TotalPeople");
}
private List<int> _totalPeople;
public IEnumerable<int> TotalPeople
{
get { return _totalPeople; }
}
public ExpenseViewModel()
{
_totalPeople = new List<int>();
populate();
}
public int SelectedIndex { get; set; }
private string _error;
public string Error
{
get { return _error; }
}
public string this[string columnName]
{
get
{
if (columnName.Equals("SelectedIndex"))
{
if(SelectedIndex==0)
{
_error = "Please Select One";
}
}
return Error;
}
}
}
Related
I have created a custom control derived from a ListBox and I am having issues getting the "SelectedItemsList" to bind to it's corresponding property in the view model.
The problem: The selected items in the list box do not make it into the property it is bound to in the view model. The list box allows multiple selections but none of these make into the List in the view model.
MultiSelectListBox:
public class MultiSelectListBox : ListBox
{
public MultiSelectListBox() { }
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register(
"SelectedItemsList",
typeof(IList),
typeof(MultiSelectListBox),
new PropertyMetadata(default(IList)));
public IList SelectedItemsList
{
get { return (IList) GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
}
declaration in MainWindow.xaml:
<local:MultiSelectListBox
DataContext="{StaticResource viewModel}"
DockPanel.Dock="Left"
Visibility="{Binding IsThailandFinal, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}"
ItemsSource="{Binding SelectedOutputtapeList}"
SelectionMode="Multiple"
SelectedItemsList="{Binding SelectedOutputTapes, Mode=TwoWay}"
HorizontalAlignment="Right"
Background="DeepSkyBlue"
Foreground="MidnightBlue"
ScrollViewer.VerticalScrollBarVisibility="Visible"
Height="100"
Width="70"
Margin="5"/>
View Model (simplified):
public class BTLogFrontEndViewModel : ViewModelBase
{
private List<string> selectedOutputTapes;
public BTLogFrontEndViewModel()
{
selectedOutputTapes = new List<string>();
}
public List<string> SelectedOutputTapes
{
get
{
return selectedOutputTapes;
}
set
{
selectedOutputTapes = value;
OnPropertyChanged("SelectedOutputTapes");
}
}
}
One way is to not use a custom ListBox and use objects in your list that extend INotifyPropertyChanged:
<ListBox
Width="70"
Height="100"
HorizontalAlignment="Right"
Margin="5"
Background="DeepSkyBlue"
DockPanel.Dock="Left"
Foreground="MidnightBlue"
ItemsSource="{Binding SelectedOutputtapeList}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Multiple"
DisplayMemberPath="Description"
Visibility="{Binding IsThailandFinal, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Assuming BTLogFrontEndViewModel is your DataContext:
public class BTLogFrontEndViewModel : ViewModelBase
{
private ObservableCollection<OutputTapeViewModel> m_SelectedOutputtapeList;
public ObservableCollection<OutputTapeViewModel> SelectedOutputtapeList
{
get
{
return m_SelectedOutputtapeList;
}
set
{
m_SelectedOutputtapeList = value;
NotifyPropertyChanged("SelectedOutputtapeList");
}
}
}
Where OutputTapeViewModel is declared like:
public class OutputTapeViewModel : ViewModelBase
{
private string m_Description;
public string Description
{
get
{
return m_Description;
}
set
{
m_Description = value;
NotifyPropertyChanged("Description");
}
}
private bool m_IsSelected;
public string IsSelected
{
get
{
return m_IsSelected;
}
set
{
m_IsSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
Important things to note is on the listbox I have added the DisplayMemberPath property for use of the description field in the OutputTapeViewModel class for what to show on the listbox. Also it has an item container style, which binds to the OutputTapeViewModel.IsSelected property when selected in the ListBox. This OutputTapeViewModel.IsSelected allows you to use the BTLogFrontEndViewModel.SelectedOutputtapeList property in such ways as:
var selectedItems = SelectedOutputtapeList.Where(item => item.IsSelected);
This only works in your case if you don't care about the overhead of doing the LINQ expression.
I have a Class named Layer2Info
public class Layer2Info
{
public ObservableCollection<totalAvailablePorts> availableClientPorts = new ObservableCollection<totalAvailablePorts>();
}
The totalAvailablePorts Class is
public class totalAvailablePorts : INotifyPropertyChanged
{
public int _portID;
public Boolean _isSelected;
public int portID
{
get { return _portID; }
set { _portID = value; NotifyPropertyChanged("portID"); }
}
public Boolean isSelected
{
get { return _isSelected; }
set { _isSelected = value; NotifyPropertyChanged("isSelected"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return string.Format("{0}", portID);
}
}
The creation of the data in availableClientPorts is:
for (int i = 1; i <= 3; i++)
{
totalAvailablePorts newPort = new totalAvailablePorts();
newPort.portID = i;
newPort.isSelected = false;
layer2InfoConfig.availableClientPorts.Add(newPort);
}
Now, in my MainWindow I'm binding the ListBox to the Layer2Info.availableClientPorts like this:
clientPortsList.ItemsSource = layer2InfoConfig.availableClientPorts;
and last is my xaml:
<ListBox x:Name="clientPortsList"
SelectionMode="Extended"
DisplayMemberPath="{Binding Path=portID}"
SelectedValuePath="{Binding Path=isSelected}"
Height="50"/>
Now, i'm able to see all the ports (1-3) in the ListBox, but what I want to do is that on every line that I select in the ListBox, I want the isSelected value in the availableClientPorts to change to true, and I have no idea where to start.
Any suggestions?
First, SelectedValuePath isn't what you think. MSDN says it "Gets or sets the path that is used to get the SelectedValue from the SelectedItem." So when the user selects an item, the clientPortsList will take that property of its own SelectedItem, and return the value of that property from clientPortsList.SelectedValue. With multi-select, that's not a real useful concept for you, and anyhow it's unrelated to the question you're asking here.
What you want to do is, for each totalAvailablePorts instance, bind the isSelected property of that instance to the IsSelected property of the ListBoxItem that owns it. You could do that with an item template, but a Style is much simpler (and better, if you aren't interested in recreating or altering the default ListBoxItem visual behavior). That answer is already on StackOverflow:
<ListBox ItemsSource="..."
x:Name="clientPortsList"
SelectionMode="Extended"
DisplayMemberPath="{Binding Path=portID}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<!-- binding totalAvailablePorts.isSelected to ListBoxItem.IsSelected -->
<Setter Property="IsSelected" Value="{Binding isSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
For the ListBoxItem instances, their DataContext will be their respective totalAvailablePorts instances, so isSelected (lower-case I) will be "in scope".
I have created my own radiobutton to include index property as following:
public class IndexedRadioButton : RadioButton
{
public int Index { get; set; }
}
I am using this custom radio button in a list box:
<ListBox Name="myListBox" Grid.Row="1" VerticalAlignment="Top">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" >
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<my:IndexedRadioButton Content="{Binding price}" GroupName="myGroup" IsChecked="{Binding isChecked}" Index="{Binding priceIndex}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now, I want to fill this list box with values.
Code behind:
public MainClass()
{
public MainClass()
{
InitializeComponent();
string[] priceList = new string["1","2","3"];
List<MyClass> myClassList = new List<MyClass>();
for (int i = 0; i < priceList.Count; i++)
{
MyClass listClass = new MyClass()
{
price = response.priceList[i],
priceIndex = i,
isChecked = i==0?true:false
};
myClassList.Add(listClass);
}
myListBox.ItemsSource = myClassList;
}
private class MyClass
{
public string price {get; set;}
public int priceIndex {get; set;}
public bool isChecked { get; set; }
}
}
When I run the app, I get this error ->>{System.ArgumentException: Value does not fall within the expected range.} (No stack trace info)
What do you think is causing the error? There is no problem when I set some value to Index staticly in XAML Index="0" but there is a problem in binding Index="{Binding priceIndex}".
Thanks,
In order to allow bindings, you have to declare a Dependency Property. Try this:
public class IndexedRadioButton : RadioButton
{
public static readonly DependencyProperty IndexProperty = DependencyProperty.Register(
"Index",
typeof(int),
typeof(IndexedRadioButton),
null);
public int Index
{
get { return (int)GetValue(IndexProperty); }
set { SetValue(IndexProperty, value); }
}
}
You'll find more info here:
Dependency properties for Windows Phone
So here is the XAML that I have:
<ItemsControl ItemsSource="{Binding Path=Groups}" ItemTemplateSelector="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ListTemplateSelector}"/>
Here is my ListTemplateSelector class:
public class ListTemplateSelector : DataTemplateSelector {
public DataTemplate GroupTemplate { get; set; }
public DataTemplate ItemTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
GroupList<Person> list = item as GroupList<Person>;
if (list != null && !list.IsLeaf)
return GroupTemplate;
return ItemTemplate;
}
}
The GroupTemplate data template references the ListTemplateSelector inside itself, so this is why I have set up like I have it set up. It's the only recursive hack I could put together. But that's not the problem I'm having.
My problem is, I want to change from ItemTemplate to GroupTemplate when the IsLeaf property changes. This works beautifully the very first time since it reads the property the first time. But once this property changes, the template selector doesn't get reapplied. Now, I could use triggers to bind to the value and set the item template appropriately, but I need to be able to set a different template for each item, as they could be in a different state.
For instance, say I have a list of groups like this:
Group 1: IsLeaf = false, so template = GroupTemplate
Group 2: IsLeaf = true, so template = ItemTemplate
Group 3: IsLeaf = false, so template = GroupTemplate
And once group 1's IsLeaf property changes to true, the template needs to automatically change to ItemTemplate.
EDIT:
Here is my temporary solution. Any better way to do it?
<ItemsControl ItemsSource="{Binding Path=Groups}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{DynamicResource ItemTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsLeaf}" Value="False">
<Setter Property="ContentTemplate" Value="{DynamicResource GroupTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I found this workaround that seems easier to me. From within the TemplateSelector listen to the property that your care about and then reapply the template selector to force a refresh.
public class DataSourceTemplateSelector : DataTemplateSelector
{
public DataTemplate IA { get; set; }
public DataTemplate Dispatcher { get; set; }
public DataTemplate Sql { get; set; }
public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
var ds = item as DataLocationViewModel;
if (ds == null)
{
return base.SelectTemplate(item, container);
}
PropertyChangedEventHandler lambda = null;
lambda = (o, args) =>
{
if (args.PropertyName == "SelectedDataSourceType")
{
ds.PropertyChanged -= lambda;
var cp = (ContentPresenter)container;
cp.ContentTemplateSelector = null;
cp.ContentTemplateSelector = this;
}
};
ds.PropertyChanged += lambda;
switch (ds.SelectedDataSourceType.Value)
{
case DataSourceType.Dispatcher:
return Dispatcher;
case DataSourceType.IA:
return IA;
case DataSourceType.Sql:
return Sql;
default:
throw new NotImplementedException(ds.SelectedDataSourceType.Value.ToString());
}
}
}
Regarding your EDIT, wouldn't a DataTemplate Trigger be enough instead of using a Style? That is:
<ItemsControl ItemsSource="{Binding Path=Groups}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="cc" Content="{Binding}" ContentTemplate="{DynamicResource ItemTemplate}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsLeaf}" Value="False">
<Setter TargetName="cc" Property="ContentTemplate" Value="{DynamicResource GroupTemplate}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Returning back to your original solution and the problem of "the template selector doesn't get reapplied": you can refresh your view like that
CollectionViewSource.GetDefaultView(YourItemsControl.ItemsSource).Refresh();
where for brevity sake your ItemsControl is referenced by its name ("YourItemsControl") added to your XAML:
<ItemsControl x:Name="YourItemsControl" ItemsSource="{Binding Path=Groups}"
ItemTemplateSelector="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ListTemplateSelector}"/>
The only problem may be how to choose right place in your project for this refresh instruction. It could go into a view code-behind, or, if your IsLeaf is a DP, the right place would be a dependency-property-changed callback.
I do it with a binding proxy.
It works like a normal binding proxy (but with 2 Props - copies data from DataIn to DataOut), but sets the DataOut to NULL and back to the DataIn value whenever the Trigger value changes:
public class BindingProxyForTemplateSelector : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxyForTemplateSelector();
}
#endregion
public object DataIn
{
get { return (object)GetValue(DataInProperty); }
set { SetValue(DataInProperty, value); }
}
public object DataOut
{
get { return (object) GetValue(DataOutProperty); }
set { SetValue(DataOutProperty, value); }
}
public object Trigger
{
get { return (object) GetValue(TriggerProperty); }
set { SetValue(TriggerProperty, value); }
}
public static readonly DependencyProperty TriggerProperty = DependencyProperty.Register(nameof(Trigger), typeof(object), typeof(BindingProxyForTemplateSelector), new PropertyMetadata(default(object), OnTriggerValueChanged));
public static readonly DependencyProperty DataInProperty = DependencyProperty.Register(nameof(DataIn), typeof(object), typeof(BindingProxyForTemplateSelector), new UIPropertyMetadata(null, OnDataChanged));
public static readonly DependencyProperty DataOutProperty = DependencyProperty.Register(nameof(DataOut), typeof(object), typeof(BindingProxyForTemplateSelector), new PropertyMetadata(default(object)));
private static void OnTriggerValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// this does the whole trick
var sender = d as BindingProxyForTemplateSelector;
if (sender == null)
return;
sender.DataOut = null; // set to null and then back triggers the TemplateSelector to search for a new template
sender.DataOut = sender.DataIn;
}
private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sender = d as BindingProxyForTemplateSelector;
if (sender == null)
return;
sender.DataOut = e.NewValue;
}
}
Use it like this:
<Grid>
<Grid.Resources>
<local:BindingProxyForTemplateSelector DataIn="{Binding}" Trigger="{Binding Item.SomeBool}" x:Key="BindingProxy"/>
</Grid.Resources>
<ContentControl Content="{Binding Source={StaticResource BindingProxy}, Path=DataOut.Item}" ContentTemplateSelector="{StaticResource TemplateSelector}"/>
</Grid>
So you don't bind to your DataContext directly, but to the BindingProxy's DataOut, which mirrors the original DataContext, but with a small difference: When the trigger changes (in this example a bool value inside the 'Item'), the TemplateSelector gets retriggered.
You don't have to change your TemplateSelector for this.
It is also possible to add more Triggers, just add a Trigger2.
I wasn't really satisfied with the solutions I will post the way I've managed to get the selector check for changes:
public class DynamicSelectorContentControl : ContentControl
{
// Using a DependencyProperty as the backing store for ListenToProperties. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ListenToPropertiesProperty =
DependencyProperty.Register("ListenToProperties", typeof(string),
typeof(DynamicSelectorContentControl),
new FrameworkPropertyMetadata(string.Empty));
public DynamicSelectorContentControl()
{
this.DataContextChanged += DynamicSelectorContentControl_DataContextChanged;
}
public string ListenToProperties
{
get { return (string)GetValue(ListenToPropertiesProperty); }
set { SetValue(ListenToPropertiesProperty, value); }
}
private void CheckForProperty(object sender, PropertyChangedEventArgs e)
{
if (ListenToProperties.Contains(e.PropertyName))
{
ClearSelector();
}
}
private void ClearSelector()
{
var oldSelector = this.ContentTemplateSelector;
if (oldSelector != null)
{
this.ContentTemplateSelector = null;
this.ContentTemplateSelector = oldSelector;
}
}
private void DynamicSelectorContentControl_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
var listOfProperties = ListenToProperties.Split(',').Select(s => s.Trim());
var oldObservable = e.OldValue as INotifyPropertyChanged;
if (oldObservable != null && listOfProperties.Any())
{
PropertyChangedEventManager.RemoveHandler(oldObservable, CheckForProperty, string.Empty);
}
var newObservable = e.NewValue as INotifyPropertyChanged;
if (newObservable != null && listOfProperties.Any())
{
PropertyChangedEventManager.AddHandler(newObservable, CheckForProperty, string.Empty);
}
if (e.OldValue != null)
{
ClearSelector();
}
}
}
Usage in XAML:
<controls:DynamicSelectorContentControl DockPanel.Dock="Top"
ContentTemplateSelector="{StaticResource AgeGenderSelector}"
ListenToProperties="Gender, Age"
Content="{Binding .}"/>
This could be changed to have the dependency be a list, but a string was better for my case.
It works well and has no memory leak. Besides, you can have you DataTemplates in an extra file which does not garbage your main xaml.
Cheers,
Marco
I'm tracking ListView selection changes in an MVVM design by binding to IsSelected. I also need to track the current item by enabling IsSynchronizedWithCurrentItem.
I find that when I have two ListView binding to the same collection I get the InvalidOperationException: "Collection was modified; enumeration operation may not execute." It seems to be a synchonization error between the two ListViews; one is triggering a PropertyChanged event while the other is updating the Selector perhaps?
I can't figure out how to get around this other than forgoing use of IsSynchronizedWithCurrentItem and managing it myself. Any ideas?
Thanks.
The ViewModel and code behind:
public class Item : INotifyPropertyChanged
{
public string Name{ get; set; }
public bool IsSelected
{
get { return isSelected; }
set { isSelected = value; OnPropertyChanged("IsSelected"); }
}
private bool isSelected;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ViewModel
{
public ViewModel()
{
Items = new ObservableCollection<Item>()
{
new Item(){Name = "Foo"},
new Item(){Name = "Bar"}
};
}
public ObservableCollection<Item> Items { get; private set; }
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
The XAML:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="100" Width="100">
<StackPanel>
<ListView DataContext="{Binding Items}" ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name, Mode=OneWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView DataContext="{Binding Items}" ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name, Mode=OneWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
I cannot offer a direct fix for your problem. However, I do have a solution that will work.
What you can do is introduce a second property on your View Model called 'SelectedItem' that will hold a reference to the Item that is selected in your ListView. In addition, in your View Model you listen for the PropertyChanged event. If the associated Property Name is IsSelected then you update the SelectedItem property to be the sender of that event (the Item that now has IsSelected = true). You can then bind the SelectedItem property of the ListView to the property of the same name of the ViewModel class.
My code for the revised ViewModel class is below.
public class ViewModel : INotifyPropertyChanged
{
private Item _selectedItem;
public ViewModel()
{
Items = new ObservableCollection<Item>()
{
new Item {Name = "Foo"},
new Item {Name = "Bar"}
};
foreach ( Item anItem in Items )
{
anItem.PropertyChanged += OnItemIsSelectedChanged;
}
}
public ObservableCollection<Item> Items { get; private set; }
public Item SelectedItem
{
get { return _selectedItem; }
set
{
// only update if the value is difference, don't
// want to send false positives
if ( _selectedItem == value )
{
return;
}
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnItemIsSelectedChanged(object sender, PropertyChangedEventArgs e)
{
if ( e.PropertyName != "IsSelected" )
{
return;
}
SelectedItem = sender as Item;
}
private void OnPropertyChanged(string propertyName)
{
if ( PropertyChanged != null )
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The issue seems to happen when you bind to a listbox's IsSelected and use SelectionMode='Single'
I found that changing the SelectionMode = 'Multiple' and then just added logic to the ViewModel to ensure that there was ever only one item with IsSelected set to true worked.