WinRT ListView virtualization - c#

I want to display 10k items in list view from database.
I tried to use visualization using IList interface.
Idea was to fetch items one by one, as requested by listview. (Only visible items).
But getting following exception when I create VirtualComboList object and assign it to ListView.
Object reference not set to an instance of an object.
Similar code works fine for WP8(Silverlight).
Now, can anybody tell what am I missing?
public void initializeList()
{
int ItemsCount = getItemsCountFromDatabase();
VirtualComboList list = new VirtualComboList(ItemsCount);
listBox1.ItemsSource = list; //Exception at this line
}
My VirtualComboList class implementing IList interface
class VirtualComboList : IList<string>
{
int ItemCount;
public VirtualComboList(int count)
{
ItemCount = count;
}
public int IndexOf(string item)
{
return -1;
}
public void Insert(int index, string item)
{
}
public void RemoveAt(int index)
{
}
public string this[int index]
{
get
{
return getStringFromDatabaseForIndex(index);
}
set
{
}
}
public void Add(string item)
{
}
public void Clear()
{
}
public bool Contains(string item)
{
return false;
}
public void CopyTo(string[] array, int arrayIndex)
{
}
public int Count
{
get { return ItemCount; }
}
public bool IsReadOnly
{
get { return true ; }
}
public bool Remove(string item)
{
return true;
}
public IEnumerator<string> GetEnumerator()
{
return null;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return null;
}
}

You have the GetEnumerator methods return null. Don't do that. The ItemsControl (ListView in this case) is trying to iterate over the list using the Enumerator, which is null, causing the exception.
You should look into a proper implementation of GetEnumerator. Or, you can have a List<string> as a field/property of your custom class and simply expose the methods of the underlying collection so that you don't have to implement any of them yourself.

Related

AdvancedCollectionView implements the ISupportIncrementalLoading interface, but it doesn't work

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
}

call function when a list gets modified? [duplicate]

