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

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
}

Related

WinUI 3 UWP TabView NOT displaying New Tab when a new Item is added to the bound ItemsSource

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?

Changing the display names in a propertygrid

I am creating a List<> of a Test class, I made. I then convert it to an array and assign it to a propertygrid control.
List<Test> lc_Test;
public class Test : UserControl
{
[Category("lc_Test"), Description("Testing using List<>, for storage."), DisplayName("Images")]
public Image img { get; set; }
}
// Above in Form1(), I create it
// lc_Test = new List<Test>();
private void button7_Click(object sender, EventArgs e)
{
lc_Test.Clear();
lc_Test.Add(new Test());
lc_Test.Add(new Test());
lc_Test[0].img = pb1.Image;
lc_Test[1].img = pb2.Image;
MessageBox.Show(lc_Test.Count.ToString());
pgTest.SelectedObject = lc_Test.ToArray();
}
As seen in the picture, it works:
I am now wondering, if there is any way to change the Display Name of each item. Cause, it names them "(0)" and "(1)". In this test, I'd like to change it to say. "Test Item 0" and "Test Item 1". I need to change the "Help Text" for each item, also. Got to be a way.
Anyone, need anything from me let me know.
Solution:
Your form1.cs code file:
using System;
using System.Windows.Forms;
namespace PropertyGridSample
{
public class Form1 : System.Windows.Forms.Form
{
Layers layers_test;
internal System.Windows.Forms.PropertyGrid PropertyGrid1;
private System.ComponentModel.Container components = null; //Required designer variable
public Form1()
{
InitializeComponent();
layers_test = new Layers();
PropertyGrid1.SelectedObject = layers_test;
}
// Clean up any resources being used
protected override void Dispose( bool disposing )
{
if (disposing) { if (components != null) { components.Dispose(); } }
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
this.PropertyGrid1 = new System.Windows.Forms.PropertyGrid();
this.SuspendLayout();
//
// PropertyGrid1
//
this.PropertyGrid1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.PropertyGrid1.LineColor = System.Drawing.SystemColors.ScrollBar;
this.PropertyGrid1.Location = new System.Drawing.Point(0, -1);
this.PropertyGrid1.Name = "PropertyGrid1";
this.PropertyGrid1.PropertySort = System.Windows.Forms.PropertySort.Alphabetical;
this.PropertyGrid1.Size = new System.Drawing.Size(408, 254);
this.PropertyGrid1.TabIndex = 1;
this.PropertyGrid1.ToolbarVisible = false;
this.PropertyGrid1.SelectedGridItemChanged += new System.Windows.Forms.SelectedGridItemChangedEventHandler(this.PropertyGrid1_SelectedGridItemChanged);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(408, 254);
this.Controls.Add(this.PropertyGrid1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.Name = "Form1";
this.Text = "Customizing Collections in Property Grid Demo";
this.ResumeLayout(false);
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void PropertyGrid1_SelectedGridItemChanged(object sender, SelectedGridItemChangedEventArgs e)
{
PropertyGrid1.Refresh();
}
}
}
I made a separate cs file, called LayerCollection.cs:
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
namespace PropertyGridSample
{
[TypeConverter(typeof(LayerConverter))]
public class Layer
{
public Image image { get; set; }
public Layer() { }
}
public class Layers
{
LayerCollection layercollection = new LayerCollection();
public Layers()
{
Layer[] layz = new Layer[2];
Layer lay1 = new Layer(); Layer lay2 = new Layer(); //Create two test layers and add them to the layer collection
layercollection.Add(lay1); layercollection.Add(lay2);
}
[TypeConverter(typeof(LayerCollectionConverter))]
public LayerCollection Layer_Collection { get { return layercollection; } }
}
public class LayerCollection : CollectionBase, ICustomTypeDescriptor
{
#region collection impl
public void Add(Layer lay) { List.Add(lay); } //Adds a layer object to the collection
public void Remove(Layer lay) { List.Remove(lay); } //Removes a layer object from the collection
public Layer this[int index] { get { return (List.Count > -1 && index < List.Count) ? (Layer)List[index] : null; } } //Return a layer object at index position
#endregion
#region ICustomTypeDescriptor impl
public AttributeCollection GetAttributes() { return TypeDescriptor.GetAttributes(this, true); }
public String GetClassName() { return TypeDescriptor.GetClassName(this, true); }
public String GetComponentName() { return TypeDescriptor.GetComponentName(this, true); }
public TypeConverter GetConverter() { return TypeDescriptor.GetConverter(this, true); }
public EventDescriptor GetDefaultEvent() { return TypeDescriptor.GetDefaultEvent(this, true); }
public PropertyDescriptor GetDefaultProperty() { return TypeDescriptor.GetDefaultProperty(this, true); }
public EventDescriptorCollection GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, true); }
public EventDescriptorCollection GetEvents() { return TypeDescriptor.GetEvents(this, true); }
public object GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, true); }
public object GetPropertyOwner(PropertyDescriptor pd) { return this; }
//Called to get the properties of this type. Returns properties with certain attributes. this restriction is not implemented here.
public PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return GetProperties(); }
//Called to get the properties of this type.
public PropertyDescriptorCollection GetProperties()
{
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null); // Create a collection object to hold property descriptors
// Iterate the list of layers and create a property descriptor for each layer item and add to the property descriptor collection
for (int i = 0; i < this.List.Count; i++) { LayerCollectionPropertyDescriptor pd = new LayerCollectionPropertyDescriptor(this, i); pds.Add(pd); }
return pds; // return the descriptor collection
}
#endregion
}
class LayerConverter : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destType)
{
return (destType == typeof(string) && value is Layer) ? "Layer Data": base.ConvertTo(context, culture, value, destType);
}
}
class LayerCollectionConverter : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destType)
{
return (destType == typeof(string) && value is LayerCollection) ? "Items": base.ConvertTo(context, culture, value, destType);
}
}
public class LayerCollectionPropertyDescriptor : PropertyDescriptor
{
private LayerCollection collection = null;
private int index = -1;
public LayerCollectionPropertyDescriptor(LayerCollection coll, int idx) : base("#" + idx.ToString(), null)
{
collection = coll; index = idx;
}
public override AttributeCollection Attributes { get { return new AttributeCollection(null); } }
public override bool CanResetValue(object component) { return true; }
public override bool IsReadOnly { get { return false; } }
public override bool ShouldSerializeValue(object component) { return true; }
public override string Description { get { return "Layer Description"; } }
public override string DisplayName { get { return "Layer " + index.ToString(); } }
public override string Name { get { return "#" + index.ToString(); } }
public override object GetValue(object component) { return collection[index]; }
public override Type ComponentType { get { return collection.GetType(); } }
public override Type PropertyType { get { return collection[index].GetType(); } }
public override void ResetValue(object component) { }
public override void SetValue(object component, object value) { } // this.collection[index] = value;
}
}
On your form, you just need to throw a propertygrid on it. I'm still looking at ways to improve it and need the ability to trap the add and remove buttons, on the built in collection editor

