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.
Related
I am using WinUI 3 UWP TabView in my App. I know that WinUI 3 is still in Preview stage for UWP. But still I want to know a workaround for my issue as I want to use TabView in my App. I have gone through the Official Documentation and GitHub Samples but I couldn't find a solution. The TabView is NOT displaying a New Tab whenever a New Document is added to the Collection. I have searched a lot but couldn't find a solution. Kindly, share a solution/workaround. You might suggest using WinUI 2 since it is stable for UWP. But, I have already tried that and WinUI 2 controls are not blending well with existing UWP Controls. But WinUI 3 blends perfectly. All other controls except TabView are working well. When I switch from DataBinding to Manually maintaining a list of TabItems, it works perfectly. But, I don't want Boilerplate code. I want to achieve the same with DataBinding. I am new to MVVM. So, if there's a problem with my ViewModel, do share a workaround.
This is my ViewModel Class:
using Microsoft.UI.Xaml.Controls;
using System.ComponentModel;
using System.IO;
using System.Text;
using MyApp.Utilities;
using System.Runtime.CompilerServices;
namespace MyApp.ViewModels
{
public class TextDocument : INotifyPropertyChanged
{
private int _documentId;
private string _fileName;
private string _filePath;
private string _textContent;
private Encoding _encoding;
private LineEnding _lineEnding;
private bool _isSaved;
public int DocumentId
{
get
{
return _documentId;
}
set
{
_documentId = value;
OnPropertyChanged(ref _documentId, value);
}
}
public string FileName
{
get
{
return _fileName;
}
set
{
OnPropertyChanged(ref _fileName, value);
}
}
public string FilePath
{
get
{
return _filePath;
}
set
{
OnPropertyChanged(ref _filePath, value);
FileName = Path.GetFileName(_filePath);
}
}
public string TextContent
{
get
{
return _textContent;
}
set
{
OnPropertyChanged(ref _textContent, value);
}
}
public Encoding FileEncoding
{
get
{
return _encoding;
}
set
{
OnPropertyChanged(ref _encoding, value);
}
}
public LineEnding LineEnding
{
get
{
return _lineEnding;
}
set
{
OnPropertyChanged(ref _lineEnding, value);
}
}
public string TabHeader
{
get
{
return string.IsNullOrEmpty(FileName) ? "Untitled Document" : FileName;
}
}
public bool IsSaved
{
get
{
return _isSaved;
}
set
{
OnPropertyChanged(ref _isSaved, value);
}
}
public bool IsInvalidFile
{
get
{
return (string.IsNullOrEmpty(FilePath) || string.IsNullOrEmpty(FileName));
}
}
public override bool Equals(object obj)
{
if (ReferenceEquals(obj, null))
return false;
if (ReferenceEquals(this, obj))
return true;
var comp = (TextDocument)obj;
return this.DocumentId == comp.DocumentId;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged<T>(ref T property, T value, [CallerMemberName] string propertyName = "")
{
property = value;
var handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And this is my XAML Code for TabView:
<TabView x:Name="MyTabView" AddTabButtonClick="TabView_AddTabButtonClick" TabCloseRequested="TabView_TabCloseRequested"
SelectionChanged="TabView_SelectionChanged"
Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Background="White"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
TabItemsChanged="TabView_TabItemsChanged"
SelectedIndex="0"
TabItemsSource="{x:Bind MyDocuments,Mode=OneWay}"
>
<TabView.TabItemTemplate>
<DataTemplate x:DataType="viewmodels:TextDocument">
<TabViewItem Header="{x:Bind TabHeader,Mode=OneWay}" IconSource="{x:Null}">
<TabViewItem.Content>
<TextBox x:Name="TextBoxInsideTab" Grid.Column="0" Grid.Row="0"
PlaceholderText="Drag and drop a file here or start typing"
Text="{x:Bind TextContent,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="24"
UseSystemFocusVisuals="False"
BorderThickness="0"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
TextWrapping="Wrap"
IsSpellCheckEnabled="False"
CanBeScrollAnchor="True"
TextChanged="TextBox_TextChanged"
AcceptsReturn="True"
IsTabStop="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
/>
</TabViewItem.Content>
</TabViewItem>
</DataTemplate>
</TabView.TabItemTemplate>
</TabView>
And this is my C# code:
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using MyApp.ViewModels;
using Windows.Storage.Pickers;
using Windows.Storage;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace MyApp
{
public sealed partial class MainPage : Page
{
private ObservableCollection<TextDocument> MyDocuments;
public MainPage()
{
this.InitializeComponent();
MyDocuments = new ObservableCollection<TextDocument>()
{
new TextDocument()
};
}
private void TabView_AddTabButtonClick(TabView sender, object args)
{
AddTabViewItemDefault(sender.TabItems.Count);
}
private void AddTabViewItemDefault(int index)
{
MyDocuments.Add(new TextDocument());
}
private void TabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
{
MyDocuments.Remove(args.Item as TextDocument);
}
private void TabView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
}
}
}
ObservableCollection<T> and INotifyCollectionChanged currently don't work in UWP apps.
You need to implement your own custom collection as a workaround:
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Interop;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using NotifyCollectionChangedAction = Microsoft.UI.Xaml.Interop.NotifyCollectionChangedAction;
public class CustomObservableCollection<T> : Collection<T>, Microsoft.UI.Xaml.Interop.INotifyCollectionChanged, INotifyPropertyChanged
{
private ReentrancyGuard reentrancyGuard = null;
private class ReentrancyGuard : IDisposable
{
private CustomObservableCollection<T> owningCollection;
public ReentrancyGuard(CustomObservableCollection<T> owningCollection)
{
owningCollection.CheckReentrancy();
owningCollection.reentrancyGuard = this;
this.owningCollection = owningCollection;
}
public void Dispose()
{
owningCollection.reentrancyGuard = null;
}
}
public CustomObservableCollection() : base() { }
public CustomObservableCollection(IList<T> list) : base(list.ToList()) { }
public CustomObservableCollection(IEnumerable<T> collection) : base(collection.ToList()) { }
public event Microsoft.UI.Xaml.Interop.NotifyCollectionChangedEventHandler CollectionChanged;
public void Move(int oldIndex, int newIndex)
{
MoveItem(oldIndex, newIndex);
}
protected IDisposable BlockReentrancy()
{
return new ReentrancyGuard(this);
}
protected void CheckReentrancy()
{
if (reentrancyGuard != null)
{
throw new InvalidOperationException("Collection cannot be modified in a collection changed handler.");
}
}
protected override void ClearItems()
{
CheckReentrancy();
TestBindableVector<T> oldItems = new TestBindableVector<T>(this);
base.ClearItems();
OnCollectionChanged(
NotifyCollectionChangedAction.Reset,
null, oldItems, 0, 0);
}
protected override void InsertItem(int index, T item)
{
CheckReentrancy();
TestBindableVector<T> newItem = new TestBindableVector<T>();
newItem.Add(item);
base.InsertItem(index, item);
OnCollectionChanged(
NotifyCollectionChangedAction.Add,
newItem, null, index, 0);
}
protected virtual void MoveItem(int oldIndex, int newIndex)
{
CheckReentrancy();
TestBindableVector<T> oldItem = new TestBindableVector<T>();
oldItem.Add(this[oldIndex]);
TestBindableVector<T> newItem = new TestBindableVector<T>(oldItem);
T item = this[oldIndex];
base.RemoveAt(oldIndex);
base.InsertItem(newIndex, item);
OnCollectionChanged(
NotifyCollectionChangedAction.Move,
newItem, oldItem, newIndex, oldIndex);
}
protected override void RemoveItem(int index)
{
CheckReentrancy();
TestBindableVector<T> oldItem = new TestBindableVector<T>();
oldItem.Add(this[index]);
base.RemoveItem(index);
OnCollectionChanged(
NotifyCollectionChangedAction.Remove,
null, oldItem, 0, index);
}
protected override void SetItem(int index, T item)
{
CheckReentrancy();
TestBindableVector<T> oldItem = new TestBindableVector<T>();
oldItem.Add(this[index]);
TestBindableVector<T> newItem = new TestBindableVector<T>();
newItem.Add(item);
base.SetItem(index, item);
OnCollectionChanged(
NotifyCollectionChangedAction.Replace,
newItem, oldItem, index, index);
}
protected virtual void OnCollectionChanged(
NotifyCollectionChangedAction action,
IBindableVector newItems,
IBindableVector oldItems,
int newIndex,
int oldIndex)
{
OnCollectionChanged(new Microsoft.UI.Xaml.Interop.NotifyCollectionChangedEventArgs(action, newItems, oldItems, newIndex, oldIndex));
}
protected virtual void OnCollectionChanged(Microsoft.UI.Xaml.Interop.NotifyCollectionChangedEventArgs e)
{
using (BlockReentrancy())
{
CollectionChanged?.Invoke(this, e);
}
}
#pragma warning disable 0067 // PropertyChanged is never used, raising a warning, but it's needed to implement INotifyPropertyChanged.
public event PropertyChangedEventHandler PropertyChanged;
#pragma warning restore 0067
}
public class TestBindableVector<T> : IList<T>, IBindableVector
{
IList<T> implementation;
public TestBindableVector() { implementation = new List<T>(); }
public TestBindableVector(IList<T> list) { implementation = new List<T>(list); }
public T this[int index] { get => implementation[index]; set => implementation[index] = value; }
public int Count => implementation.Count;
public virtual bool IsReadOnly => implementation.IsReadOnly;
public void Add(T item)
{
implementation.Add(item);
}
public void Clear()
{
implementation.Clear();
}
public bool Contains(T item)
{
return implementation.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
implementation.CopyTo(array, arrayIndex);
}
public IEnumerator<T> GetEnumerator()
{
return implementation.GetEnumerator();
}
public int IndexOf(T item)
{
return implementation.IndexOf(item);
}
public void Insert(int index, T item)
{
implementation.Insert(index, item);
}
public bool Remove(T item)
{
return implementation.Remove(item);
}
public void RemoveAt(int index)
{
implementation.RemoveAt(index);
}
IEnumerator IEnumerable.GetEnumerator()
{
return implementation.GetEnumerator();
}
public object GetAt(uint index)
{
return implementation[(int)index];
}
public IBindableVectorView GetView()
{
return new TestBindableVectorView<T>(implementation);
}
public bool IndexOf(object value, out uint index)
{
int indexOf = implementation.IndexOf((T)value);
if (indexOf >= 0)
{
index = (uint)indexOf;
return true;
}
else
{
index = 0;
return false;
}
}
public void SetAt(uint index, object value)
{
implementation[(int)index] = (T)value;
}
public void InsertAt(uint index, object value)
{
implementation.Insert((int)index, (T)value);
}
public void RemoveAt(uint index)
{
implementation.RemoveAt((int)index);
}
public void Append(object value)
{
implementation.Add((T)value);
}
public void RemoveAtEnd()
{
implementation.RemoveAt(implementation.Count - 1);
}
public uint Size => (uint)implementation.Count;
public IBindableIterator First()
{
return new TestBindableIterator<T>(implementation);
}
}
public class TestBindableVectorView<T> : TestBindableVector<T>, IBindableVectorView
{
public TestBindableVectorView(IList<T> list) : base(list) { }
public override bool IsReadOnly => true;
}
public class TestBindableIterator<T> : IBindableIterator
{
private readonly IEnumerator<T> enumerator;
public TestBindableIterator(IEnumerable<T> enumerable) { enumerator = enumerable.GetEnumerator(); }
public bool MoveNext()
{
return enumerator.MoveNext();
}
public object Current => enumerator.Current;
public bool HasCurrent => enumerator.Current != null;
}
Page:
public sealed partial class MainPage : Page
{
private CustomObservableCollection<TextDocument> MyDocuments;
public MainPage()
{
this.InitializeComponent();
MyDocuments = new CustomObservableCollection<TextDocument>()
{
new TextDocument()
};
}
...
}
I think your code in the constructor might be breaking the initial binding to the ObservableCollection. Try this code:
private ObservableCollection<TextDocument> MyDocuments {get;} = new ObservableCollection<TextDocument>();
public MainPage()
{
this.InitializeComponent();
MyDocuments.Add(new TextDocument());
}
Does it help?
I am trying to update a Listbox whenever a new 'key','value' is added to a dictionary.
I have a class that implements IDictionary and INotifyPropertyChanged ( copied from MSDN) and it has a method Add. (I have not added other methods to keep the questions short);
public class SimpleDictionary : IDictionary, INotifyPropertyChanged
{
public DictionaryEntry[] items;
public Int32 ItemsInUse = 0;
public void Add(object key, object value)
{
// Add the new key/value pair even if this key already exists in the dictionary.
if (ItemsInUse == items.Length)
throw new InvalidOperationException("The dictionary cannot hold any more items.");
items[ItemsInUse++] = new DictionaryEntry(key, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("key"));
}
}
In Main void method I have following:
public static SimpleDictionary clients = new SimpleDictionary(3);
static void Main()
{
clients.Add("key1", "Value1");
display_List();
clients.PropertyChanged += Clients_PropertyChanged;
}
private static void Clients_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MessageBox.Show("Dictionary changed: "+e.PropertyName);
}
static void display_List()
{
try {
form_m.listBox1.DataSource = new BindingSource(clients,null);
form_m.listBox1.DisplayMember = "key";
}
catch (Exception e) {
form_m.Program_console_1("Displat Binding Error: "+e.ToString());
}
}
public static void Update_Dictionary(object sender, System.EventArgs e)
{
clients.Add("key3","Value3");
}
When the program starts listBox1 is initialized and "key1","value" added to 'clients'. Method display_List binds clients and displays all the keys.
When I invoke Update_Dictionary method "key2","value2" are added and Clients_PropertyChanged is fired. However, listbox1 does not reflect the new key added i.e "key2". It still only shows "key1".
If I add the Datasource again then "key1" and "key2" are shown. What needs to be done so that listbox is updated when Clients_PropertyChanged is called automatically?
UPDATE: Tried using INotifyCollectionChanged still the same problem
He is the complete class I am using
using System;
using System.Collections;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
namespace x_server
{
// This class implements a simple dictionary using an array of DictionaryEntry objects (key/value pairs).
class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
private const string CountString = "Count";
private const string IndexerName = "Item[]";
private const string KeysName = "Keys";
private const string ValuesName = "Values";
private IDictionary<TKey, TValue> _Dictionary;
protected IDictionary<TKey, TValue> Dictionary
{
get { return _Dictionary; }
}
#region Constructors
public ObservableDictionary()
{
_Dictionary = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_Dictionary = new Dictionary<TKey, TValue>(dictionary);
}
public ObservableDictionary(IEqualityComparer<TKey> comparer)
{
_Dictionary = new Dictionary<TKey, TValue>(comparer);
}
public ObservableDictionary(int capacity)
{
_Dictionary = new Dictionary<TKey, TValue>(capacity);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
_Dictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
_Dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
#endregion
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value)
{
Insert(key, value, true);
}
public bool ContainsKey(TKey key)
{
return Dictionary.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return Dictionary.Keys; }
}
public bool Remove(TKey key)
{
if (key == null) throw new ArgumentNullException("key");
TValue value;
Dictionary.TryGetValue(key, out value);
var removed = Dictionary.Remove(key);
if (removed)
//OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value));
OnCollectionChanged();
return removed;
}
public bool TryGetValue(TKey key, out TValue value)
{
return Dictionary.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return Dictionary.Values; }
}
public TValue this[TKey key]
{
get
{
return Dictionary[key];
}
set
{
Insert(key, value, false);
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item)
{
Insert(item.Key, item.Value, true);
}
public void Clear()
{
if (Dictionary.Count > 0)
{
Dictionary.Clear();
OnCollectionChanged();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return Dictionary.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
Dictionary.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Dictionary.Count; }
}
public bool IsReadOnly
{
get { return Dictionary.IsReadOnly; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return Dictionary.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)Dictionary).GetEnumerator();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void AddRange(IDictionary<TKey, TValue> items)
{
if (items == null) throw new ArgumentNullException("items");
if (items.Count > 0)
{
if (Dictionary.Count > 0)
{
if (items.Keys.Any((k) => Dictionary.ContainsKey(k)))
throw new ArgumentException("An item with the same key has already been added.");
else
foreach (var item in items) Dictionary.Add(item);
}
else
_Dictionary = new Dictionary<TKey, TValue>(items);
OnCollectionChanged(NotifyCollectionChangedAction.Add, items.ToArray());
}
}
private void Insert(TKey key, TValue value, bool add)
{
if (key == null) throw new ArgumentNullException("key");
TValue item;
if (Dictionary.TryGetValue(key, out item))
{
if (add) throw new ArgumentException("An item with the same key has already been added.");
if (Equals(item, value)) return;
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));
}
else
{
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value));
}
}
private void OnPropertyChanged()
{
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnPropertyChanged(KeysName);
OnPropertyChanged(ValuesName);
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionChanged()
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems));
}
}
}
INotifyPropertyChanged is used if the value of a property changes. The collection itself does not change, its contents do. This is signaled by another interface: INotifyCollectionChanged.
You don't need a custom collection type for that, to get updates when a collection is updated use ObservableCollection instead.
EDIT: I just realized ObservableCollection is not available by default in Windows Forms, but it looks like you can easily add it.
UWP Community Toolkit's (now known as Windows Community Toolkit) AdvancedCollectionView collection implements the ISupportIncrementalLoading interface, and so I'm trying to use it with my ListView to only load portions of items at one time, but the ListView still loads all items at once. What am I missing?
Here is the XAML:
<ListView x:Name="MyListView"
DataFetchSize="10"
IncrementalLoadingTrigger="Edge"
IncrementalLoadingThreshold="10"
ItemSource="{x:Bind ACV, Mode=TwoWay}">
</ListView>
And here is the code-behind:
public class MainPage
{
public AdvancedCollectionView ACV { get; set; }
// Lets say that DocCollection contains 1000 items
public ObservableCollection<Document> DocCollection;
public MainPage()
{
ACV = new AdvancedCollectionView(DocCollection, true);
}
}
The AdvancedCollectionView is a collection view implementation that support filtering, sorting and incremental loading. But according to the usage section of the document I linked,
incremental loading: if your source collection supports the feature then AdvancedCollectionView will do as well (it simply forwards the calls)
So that the AdvancedCollectionView does't have incremental feature itself, it simple forwards the calls. That means the source collection you provided for the AdvancedCollectionView should inherit from ISupportIncrementalLoading interface. Also if you check the AdvancedCollectionView.LoadMoreItemsAsync method, it shows Not implemented yet which indicates AdvancedCollectionView doesn't implement the ISupportIncrementalLoading interface.
And in your case, you just use ObservableCollection for the source collection that doesn't support ISupportIncrementalLoading by default. For creating collection views that support incremental loading please reference this official sample.
DocCollection = new GeneratorIncrementalLoadingClass<DataTest>(1000, (count) =>
{
return new DataTest() { Country = "Ghana" + count, City = "Wa" + count };
});
DocCollection.CollectionChanged += DocCollection_CollectionChanged;
ACV = new AdvancedCollectionView(DocCollection, true);
MyListView.ItemsSource = ACV;
public class GeneratorIncrementalLoadingClass<T> : IncrementalLoadingBase
{
public GeneratorIncrementalLoadingClass(uint maxCount, Func<int, T> generator)
{
_generator = generator;
_maxCount = maxCount;
}
protected async override Task<IList<object>> LoadMoreItemsOverrideAsync(System.Threading.CancellationToken c, uint count)
{
uint toGenerate = System.Math.Min(count, _maxCount - _count);
// Wait for work
await Task.Delay(10);
// This code simply generates
var values = from j in Enumerable.Range((int)_count, (int)toGenerate)
select (object)_generator(j);
_count += toGenerate;
return values.ToArray();
}
protected override bool HasMoreItemsOverride()
{
return _count < _maxCount;
}
#region State
Func<int, T> _generator;
uint _count = 0;
uint _maxCount;
#endregion
}
public abstract class IncrementalLoadingBase : IList, ISupportIncrementalLoading, INotifyCollectionChanged
{
#region IList
public int Add(object value)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(object value)
{
return _storage.Contains(value);
}
public int IndexOf(object value)
{
return _storage.IndexOf(value);
}
public void Insert(int index, object value)
{
throw new NotImplementedException();
}
public bool IsFixedSize
{
get { return false; }
}
public bool IsReadOnly
{
get { return true; }
}
public void Remove(object value)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
public object this[int index]
{
get
{
return _storage[index];
}
set
{
throw new NotImplementedException();
}
}
public void CopyTo(Array array, int index)
{
((IList)_storage).CopyTo(array, index);
}
public int Count
{
get { return _storage.Count; }
}
public bool IsSynchronized
{
get { return false; }
}
public object SyncRoot
{
get { throw new NotImplementedException(); }
}
public IEnumerator GetEnumerator()
{
return _storage.GetEnumerator();
}
#endregion
#region ISupportIncrementalLoading
public bool HasMoreItems
{
get { return HasMoreItemsOverride(); }
}
public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
if (_busy)
{
throw new InvalidOperationException("Only one operation in flight at a time");
}
_busy = true;
return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count));
}
#endregion
#region INotifyCollectionChanged
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region Private methods
async Task<LoadMoreItemsResult> LoadMoreItemsAsync(CancellationToken c, uint count)
{
try
{
var items = await LoadMoreItemsOverrideAsync(c, count);
var baseIndex = _storage.Count;
_storage.AddRange(items);
// Now notify of the new items
NotifyOfInsertedItems(baseIndex, items.Count);
return new LoadMoreItemsResult { Count = (uint)items.Count };
}
finally
{
_busy = false;
}
}
void NotifyOfInsertedItems(int baseIndex, int count)
{
if (CollectionChanged == null)
{
return;
}
for (int i = 0; i < count; i++)
{
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _storage[i + baseIndex], i + baseIndex);
CollectionChanged(this, args);
}
}
#endregion
#region Overridable methods
protected abstract Task<IList<object>> LoadMoreItemsOverrideAsync(CancellationToken c, uint count);
protected abstract bool HasMoreItemsOverride();
#endregion
#region State
List<object> _storage = new List<object>();
bool _busy = false;
#endregion
}
If one binds to a listbox's ItemsSource to a list type structure such as an ObservableCollection (or any value-only container really) with the type of a class (List< MyClass>) one can access the properties of said class.
But if one has a Dictionary<long, MyClass> and binds the itemssource to Dictionary.Values one can not access the class properties anymore.
So basically this is my XAML
<ListBox ItemsSource="{Binding bm.Values}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Property2}"/>
<TextBlock Text="{Binding Property3}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in the view-model it's
public Dictionary<long,MyClass> bm { get; set; }
With code like this you have the Texblock DataContext set to MyClass, but you do not have access to Name, property2 and property3. But if you change the dictionary to a List the TextBlock DataContext will also be set to MyClass, but it will have access to those fields.
For architectural reasons I can not switch from the Dictionary to a non-key collection.
So the question is: how can you make XAML see the properties and why exactly does this situation actually happen?
Feel free to use this code of ObservableDictionary class
public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged {
private const string CountString = "Count";
private const string IndexerName = "Item[]";
private const string KeysName = "Keys";
private const string ValuesName = "Values";
private IDictionary<TKey, TValue> _Dictionary;
protected IDictionary<TKey, TValue> Dictionary {
get { return _Dictionary; }
}
#region Constructors
public ObservableDictionary() {
_Dictionary = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary) {
_Dictionary = new Dictionary<TKey, TValue>(dictionary);
}
public ObservableDictionary(IEqualityComparer<TKey> comparer) {
_Dictionary = new Dictionary<TKey, TValue>(comparer);
}
public ObservableDictionary(int capacity) {
_Dictionary = new Dictionary<TKey, TValue>(capacity);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
_Dictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer) {
_Dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
#endregion
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value) {
Insert(key, value, true);
}
public bool ContainsKey(TKey key) {
return Dictionary.ContainsKey(key);
}
public ICollection<TKey> Keys {
get { return Dictionary.Keys; }
}
public bool Remove(TKey key) {
if(key == null) throw new ArgumentNullException("key");
TValue value;
Dictionary.TryGetValue(key, out value);
var removed = Dictionary.Remove(key);
if(removed)
//OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value));
OnCollectionChanged();
return removed;
}
public bool TryGetValue(TKey key, out TValue value) {
return Dictionary.TryGetValue(key, out value);
}
public ICollection<TValue> Values {
get { return Dictionary.Values; }
}
public TValue this[TKey key] {
get {
return Dictionary[key];
}
set {
Insert(key, value, false);
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item) {
Insert(item.Key, item.Value, true);
}
public void Clear() {
if(Dictionary.Count > 0) {
Dictionary.Clear();
OnCollectionChanged();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item) {
return Dictionary.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
Dictionary.CopyTo(array, arrayIndex);
}
public int Count {
get { return Dictionary.Count; }
}
public bool IsReadOnly {
get { return Dictionary.IsReadOnly; }
}
public bool Remove(KeyValuePair<TKey, TValue> item) {
return Remove(item.Key);
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
return Dictionary.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator() {
return ((IEnumerable) Dictionary).GetEnumerator();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void AddRange(IDictionary<TKey, TValue> items) {
if(items == null) throw new ArgumentNullException("items");
if(items.Count > 0) {
if(Dictionary.Count > 0) {
if(items.Keys.Any((k) => Dictionary.ContainsKey(k)))
throw new ArgumentException("An item with the same key has already been added.");
else
foreach(var item in items) Dictionary.Add(item);
} else
_Dictionary = new Dictionary<TKey, TValue>(items);
OnCollectionChanged(NotifyCollectionChangedAction.Add, items.ToArray());
}
}
private void Insert(TKey key, TValue value, bool add) {
if(key == null) throw new ArgumentNullException("key");
TValue item;
if(Dictionary.TryGetValue(key, out item)) {
if(add) throw new ArgumentException("An item with the same key has already been added.");
if(Equals(item, value)) return;
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));
} else {
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value));
}
}
private void OnPropertyChanged() {
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnPropertyChanged(KeysName);
OnPropertyChanged(ValuesName);
}
protected virtual void OnPropertyChanged(string propertyName) {
if(PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionChanged() {
OnPropertyChanged();
if(CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem) {
OnPropertyChanged();
if(CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem) {
OnPropertyChanged();
if(CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems) {
OnPropertyChanged();
if(CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems));
}
}
I copied this ObservableDictionary implementation from a VS2013 generated project. It is a strongly typed implementation (Key is string and Value is object). However, I must be getting the bindings wrong because I don't see any data, I get a BindingExpression path error.
One thing I note is that the MapChangedEventHandler is always null, meaning that nobody every registered for the event!
1. Who or what registers for the MapChangedEventHandler?
2. What should my bindings be? I've tried several VARIOUS permutations of bindings. All to no avail, but here's the latest permutation of the code:
public class ObservableDictionary : IObservableMap<string, object>
{
private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<string>
{
public ObservableDictionaryChangedEventArgs(CollectionChange change, string key)
{
this.CollectionChange = change;
this.Key = key;
}
public CollectionChange CollectionChange { get; private set; }
public string Key { get; private set; }
}
private Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public event MapChangedEventHandler<string, object> MapChanged;
private void InvokeMapChanged(CollectionChange change, string key)
{
var eventHandler = MapChanged;
if (eventHandler != null)
{
eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key));
}
}
public void Add(string key, object value)
{
this._dictionary.Add(key, value);
this.InvokeMapChanged(CollectionChange.ItemInserted, key);
}
public void Add(KeyValuePair<string, object> item)
{
this.Add(item.Key, item.Value);
}
public bool Remove(string key)
{
if (this._dictionary.Remove(key))
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
return true;
}
return false;
}
public bool Remove(KeyValuePair<string, object> item)
{
object currentValue;
if (this._dictionary.TryGetValue(item.Key, out currentValue) &&
Object.Equals(item.Value, currentValue) && this._dictionary.Remove(item.Key))
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
return true;
}
return false;
}
public object this[string key]
{
get
{
return this._dictionary[key];
}
set
{
this._dictionary[key] = value;
this.InvokeMapChanged(CollectionChange.ItemChanged, key);
}
}
public void Clear()
{
var priorKeys = this._dictionary.Keys.ToArray();
this._dictionary.Clear();
foreach (var key in priorKeys)
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
}
}
public ICollection<string> Keys
{
get { return this._dictionary.Keys; }
}
public bool ContainsKey(string key)
{
return this._dictionary.ContainsKey(key);
}
public bool TryGetValue(string key, out object value)
{
return this._dictionary.TryGetValue(key, out value);
}
public ICollection<object> Values
{
get { return this._dictionary.Values; }
}
public bool Contains(KeyValuePair<string, object> item)
{
return this._dictionary.Contains(item);
}
public int Count
{
get { return this._dictionary.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return this._dictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this._dictionary.GetEnumerator();
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
int arraySize = array.Length;
foreach (var pair in this._dictionary)
{
if (arrayIndex >= arraySize) break;
array[arrayIndex++] = pair;
}
}
}
And the xaml:
<custom:RESTAPHandler
x:Class="K1MobilePhone.Views.HomePageAdmin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:custom="clr-namespace:K1MobilePhone.Utilities"
xmlns:cells="clr-namespace:K1MobilePhone.Common"
xmlns:wptoolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True" Loaded="phoneApplicationPage_Loaded" Margin="0,6,0,-6">
<custom:RESTAPHandler.Resources>
<CollectionViewSource
x:Name="sd_source" Source="{Binding Path=SDSummaries, Mode=TwoWay}" />
<CollectionViewSource
x:Name="ticketsViewSource" Source="{Binding Path=Tickets, Mode=TwoWay}" />
</custom:RESTAPHandler.Resources>
And now the xaml.cs:
public sealed partial class HomePageAdmin : RESTAPHandler
{
private ObservableDictionary defaultViewModel = new ObservableDictionary();
public ObservableDictionary DefaultViewModel { get { return this.defaultViewModel; } }
public HomePageAdmin() : base()
{
this.InitializeComponent();
DataContext = this.DefaultViewModel;
this.DefaultViewModel["SDSummaries"] = new SDSummaries();
this.DefaultViewModel["Tickets"] = new RecentlyViewedTickets();
}
Okay I wasn't able to ever figure out why I couldn't access string-based keys in my xaml code. The default Windows store app works great. What I wound up doing is this:
Page.xaml:
<custom:RESTAPHandler.Resources>
<CollectionViewSource
x:Name="sd_source"/>
<CollectionViewSource
x:Name="ticketsViewSource"/>
</custom:RESTAPHandler.Resources>
I built everything up in the code-behind:
public sealed partial class HomePageAdmin : RESTAPHandler
{
private Grid visibleGrid;
private SDSummaries _sdSummaries = new SDSummaries();
public SDSummaries SDSummaries { get { return this._sdSummaries; } }
public HomePageAdmin() : base()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.sd_source.Source = this.SDSummaries;
base.OnNavigatedTo(e);
}
private void phoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
this.Address = this.NavigationContext.QueryString["URL"];
}
private void TicketsStatsResponse (TicketsStatsResponse response)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
PlainTitleDisclosureCellItem cell = (PlainTitleDisclosureCellItem)this.SDSummaries[1];
cell.Label = response.NewTicketCount.ToString();
cell = (PlainTitleDisclosureCellItem)this.SDSummaries[2];
cell.Label = response.UnassignedTicketCount.ToString();
});
}
Seems a bit of a hack, but I'm not sure.