INotifyPropertyChanged 'Double' binding - c#

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));
}
}
}

Related

Access to ObservableCollection

I'm asking how we can acceed to an ObservableCollection.
Actually I'm working on a project and i have to collect the checked elements in an ObservableCollection in order to copy these elements to a PDF file.
public Class FianlElements
{
private int chapAr;
public int ChapAr
{
get { return chapAr; }
set
{
chapAr = value;
OnPropertyChanged("ChaprAr");
OnPropertyChanged("Article");
}
}
private string article;
public string Article
{
get { return article; }
set
{
article = value;
OnPropertyChanged("ChapAr");
OnPropertyChanged("Article");
}
}
private float somme;
public float Somme
{
get { return somme; }
set
{
somme = value;
OnPropertyChanged("Somme");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertySomme)
{
if (PropertyChanged != null)
PropertyChanged(this, new
PropertyChangedEventArgs(propertySomme));
}
}
Actually this is the class of the type of ObservableCollection
My declaration of the ObservableCollection is here
public ObservableCollection<FinalSelection> LesElem { get; set; }
I have another ObservableCollection
public ObservableCollection<ListBoxArticle> LesArticles { get; set; }
This one is Binded to a ListBox which contains CheckBox and TextBox
like this
so I want to copy only the checked elements to "LesElem"
So how can I get access to this ObservableCollection
Thanx
Let's imagine you are interested in LesArticle which are ticked.
AND
You have an IsChecked property on LesArticle.
AND
This is bound to the IsChecked property of the checkbox.
The list of checked ones would be
var checkedList = LesArticle.Where(x => x.IsChecked == true).ToList();
Observablecollection can take a List as it's constructor. So you can do:
FinalElem = new ObservableCollection<ListBoxArticle>(checkedList);

Accessing ViewModel properties in parent ViewModel

I'm using this technique for navigation between views: https://social.technet.microsoft.com/wiki/contents/articles/30898.simple-navigation-technique-in-wpf-using-mvvm.aspx
I have the main ViewModel with menu buttons bound to SelectedViewModel property change commands:
class MainViewModel : INotifyPropertyChanged
{
public ICommand SomeViewCommand { get; set; }
public ICommand OtherViewCommand { get; set; }
private object selectedViewModel;
public event PropertyChangedEventHandler PropertyChanged;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public MainViewModel()
{
SomeViewCommand = new RelayCommand<object, object>(null, (object o) => OpenSomeView());
OtherViewCommand = new RelayCommand<object, object>(null, (object o) => OpenOtherView());
}
private void OpenSomeView()
{
SelectedViewModel = new SomeViewModel();
}
private void OpenOtherView(object obj)
{
if(SelectedViewModel != null && SelectedViewModel.GetType() == typeof(SomeViewModel))
{
SomeViewModel s = (SomeViewModel)SelectedViewModel;
// always 0
if (s.NumberOfChanges > 0)
{
MessageBox.Show("test", "Error");
}
// SelectedViewModel = new OtherViewModel(); after confirmation dialog
}
else
SelectedViewModel = new OtherViewModel();
}
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
If I'm in SomeView, I'd like to check its property (number of changes) before switching to OtherView and show a confirmation dialog to the user to confirm their action. I need the current value, but any property seems to have its initialization value. Why?
What would be the cleanest way of doing this? I know it can be done by making the property static, but that seems dirty to me.
In OnPropertyChanged method you can set NumberOfChanges.

How to properly update UserControl combobox's Itemsource?

I'm completely new to WPF and I'm having problems with ItemsSource updates.
I created a single main window Metro application with tabs (TabItem(s) as UserControl DataContext="{Binding}") in which different data is displayed / different methods used.
What I've found myself struggling with is INotifyPropertyChanged (I wasn't able to understand the solution of my problem from similar examples/questions) interface's concept. I'm trying to make that if new data is entered in a window (which is initialized from one of the UserControl), a ComboBoxin another UserControl (or TabItem) would be automatically updated. Here's what I have:
UserControl1.xaml
public partial class UserControl1: UserControl
{
private userlist addlist;
public UserControl1()
{
InitializeComponent();
fillcombo();
}
public void fillcombo()
{
Fillfromdb F = new Fillfromdb(); // class that simply connects
// to a database sets a datatable as ListCollectionView
addlist = new addlist { List = F.returnlistview() }; // returns ListCollectionView
UsersCombo.ItemsSource = addlist.List;
}
userlist.cs
public class userlist: INotifyPropertyChanged
{
private ListCollectionView _list;
public ListCollectionView List
{
get { return this._list; }
set
{
if (this._list!= value)
{
this._list= value;
this.NotifyPropertyChanged("List");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Registration.xaml (called from another UserControl)
public partial class Registration: MetroWindow
{
public Registration()
{
InitializeComponent();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
Here's the ComboBox's setting in UserControl.xaml:
<ComboBox x:Name="UsersCombo"
ItemsSource="{Binding List, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Since I don't have any programming education/experience a very generic advice/explanation would be very much appreciated.
EDIT: Registration.xaml with propertychanged (still doesn't work):
public partial class Registration : MetroWindow
{
public userlist instance = new userlist();
public ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is save to database
// still don't now what to do with new ListCollectionView from database
}
public void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List; // when new data from database should be loaded?
}
This is where PropertyChanged event comes handy.
Bind the combobox in second xaml page to a List and create a similar property like in first xaml.
In second xaml.cs
public partial class Registration: MetroWindow, INotifyPropertyChanged
{
private userlist instance = new userlist();
private ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List;
//or iterate through the list and add as below
foreach(var item in instance.List)
{
List1.Add(item);
}
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}

WPF / C# Updating property change of an item in an ObservableCollection to the ListBox

I have a list box:
<ListBox x:Name="lbxAF" temsSource="{Binding}">
that gets its data from this from this modified Observable Collection:
public ObservableCollectionEx<FileItem> folder = new ObservableCollectionEx<FileItem>();
which is created within a class that uses FileSystemWatcher to monitor a specific folder for addition, deletion and modification of files.
The ObservableCollection was modified (hence the Ex at the end) so that I can modify it from an outside thread (code is not mine, I actually did some searching through this website and found it, works like a charm):
// This is an ObservableCollection extension
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
// Override the vent so this class can access it
public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
using (BlockReentrancy())
{
System.Collections.Specialized.NotifyCollectionChangedEventHandler eventHanlder = CollectionChanged;
if (eventHanlder == null)
return;
Delegate[] delegates = eventHanlder.GetInvocationList();
// Go through the invocation list
foreach (System.Collections.Specialized.NotifyCollectionChangedEventHandler handler in delegates)
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
// If the subscriber is a DispatcherObject and different thread do this:
if (dispatcherObject != null && dispatcherObject.CheckAccess() == false)
{
// Invoke handler in the target dispatcher's thread
dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);
}
// Else, execute handler as is
else
{
handler(this, e);
}
}
}
}
}
The collection is made up of these:
public class FileItem
{
public string Name { get; set; }
public string Path { get; set; }
}
which allow me to store names and paths of files.
Everything works great as far as deletion and addition of files, and the List Box gets updated flawlessly with respect to those two... however, if I change the name of any of the files, it doesn't update the list box.
How would I notify list box of the changes in FileItem's properties? I assumed that ObservableCollection would handle that, but apparently it raises flag only when FileItem is added or deleted, not when its contents are changed.
Your FileItem class should implement INotifyPropertyChanged. Below is a simple working implementation of it.
public class FileItem : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return _Name; }
set {
if (_Name != value)
{
_Name = value;
OnPropertyChanged("Name");
}
}
}
private string _Path;
public string Path
{
get { return _Path; }
set {
if (_Path != value)
{
_Path = value;
OnPropertyChanged("Path");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
That's how the ObservableCollection works - it monitors only insertion/deletion/moving of items.
To update the View when each item (or FileItem, in your case) changes, the FileItem must implement INotifyPropertyChanged and fire the appropriate event when you set each property that you want to observe.
Here's an example of how to do this: http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
Try this simple one:
public class NotifyObservableCollection<TItem> : ObservableCollection<TItem>
where TItem : class , INotifyPropertyChanged, new()
{
#region Fields
private Action _itemPropertyChanged;
#endregion
#region Constructor
public NotifyObservableCollection(Action itemPropertyChanged)
{
_itemPropertyChanged = itemPropertyChanged;
}
#endregion
#region Methods
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
var notifyItem = item as INotifyPropertyChanged;
if (notifyItem != null)
{
notifyItem.PropertyChanged += ItemPropertyChanged;
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.OldItems)
{
var notifyItem = item as INotifyPropertyChanged;
if (notifyItem != null)
{
notifyItem.PropertyChanged -= ItemPropertyChanged;
}
}
}
base.OnCollectionChanged(e);
}
#endregion
#region Private Methods
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(_itemPropertyChanged!=null)
{
_itemPropertyChanged();
}
}
#endregion
}

PropertyChangedCallback on DependencyProperty Only Firing Once

I have the exact problem as this guy in the Silverlight Forum and the accepted answer is :
In this case, your property didn't actually change value. You added
something to your List, but the list is the same List so when the
DependencyProperty mechanism sees that the actual value (reference to
your List) didn't change, it didn't raise your OnChanged handler
This is a great explication but not an answer to fix this problem. I can find on Google many suggestion for WPF but not for Silverlight.
The problem is describe as this one : You have a DependencyProperty that is called when the variable is initialized but after then nothing is updated.
public partial class MyGrid : UserControl
{
public MyGrid()
{
InitializeComponent();
}
public static readonly DependencyProperty ShapesProperty = DependencyProperty.Register(
"Shapes", typeof(ObservableCollection<ModelItem>), typeof(MyGrid), new PropertyMetadata(OnShapesPropertyChanged));
public ObservableCollection<ModelItem> Shapes
{
private get { return (ObservableCollection<ModelItem>)GetValue(ShapesProperty); }
set { SetValue(ShapesProperty, value); }
}
private static void OnShapesPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((MyGrid)o).OnShapesPropertyChanged(e); //Fire Only Once
}
private void OnShapesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
dg.ItemsSource = e.NewValue as ObservableCollection<ModelItem>;
}
}
//--------
public class ViewModel : INotifyPropertyChanged
{
public Model Model { get; set; }
public RelayCommand cmd;
public ObservableCollection<ModelItem> ModelItemCollection
{
get
{
return Model.ModelItem;
}
}
public ViewModel()
{
Model = new Model();
Model.PropertyChanged += Model_PropertyChanged;
}
void Model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.PropertyName);
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("ModelItemCollection"));
}
}
public ICommand AddCmd
{
get { return cmd ?? (cmd = new RelayCommand(a => Model.ModelItem.Add(new ModelItem {Name = "asd"}))); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
///----------------------
public class Model: INotifyPropertyChanged
{
public ObservableCollection<ModelItem> ModelItem { get; set; }
public Model()
{
ModelItem = new ObservableCollection<ModelItem>();
ModelItem.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ModelItem_CollectionChanged);
}
void ModelItem_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("ModelItem"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ModelItem
{
public String Name { get; set; }
}
Even with explicit call of PropertyChanged() nothing is updated.
What is the workaround to let know the DependencyProperty that the ObservableCollection has elements that have changed?
Pseudocode:
BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty).UpdateTarget();
Look here: forcing a WPF binding to 'refresh' ...
Try this, usually works :)

Categories