WinRT ListView virtualization

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.

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.

Abstract DomainCollectionView (Entity Framework) code into its own service (MVVM Silverlight4)

I've got a VM class below that's being used to wire up a view to my ADO.NET Entity Data Model, utilizing a P_BUDGET class. This works fine, gets my data, makes everything pretty. I have about 15-20 pages that are all going to be based on the same structure as this, and the code is going to be virtually identical except for the EntityType (P_BUDGET, P_ACCOUNT, P_CIRCUIT, etc etc). I feel like there should be a way to abstract this out, but I tried, and failed miserably! I also feel like I should be able to use one view, one viewmodel, and just swap out the entities binding to the GV... I just haven't been able to find a way to variablize the type, which permeates the entire viewmodel.
Appreciate your help,
Scott
public class TestViewModel : ViewModelBase
{
private readonly ODADomainContext _context = new ODADomainContext();
private readonly DomainCollectionView<P_BUDGET> _view;
private readonly DomainCollectionViewLoader<P_BUDGET> _loader;
private readonly EntityList<P_BUDGET> _source;
private bool _isGridEnabled;
/// <summary>
/// Initializes a new instance of the TestViewModel class.
/// </summary>
public TestViewModel()
{
this._source = new EntityList<P_BUDGET>(this._context.P_BUDGETs);
this._loader = new DomainCollectionViewLoader<P_BUDGET>(
this.LoadSampleEntities,
this.OnLoadSampleEntitiesCompleted);
this._view = new DomainCollectionView<P_BUDGET>(this._loader, this._source);
INotifyCollectionChanged notifyingSortDescriptions =
(INotifyCollectionChanged)this.CollectionView.SortDescriptions;
notifyingSortDescriptions.CollectionChanged +=
(sender, e) => this._view.MoveToFirstPage();
using (this.CollectionView.DeferRefresh())
{
this._view.PageSize = 10;
this._view.MoveToFirstPage();
}
}
#region View Properties
public bool IsGridEnabled
{
get
{
return this._isGridEnabled;
}
private set
{
if (this._isGridEnabled != value)
{
this._isGridEnabled = value;
this.RaisePropertyChanged("IsGridEnabled");
}
}
}
public ICollectionView CollectionView
{
get { return this._view; }
}
#endregion
private LoadOperation<P_BUDGET> LoadSampleEntities()
{
this.IsGridEnabled = false;
return this._context.Load(
this._context.GetBudgetsQuery());
}
private void OnLoadSampleEntitiesCompleted(LoadOperation<P_BUDGET> op)
{
this.IsGridEnabled = true;
if (op.HasError)
{
// TODO: handle errors
_view.PageSize = 0;
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this._source.Source = op.Entities;
_view.PageSize = 10;
this._view.MoveToFirstPage();
if (op.TotalEntityCount != -1)
{
this._view.SetTotalItemCount(op.TotalEntityCount);
}
}
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
}
Try something like this. This is not tested (obviously), not even complied. This also assumes that the EntityTypes (P_BUDGET, P_ACCOUNT, P_CIRCUIT etc.) are not POCOs.
public class TestViewModel<TEntity> : ViewModelBase
{
private readonly ODADomainContext _context = new ODADomainContext();
private readonly DomainCollectionView<TEntity> _view;
private readonly DomainCollectionViewLoader<TEntity> _loader;
private readonly EntityList<TEntity> _source;
private bool _isGridEnabled;
/// <summary>
/// Initializes a new instance of the TestViewModel class.
/// </summary>
public TestViewModel()
{
this._source = new EntityList<TEntity>(this._context.GetEntitySet<TEntity>);
this._loader = new DomainCollectionViewLoader<TEntity>(
this.LoadSampleEntities,
this.OnLoadSampleEntitiesCompleted);
this._view = new DomainCollectionView<TEntity>(this._loader, this._source);
INotifyCollectionChanged notifyingSortDescriptions =
(INotifyCollectionChanged)this.CollectionView.SortDescriptions;
notifyingSortDescriptions.CollectionChanged +=
(sender, e) => this._view.MoveToFirstPage();
using (this.CollectionView.DeferRefresh())
{
this._view.PageSize = 10;
this._view.MoveToFirstPage();
}
}
#region View Properties
public bool IsGridEnabled
{
get
{
return this._isGridEnabled;
}
private set
{
if (this._isGridEnabled != value)
{
this._isGridEnabled = value;
this.RaisePropertyChanged("IsGridEnabled");
}
}
}
public ICollectionView CollectionView
{
get { return this._view; }
}
#endregion
private LoadOperation<TEntity> LoadSampleEntities()
{
this.IsGridEnabled = false;
return this._context.Load(
this._context.GetBudgetsQuery());
}
private void OnLoadSampleEntitiesCompleted(LoadOperation<TEntity> op)
{
this.IsGridEnabled = true;
if (op.HasError)
{
// TODO: handle errors
_view.PageSize = 0;
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this._source.Source = op.Entities;
_view.PageSize = 10;
this._view.MoveToFirstPage();
if (op.TotalEntityCount != -1)
{
this._view.SetTotalItemCount(op.TotalEntityCount);
}
}
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
}
// http://blog.zoolutions.se/post/2010/04/05/Generic-Repository-for-Entity-Framework-for-Pluralized-Entity-Set.aspx
public static class ObjectContextExtensions
{
internal static EntitySetBase GetEntitySet<TEntity>(this ObjectContext context)
{
EntityContainer container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
Type baseType = GetBaseType(typeof(TEntity));
EntitySetBase entitySet = container.BaseEntitySets
.Where(item => item.ElementType.Name.Equals(baseType.Name))
.FirstOrDefault();
return entitySet;
}
private static Type GetBaseType(Type type)
{
var baseType = type.BaseType;
if (baseType != null && baseType != typeof(EntityObject))
{
return GetBaseType(type.BaseType);
}
return type;
}
}

Categories

Resources