I created a Class EventList inheriting List which fires an Event each time something is Added, Inserted or Removed:
public class EventList<T> : List<T>
{
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
public new void Add(T item)
{
base.Add(item);
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
...
}
At the Moment I use it as a Property like this:
public EventList List
{
get { return m_List; }
set
{
m_List.ListChanged -= List_ListChanged;
m_List = value;
m_List.ListChanged += List_ListChanged;
List_ListChanged();
}
}
Now my Problem is, can I somehow handle if a new Object is referred to it or prevent that, so I do not have to do the event wiring stuff in the setter?
Of course, I can change the property to "private set" but I would like to be able to use the class as variable as well.
You seldom create a new instance of a collection class in a class. Instantiate it once and clear it instead of creating a new list. (and use the ObservableCollection since it already has the INotifyCollectionChanged interface inherited)
private readonly ObservableCollection<T> list;
public ctor() {
list = new ObservableCollection<T>();
list.CollectionChanged += listChanged;
}
public ObservableCollection<T> List { get { return list; } }
public void Clear() { list.Clear(); }
private void listChanged(object sender, NotifyCollectionChangedEventArgs args) {
// list changed
}
This way you only have to hook up events once, and can "reset it" by calling the clear method instead of checking for null or equality to the former list in the set accessor for the property.
With the changes in C#6 you can assign a get property from a constructor without the backing field (the backing field is implicit)
So the code above can be simplified to
public ctor() {
List = new ObservableCollection<T>();
List.CollectionChanged += OnListChanged;
}
public ObservableCollection<T> List { get; }
public void Clear()
{
List.Clear();
}
private void OnListChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// react to list changed
}
ObservableCollection is a List with a CollectionChanged event
ObservableCollection.CollectionChanged Event
For how to wire up the event handler see answer from Patrick. +1
Not sure what you are looking for but I use this for a collection with one event that fires on add, remove, and change.
public class ObservableCollection<T>: INotifyPropertyChanged
{
private BindingList<T> ts = new BindingList<T>();
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged( String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public BindingList<T> Ts
{
get { return ts; }
set
{
if (value != ts)
{
Ts = value;
if (Ts != null)
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
NotifyPropertyChanged("Ts");
}
}
}
private static void OnListChanged(ObservableCollection<T> vm)
{
// this will fire on add, remove, and change
// if want to prevent an insert this in not the right spot for that
// the OPs use of word prevent is not clear
// -1 don't be a hater
vm.NotifyPropertyChanged("Ts");
}
public ObservableCollection()
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
}
If you do not want to or can not convert to an Observable Collection, try this:
public class EventList<T> : IList<T> /* NOTE: Changed your List<T> to IList<T> */
{
private List<T> list; // initialize this in your constructor.
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
private void notify()
{
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
public new void Add(T item)
{
list.Add(item);
notify();
}
public List<T> Items {
get { return list; }
set {
list = value;
notify();
}
}
...
}
Now, for your property, you should be able to reduce your code to this:
public EventList List
{
get { return m_List.Items; }
set
{
//m_List.ListChanged -= List_ListChanged;
m_List.Items = value;
//m_List.ListChanged += List_ListChanged;
//List_ListChanged();
}
}
Why? Setting anything in the EventList.Items will call your private notify() routine.
I have a Solution for when someone calls the Generic method from IList.add(object). So that you also get notified.
using System;
using System.Collections;
using System.Collections.Generic;
namespace YourNamespace
{
public class ObjectDoesNotMatchTargetBaseTypeException : Exception
{
public ObjectDoesNotMatchTargetBaseTypeException(Type targetType, object actualObject)
: base(string.Format("Expected base type ({0}) does not match actual objects type ({1}).",
targetType, actualObject.GetType()))
{
}
}
/// <summary>
/// Allows you to react, when items were added or removed to a generic List.
/// </summary>
public abstract class NoisyList<TItemType> : List<TItemType>, IList
{
#region Public Methods
/******************************************/
int IList.Add(object item)
{
CheckTargetType(item);
Add((TItemType)item);
return Count - 1;
}
void IList.Remove(object item)
{
CheckTargetType(item);
Remove((TItemType)item);
}
public new void Add(TItemType item)
{
base.Add(item);
OnItemAdded(item);
}
public new bool Remove(TItemType item)
{
var result = base.Remove(item);
OnItemRemoved(item);
return result;
}
#endregion
# region Private Methods
/******************************************/
private static void CheckTargetType(object item)
{
var targetType = typeof(TItemType);
if (item.GetType().IsSubclassOf(targetType))
throw new ObjectDoesNotMatchTargetBaseTypeException(targetType, item);
}
#endregion
#region Abstract Methods
/******************************************/
protected abstract void OnItemAdded(TItemType addedItem);
protected abstract void OnItemRemoved(TItemType removedItem);
#endregion
}
}
If an ObservableCollection is not the solution for you, you can try that:
A) Implement a custom EventArgs that will contain the new Count attribute when an event will be fired.
public class ChangeListCountEventArgs : EventArgs
{
public int NewCount
{
get;
set;
}
public ChangeListCountEventArgs(int newCount)
{
NewCount = newCount;
}
}
B) Implement a custom List that inherits from List and redefine the Count attribute and the constructors according to your needs:
public class CustomList<T> : List<T>
{
public event EventHandler<ChangeListCountEventArgs> ListCountChanged;
public new int Count
{
get
{
ListCountChanged?.Invoke(this, new ChangeListCountEventArgs(base.Count));
return base.Count;
}
}
public CustomList()
{ }
public CustomList(List<T> list) : base(list)
{ }
public CustomList(CustomList<T> list) : base(list)
{ }
}
C) Finally subscribe to your event:
var myList = new CustomList<YourObject>();
myList.ListCountChanged += (obj, e) =>
{
// get the count thanks to e.NewCount
};

AutoCompleteStringCollection with tags for each string

Is it possible to give an string entry in an AutoCompleteStringCollection an additional Tag? Cause there is no native way that I found in my research, I tried to implement it by myself. But now the issue is, that it doesn't seem to fire an additional event when the user clicked on an entry in the AutoCompleteStringCollection, lets say for example in a TextBox.
Is it possible to do this natively?
If not, how can I know what index the user clicked on so I can tell whether input was typed manually (TextChanged-Event) or by selection (??-event)?
Since there seems to be no alternative solution, I've implemented my own.
Use this if you want. You can use the TextChanged-Event of TextBox or ComboBox to grab the values using the GetTag extension method.
public class TaggedAutoCompleteStringCollection : AutoCompleteStringCollection
{
private List<object> _tags;
public TaggedAutoCompleteStringCollection()
: base()
{
_tags = new List<object>();
}
public int Add(string value, object tag)
{
int result = this.Add(value);
_tags.Add(tag);
return result;
}
public void AddRange(string[] value, object[] tag)
{
base.AddRange(value);
_tags.AddRange(tag);
}
public new void Clear()
{
base.Clear();
_tags.Clear();
}
public bool ContainsTag(object tag)
{
return _tags.Contains(tag);
}
public int IndexOfTag(object tag)
{
return _tags.IndexOf(tag);
}
public void Insert(int index, string value, object tag)
{
base.Insert(index, value);
_tags.Insert(index, tag);
}
public new void Remove(string value)
{
int index = this.IndexOf(value);
if (index != -1)
{
base.RemoveAt(index);
_tags.RemoveAt(index);
}
}
public new void RemoveAt(int index)
{
base.RemoveAt(index);
_tags.RemoveAt(index);
}
public object TagOfString(string value)
{
int index = base.IndexOf(value);
if (index == -1)
{
return null;
}
return _tags[index];
}
}
public static class TaggedAutoCompleteStringCollectionHelper
{
public static object GetTag(this TextBox control)
{
var source = control.AutoCompleteCustomSource as TaggedAutoCompleteStringCollection;
if (source == null)
{
return null;
}
return source.TagOfString(control.Text);
}
public static object GetTag(this ComboBox control)
{
var source = control.DataSource as TaggedAutoCompleteStringCollection;
if (source == null)
{
return null;
}
return source.TagOfString(control.Text);
}
}

