I'm attempting to make a ComboBox that switches between objects. The general gist is that an object has a Key that appears in the ComboBox and a Data component that can theoretically be anything. The Data component is complex while the Key is just a string. For the example below, Data is just a Uri, it actually turns out the type of the Data doesn't matter.
The basic intent is to bind the SelectedItem of the ComboBox to the Model so that the Data of the SelectedItem can be modified through other interactions.
The code is setup such that a couple items are added to the ComboBox, and then the SelectedItem is choose as the first element. That works just fine. When I then click the button, the SelectedItem is assigned null where I throw the exception.
Why does the SelectedItem get assigned null?
Here is complete working code; my target is .NET 4.0, but I'm guessing it doesn't matter too much. Xaml follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace Sandbox
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public Model Model { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Model = new Model();
this.Model.Items.Add(
new ObservableKeyValuePair<string, Uri>()
{
Key = "Apple",
Value = new Uri("http://apple.com")
});
this.Model.Items.Add(
new ObservableKeyValuePair<string, Uri>()
{
Key = "Banana",
Value = new Uri("http://Banana.net")
});
this.Model.SelectedItem = this.Model.Items.First();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Model.SelectedItem.Value = new Uri("http://cranberry.com");
}
}
public class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems)
: this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
public class ObservableKeyValuePair<TKey, TValue> :
INotifyPropertyChanged,
IEquatable<ObservableKeyValuePair<TKey, TValue>>
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
public override bool Equals(object rhs)
{
var obj = rhs as ObservableKeyValuePair<TKey, TValue>;
if (obj != null)
{
return this.Key.Equals(obj.Key);
}
return false;
}
public bool Equals(ObservableKeyValuePair<TKey, TValue> other)
{
return this.Key.Equals(other.Key);
}
public override int GetHashCode()
{
return this.Key.GetHashCode();
}
protected TKey _Key;
public TKey Key
{
get
{
return _Key;
}
set
{
if (value is INotifyPropertyChanged)
{
(value as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(KeyChanged);
}
_Key = value;
OnPropertyChanged("Key");
}
}
void KeyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("Key");
}
protected TValue _Value;
public TValue Value
{
get
{
return _Value;
}
set
{
if (value is INotifyPropertyChanged)
{
(value as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(ValueChanged);
}
_Value = value;
OnPropertyChanged("Value");
}
}
void ValueChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("Value");
}
}
public class Model : INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public Model()
{
Items = new TrulyObservableCollection<ObservableKeyValuePair<string, Uri>>();
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
public TrulyObservableCollection<ObservableKeyValuePair<string, Uri>> Items { get; set; }
public ObservableKeyValuePair<string, Uri> _SelectedItem = null;
public ObservableKeyValuePair<string, Uri> SelectedItem
{
get
{
return Items.FirstOrDefault(x => _SelectedItem != null && x.Key == _SelectedItem.Key);
}
set
{
if (value == null)
{
throw new Exception("This is the problem");
}
if (_SelectedItem != value)
{
_SelectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
}
}
Xaml:
<Window x:Class="Sandbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Click="Button_Click" Content="Clsick Me"/>
<ComboBox IsEditable="False" ItemsSource="{Binding Model.Items}" SelectedItem="{Binding Model.SelectedItem, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}">
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>
I'm at a total lose trying to explain why "value" is null.
The underlying problem is in the implementation of Selector.OnItemsChanged. Towards the end of the method, the currently SelectedItem is nulled.
I worked around this by deriving a new ComboBox class that overrides OnItemsChanged, saves the current SelectedItem, calls base.OnItemsChanged and then resets the SelectedItem. This may require the propagation of an "InhibitEvents" flag into the model if the SelectedItem transition from valid=>null=>valid is not desired.
The value becomes null because you are trying to assign a selected item value which is not in the collection
Related
I will try to keep this as brief as possible, but there is a fair amount of nuance to this question.
The Workflow
I am working in C# and am using WPF and MVVM for the UI for an addin for Revit (a 3D modeling software from Autodesk).
The overarching goal is to create a window that shows the parameters of a 3D element after it is selected. This is a filtered list that are specific to my organization and our users needs, and allow the user to edit them in order to streamline their workflow. The complication is that because I am working with an API I can only use what tools I am given when interacting with the model.
The issue I am running into lies in the workflow. I have detailed the workflow below.
User Selects a 3D Element
The addin uses the API to pull the parameters and wraps them in a wrapper class and adds them into a custom ObservableCollection to display them in the DataGrid
The user then changes a value in the DataGrid. When the cell loses focus it fires off a command that hooks the API and updates the parameter's value.
The change is made and the internal logic of the element calculates it's values based on the changed parameter
The calculated parameter values are changed in the model
The ViewModel checks each parameter to see if its value has changed, and updates any of the wrapped parameters in the ObservableCollection to reflect the changes.
The ObservableCollection fires off it's collection changed event to notify the DataGrid that values have changed
The DataGrid updates it's values to complete the process.
The issue currently lies in the very last step. Once the collection changed event is complete the wrapped parameter value matches the parameter value from the API, but the DataGrid will not redraw the information. Once you minimize the window, click into the cell, or scroll the DataGrid to where the cell is not visible the cell will show the new value when it comes back into view.
I can't seem to find a way to keep with MVVM principles and force the cells to redraw with their updated value. Am I missing something with this? How do I get the DataGrid to update without having to completely clear and reset the ObservableCollection items?
Things I have Tried
I had to create a custom ObservableCollection to implement INPC for the items in the collection, and from debugging it appears to work as intended. Each time an item in the ObservableCollection is updated it makes the change subscribes it to INPC and raises the collection changed event.
For each of the columns I have the binding set to Mode="TwoWay" and have tried setting the UpdateSourceTrigger="PropertyChanged", and neither helped.
I originally was using a <ContentPresenter/> in a <DataGridTemplteColumn/> to present different cell types, but even using a basic <DataGridTextColumn doesn't work.
---- CODE ----
XAML:
<DataGrid Grid.Row="2" ItemsSource="{Binding TestingParameters, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn IsReadOnly="True" Binding="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Name"/>
<DataGridTextColumn IsReadOnly="False" Binding="{Binding Path=ParamValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Param Value">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding UpdateParametersCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
C# ViewModel
public class ViewModelParameterPane : ViewModelBase
{
private ExternalEvent _event;
private HandlerED _handler;
private UIApplication _uiapp;
private UIDocument _uidoc;
private Document _doc;
private TestObservableCollection<WrappedParameter> _testParameter = new TestObservableCollection<WrappedParameter>();
public TestObservableCollection<WrappedParameter> TestParameter
{
get => _testParameter;
set
{
_testParameter = value;
RaiseProperty(nameof(_testParameter));
}
}
public ViewModelParameterPane(ExternalEvent exEvent, HandlerED handler, UIApplication uiapp)
{
_event = exEvent;
_handler = handler;
_uiapp = uiapp;
_uidoc = _uiapp.ActiveUIDocument;
_doc = _uidoc.Document;
_testParameter.ItemPropertyChanged += _testParameter_ItemPropertyChanged;
_testParameter.CollectionChanged += _testParameter_CollectionChanged;
UpdateParametersCommand = new RelayCommand(CallUpdateParameters);
}
private void _testParameter_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs<WrappedParameter> e)
{
Debug.WriteLine("PROPERTY CHANGE");
RaiseProperty(nameof(TestParameter));
int index = TestParameter.IndexOf(e.Item);
TestParameter[index] = e.Item;
}
private void _testParameter_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("COLLECTION CHANGE");
}
private void MakeRequest(RequestIdED request)
{
_handler.Request.Make(request);
_event.Raise();
}
private void CallUpdateParameters() { MakeRequest(RequestIdED.UpdateParameters); }
public void UpdateParameters()
{
Debug.WriteLine("Running Update Parameters");
try
{
using (var transaction = new Transaction(_doc))
{
transaction.Start("T_UpdateParameters");
foreach (WrappedParameter p in TestParameter)
{
string currenValue = p.RevitParameter.AsValueString();
if (p.RevitParameter.AsValueString() != p.ParamValue)
{
bool setValueSuccess = p.SetRevitParameterValue(p.ParamValue);
if(!setValueSuccess)
{
TaskDialog.Show("Parameter Value Not Set", "The parameter value for the parameter " + p.Name + " was not given a valid value and was not changed. Please ensure the units are correct.");
}
}
}
transaction.Commit();
}
}
catch (Exception e)
{
throw new Exception("Something Went Wrong. Check your values.");
}
}
public void UpdateParameterValues()
{
for(var i = 0; i < TestParameter.Count; i++)
{
TestParameter[i].UpdateValues();
}
}
}
C# Parameter Wrapper Class
public class TestParameter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(_name));
}
}
private string _categories;
public string Categories
{
get => _categories;
set
{
_categories = value;
OnPropertyChanged(nameof(_categories));
}
}
private string _paramValue;
public string ParamValue
{
get => _paramValue;
set
{
_paramValue = value;
OnPropertyChanged(nameof(_paramValue));
}
}
private Parameter _revitParameter;
public Parameter RevitParameter
{
get => _revitParameter;
set
{
_revitParameter = value;
OnPropertyChanged(nameof(_revitParameter));
}
}
private ElementId _elementId;
public ElementId ElementId
{
get => _elementId;
set
{
_elementId = value;
OnPropertyChanged(nameof(_elementId));
}
}
public TestParameter(Parameter param)
{
GetRevitParameterValue();
}
public void GetRevitParameterValue()
{
//Get parameter value logic
}
public bool SetRevitParameterValue(string Value)
{
//Set parameter value logic
}
}
C# TestObservableCollection Class
public class TestObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
item.PropertyChanged += item_PropertyChanged;
}
protected override void RemoveItem(int index)
{
var item = this[index];
base.RemoveItem(index);
item.PropertyChanged -= item_PropertyChanged;
}
protected override void ClearItems()
{
foreach (var item in this)
{
item.PropertyChanged -= item_PropertyChanged;
}
base.ClearItems();
}
protected override void SetItem(int index, T item)
{
var oldItem = this[index];
oldItem.PropertyChanged -= item_PropertyChanged;
base.SetItem(index, item);
item.PropertyChanged += item_PropertyChanged;
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnItemPropertyChanged((T)sender, e.PropertyName);
}
private void OnItemPropertyChanged(T item, string propertyName)
{
var handler = this.ItemPropertyChanged;
if (handler != null)
{
handler(this, new ItemPropertyChangedEventArgs<T>(item, propertyName));
}
}
}
public sealed class ItemPropertyChangedEventArgs<T> : EventArgs
{
private readonly T _item;
private readonly string _propertyName;
public ItemPropertyChangedEventArgs(T item, string propertyName)
{
_item = item;
_propertyName = propertyName;
}
public T Item
{
get { return _item; }
}
public string PropertyName
{
get { return _propertyName; }
}
}
I'm new to WPF and I'm having some trouble with my existing setup to get the list box selected item to appear in the text box.
The picture here represents the issue. I typed "12 HOUR" in the text box, which then filters the listbox to those items with "12 HOUR" anywhere in the string. But when I click "12 Hour Nasal" in the list box, I now want to reflect that choice back in the text box:
http://i.imgur.com/ZCYAolT.png
Here is my XAML for the user control containing the listbox and textbox:
<UserControl x:Class="SCM_AllergyRecModule.SearchAndSelectView"
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"
mc:Ignorable="d">
<StackPanel Width="300">
<TextBox x:Name="Filter" Text="{Binding Path=Filter, UpdateSourceTrigger=PropertyChanged}"/>
<ListBox Width ="300" Height="50" x:Name="ListBoxControl"
ItemsSource="{Binding Path=Allergens}"
SelectedItem="{Binding Path=SelectedAllergen}">
</ListBox>
</StackPanel>
And here is the ViewModel:
namespace SCM_AllergyRecModule
{
public class SearchAndSelectViewModel
{
private ICollectionView allergens;
private string selectedAllergen;
private string filter = "";
public string Filter
{
get
{
return this.filter.ToUpperInvariant();
}
set
{
if (this.filter != value)
{
this.filter = value;
this.Allergens.Refresh();
}
}
}
private bool ContainsFilter(object item)
{
var product = item as string;
if (product == null)
{
return false;
}
if (string.IsNullOrEmpty(this.Filter))
{
return true;
}
if (product.ToUpperInvariant().Contains(this.Filter))
{
return true;
}
return false;
}
public SearchAndSelectViewModel()
{
var cvs = new CollectionViewSource();
cvs.Source = MainWindow.scmAllergens;
this.allergens = cvs.View;
this.allergens.Filter = ContainsFilter;
}
public ICollectionView Allergens
{
get
{
return this.allergens;
}
}
public string SelectedAllergen
{
get
{
return this.selectedAllergen;
}
set
{
if (this.selectedAllergen != value)
{
this.selectedAllergen = value;
}
}
}
}
}
Update 1
I added the INotifyPropertyChanged interface to my class and have it being raised on SelectedAllergen in the setter. I added an event handler called SearchAndSelectViewModel_PropertyChanged to handle the SelectedAllergen property changing and set it in the constructor.
Now when I click an item in the listbox, I do see it setting the Filter to the SelectedItem and the list filters to that item so nothing else shows. But still, the text box text is not changing? See screenshot below. This is after I typed in "PEAN" in the textbox, then the listbox filtered to two choices, and I chose "PEANUTS (FOOD)", which then refiltered the list box to just show that choice but didn't set the text box to "PEANUTS (FOOD)":
http://imgur.com/dNxuVI5
Updated ViewModel
public class SearchAndSelectViewModel : INotifyPropertyChanged
{
private ICollectionView allergens;
private string selectedAllergen;
private string filter;
public string Filter
{
get
{
return this.filter.ToUpperInvariant();
}
set
{
if (this.filter != value)
{
this.filter = value;
this.Allergens.Refresh();
}
}
}
private bool ContainsFilter(object item)
{
var product = item as string;
if (product == null)
{
return false;
}
if (string.IsNullOrEmpty(this.Filter))
{
return true;
}
if (product.ToUpperInvariant().Contains(this.Filter))
{
return true;
}
return false;
}
private void SearchAndSelectViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SelectedAllergen":
this.Filter = this.SelectedAllergen;
break;
}
}
public SearchAndSelectViewModel()
{
filter = "";
var cvs = new CollectionViewSource();
cvs.Source = MainWindow.scmAllergens;
this.allergens = cvs.View;
this.allergens.Filter = ContainsFilter;
this.PropertyChanged += SearchAndSelectViewModel_PropertyChanged;
}
public ICollectionView Allergens
{
get
{
return this.allergens;
}
}
public string SelectedAllergen
{
get
{
return this.selectedAllergen;
}
set
{
if (this.selectedAllergen != value && value != null)
{
this.selectedAllergen = value;
OnPropertyChanged("SelectedAllergen");
}
}
}
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You need to implement the INotifyPropertyChanged interface and you can raise it in your property setter. Since you are also binding your TextBox to the Filter property you need to set the Filter property when your SelectedAllergen changes.
INotifyPropertyChanged example:
public class MyViewModel : INotifyPropertyChanged
{
//...
private int myProperty = 0;
public int MyProperty
{
get { return myProperty; }
set
{
myProperty = value;
// Raise the property changed notification
OnPropertyChanged("MyProperty");
}
}
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have read several posts on this topic but many are from a previous versions of VS or framework. What I am trying to do is selected multiple rows from a dataGrid and return those rows into a bound observable collection.
I have tried creating a property(of type) and adding it to an observable collection and it works with single records but the code never fires with multiple records.
Is there a clean way to do this in VS2013 using an MVVM patern?
Any thoughts would be appreciated.
<DataGrid x:Name="MainDataGrid" Height="390" Width="720"
VerticalAlignment="Center" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False"
ItemsSource="{Binding Path=DisplayInDataGrid}"
SelectedItem="{Binding Path=DataGridItemSelected}"
SelectionMode="Extended"
private ObservableCollection<ScannedItem> _dataGridItemsSelected;
public ObservableCollection<ScannedItem> DataGridItemsSelected
{
get { return _dataGridItemsSelected; }
set
{
_dataGridItemsSelected = value;
OnPropertyChanged("DataGridItemsSelected");
}
}
private ScannedItem _dataGridItemSelected;
public ScannedItem DataGridItemSelected
{
get { return _dataGridItemSelected;}
set
{
_dataGridItemSelected = value;
OnPropertyChanged("DataGridItemSelected");
EnableButtons();
LoadSelectedCollection(DataGridItemSelected);
}
}
void LoadSelectedCollection(ScannedItem si)
{
if (DataGridItemsSelected == null)
{
DataGridItemsSelected = new ObservableCollection<ScannedItem>();
}
DataGridItemsSelected.Add(si);
}
I have implemented a two-way data binding for MultiSelector.SelectedItems property using attached behavior pattern.
The following image shows how it works:
There are two DataGrids bound to the same model and they share selected items. Left DataGrid is active so selected items are blue and right DataGrid is inactive so selected items are gray.
Following is the sample code how to use it:
MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
<DataGrid Grid.Column="1" ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2">
<Button DockPanel.Dock="Top" Content="Select All" Command="{Binding SelectAllCommand}"/>
<Button DockPanel.Dock="Top" Content="Unselect All" Command="{Binding UnselectAllCommand}"/>
<Button DockPanel.Dock="Top" Content="Select Next Range" Command="{Binding SelectNextRangeCommand}"/>
</StackPanel>
</Grid>
</Window>
Model.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
namespace WpfApplication
{
abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void Set<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
sealed class DelegateCommand : ICommand
{
private readonly Action action;
public DelegateCommand(Action action)
{
if (action == null)
throw new ArgumentNullException("action");
this.action = action;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute()
{
this.action();
}
bool ICommand.CanExecute(object parameter)
{
return true;
}
void ICommand.Execute(object parameter)
{
this.Execute();
}
}
class Person : ObservableObject
{
private string name, surname;
public Person()
{
}
public Person(string name, string surname)
{
this.name = name;
this.surname = surname;
}
public string Name
{
get { return this.name; }
set { this.Set(ref this.name, value, "Name"); }
}
public string Surname
{
get { return this.surname; }
set { this.Set(ref this.surname, value, "Surname"); }
}
public override string ToString()
{
return this.name + ' ' + this.surname;
}
}
class MainWindowModel : ObservableObject
{
public ObservableCollection<Person> People { get; private set; }
public SelectedItemCollection<Person> SelectedPeople { get; private set; }
public DelegateCommand SelectAllCommand { get; private set; }
public DelegateCommand UnselectAllCommand { get; private set; }
public DelegateCommand SelectNextRangeCommand { get; private set; }
public MainWindowModel()
{
this.People = new ObservableCollection<Person>(Enumerable.Range(1, 1000).Select(i => new Person("Name " + i, "Surname " + i)));
this.SelectedPeople = new SelectedItemCollection<Person>();
for (int i = 0; i < this.People.Count; i += 2)
this.SelectedPeople.Add(this.People[i]);
this.SelectAllCommand = new DelegateCommand(() => this.SelectedPeople.Reset(this.People));
this.UnselectAllCommand = new DelegateCommand(() => this.SelectedPeople.Clear());
this.SelectNextRangeCommand = new DelegateCommand(() =>
{
var index = this.SelectedPeople.Count > 0 ? this.People.IndexOf(this.SelectedPeople[this.SelectedPeople.Count - 1]) + 1 : 0;
int count = 10;
this.SelectedPeople.Reset(Enumerable.Range(index, count).Where(i => i < this.People.Count).Select(i => this.People[i]));
});
this.SelectedPeople.CollectionChanged += this.OnSelectedPeopleCollectionChanged;
}
private void OnSelectedPeopleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("Action = {0}, NewItems.Count = {1}, NewStartingIndex = {2}, OldItems.Count = {3}, OldStartingIndex = {4}, Total.Count = {5}", e.Action, e.NewItems != null ? e.NewItems.Count : 0, e.NewStartingIndex, e.OldItems != null ? e.OldItems.Count : 0, e.OldStartingIndex, this.SelectedPeople.Count);
}
}
class SelectedItemCollection<T> : ObservableCollection<T>
{
public void Reset(IEnumerable<T> items)
{
int oldCount = this.Count;
this.Items.Clear();
foreach (var item in items)
this.Items.Add(item);
if (!(oldCount == 0 && this.Count == 0))
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
if (this.Count != oldCount)
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
}
}
}
}
Following is the implementation, which unfortunately is not documented. Implementation uses various tricks (via reflection) to reduce the number of changes to underlying collections as much as possible (to suspend excessive collection changed notifications).
It is worth noting that if selected items collection in model has Select(IEnumerable) or Select(IEnumerable) method, that method will be used for performing bulk updates (updates which affect more than one item) which offers better performance if, for example, all items in the DataGrid are selected or un-selected.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Markup;
namespace WpfApplication
{
static class MultiSelectorExtension
{
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(MultiSelectorExtension), new PropertyMetadata(new PropertyChangedCallback(OnSelectedItemsChanged)));
private static readonly DependencyProperty SelectedItemsBinderProperty = DependencyProperty.RegisterAttached("SelectedItemsBinder", typeof(SelectedItemsBinder), typeof(MultiSelectorExtension));
[AttachedPropertyBrowsableForType(typeof(MultiSelector))]
[DependsOn("ItemsSource")]
public static IList GetSelectedItems(this MultiSelector multiSelector)
{
if (multiSelector == null)
throw new ArgumentNullException("multiSelector");
return (IList)multiSelector.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(this MultiSelector multiSelector, IList selectedItems)
{
if (multiSelector == null)
throw new ArgumentNullException("multiSelector");
multiSelector.SetValue(SelectedItemsProperty, selectedItems);
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var multiSelector = d as MultiSelector;
if (multiSelector == null)
return;
var binder = (SelectedItemsBinder)multiSelector.GetValue(SelectedItemsBinderProperty);
var selectedItems = e.NewValue as IList;
if (selectedItems != null)
{
if (binder == null)
binder = new SelectedItemsBinder(multiSelector);
binder.SelectedItems = selectedItems;
}
else if (binder != null)
binder.Dispose();
}
private sealed class SelectedItemsBinder : IDisposable
{
private static readonly IList emptyList = new object[0];
private static readonly Action<MultiSelector> multiSelectorBeginUpdateSelectedItems, multiSelectorEndUpdateSelectedItems;
private readonly MultiSelector multiSelector;
private IList selectedItems;
private IResetter selectedItemsResetter;
private bool suspendMultiSelectorUpdate, suspendSelectedItemsUpdate;
static SelectedItemsBinder()
{
GetMultiSelectorBeginEndUpdateSelectedItems(out multiSelectorBeginUpdateSelectedItems, out multiSelectorEndUpdateSelectedItems);
}
public SelectedItemsBinder(MultiSelector multiSelector)
{
this.multiSelector = multiSelector;
this.multiSelector.SelectionChanged += this.OnMultiSelectorSelectionChanged;
this.multiSelector.Unloaded += this.OnMultiSelectorUnloaded;
this.multiSelector.SetValue(SelectedItemsBinderProperty, this);
}
public IList SelectedItems
{
get { return this.selectedItems; }
set
{
this.SetSelectedItemsChangedHandler(false);
this.selectedItems = value;
this.selectedItemsResetter = GetResetter(this.selectedItems.GetType());
this.SetSelectedItemsChangedHandler(true);
if (this.multiSelector.IsLoaded)
this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
else
{
RoutedEventHandler multiSelectorLoadedHandler = null;
this.multiSelector.Loaded += multiSelectorLoadedHandler = new RoutedEventHandler((sender, e) =>
{
this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
this.multiSelector.Loaded -= multiSelectorLoadedHandler;
});
}
}
}
private int ItemsSourceCount
{
get
{
var collection = this.multiSelector.ItemsSource as ICollection;
return collection != null ? collection.Count : -1;
}
}
public void Dispose()
{
this.multiSelector.ClearValue(SelectedItemsBinderProperty);
this.multiSelector.Unloaded -= this.OnMultiSelectorUnloaded;
this.multiSelector.SelectionChanged -= this.OnMultiSelectorSelectionChanged;
this.SetSelectedItemsChangedHandler(false);
}
private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.suspendMultiSelectorUpdate || e.Action == NotifyCollectionChangedAction.Move)
return;
this.suspendSelectedItemsUpdate = true;
if (this.selectedItems.Count == 0)
this.multiSelector.UnselectAll();
else if (this.selectedItems.Count == this.ItemsSourceCount)
this.multiSelector.SelectAll();
else if (e.Action != NotifyCollectionChangedAction.Reset && (e.NewItems == null || e.NewItems.Count <= 1) && (e.OldItems == null || e.OldItems.Count <= 1))
UpdateList(this.multiSelector.SelectedItems, e.NewItems ?? emptyList, e.OldItems ?? emptyList);
else
{
if (multiSelectorBeginUpdateSelectedItems != null)
{
multiSelectorBeginUpdateSelectedItems(this.multiSelector);
this.multiSelector.SelectedItems.Clear();
UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
multiSelectorEndUpdateSelectedItems(this.multiSelector);
}
else
{
this.multiSelector.UnselectAll();
UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
}
}
this.suspendSelectedItemsUpdate = false;
}
private void OnMultiSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (this.suspendSelectedItemsUpdate)
return;
this.suspendMultiSelectorUpdate = true;
if (e.AddedItems.Count <= 1 && e.RemovedItems.Count <= 1)
UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
else
{
if (this.selectedItemsResetter != null)
this.selectedItemsResetter.Reset(this.selectedItems, this.multiSelector.SelectedItems.Cast<object>().Where(item => item != CollectionView.NewItemPlaceholder));
else
UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
}
this.suspendMultiSelectorUpdate = false;
}
private void OnMultiSelectorUnloaded(object sender, RoutedEventArgs e)
{
this.Dispose();
}
private void SetSelectedItemsChangedHandler(bool add)
{
var notifyCollectionChanged = this.selectedItems as INotifyCollectionChanged;
if (notifyCollectionChanged != null)
{
if (add)
notifyCollectionChanged.CollectionChanged += this.OnSelectedItemsCollectionChanged;
else
notifyCollectionChanged.CollectionChanged -= this.OnSelectedItemsCollectionChanged;
}
}
private static void UpdateList(IList list, IList newItems, IList oldItems)
{
int addedCount = 0;
for (int i = 0; i < oldItems.Count; ++i)
{
var index = list.IndexOf(oldItems[i]);
if (index >= 0)
{
object newItem;
if (i < newItems.Count && (newItem = newItems[i]) != CollectionView.NewItemPlaceholder)
{
list[index] = newItem;
++addedCount;
}
else
list.RemoveAt(index);
}
}
for (int i = addedCount; i < newItems.Count; ++i)
{
var newItem = newItems[i];
if (newItem != CollectionView.NewItemPlaceholder)
list.Add(newItem);
}
}
private static void GetMultiSelectorBeginEndUpdateSelectedItems(out Action<MultiSelector> beginUpdateSelectedItems, out Action<MultiSelector> endUpdateSelectedItems)
{
try
{
beginUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("BeginUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
endUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("EndUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
}
catch
{
beginUpdateSelectedItems = endUpdateSelectedItems = null;
}
}
private static IResetter GetResetter(Type listType)
{
try
{
MethodInfo genericReset = null, nonGenericReset = null;
Type genericResetItemType = null;
foreach (var method in listType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
if (method.Name != "Reset")
continue;
if (method.ReturnType != typeof(void))
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
continue;
var parameterType = parameters[0].ParameterType;
if (parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
genericResetItemType = parameterType.GetGenericArguments()[0];
genericReset = method;
break;
}
else if (parameterType == typeof(IEnumerable))
nonGenericReset = method;
}
if (genericReset != null)
return (IResetter)Activator.CreateInstance(typeof(GenericResetter<,>).MakeGenericType(genericReset.DeclaringType, genericResetItemType), genericReset);
else if (nonGenericReset != null)
return (IResetter)Activator.CreateInstance(typeof(NonGenericResetter<>).MakeGenericType(nonGenericReset.DeclaringType), nonGenericReset);
else
return null;
}
catch
{
return null;
}
}
private interface IResetter
{
void Reset(IList list, IEnumerable items);
}
private sealed class NonGenericResetter<TTarget> : IResetter
{
private readonly Action<TTarget, IEnumerable> reset;
public NonGenericResetter(MethodInfo method)
{
this.reset = (Action<TTarget, IEnumerable>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable>), method);
}
public void Reset(IList list, IEnumerable items)
{
this.reset((TTarget)list, items);
}
}
private sealed class GenericResetter<TTarget, T> : IResetter
{
private readonly Action<TTarget, IEnumerable<T>> reset;
public GenericResetter(MethodInfo method)
{
this.reset = (Action<TTarget, IEnumerable<T>>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable<T>>), method);
}
public void Reset(IList list, IEnumerable items)
{
this.reset((TTarget)list, items.Cast<T>());
}
}
}
}
}
Create a command that fires on the DataGrid's SelectionChanged event, passing in the DataGrid's SelectedItems.
In your ViewModel, have a List of selected objects.
Your SelectionChangedCommand execution method would then update that collection of selected objects.
For example:
In my XAML:
<DataGrid ItemsSource="{Binding Datasets, NotifyOnTargetUpdated=True}" Name="dsDatagrid" SelectionMode="Extended" MouseDoubleClick="ViewDataset">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=dsDatagrid, Path=SelectedItems}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
In my ViewModel:
private List<ObservableDataset> selectedDatasets;
private void SelectionChangedExecuted(object datasets)
{
this.selectedDatasets = new List<ObservableDataset>((datasets as IList).Cast<ObservableDataset>());
}
EDIT: I'm using MVVMLight.
I'm creating an EPOS system for a bar, for my own project just to test my skills.
I've come into a problem, I've managed to put all products in a WrapPanel and when clicked ive also managed to get them to show in a ListView control.
However, I cannot seem to get the total to show in a label below the ListView, essentially, each time a product is added to the ListView i want the total to also be updated by adding up all of the prices in the "Price" column and displaying them in a label below. But i cannot even seem to print the total via a button let alone do it automatically.
Here is my code for the button so far.
Don't suggest SubItems as it doesnt work in WPF.
private void Button_Click_1(object sender, RoutedEventArgs e) {
decimal total = 0;
foreach (ListViewItem o in orderDetailsListView.Items)
{
total = total + (decimal)(orderDetailsListView.SelectedItems[1]);
}
totalOutputLabel.Content = total;
}
I wrote this below answer to another question of yours on this same program that you deleted before I could post it. It covers updating the price but it also covers a lot more (using information that was in the deleted question).
First of all, if you want the screen to update when the items in the list are updated you must make the class implement INotifyPropertyChanged
public class OrderDetailsListItem : INotifyPropertyChanged
{
private string _name;
private decimal _price;
private int _quantity;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public decimal Price
{
get { return _price; }
set
{
if (value == _price) return;
_price = value;
OnPropertyChanged();
}
}
public int Quantity
{
get { return _quantity; }
set
{
if (value == _quantity) return;
_quantity = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now when the Price or Quantity is changed it will let the bindings know that the item was changed.
Next the reason that your if (OrderItem.Contains( caused duplicate items to show up is you must implement Equals( (and preferably GetHashCode()) for things like Contains( to work.
public class OrderDetailsListItem : INotifyPropertyChanged, IEquatable<OrderDetailsListItem>
{
//(Snip everything from the first example)
public bool Equals(OrderDetailsListItem other)
{
if (ReferenceEquals(null, other)) return false;
return string.Equals(_name, other._name);
}
public override bool Equals(object obj)
{
return Equals(obj as OrderDetailsListItem);
}
public override int GetHashCode()
{
return (_name != null ? _name.GetHashCode() : 0);
}
}
Another point, don't do OrderItem.CollectionChanged += in your button click, you will be creating extra event calls every collection changed event. Just set it one in the constructor and that is the only even subscription you need. However, there is a even better collection to use, BindingList<T> and its ListChanged event. A BindingList will raise the ListChange event in all the situations that ObserveableCollection raises CollectionChanged but in addition it will also raise the event when any item in the collection raises the INotifyPropertyChanged event.
public MainWindow()
{
_orderItem = new BindingList<OrderDetailsListItem>();
_orderItem.ListChanged += OrderItemListChanged;
InitializeComponent();
GetBeerInfo();
//You will see why all the the rest of the items were removed in the next part.
}
private void OrderItemListChanged(object sender, ListChangedEventArgs e)
{
TotalPrice = OrderItem.Select(x => x.Price).Sum();
}
Lastly, I would bet you came from a Winforms background. WPF is based around binding a lot more than winforms, I used to write code a lot like what you are doing before I really took that point in. All of those assignments to labels and collections should be done in the XAML with bindings, this allows for things like the INotifyPropertyChanged events to automatically update the screen without needing a function call.
Here is a simple recreation of your program that runs and uses bindings and all of the other things I talked about.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myNamespace ="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button Content="{x:Static myNamespace:GlobalVariables._amstelProductName}" Click="amstelBeerButton_Click"/>
<TextBlock Text="{Binding TotalPrice, StringFormat=Total: {0:c}}"/>
</StackPanel>
<ListView Grid.Column="1" ItemsSource="{Binding OrderItem}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=Name}" Header="Name"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Price, StringFormat=c}" Header="Price"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Quantity, StringFormat=N0}" Header="Quantity"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public static readonly DependencyProperty TotalPriceProperty = DependencyProperty.Register(
"TotalPrice", typeof (decimal), typeof (MainWindow), new PropertyMetadata(default(decimal)));
private readonly BindingList<OrderDetailsListItem> _orderItem;
public MainWindow()
{
_orderItem = new BindingList<OrderDetailsListItem>();
_orderItem.ListChanged += OrderItemListChanged;
InitializeComponent();
DataContext = this;
GetBeerInfo();
}
public BindingList<OrderDetailsListItem> OrderItem
{
get { return _orderItem; }
}
public decimal TotalPrice
{
get { return (decimal) GetValue(TotalPriceProperty); }
set { SetValue(TotalPriceProperty, value); }
}
private void GetBeerInfo()
{
OrderItem.Add(new OrderDetailsListItem
{
Name = "Some other beer",
Price = 2m,
Quantity = 1
});
}
private void OrderItemListChanged(object sender, ListChangedEventArgs e)
{
TotalPrice = _orderItem.Select(x => x.Price).Sum();
}
private void amstelBeerButton_Click(object sender, RoutedEventArgs e)
{
//This variable makes me suspicous, this probibly should be a property in the class.
var quantityItem = GlobalVariables.quantityChosen;
if (quantityItem == 0)
{
quantityItem = 1;
}
var item = OrderItem.FirstOrDefault(i => i.Name == GlobalVariables._amstelProductName);
if (item == null)
{
OrderItem.Add(new OrderDetailsListItem
{
Name = GlobalVariables._amstelProductName,
Quantity = quantityItem,
Price = GlobalVariables._amstelPrice
});
}
else if (item != null)
{
item.Quantity = item.Quantity + quantityItem;
item.Price = item.Price*item.Quantity;
}
//The UpdatePrice function is nolonger needed now that it is a bound property.
}
}
public class GlobalVariables
{
public static int quantityChosen = 0;
public static string _amstelProductName = "Amstel Beer";
public static decimal _amstelPrice = 5;
}
public class OrderDetailsListItem : INotifyPropertyChanged, IEquatable<OrderDetailsListItem>
{
private string _name;
private decimal _price;
private int _quantity;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public decimal Price
{
get { return _price; }
set
{
if (value == _price) return;
_price = value;
OnPropertyChanged();
}
}
public int Quantity
{
get { return _quantity; }
set
{
if (value == _quantity) return;
_quantity = value;
OnPropertyChanged();
}
}
public bool Equals(OrderDetailsListItem other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(_name, other._name);
}
public event PropertyChangedEventHandler PropertyChanged;
public override bool Equals(object obj)
{
return Equals(obj as OrderDetailsListItem);
}
public override int GetHashCode()
{
return (_name != null ? _name.GetHashCode() : 0);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Just did a test on this, you should make sure you are getting into the event handler by adding a breakpoint. If you are not, make sure you have registered the handler to the click event, e.g.:
<Button Name="TestButton" Click="Button_Click_1"/>
If you are playing around with WPF I would strongly suggest looking at MVVM and data binding at some point.
I'm trying to bind some XAML code to a property in my ViewModel.
<Grid Visibility="{Binding HasMovies, Converter={StaticResources VisibilityConverter}}">
...
</Grid>
My ViewModel is setup like this:
private bool _hasMovies;
public bool HasMovies
{
get { return _hasMovies; }
set { _hasMovies = value; RaisePropertyChanged("HasMovies"); }
}
In the constructor of the ViewModel, I set the HasMovies link:
MovieListViewModel()
{
HasMovies = CP.Connection.HasMovies;
}
in CP:
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
private ObservableCollection<Movie> _movies;
public ObservableCollection<Movie> MovieList
{
get { return _movies; }
set
{
_movies = value;
RaisePropertyChanged("MovieList");
RaisePropertyChanged("HasMovies");
_movies.CollectionChanged += MovieListChanged;
}
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
What am I doing wrong? How should I change this binding so that it reflects the current state of CP.Connection.HasMovies?
Either directly expose the object in the ViewModel and bind directly through that (so that the value is not just copied once which is what happens now) or subscribe to the PropertyChanged event and set HasMovies to the new value every time it changes in your source object.
e.g.
CP.Connection.PropertyChanged += (s,e) =>
{
if (e.PropertyName = "HasMovies") this.HasMovies = CP.Connection.HasMovies;
};
First of all, the setter for a collection type, such as your MovieList property, is not called when you change the content of the collection (ie. Add/Remove items).
This means all your setter code for the MovieList property is pointless.
Secondly, it's very silly code. A much better solution, is to use NotifyPropertyWeaver. Then your code would look like this, in the viewmodel:
[DependsOn("MovieList")]
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
Alternatively you would have to add a listener for the CollectionChanged event when you initialize the MovieList property the first time (no reason to have a backing property, really really no reason!), and then call RaisePropertyChanged("HasMovies") in the event handler.
Example:
public class CP : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public CP()
{
MovieList = new ObservableCollection<Movie>();
MovieList.CollectionChanged += MovieListChanged;
}
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}