I'm trying to load images from the web in my wpf application.
The idea is the following:
When I click on a button, a popup with additional information is raised. In this popup I'm using some images from the web.
The problem:
When the popup is being loaded the systems hangs while waiting for the images. I'm binding
the images from my code behind. The images are stored in an ObservableCollection. I tried
using a thread for loading the images but everytime I run into an exception saying the thread is not the owner of the object.
I tried using an Invoke to get the downloaded images to the UserinterfaceThread but I can't reach it. My code is the following:
IList<Image> imagesFromWeb = downloadImagesFromWeb(url);
DispatcherHelper.UIDispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
foreach (Image img in imagesFromWeb
{
this.ObservableCollection_Images.Add(img);
}
}
As soon as the images are downloaded and it tries to add the images to the (already opened) popup I get the exception saying the thread
is not the owner of the object
Can someone please point me into the right direction?
If you have the image available on a public web server which can be adressed using a normal HTTP URI then you can set the source directly to that:
<Image Source="http://www.someserver.com/myimage.png" />
WPF will take care of downloading it - it'll even do it asynchronously I think although I'm not 100% sure.
You can of course do this with databinding as well:
<Image Source="{Binding TheImage}" />
And in the viewmodel
public string TheImage
{
get { return "http://www.someserver.com/myimage.png"; }
}
You can get a variety of issues with collections, WPF, binding and threading
The best thing (in my opinion) is to use a dispatcher-safe observable collection
here is an implementation, with also includes thread-safety:
public class SafeObservable<T> : IList<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
private readonly IList<T> collection = new List<T>();
private readonly Dispatcher dispatcher;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public event PropertyChangedEventHandler PropertyChanged;
private readonly ReaderWriterLock sync = new ReaderWriterLock();
public SafeObservable()
{
dispatcher = Dispatcher.CurrentDispatcher;
}
public void Add(T item)
{
if (Thread.CurrentThread == dispatcher.Thread)
DoAdd(item);
else
dispatcher.BeginInvoke((Action)(() => DoAdd(item)));
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
private void DoAdd(T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.Add(item);
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
sync.ReleaseWriterLock();
}
public void Clear()
{
if (Thread.CurrentThread == dispatcher.Thread)
DoClear();
else
dispatcher.BeginInvoke((Action)(DoClear));
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
private void DoClear()
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.Clear();
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
sync.ReleaseWriterLock();
}
public bool Contains(T item)
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection.Contains(item);
sync.ReleaseReaderLock();
return result;
}
public void CopyTo(T[] array, int arrayIndex)
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.CopyTo(array, arrayIndex);
sync.ReleaseWriterLock();
}
public int Count
{
get
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection.Count;
sync.ReleaseReaderLock();
return result;
}
}
public bool IsReadOnly
{
get { return collection.IsReadOnly; }
}
public bool Remove(T item)
{
if (Thread.CurrentThread == dispatcher.Thread)
return DoRemove(item);
var op = dispatcher.BeginInvoke(new Func<T, bool>(DoRemove), item);
if (op == null || op.Result == null)
return false;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
return (bool)op.Result;
}
private bool DoRemove(T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
var index = collection.IndexOf(item);
if (index == -1)
{
sync.ReleaseWriterLock();
return false;
}
var result = collection.Remove(item);
if (result && CollectionChanged != null)
CollectionChanged(this, new
NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
sync.ReleaseWriterLock();
return result;
}
public IEnumerator<T> GetEnumerator()
{
return collection.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return collection.GetEnumerator();
}
public int IndexOf(T item)
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection.IndexOf(item);
sync.ReleaseReaderLock();
return result;
}
public void Insert(int index, T item)
{
if (Thread.CurrentThread == dispatcher.Thread)
DoInsert(index, item);
else
dispatcher.BeginInvoke((Action)(() => DoInsert(index, item)));
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
private void DoInsert(int index, T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.Insert(index, item);
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
sync.ReleaseWriterLock();
}
public void RemoveAt(int index)
{
if (Thread.CurrentThread == dispatcher.Thread)
DoRemoveAt(index);
else
dispatcher.BeginInvoke((Action)(() => DoRemoveAt(index)));
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
private void DoRemoveAt(int index)
{
sync.AcquireWriterLock(Timeout.Infinite);
if (collection.Count == 0 || collection.Count <= index)
{
sync.ReleaseWriterLock();
return;
}
collection.RemoveAt(index);
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
sync.ReleaseWriterLock();
}
public T this[int index]
{
get
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection[index];
sync.ReleaseReaderLock();
return result;
}
set
{
sync.AcquireWriterLock(Timeout.Infinite);
if (collection.Count == 0 || collection.Count <= index)
{
sync.ReleaseWriterLock();
return;
}
collection[index] = value;
sync.ReleaseWriterLock();
}
}
}
I figured there's a better way to load the image.
Instead of binding to an image in the code behind it's better to bind to a string containing the location of the image. After that I use a converter in the xaml code which converts the string to an image. (the image downloader is now inside the converter class)
the code in xaml:
<Image Source="{Binding imageUrl, Converter={StaticResource url}}" Height="200" Width="200"></Image>
The code for the converter:
class ImageDownloader : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string url =(string)value;
return getImage(url);
}
private object getImage(string imagefile)
{
/// IMPLEMENT FUNCTION TO DOWNLOAD IMAGE FROM SERVER HERE
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
and ofcourse don't forget to set-up the resource in the app.xaml with:
<Application.Resources>
<ResourceDictionary>
<namespace:ImageDownloader x:Key="ImageDownloader" />
</ResourceDictionary>
</Application.Resources>
Related
i had some problems with the speed of my application and done some performance profiling. The result shows that there is a lot of time in my application that is spended with linq querys especialy to the ID's of the Models. My idea was to create a observable dictionary with the ID as the key and the model as the Value. This works pretty good and is much faster than the linq querys with
.Any(x => x.ID == id)
or the linq query with
.First(x => x.ID == id)
As ObservableDictionary i used this sample
http://blogs.microsoft.co.il/shimmy/2010/12/26/observabledictionarylttkey-tvaluegt-c/
the problem now is that i need to create a ObservableCollection wich i can bind to my Views. I tried to expand the ObservableDictionary with a ObservableValue Property but that does not work
public ObservableCollection<TValue> ObservableValues
{
get
{
if (observableValues == null)
{
lock (lockObject)
{
if (observableValues == null)
observableValues = new ObservableCollection<TValue>(Dictionary.Values);
}
}
return observableValues;
}
}
When i add a Model to my dictionary or update a model the ObservableCollection which is bound to the Views will not update.
The best solution I can think of is to keep in your ObservableDictionnary an ObservableCollection containing all the values of the dictionnary.
You then have to change your class so that when you insert/update/delete a value in the dicionnary, it does the same in the ObservableCollection thus, firing the event to update the view.
Thanks for the suggestion but i tryed some implementations of a KeyedCollection and decided to switch all Collections / Dictionarys to my custom implementation of a KeyedCollection. The Performance is as fast as with the dictionary but without the using of the KVP's. Heres my observable implementation with some Replace Methods and it worked very fast.
public class ObservableKeyedCollection<TKey, TItem> : KeyedCollection<TKey, TItem>, INotifyCollectionChanged
{
private const string CountString = "Count";
private readonly Func<TItem, TKey> _getKeyForItemDelegate;
// Constructor now requires a delegate to get the key from the item
public ObservableKeyedCollection(Func<TItem, TKey> getKeyForItemDelegate) : base()
{
if (getKeyForItemDelegate == null)
throw new ArgumentNullException("Delegate passed can't be null!");
_getKeyForItemDelegate = getKeyForItemDelegate;
}
protected override TKey GetKeyForItem(TItem item)
{
return _getKeyForItemDelegate(item);
}
/// <summary>
/// Method to add a new object to the collection, or to replace an existing one if there is
/// already an object with the same key in the collection.
/// </summary>
public void AddOrReplace(TItem newObject)
{
int i = GetItemIndex(newObject);
if (i != -1)
SetItem(i, newObject);
else
Add(newObject);
}
/// <summary>
/// Method to replace an existing object in the collection, i.e., an object with the same key.
/// An exception is thrown if there is no existing object with the same key.
/// </summary>
public void Replace(TItem newObject)
{
int i = GetItemIndex(newObject);
if (i != -1)
SetItem(i, newObject);
else
throw new Exception("Object to be replaced not found in collection.");
}
/// <summary>
/// Method to get the index into the List{} in the base collection for an item that may or may
/// not be in the collection. Returns -1 if not found.
/// </summary>
private int GetItemIndex(TItem itemToFind)
{
TKey keyToFind = GetKeyForItem(itemToFind);
if (this.Contains(keyToFind))
return this.IndexOf(this[keyToFind]);
else return -1;
}
// Overrides a lot of methods that can cause collection change
protected override void SetItem(int index, TItem item)
{
var oldItem = base[index];
base.SetItem(index, item);
OnCollectionChanged(NotifyCollectionChangedAction.Replace, item, oldItem);
}
protected override void InsertItem(int index, TItem item)
{
base.InsertItem(index, item);
OnCollectionChanged(NotifyCollectionChangedAction.Add, item);
}
protected override void ClearItems()
{
base.ClearItems();
OnCollectionChanged();
}
protected override void RemoveItem(int index)
{
TItem item = this[index];
base.RemoveItem(index);
OnCollectionChanged(NotifyCollectionChangedAction.Remove, item);
}
private bool _deferNotifyCollectionChanged = false;
public void AddRange(IEnumerable<TItem> items)
{
_deferNotifyCollectionChanged = true;
foreach (var item in items)
Add(item);
_deferNotifyCollectionChanged = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (_deferNotifyCollectionChanged)
return;
if (CollectionChanged != null)
CollectionChanged(this, e);
}
#region INotifyCollectionChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void OnPropertyChanged()
{
OnPropertyChanged(CountString);
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionChanged()
{
if (_deferNotifyCollectionChanged)
return;
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, TItem changedItem)
{
if (_deferNotifyCollectionChanged)
return;
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, TItem newItem, TItem oldItem)
{
if (_deferNotifyCollectionChanged)
return;
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
{
if (_deferNotifyCollectionChanged)
return;
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems));
}
#endregion
}
I have implemented INotifyDataErrorInfo exactly as described in the following link:
http://blog.micic.ch/net/easy-mvvm-example-with-inotifypropertychanged-and-inotifydataerrorinfo
I have a TextBox which is bound to a string property in my model.
XAML
<TextBox Text="{Binding FullName,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged}" />
Model
private string _fullName;
public string FullName
{
get { return _fullName; }
set
{
// Set raises OnPropertyChanged
Set(ref _fullName, value);
if (string.IsNullOrWhiteSpace(_fullName))
AddError(nameof(FullName), "Name required");
else
RemoveError(nameof(FullName));
}
}
INotifyDataError Code
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// get errors by property
public IEnumerable GetErrors(string propertyName)
{
if (_errors.ContainsKey(propertyName))
return _errors[propertyName];
return null;
}
public bool HasErrors => _errors.Count > 0;
// object is valid
public bool IsValid => !HasErrors;
public void AddError(string propertyName, string error)
{
// Add error to list
_errors[propertyName] = new List<string>() { error };
NotifyErrorsChanged(propertyName);
}
public void RemoveError(string propertyName)
{
// remove error
if (_errors.ContainsKey(propertyName))
_errors.Remove(propertyName);
NotifyErrorsChanged(propertyName);
}
public void NotifyErrorsChanged(string propertyName)
{
// Notify
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
Now all this works fine, but it only validates as soon as I type something in my TextBox. I would like some way to validate on demand, without even touching the textbox, say on a button click.
I have tried raising PropertyChanged for all my properties as described in this question, but it does not detect the errors. I somehow need my property setter to be called so the errors can be detected. I'm looking for a MVVM solution.
The INotifyDataErrorInfo implementation you use is somewhat flawed IMHO. It relies on errors kept in a state (a list) attached to the object. Problem with stored state is, sometimes, in a moving world, you don't have the chance to update it when you want. Here is another MVVM implementation that doesn't rely on a stored state, but computes error state on the fly.
Things are handled a bit differently as you need to put validation code in a central GetErrors method (you could create per-property validation methods called from this central method), not in the property setters.
public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors
{
get
{
return GetErrors(null).OfType<object>().Any();
}
}
public virtual void ForceValidation()
{
OnPropertyChanged(null);
}
public virtual IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
return Enumerable.Empty<object>();
}
protected void OnErrorsChanged([CallerMemberName] string propertyName = null)
{
OnErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
var handler = ErrorsChanged;
if (handler != null)
{
handler(sender, e);
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(sender, e);
}
}
}
And here are two sample classes that demonstrate how to use it:
public class Customer : ModelBase
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
public override IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Name))
{
if (string.IsNullOrWhiteSpace(_name))
yield return "Name cannot be empty.";
}
}
}
public class CustomerWithAge : Customer
{
private int _age;
public int Age
{
get
{
return _age;
}
set
{
if (_age != value)
{
_age = value;
OnPropertyChanged();
}
}
}
public override IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
foreach (var obj in base.GetErrors(propertyName))
{
yield return obj;
}
if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Age))
{
if (_age <= 0)
yield return "Age is invalid.";
}
}
}
It works like a charm with a simple XAML like this:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" />
(UpdateSourceTrigger is optional, if you don't use it it will only work when focus is lost).
With this MVVM base class, you shouldn't have to force any validation. But should you need it, I have added a ForceValidation sample method in ModelBase that should work (I have tested it with for example a member value like _name that would have been changed without passing through the public setter).
Your best bet is to use a relay command interface. Take a look at this:
public class RelayCommand : ICommand
{
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public RelayCommand(Action executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod)
{
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null)
{
return true;
}
return false;
}
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter)
{
if (_TargetExecuteMethod != null)
{
_TargetExecuteMethod();
}
}
#endregion
}
You would declare this relay command in your view model like:
public RelayCommand SaveCommand { get; private set; }
Now, in addition to registering your SaveCommand with OnSave and a CanSave methods, since you extend from INotifyDataErrorInfo, you can sign up to ErrorsChanged in your constructor as well:
public YourViewModel()
{
SaveCommand = new RelayCommand(OnSave, CanSave);
ErrorsChanged += RaiseCanExecuteChanged;
}
And you'll need the methods:
private void RaiseCanExecuteChanged(object sender, EventArgs e)
{
SaveCommand.RaiseCanExecuteChanged();
}
public bool CanSave()
{
return !this.HasErrors;
}
private void OnSave()
{
//Your save logic here.
}
Also, each time after you call PropertyChanged, you can call this validation method:
private void ValidateProperty<T>(string propertyName, T value)
{
var results = new List<ValidationResult>();
ValidationContext context = new ValidationContext(this);
context.MemberName = propertyName;
Validator.TryValidateProperty(value, context, results);
if (results.Any())
{
_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
}
else
{
_errors.Remove(propertyName);
}
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
With this setup, and if your viewmodel both extends from INotifyPropertyChanged and INotifyDataErrorInfo (or from a base class that extends from these two), when you bind a button to the SaveCommand above, WPF framework will automatically disable it if there are validation errors.
Hope this helps.
I have implemented an ObservableCollection to support bulk addition like below and bind to a WPF UI -
public void AddRange(IEnumerable<T> list)
{
lock (_lock)
{
if (list == null)
{
throw new ArgumentNullException("list");
}
_suspendCollectionChangeNotification = true;
var newItems = new List<T>();
foreach (T item in list)
{
if (!Contains(item))
{
Add(item);
newItems.Add(item);
}
}
_suspendCollectionChangeNotification = false;
var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems);
OnCollectionChanged(arg); //NotifyCollectionChangedAction.Reset works!!!
}
}
}
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChanged(e);
}
internal void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (IsCollectionChangeSuspended)
{
return;
}
NotifyCollectionChangedEventHandler handler = CollectionChanged;
if (handler != null)
{
if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.DataBind,handler, this, e);
}
else
{
handler(this, e);
}
}
}
private bool IsCollectionChangeSuspended
{
get { return _suspendCollectionChangeNotification; }
}
I get this error - {"Collection was modified; enumeration operation may not execute."}
But, if I change the NotifyCollectionChangedAction.Add to NotifyCollectionChangedAction.Reset and do not pass any changed list, then it binds correctly to the UI. But, I want to use NotifyCollectionChangedAction.Add such that I can observe on the changes.
Can anyone please correct me ?
If you use the internal IList<T> to add the items it wont notify all the events like using Add method,
The Add method works like this:
CheckReentrancy
InsertItem
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
So if you skip the Add method and add the items directly to the underlying collection you may get this to wok.
Example: (untested)
public void AddRange(IEnumerable<T> rangeItems)
{
foreach (var item in rangeItems)
{
Items.Add(item);
}
base.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
base.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, rangeItems);
OnCollectionChanged(arg);
}
I have wrote my public sealed class ObservableDictionary<TKey,TValue> : NotifyPropertyChangedClass, INotifyCollectionChanged, IDictionary<TKey, TValue> where TKey: IEquatable<TKey> class.
It marked as [Serializable].
But I got exception, when tried to serialize instance of ObservableDictionary<Int64, String>.
Exception message:
The MS.Internal.Data.EnumerableCollectionView type in assembly
"PresentationFramework, Version=4.0.0.0, Culture=neutral," isn't
marked with PublicKeyToken=31bf3856ad364e35 as serializable.
But I never used the MS.Internal.Data.EnumerableCollectionView type.
Where my mistake? My code below are located:
using System;
using System.ComponentModel;
namespace RememberEmployees {
[Serializable]
public abstract class NotifyPropertyChangedClass : INotifyPropertyChanged {
protected void NotifyPropertyChanged(string propertyName) {
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null) {
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
and
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.Specialized;
namespace RememberEmployees {
[Serializable]
public sealed class ObservableDictionary<TKey,TValue> : NotifyPropertyChangedClass,
INotifyCollectionChanged, IDictionary<TKey, TValue> where TKey: IEquatable<TKey> {
SortedDictionary<TKey, TValue> dict;
IComparer<TKey> Comparer {
get { return dict.Comparer; }
}
public ObservableDictionary() {
dict = new SortedDictionary<TKey, TValue>();
IsReadOnly = false;
}
private void NotifyCollectionChanged(NotifyCollectionChangedAction action) {
NotifyCollectionChangedEventHandler temp = CollectionChanged;
if (temp != null) {
temp(this, new NotifyCollectionChangedEventArgs(action));
}
}
private void NotifyCollectionChanged(NotifyCollectionChangedAction action, object item) {
NotifyCollectionChangedEventHandler temp = CollectionChanged;
if (temp != null) {
temp(this, new NotifyCollectionChangedEventArgs(action, item));
}
}
private void NotifyCollectionChanged(NotifyCollectionChangedAction action, int index) {
NotifyCollectionChangedEventHandler temp = CollectionChanged;
if (temp != null) {
temp(this, new NotifyCollectionChangedEventArgs(action, index));
}
}
private void NotifyCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> obj, int index) {
NotifyCollectionChangedEventHandler temp = CollectionChanged;
if (temp != null) {
temp(this, new NotifyCollectionChangedEventArgs(action, obj, index));
}
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void Add(TKey key, TValue value) {
if (IsReadOnly)
throw new Exception("Is Read Only");
dict.Add(key, value);
NotifyPropertyChanged("Count");
NotifyCollectionChanged(NotifyCollectionChangedAction.Add,
dict.Cast<KeyValuePair<TKey, TValue>>().FirstOrDefault(n => n.Key.Equals(key)));
}
public bool ContainsKey(TKey key) {
return dict.ContainsKey(key);
}
public ICollection<TKey> Keys {
get { return dict.Keys; }
}
public bool Remove(TKey key) {
if (IsReadOnly)
throw new Exception("Is Read Only");
if (!dict.Keys.Contains(key))
return false;
int x = 0;
foreach (TKey item in dict.Keys) {
if (item.Equals(key))
break;
++x;
}
KeyValuePair<TKey, TValue> val = dict.Cast<KeyValuePair<TKey, TValue>>().FirstOrDefault(n => n.Key.Equals(key));
bool result = dict.Remove(key);
if (result) {
NotifyPropertyChanged("Count");
NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, val, x);
}
return result;
}
public bool Remove(KeyValuePair<TKey, TValue> item) {
return Remove(item.Key);
}
public bool TryGetValue(TKey key, out TValue value) {
return dict.TryGetValue(key, out value);
}
public ICollection<TValue> Values {
get { return dict.Values; }
}
public TValue this[TKey key] {
get {
return dict[key];
}
set {
dict[key] = value;
NotifyCollectionChanged(NotifyCollectionChangedAction.Reset);
}
}
public void Add(KeyValuePair<TKey, TValue> item) {
Add(item.Key, item.Value);
}
public void Clear() {
if (IsReadOnly)
throw new Exception("Is Read Only");
dict.Clear();
NotifyCollectionChanged(NotifyCollectionChangedAction.Reset);
}
public bool Contains(KeyValuePair<TKey, TValue> item) {
return dict.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
if (arrayIndex > dict.Count)
throw new IndexOutOfRangeException();
int max = dict.Count - arrayIndex <= array.Count() ? dict.Count - arrayIndex : array.Count();
for (int i = 0; i < max; i++) {
array[i] = dict.Skip(arrayIndex).ToArray()[i];
}
}
public int Count {
get { return dict.Count; }
}
bool readOnly;
public bool IsReadOnly {
get { return readOnly; }
set { readOnly = value; NotifyPropertyChanged("IsReadOnly"); }
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
return dict.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return dict.GetEnumerator();
}
}
}
This has less to do with your class but with the data you did store in your collection. It seems that in your collection you have stored a ViewObject which internally does contain an EnumerableCollectionView object.
When you serialize data you must be sure what parts of your object graph you do want to serialize. Just putting objects in your collection could cause half of you application data sent over the wire or to disc. There is a good reason why DataContractSerializer was invented.
You should know before the serialize call what data you are going to serialize. Otherwise it could happen that e.g. in a client server application you are trying to deserialize types which are located in assemblies that do exist only on the server.
I have a base DependencyObject class where I have a method that takes an object, gets the properties, and for each property that is a type that implements INotifyPropertyChanged, I add a new PropertyChangedEventHandler. Now in the handler method, it gets the parameters of an object "sender" and the PropertyChangedEventArgs "e". My question is, does anyone know how to dynamically get the property name if sender is a property of a type that implements the INotifyPropertyChanged.
Here is what I'm working with:
public class BaseDependencyObject : DependencyObject, INotifyPropertyChanged
{
public BaseDependencyObject()
{
}
protected void SetValues(Object thisObject, Object entity)
{
try
{
PropertyInfo[] properties = entity.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
var value = property.GetValue(entity, null);
var valueIsEntity = value is System.ServiceModel.DomainServices.Client.Entity;
var thisObjectsProperty = thisObject.GetType().GetProperty(property.Name);
if (thisObjectsProperty != null && value != null)
{
if (valueIsEntity)
{
if (thisObjectsProperty.PropertyType.GetInterface("INotifyPropertyChanged", true) != null)
{
var propertyInstance = Activator.CreateInstance(thisObjectsProperty.PropertyType);
((INotifyPropertyChanged)propertyInstance).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
}
SetValues(thisObjectsProperty, value);
}
else if (thisObjectsProperty.PropertyType.GetInterface("ICollection", true) != null
&& thisObjectsProperty.PropertyType.GetGenericArguments().Count() > 0)
{
Type genericType = thisObjectsProperty.PropertyType.GetGenericArguments()[0];
var observableCollection = Activator.CreateInstance(thisObjectsProperty.PropertyType) as IList;
if (observableCollection is INotifyCollectionChanged)
((INotifyCollectionChanged)observableCollection).CollectionChanged += this.Object_CollectionChanged;
if (observableCollection is INotifyPropertyChanged)
((INotifyPropertyChanged)observableCollection).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
foreach (var item in (IEnumerable)value)
{
var newItem = Activator.CreateInstance(genericType);
if (newItem != null)
{
SetValues(newItem, item);
observableCollection.Add(newItem);
}
}
}
else
{
thisObjectsProperty.SetValue(thisObject, value, null);
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
protected void Object_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems)
{
if (item is INotifyPropertyChanged)
{
((INotifyPropertyChanged)item).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
if (item is INotifyPropertyChanged)
{
((INotifyPropertyChanged)item).PropertyChanged -= this.Object_PropertyChanged;
}
}
break;
}
}
protected void Object_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.NotifyPropertyChanged(e.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The SetValues method's first param is the DependencyObject type that will be used in the view model. The second param is the entity that is being returned from the DomainService's Context.LoadOperation.
What my issue boils down to is when the INotifyCollectionChanged.CollectionChanged fires I'm needing to be able to raise the PropertyChanged event with the collection's property name. So if anyone has any advise I would greatly appreciate it. Thanks in advance.
Edit
Figured out how to get the properties name that is firing the event. Here is an edited version of my PropertyChangedEventHandler.
protected void Object_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var properties = this.GetType().GetProperties().Where(x => x.PropertyType == sender.GetType()).ToArray();
foreach (var property in properties)
{
this.NotifyPropertyChanged(property.Name);
}
//this.NotifyPropertyChanged(e.PropertyName);
}
Basically this does what I was looking for, but aparentyly I am still not doing something right. The UIElement is still not updating when the ObservableCollection that is a property of another type is being added to.
Here is an example of my DependencyObjects and ViewModel:
public class LOB : DependencyObject
{
public Int32 ID
{
get { return (Int32)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
NotifyPropertyChanged("ID");
}
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(Int32), typeof(LOB), null);
public ObservableCollection<Group> Groups
{
get { return (ObservableCollection<Group>)GetValue(GroupsProperty); }
set
{
SetValue(GroupsProperty, value);
NotifyPropertyChanged("Groups");
}
}
public static readonly DependencyProperty GroupsProperty =
DependencyProperty.Register("Groups", typeof(ObservableCollection<Group>), typeof(LOB), new PropertyMetadata(null, OnGroupsPropertyChanged));
static void OnGroupsPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
((INotifyCollectionChanged)e.NewValue).CollectionChanged += new NotifyCollectionChangedEventHandler(((LOB)obj).Object_CollectionChanged);
((INotifyPropertyChanged)e.NewValue).PropertyChanged += new PropertyChangedEventHandler(((LOB)obj).Object_PropertyChanged);
}
if (e.OldValue != null)
{
((INotifyCollectionChanged)e.OldValue).CollectionChanged -= ((LOB)obj).Object_CollectionChanged;
((INotifyPropertyChanged)e.OldValue).PropertyChanged -= ((LOB)obj).Object_PropertyChanged;
}
}
}
public class Group : DependencyObject
{
public Int32 ID
{
get { return (Int32)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
NotifyPropertyChanged("ID");
}
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(Int32), typeof(Group), null);
public String GroupName
{
get { return (String)GetValue(GroupNameProperty); }
set
{
SetValue(GroupNameProperty, value);
NotifyPropertyChanged("GroupName");
}
}
public static readonly DependencyProperty GroupNameProperty =
DependencyProperty.Register("GroupName", typeof(String), typeof(Group), null);
}
public class MyViewModel : DependencyObject
{
public static readonly DependencyProperty LobCollectionProperty =
DependencyProperty.Register("LobCollection",
typeof(ObservableCollection<LOB>),
typeof(MyViewModel),
new PropertyMetadata(null, LobCollectionPropertyChanged));
public ObservableCollection<LOB> LobCollection
{
get { return (ObservableCollection<MainBusinessLine>)GetValue(LobCollectionPropertyChanged); }
set
{
SetValue(MainBusinessLineCollectionProperty, value);
NotifyPropertyChanged("LobCollection");
}
}
static void LobCollectionPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var viewModel = obj as MyViewModel;
if (viewModel == null)
return;
if (e.OldValue != null)
{
((INotifyCollectionChanged)e.OldValue).CollectionChanged -= viewModel.LobCollection_Changed;
}
if (e.NewValue != null)
{
((INotifyCollectionChanged)e.NewValue).CollectionChanged += viewModel.LobCollection_Changed;
}
}
void LobCollection_Changed(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("LobCollection");
}
}
After our conversation above, this is rather moot, but I thought about how I'd implement a base class that fired PropertyChanged events when a collection changed in a property that was defined by the subclass. As I said, it's a bit non-standard, but here's how I'd do it.
class FancyCollectionAndPropertyChangedBase : INotifyPropertyChanged
{
private Dictionary<ICollectionChanged, String> collectionNameLookup = new Dictionary<ICollectionChanged, String>();
protected FancyCollectionAndPropertyChangedBase()
{
this.PropertyChanged += MyPropertyChanged;
}
private void MyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(this.collectionNameLookup.ContainsValue(e.PropertyName)
{
KeyValuePair<INotifyCollectionChanged, String> oldValue = this.collectionNameLookup.First(kvp => kvp.Value == e.Name);
oldValue.Key -= MyCollectionChanged;
this.collecitonNameLookup.Remove(oldValue.Key);
INotifyCollectionChanged collection = this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).GetValue(this, null);
collection.CollectionChanged += MyCollectionChanged;
this.collectionNameLookup.Add(collection, e.Name);
}
else if(typeof(INotifyCollectionChanged).IsAssignableFrom(this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).PropertyType))
{
// Note: I may have gotten the IsAssignableFrom statement, above, backwards.
INotifyCollectionChanged collection = this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).GetValue(this, null);
collection.CollectionChanged += MyCollectionChanged;
this.collectionNameLookup.Add(collection, e.Name);
}
}
private void MyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.NotifyPropertyChanged(this.collectionNameLookup[sender];
}
}