SortedList doesn't seem to be sorting

I made a wrapper class around SortedList. I add objects to this class expecting them to be automatically sorted alphabetically, but when I bind to a ListBox in WPF, I see then in unsorted order.
public class SortedObservableCollection<T> : ICollection<T>, INotifyPropertyChanged, INotifyCollectionChanged where T : INotifyPropertyChanged//, IComparable<T>
{
private readonly SortedList<string,T> _innerSortedList;
public SortedObservableCollection()
{
_innerSortedList = new SortedList<string, T>();
}
public void Add(T item)
{
_innerSortedList.Add(item.ToString(), item);
this.OnPropertyChanged("Count");
this.OnPropertyChanged("Item[]");
this.OnCollectionChanged(NotifyCollectionChangedAction.Add, item);
item.PropertyChanged += ItemPropertyChanged;
}
void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
public void Clear()
{
_innerSortedList.Clear();
}
public bool Contains(T item)
{
return _innerSortedList.ContainsKey(item.ToString());
}
public void CopyTo(T[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public int Count
{
get { return _innerSortedList.Count; }
}
public bool IsReadOnly
{
get { throw new NotImplementedException(); }
}
public bool Remove(T item)
{
bool success = _innerSortedList.Remove(item.ToString());
if (success)
{
item.PropertyChanged -= ItemPropertyChanged;
this.OnPropertyChanged("Count");
this.OnPropertyChanged("Item[]");
this.OnCollectionChanged(NotifyCollectionChangedAction.Remove, item);
}
return success;
}
public IEnumerator<T> GetEnumerator()
{
throw new NotImplementedException();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _innerSortedList.GetEnumerator();
}
protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected virtual void OnCollectionChanged(NotifyCollectionChangedAction action, object item)
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, item));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
To bind I simply do :
SortedObservableCollection<IRCUser> Users { get; private set; }
.. fill users...
lstUsers.ItemsSource = users;
Sample input :
5Muhammad0
2Muhammad1
5Muhammad2
The output also shows similar, with the ones beginning with 2, 4 etc riddled between the 5's.
Note: My IRCUser class did implement IComparable, but since I only want an alphabetical sort now I commented the implentation out hoping the default sorting would kick in.
NOTE 2: I have override the toString() method, which I forgot to mention :
public override string ToString()
{
return (int)Type + Nick;
}
UPDATE :
It seems internally the sortedList maintains the right order, but it is not passed to the ListBox in the right order...
You are not correctly create the event object NotifyCollectionChangedEventArgs. This object has different overloads of constructor depending on the action. You must use the overload that uses the index of the new item when you create a new item:
new NotifyCollectionChangedEventArgs(action, item, index)
Here's quote from MSDN:
Initializes a new instance of the NotifyCollectionChangedEventArgs
class that describes an Add or Remove change.
NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction, Object, Int32)
UPDATE 0
Also it is better not to use an overload of method ToString to compare items, and use the special IComparer<TKey> for this.
In your case, the correct code looks like this:
public void Add(T item)
{
var key = item.ToString();
_innerSortedList.Add(key, item);
this.OnPropertyChanged("Count");
this.OnPropertyChanged("Item[]");
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _innerSortedList.IndexOfKey(key)));
item.PropertyChanged += ItemPropertyChanged;
}
public bool Remove(T item)
{
var indexOfKey = _innerSortedList.IndexOfKey(item.ToString());
if (indexOfKey == -1)
return false;
_innerSortedList.RemoveAt(indexOfKey);
item.PropertyChanged -= ItemPropertyChanged;
this.OnPropertyChanged("Count");
this.OnPropertyChanged("Item[]");
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item,
indexOfKey));
return true;
}
public IEnumerator<T> GetEnumerator()
{
return _innerSortedList.Values.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
var handler = this.CollectionChanged;
if (handler != null)
{
handler(this, args);
}
}
The problem is with
_innerSortedList.Add(item.ToString(), item);
Let suppose if item is of type Project.Test.CustomEntity then item.ToString() will give you "Project.Test.CustomEntity" which is getting sorted by Entity's fullname and not by the value.
you need to write a custom sorter based on property selector of your T.
Have a look at this article.
I'm not sure but:
1) If the UI has a data template, and the sample output you showed there is not the result of IRCUser.ToString() - than, maybe, the ToString() does not provide a "good" hash value for sorting.
2) You may be better served by using a PagedCollectionView to wrap your collection, and set the ordering there.
You are sorting your data on item.ToString() which may not be a very useful value to sort on.
Unless it is overridden, it will be the type name of the class and therefore the same for everything you add.
This obviously leads to the question, how should generic data be sorted. This is what IComparable<T> is for.
You'll note that there are several SortedList<T> constructors that allow you to pass an IComparable implementation to define your own order.
In any case, if you want to use the default IComparable implementation of string, you will need to use strings that differ in accordance with your desired order. Not type names that do not differ at all.

SortableBindingList, Index was out of range Error, how to make it Thread-Safe?

using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace MyApplication
{
public class SortableBindingList<T> : BindingList<T>
{
private static object syncLock = new object();
private readonly Dictionary<Type, PropertyComparer<T>> comparers;
private bool isSorted;
private ListSortDirection listSortDirection;
private PropertyDescriptor propertyDescriptor;
private System.ComponentModel.ISynchronizeInvoke _SyncObject;
private System.Action<System.ComponentModel.ListChangedEventArgs> _FireEventAction;
public SortableBindingList()
: base(new List<T>())
{
this.comparers = new Dictionary<Type, PropertyComparer<T>>();
}
public SortableBindingList(IEnumerable<T> enumeration, System.ComponentModel.ISynchronizeInvoke syncObject)
: base(new List<T>(enumeration))
{
_SyncObject = syncObject;
_FireEventAction = FireEvent;
this.comparers = new Dictionary<Type, PropertyComparer<T>>();
}
protected override bool SupportsSortingCore
{
get { return true; }
}
protected override bool IsSortedCore
{
get { return this.isSorted; }
}
protected override PropertyDescriptor SortPropertyCore
{
get { return this.propertyDescriptor; }
}
protected override ListSortDirection SortDirectionCore
{
get { return this.listSortDirection; }
}
protected override bool SupportsSearchingCore
{
get { return true; }
}
protected override void InsertItem(int index, T item)
{
lock (syncLock)
{
base.InsertItem(index, item);
}
}
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
{
List<T> itemsList = (List<T>)this.Items;
Type propertyType = property.PropertyType;
PropertyComparer<T> comparer;
if (!this.comparers.TryGetValue(propertyType, out comparer))
{
comparer = new PropertyComparer<T>(property, direction);
this.comparers.Add(propertyType, comparer);
}
comparer.SetPropertyAndDirection(property, direction);
itemsList.Sort(comparer);
this.propertyDescriptor = property;
this.listSortDirection = direction;
this.isSorted = true;
this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
protected override void RemoveSortCore()
{
this.isSorted = false;
this.propertyDescriptor = base.SortPropertyCore;
this.listSortDirection = base.SortDirectionCore;
this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
protected override int FindCore(PropertyDescriptor property, object key)
{
int count = this.Count;
for (int i = 0; i < count; ++i)
{
T element = this[i];
if (property.GetValue(element).Equals(key))
{
return i;
}
}
return -1;
}
protected override void OnListChanged(System.ComponentModel.ListChangedEventArgs args)
{
lock (syncLock)
{
if (_SyncObject == null)
{
FireEvent(args);
}
else
{
_SyncObject.Invoke(_FireEventAction, new object[] { args });
}
}
}
private void FireEvent(System.ComponentModel.ListChangedEventArgs args)
{
base.OnListChanged(args);
}
}
}
I am getting following error:
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
The SortableBindingList is bound to DataGridView, Virtual mode
Multiple threads fires event which adds data to SortableBindingList
private void ProxyScraper_OnProxyFound(Proxy Proxy, int Count)
{
ProxyProcessedCount.Text = Count.ToString();
_ProxyList.Add(Proxy);
}
I have tried to lock the SortableBindingList but still getting error, searched a lot but unable to find a solution.
Ultimately I suspect it is a false hope to make a truly thread-safe binding list, as there will be cases where multiple operations are performed - regardless of whether that is a "check the Count then iterate rows to Count-1", or "enumerate with foreach" - and there is no easy way to lock over the duration of those, since the calling code is outside of your control.
Even for a half-working version, you'd need to add your syncLock code to every access, via overriding all the available methods - however, I can't see a virtual method for the get on this[index], which might render that futile - it is only synchronized if all callers agree to use the lock.
Ultimately, I suspect that trying to use threading with tight UI coupling is fairly doomed. IMO, you might have more success separating the two things, and having the UI worry about handling an event and calling .Invoke(...) to update the binding on the UI thread.

Categories

Resources