I have a set of items (~12.000) i want to show in a ListView. Each of those items is a view model that has an assigned image that is not part of the app package (it's in an 'external' folder on a local disc). And because of UWP's limitations i can't (afaik and tested) assign an Uri to the ImageSource and have to use the SetSourceAsync method instead. Because of this the initial loading time of the app is too high, because all ImageSource objects have to be initialized at start up even if the image will not be seen by the user (the list is unfiltered at startup) and the resulting memory consumption is ~4GB. Copying the image files to an app data directory would resolve the issue, but is no solution for me, because the images are updated regularly and it would waste disc space.
The items are displayed in a ListView that uses a grouped ICollectionView as source.
Now i thought i could implement either IItemsRangeInfo or ISupportIncrementalLoading on each group and defer the initialization of the view model so only images are loaded if they are to be displayed. I testet this and it does not seem to work because neither interface's method gets called on the groups at runtime (please correct me here if that is not true and can be achieved). The current (not working) version uses a custom ICollectionView (for testing purposes) but the DeferredObservableCollection could as well implement IGrouping<TKey, TElement> and be used in a CollectionViewSource.
Is there any way i could achieve the deferred initialization or use an Uri for the image source or do i have to use a 'plain' collection or custom ICollectionView as ItemsSource on the ListView that implements the desired behaviour?
Current target version of the app: 1803 (Build 17134)
Current target version of the app: Fall Creators Update (Build 16299)
Both (minimum and target version) can be changed.
Code for creating the image source:
public class ImageService
{
// ...
private readonly IDictionary<short, ImageSource> imageSources;
public async Task<ImageSource> GetImageSourceAsync(Item item)
{
if (imageSources.ContainsKey(item.Id))
return imageSources[item.Id];
try
{
var imageFolder = await storageService.GetImagesFolderAsync();
var imageFile = await imageFolder.GetFileAsync($"{item.Id}.jpg");
var source = new BitmapImage();
await source.SetSourceAsync(await imageFile.OpenReadAsync());
return imageSources[item.Id] = source;
}
catch (FileNotFoundException)
{
// No image available.
return imageSources[item.Id] = unknownImageSource;
}
}
}
Code for the resulting groups that are returned by the ICollectionView.CollectionGroups property:
public class CollectionViewGroup : ICollectionViewGroup
{
public object Group { get; }
public IObservableVector<object> GroupItems { get; }
public CollectionViewGroup(object group, IObservableVector<object> items)
{
Group = group ?? throw new ArgumentNullException(nameof(group));
GroupItems = items ?? throw new ArgumentNullException(nameof(items));
}
}
Code of the collection that contains the items of each group:
public sealed class DeferredObservableCollection<T, TSource>
: ObservableCollection<T>, IObservableVector<T>, IItemsRangeInfo //, ISupportIncrementalLoading
where T : class
where TSource : class
{
private readonly IList<TSource> source;
private readonly Func<TSource, Task<T>> conversionFunc;
// private int currentIndex; // Used for ISupportIncrementalLoading.
// Used to get the total number of items when using ISupportIncrementalLoading.
public int TotalCount => source.Count;
/// <summary>
/// Initializes a new instance of the <see cref="DeferredObservableCollection{T, TSource}"/> class.
/// </summary>
/// <param name="source">The source collection.</param>
/// <param name="conversionFunc">The function used to convert item from <typeparamref name="TSource"/> to <typeparamref name="T"/>.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/> is <see langword="null"/> or
/// <paramref name="conversionFunc"/> is <see langword="null"/>.
/// </exception>
public DeferredObservableCollection(IList<TSource> source, Func<TSource, Task<T>> conversionFunc)
{
this.source = source ?? throw new ArgumentNullException(nameof(source));
this.conversionFunc = conversionFunc ?? throw new ArgumentNullException(nameof(conversionFunc));
// Ensure the underlying lists capacity.
// Used for IItemsRangeInfo.
for (var i = 0; i < source.Count; ++i)
Items.Add(default);
}
private class VectorChangedEventArgs : IVectorChangedEventArgs
{
public CollectionChange CollectionChange { get; }
public uint Index { get; }
public VectorChangedEventArgs(CollectionChange collectionChange, uint index)
{
CollectionChange = collectionChange;
Index = index;
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
// For testing purposes the peformed action is not differentiated.
VectorChanged?.Invoke(this, new VectorChangedEventArgs(CollectionChange.ItemInserted, (uint)e.NewStartingIndex));
}
//#region ISupportIncrementalLoading Support
//public bool HasMoreItems => currentIndex < source.Count;
//public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
//{
// Won't get called.
// return AsyncInfo.Run(async cancellationToken =>
// {
// if (currentIndex >= source.Count)
// return new LoadMoreItemsResult();
// var addedItems = 0u;
// while (currentIndex < source.Count && addedItems < count)
// {
// Add(await conversionFunc(source[currentIndex]));
// ++currentIndex;
// ++addedItems;
// }
// return new LoadMoreItemsResult { Count = addedItems };
// });
//}
//#endregion
#region IObservableVector<T> Support
public event VectorChangedEventHandler<T> VectorChanged;
#endregion
#region IItemsRangeInfo Support
public void RangesChanged(ItemIndexRange visibleRange, IReadOnlyList<ItemIndexRange> trackedItems)
{
// Won't get called.
ConvertItemsAsync(visibleRange, trackedItems).FireAndForget(null);
}
private async Task ConvertItemsAsync(ItemIndexRange visibleRange, IReadOnlyList<ItemIndexRange> trackedItems)
{
for (var i = visibleRange.FirstIndex; i < source.Count && i < visibleRange.LastIndex; ++i)
{
if (this[i] is null)
{
this[i] = await conversionFunc(source[i]);
}
}
}
public void Dispose()
{ }
#endregion
}
From the perspective of reducing memory consumption, the method of using BitmapImage.SetSourceAsync is not recommended, because it is not conducive to memory release. But considering your actual situation, I can provide some suggestions to help you optimize application performance.
1. Don't initialize 12000 images uniformly
Reading 12,000 pictures at a time will inevitably increase the memory usage. But we can create UserControl as one picture unit, and hand over the work of loading pictures to these units.
-ImageItem.cs
public class ImageItem
{
public string Name { get; set; }
public BitmapImage Image { get; set; } = null;
public ImageItem()
{
}
public async Task Init()
{
// do somethings..
// get image from folder, named imageFile
Image = new BitmapImage();
await Image.SetSourceAsync(await imageFile.OpenReadAsync());
}
}
-ImageItemControl.xaml
<UserControl
...>
<StackPanel>
<Image Width="200" Height="200" x:Name="MyImage"/>
</StackPanel>
</UserControl>
-ImageItemControl.xaml.cs
public sealed partial class ImageItemControl : UserControl
{
public ImageItemControl()
{
this.InitializeComponent();
}
public ImageItem Data
{
get { return (ImageItem)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(ImageItem), typeof(ImageItemControl), new PropertyMetadata(null,new PropertyChangedCallback(Data_Changed)));
private static async void Data_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(e.NewValue != null)
{
var image = e.NewValue as ImageItem;
var instance = d as ImageItemControl;
if (image.Image == null)
{
await image.Init();
}
instance.MyImage.Source = image.Image;
}
}
}
-Usage
<Page.Resources>
<DataTemplate x:DataType="local:ImageItem" x:Key="ImageTemplate">
<controls:ImageItemControl Data="{Binding}"/>
</DataTemplate>
</Page.Resources>
<Grid>
<GridView ItemTemplate="{StaticResource ImageTemplate}"
.../>
</Grid>
Please modify this code according to your actual situation
There are some advantages to this. Through the distributed method, on the one hand, the loading speed of pictures is increased (simultaneous loading). On the other hand, with virtualization, some pictures are not actually rendered, which can reduce memory usage.
2. Limiting the resolution of BitmapImage
This is very important, it can greatly reduce the memory consumption when loading a large number of pictures.
For example, you have a picture with 1920x1080 resolution, but only 200x200 resolution is displayed on the application. Then loading the original image will waste system resources.
We can modify the ImageItem.Init method:
public async Task Init()
{
// do somethings..
// get image from folder, named imageFile
Image = new BitmapImage() { DecodePixelWidth = 200 };
await Image.SetSourceAsync(await imageFile.OpenReadAsync());
}
Hope these two methods can help you reduce memory usage.
Related
I am trying to implement a collection view that allows limiting the amount of filtered out items.
I have come up with this implementation
public class LimitCollectionView : ListCollectionView
{
#region CONSTRUCTOR
public LimitCollectionView(IList source) : base(source)
{
}
#endregion
#region FIELDS
private int MAX_ITEMS = 0;
#endregion
#region PROPERTIES
/// <summary>
/// Gets or sets maximum amount of view items.
/// </summary>
public int MaxItems
{
get { return MAX_ITEMS; }
set
{
MAX_ITEMS = value;
OnPropertyChanged(new ComponentModel.PropertyChangedEventArgs(nameof(MaxItems)));
RefreshOrDefer();
}
}
#endregion
#region OVERRIDES
/// <summary>
/// Gets the estimated number of records.
/// </summary>
public override int Count
{
get
{
if (IsRefreshDeferred)
return 0;
if (MaxItems <= 0)
return base.Count;
var baseCount = base.Count;
if (MaxItems > baseCount)
return baseCount;
return Math.Min(MaxItems,baseCount);
}
}
public override bool PassesFilter(object item)
{
if (!base.PassesFilter(item))
return false;
if(item!=null)
{
return IndexOf(item) < MaxItems;
}
return true;
}
#endregion
}
In most cases the implementation works as intended BUT at some cases i get
System.InvalidOperationException: An ItemsControl is inconsistent with its items source.
So i suppose there is inconsistency with what ItemsControl expects and what my CollectionView reporting.
So the question is have i omitted something in my implementation ? Is it even possible to have an implementation that works ?
I know i could filter out items manually and recreate the collection on change but that beats the the purpose of CollectionView.
I have a class that inherits from an interface and implements a method that saves a single object and another method that saves a list of objects of type lets say 'iX':-
public class XX : iX
{
public override string A;
public override string B;
internal Some_Method(iX item)
{
A = item.A; B - item.B;
}
private void Single_Save(specific_type x)
{
//// some code here ///
}
private void Multiple_Save(List<specific_type> x)
{
/// some code here ///
}
internal void Save()
{
var y = AnotherFunction(this);
Save(y);
}
internal void SaveAll()
{
Multiple_Save(that_list); <----------- how do I get that list ??
}
}
Now from my Business class I would like to do something like this:-
public static void SaveObj(iX item)
{
var singleobj = new XX(item);
singleobj.Save();
}
The single save works. But I am having issues writing code that will allow me to save a list of objects as such:-
public static void SaveObjects(List<iX> items)
{
var multipleobj = new XX(items); <------- this will not work, because my class is expecting a single object of type iX....
multipleobj.SaveAll();
}
** The caveat to this is that, both Multiple_Save and Single_Save methods use different forms of saving info and need to be used separately by respective methods aka cannot foreach through a list of items and Single_Save one at a time.
Can someone point me to the concept I should be looking at to figure this scenario out?
I am not sure what are you trying to do. I can not see the construction of XX class but create a overload constructor
public XX(List<yourClass> item)
{
}
This will have your object.
You need multiple constructors in your class. Provided they have different parameters, method overloading allows you to create multiple methods with the same name.
Example:
public class XX : IX
{
public IX SingleItem { get; set; }
public List<IX> ListOfItems { get; set; }
public XX(IX item)
{
SingleItem = item;
}
public XX(List<IX> items)
{
ListOfItems = items;
}
}
You could even then combine your save methods into one if you wanted too:
public void Save()
{
if (ListOfItems != null)
{
//code to save multiple
}
else if (Item != null)
{
//code to save one
}
}
It would be far simpler if you had a separate a collection class. Let an XX represent a single item, and put them together in a collection if you want to work with them as a unit. It's easy to create a collection class (just inherit from List<T> and all its methods and its enumerator become available from this). For example:
class XXList : List<XX>
{
public void SaveAll()
{
foreach (var xx in this) xx.Save();
}
}
So you can use it like this:
var list = new XXList
{
new XX(item1),
new XX(item2),
new XX(item3)
};
list.SaveAll();
You can use like below:
// Create the list
List<ClassName> List = new List<Ix>();
// Create instance of your class
Ix ix = new Ix();
// Add the new object to the list
classList.Add(ix);
You can also use Types.
In your
private void Multiple_Save(List<specific_type> x)
{
If (x != null)
{
foreach (var ix in x)
SingleSave(ix);
/// some code here ///
}
}
I believe this requires System.Collections.Generic as namespace.
I have an observable collection in the view model that implements Bindable Base as follows Please have a look at the MoveUp and MoveDown methods where they are bound to two buttons in the view. When ever up button is pressed I want the selected row in the datagrid to move one step up in the based on the sequence column in the database and for down one step down.. Both the methods works PERFECTLY. Problem is the changes get shown in the datagrid only when the entire view is refreshed. My requirement is when the button is clicked I want the view to be automatically refreshed. I apologize for such long code. Please Help!!!!. I have some cs code as well for the both up and down buttons specified below the viewmodel. Only pointers in the code that needs to be emphasized is the ObservableCollection JobEntities, MoveUp and MoveDown commands.
ViewModel.cs:
public class JobConfigurationViewModel : BindableBase
{
public JobConfigurationLogic JobConfigurationLogic =
new JobConfigurationLogic(new JobConfigurationResultsRepository());
public SrcDestConfigurationLogic SrcDestConfigurationLogic =
new SrcDestConfigurationLogic(new SrcDestCofigurationRepository());
private string _enterprise;
public string Enterprise
{
get { return _enterprise; }
set { SetProperty(ref _enterprise, value); }
}
private int currentJobID;
private int currentSequence;
private int previousJobID;
private int previousSequence;
private string _site;
public string Site
{
get { return _site; }
set { SetProperty(ref _site, value); }
}
private int _siteID;
public int SiteID
{
get { return _siteID; }
set { SetProperty(ref _siteID, value); }
}
private ObservableCollection<JobConfigurationResults> _jobEntities;
public ObservableCollection<JobConfigurationResults> JobEntities
{
get { return _jobEntities; }
set
{
SetProperty(ref _jobEntities, value);
this.OnPropertyChanged("JobEntities");
}
}
//Source System List for Job
private List<SourceSiteSystem> _lstJobSrcSystems;
public List<SourceSiteSystem> LstJobSrcSystems
{
get { return _lstJobSrcSystems; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _lstJobSrcSystems, value);
}
}
//Deestination System List for Job
private List<DestinationSiteSystem> _lstJobDestSystems;
public List<DestinationSiteSystem> LstJobDestSystems
{
get { return _lstJobDestSystems; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _lstJobDestSystems, value);
}
}
//the Selected Source Site system ID
private int _selectedSrcSiteSystemId = 0;
public int SelectedSrcSiteSystemId
{
get { return _selectedSrcSiteSystemId; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _selectedSrcSiteSystemId, value);
}
}
//the Selected Source Site system from the dropdown
private SourceSiteSystem _selectedSrcSiteSystem;
public SourceSiteSystem SelectedSrcSiteSystem
{
get { return _selectedSrcSiteSystem; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
if (value != null)
{
SetProperty(ref _selectedSrcSiteSystem, value);
SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId;
}
}
}
//the Selected Destination Site system ID
private int _selectedDestSiteSystemId = 0;
public int SelectedDestSiteSystemId
{
get { return _selectedDestSiteSystemId; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _selectedDestSiteSystemId, value);
}
}
//the Selected Destination Site system from the dropdown
private DestinationSiteSystem _selectedDestSiteSystem;
public DestinationSiteSystem SelectedDestSiteSystem
{
get { return _selectedDestSiteSystem; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
if (value != null)
{
SetProperty(ref _selectedDestSiteSystem, value);
SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId;
}
}
}
private JobConfigurationResults _jeJobConfigurationResults;
public JobConfigurationResults JEJobConfigurationResults
{
get { return _jeJobConfigurationResults; }
set { _jeJobConfigurationResults = value; }
}
private List<JobTaskConfiguration> _taskSelectionList = new List<JobTaskConfiguration>();
private CancellationTokenSource _source;
private RelayCommand<object> _commandSaveInstance;
private RelayCommand<object> _hyperlinkInstance;
private RelayCommand<object> _commandRunJob;
private RelayCommand<object> _upCommand;
private RelayCommand<object> _downCommand;
private IEventAggregator _aggregator;
/// <summary>
/// This is a Subscriber to the Event published by EnterpriseViewModel
/// </summary>
/// <param name="agg"></param>
public JobConfigurationViewModel(IEventAggregator agg)
{
_aggregator = agg;
PubSubEvent<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread);
//evt.Unsubscribe();
StartPopulate();
}
private async void StartPopulate()
{
await TaskPopulate();
}
//This is to ensure that the publisher has published the data that is needed for display in this workspace
private bool TaskProc()
{
Thread.Sleep(500);
PubSubEvent<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread);
return DoPopulate();
}
private Task<bool> TaskPopulate()
{
_source = new CancellationTokenSource();
return Task.Factory.StartNew<bool>(TaskProc, _source.Token);
}
/// <summary>
/// This method handles the populating of the Source and Destination Dropdowns and the Job entity and Task Datagrid
/// This is mainly driven by the Site selected in the previous workspace
/// </summary>
/// <returns></returns>
private bool DoPopulate()
{
PopulateSourceDestinations(this.SiteID);
return true;
}
/// <summary>
/// this method displays all entities and tasks for the site.
/// This is done async so that the Publisher thread is not held up
/// </summary>
public void GetJobConfigurationResults()
{
if (SelectedSrcSiteSystem == null)
{
SelectedSrcSiteSystem = LstJobSrcSystems[0];
}
if (SelectedDestSiteSystem == null)
{
SelectedDestSiteSystem = LstJobDestSystems[0];
}
SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId;
SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId;
var jobConfigurationResults = new JobConfigurationResults
{
SourceId = SelectedSrcSiteSystemId,
DestinationId = SelectedDestSiteSystemId
};
JobEntities = new ObservableCollection<JobConfigurationResults>();
JobEntities = JobConfigurationLogic.GetResults(jobConfigurationResults.SourceId,
jobConfigurationResults.DestinationId);
_taskSelectionList = new List<JobTaskConfiguration>(JobEntities.Count * 3);
}
/// <summary>
/// //Adding a method to pupulate the Source and Destination dropdown lists.
/// This is done async so that the Publisher thread is not held up
/// </summary>
///
///
public async void PopulateSourceDestinations(int siteId)
{
this.LstJobSrcSystems = SrcDestConfigurationLogic.LoadSourceSiteSystems(siteId);
this.LstJobDestSystems = SrcDestConfigurationLogic.LoadDestinationSystems(siteId);
GetJobConfigurationResults();
}
public ICommand HyperlinkCommand
{
get
{
if (_hyperlinkInstance == null)
_hyperlinkInstance = new RelayCommand<object>(openDialog);
return _hyperlinkInstance;
}
}
private void openDialog(object obj)
{
JobConfigurationResults results = obj as JobConfigurationResults;
JEJobConfigurationResults = JobEntities.SingleOrDefault(x => x.JobEntityId == results.JobEntityId);
}
public ICommand CommandSave
{
get
{
if (_commandSaveInstance == null)
_commandSaveInstance = new RelayCommand<object>(saveJobConfigurationChanges);
return _commandSaveInstance;
}
}
public ICommand CommandRunJob
{
get { return _commandRunJob ?? (_commandRunJob = new RelayCommand<object>(RunJob)); }
}
/// <summary>
/// this saves all the changes in the selection made by the user
/// </summary>
/// <param name="ob"></param>
public void saveJobConfigurationChanges(object ob)
{
foreach (var job in JobEntities)
{
int jobEntityId = job.JobEntityId;
foreach (var task in job.TaskDetails)
{
int id = task.JobTask_ID;
bool isSelected = task.IsSelected;
_taskSelectionList.Add(task);
}
}
JobConfigurationLogic.UpdateTaskSelection(_taskSelectionList);
}
public ICommand UpCommand
{
get
{
if (_upCommand == null)
_upCommand = new RelayCommand<object>(MoveUp);
return _upCommand;
}
}
private void MoveUp(object obj)
{
if (obj != null)
{
JobConfigurationResults results = obj as JobConfigurationResults;
currentJobID = results.JobEntityId;
currentSequence = results.SequenceOrder - 1;
try
{
JobConfigurationResults res = _jobEntities.SingleOrDefault(n => n.SequenceOrder == currentSequence);
previousJobID = res.JobEntityId;
previousSequence = res.SequenceOrder + 1;
// JobConfigurationLogic.UpdateSequence(currentJobID, previousSequence, previousJobID, currentSequence);
JobConfigurationLogic.UpdateSequence(currentSequence, currentJobID, previousSequence, previousJobID);
OnPropertyChanged("JobEntities");
}
catch (NullReferenceException)
{
MessageBox.Show("Can't move the top record");
}
}
else
{
MessageBox.Show("Please Select a row that you want to sort");
}
}
public ICommand DownCommand
{
get
{
if (_downCommand == null)
_downCommand = new RelayCommand<object>(MoveDown);
return _downCommand;
}
}
private void MoveDown(object obj)
{
if (obj != null)
{
JobConfigurationResults results = obj as JobConfigurationResults;
currentJobID = results.JobEntityId;
currentSequence = results.SequenceOrder + 1;
try
{
JobConfigurationResults res = _jobEntities.SingleOrDefault(a => a.SequenceOrder == currentSequence);
previousJobID = res.JobEntityId;
previousSequence = res.SequenceOrder - 1;
JobConfigurationLogic.UpdateSequence(currentSequence, currentJobID, previousSequence, previousJobID);
OnPropertyChanged("JobEntities");
}
catch (NullReferenceException)
{
MessageBox.Show("You have reached the end");
}
}
else
{
MessageBox.Show("Please Select a row that you want to sort");
}
}
/// <summary>
/// Execute an etl job using the current job id
/// </summary>
private void RunJob(object obj)
{
JobEngine jobEngine = new JobEngine();
var jobId = JobEntities[0].JobId;
jobEngine.ProcessJob(jobId);
}
}
CS CODE:
private void btnup_Click(object sender, RoutedEventArgs e)
{
dgEntities.Items.Refresh();
//dgEntities.GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateTarget();
}
private void btndown_Click(object sender, RoutedEventArgs e)
{
dgEntities.GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateTarget();
}
An ObservableCollection will notify on change. There's no reason to do it manually, so you can remove all of the OnPropertyChanged("JobEntities");. This will get you to a cleaner solution.
MSDN
WPF provides the ObservableCollection class, which is a built-in
implementation of a data collection that implements the
INotifyCollectionChanged interface.
The next part is that an ObservableCollection will only notify on changes to the collection itself (add/remove). Any modifications to an element within the list will not have have the notify message sent. To do this, the simplest method is to implement the INotifyPropertyChanged to the elements used in the Observable Collection
I'm using PRISM 5 in the example, so it should be pretty equal to what you're doing. There's a couple of major design changes to you're code. First, I'm using a straight property for my Observable Collection. We know the framework will handle any add/remove operations to this collection. Then to notify when I change a property within the entity in an observable collection, I've used a notify property within the TestEntity class itself.
public class MainWindowViewModel : BindableBase
{
//Notice no OnPropertyChange, just a property
public ObservableCollection<TestEntity> TestEntities { get; set; }
public MainWindowViewModel()
: base()
{
this.TestEntities = new ObservableCollection<TestEntity> {
new TestEntity { Name = "Test", Count=0},
new TestEntity { Name = "Test1", Count=1},
new TestEntity { Name = "Test2", Count=2},
new TestEntity { Name = "Test3", Count=3}
};
this.UpCommand = new DelegateCommand(this.MoveUp);
}
public ICommand UpCommand { get; private set; }
private void MoveUp()
{
//Here is a dummy edit to show the modification of a element within the observable collection
var i = this.TestEntities.FirstOrDefault();
i.Count = 5;
}
}
Here's my entity, notice the BindableBase and the fact I notify on change. This allows the DataGrid or whatever you're using to be notified that the property changed.
public class TestEntity : BindableBase {
private String _name;
public String Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
//Notice I've implemented the OnPropertyNotify (Prism uses SetProperty, but it's the same thing)
private Int32 _count;
public Int32 Count
{
get { return _count; }
set { SetProperty(ref _count, value); }
}
}
Now really all the TestEntity needs to have implemented the INotifyPropertyChanged for this to work, but I'm using the PRISM BindableBase as an example.
EDIT
I found a similar question on SO. I think yours is slightly different, but they overlap on the concepts. It may help to look over it.
Observable Collection Notify when property changed in MVVM
EDIT
If the datagrid is sorted the previous method will not update the grid. To handle this you need to refresh the grid's view, but are unable to directly access it using MVVM. So to handle this you'll want to use a CollectionViewSource.
public class MainWindowViewModel : BindableBase
{
//This will bind to the DataGrid instead of the TestEntities
public CollectionViewSource ViewSource { get; set; }
//Notice no OnPropertyChange, just a property
public ObservableCollection<TestEntity> TestEntities { get; set; }
public MainWindowViewModel()
: base()
{
this.TestEntities = new ObservableCollection<TestEntity> {
new TestEntity { Name = "Test", Count=0},
new TestEntity { Name = "Test1", Count=1},
new TestEntity { Name = "Test2", Count=2},
new TestEntity { Name = "Test3", Count=3}
};
this.UpCommand = new DelegateCommand(this.MoveUp);
//Initialize the view source and set the source to your observable collection
this.ViewSource = new CollectionViewSource();
ViewSource.Source = this.TestEntities;
}
public ICommand UpCommand { get; private set; }
private void MoveUp()
{
//Here is a dummy edit to show the modification of a element within the observable collection
var i = this.TestEntities.FirstOrDefault();
i.Count = 5;
//Now anytime you want the datagrid to refresh you can call this.
ViewSource.View.Refresh();
}
}
The TestEntity class does not change, but here's the class again:
public class TestEntity : BindableBase
{
private String _name;
public String Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
//Notice I've implemented the OnPropertyNotify (Prism uses SetProperty, but it's the same thing)
private Int32 _count;
public Int32 Count
{
get { return _count; }
set { SetProperty(ref _count, value); }
}
}
For clarification, here's my XAML showing the binding to the new CollectionViewSource.
<DataGrid Grid.Row="1" ItemsSource="{Binding ViewSource.View}"></DataGrid>
For further reading you can refer to the MSDN article on this.
Here's another relevant question/answer - Re-sort WPF DataGrid after bounded Data has changed
I am creating an android app in VS2012 using Xamarin.Android. I am displaying a custom list in Main screen. I need to pass a custom object(with ID,String,String,String properties) from this Main activity to another when user clicks on list item.
Can anyone please help me with some example?
edit:
I have already tried solution mention in other question
but the problem is I am getting below exception:
This is how I am extracting in second activity
InsuranceReminderBO i = (InsuranceReminderBO)Intent.GetSerializableExtra("SelectedItemID");
i is null
and in first activity setting it like this:
Intent intent = new Intent(this, typeof(ReminderDetails));
intent.PutExtra("SelectedItemID", selectedInsurance);
StartActivity(typeof(ReminderDetails));
where class InsuranceReminderBO is defined as
public class InsuranceReminderBO : Java.Lang.Object, Java.IO.ISerializable
I have also tried using IParcelable but in that I got error Creator is not defined
in ICreator or Creator
Following the implementation of Iparcelable on CustomObject
'public class InsuranceReminderBO : Java.Lang.Object, IParcelable
{
public InsuranceReminderBO()
{
}
#region Objects and Properties
private int id;
private String strCompanyName;
private String strPremiumAmount;
private String stDueDate;
public int ID
{
get { return this.id; }
set { this.id = value; }
}
public String Company_Name
{
get { return this.strCompanyName; }
set { this.strCompanyName = value; }
}
public String Premium_Amount
{
get { return this.strPremiumAmount; }
set { this.strPremiumAmount = value; }
}
public String Due_Date
{
get { return this.stDueDate; }
set { this.stDueDate = value; }
}
#endregion
#region IParcelable implementation
// The creator creates an instance of the specified object
private static readonly GenericParcelableCreator<InsuranceReminderBO> _creator
= new GenericParcelableCreator<InsuranceReminderBO>((parcel) => new InsuranceReminderBO(parcel));
[ExportField("CREATOR")]
public static GenericParcelableCreator<InsuranceReminderBO> GetCreator()
{
return _creator;
}
// Create a new SelectListItem populated with the values in parcel
private InsuranceReminderBO(Parcel parcel)
{
ID = parcel.ReadInt();
Company_Name = parcel.ReadString();
Premium_Amount = parcel.ReadString();
Due_Date = parcel.ReadString();
}
public int DescribeContents()
{
return 0;
}
// Save this instance's values to the parcel
public void WriteToParcel(Parcel dest, ParcelableWriteFlags flags)
{
dest.WriteInt(ID);
dest.WriteString(Company_Name);
dest.WriteString(Premium_Amount);
dest.WriteString(Due_Date);
}
// Closest to the 'Java' way of implementing the creator
/*public sealed class SelectListItemCreator : Java.Lang.Object, IParcelableCreator
{
public Java.Lang.Object CreateFromParcel(Parcel source)
{
return new SelectListItem(source);
}
public Java.Lang.Object[] NewArray(int size)
{
return new SelectListItem[size];
}
}*/
#endregion
}
#region GenericParcelableCreator
/// <summary>
/// Generic Parcelable creator that can be used to create objects from parcels
/// </summary>
public sealed class GenericParcelableCreator<T> : Java.Lang.Object, IParcelableCreator
where T : Java.Lang.Object, new()
{
private readonly Func<Parcel, T> _createFunc;
/// <summary>
/// Initializes a new instance of the <see cref="ParcelableDemo.GenericParcelableCreator`1"/> class.
/// </summary>
/// <param name='createFromParcelFunc'>
/// Func that creates an instance of T, populated with the values from the parcel parameter
/// </param>
public GenericParcelableCreator(Func<Parcel, T> createFromParcelFunc)
{
_createFunc = createFromParcelFunc;
}
#region IParcelableCreator Implementation
public Java.Lang.Object CreateFromParcel(Parcel source)
{
return _createFunc(source);
}
public Java.Lang.Object[] NewArray(int size)
{
return new T[size];
}
#endregion
}
#endregion'
I am putting object in intent as
InsuranceReminderBO selectedInsurance = listOfInsurance[e.Position];
Intent intent = new Intent(this, typeof(ReminderDetails));
intent.PutExtra("SelectedItem", selectedInsurance);
And reading in second activity as
InsuranceReminderBO i = (InsuranceReminderBO)Intent.GetParcelableExtra("SelectedItem");
but getting i as null.
To coat tail on the servicestack.text solution, you can just download the android DLL's and reference them into your solution. You can use this and add it to your solution, build it separately, as alternatives. https://github.com/ServiceStack/ServiceStack.Text/tree/master/src/ServiceStack.Text.Android
Also I use a couple of methods to convert items back and forth that may be helpful, try
static public string ToJSON(this object item)
{
var myval = JsonSerializer.SerializeToString(item);
return myval;
}
static public T FromJSON<T>(string code)
{
var item = JsonSerializer.DeserializeFromString<T>(code);
return item;
}
There's an article on using IParcelable in Xamarin here.
Personally, I've always just serialised to JSON, passed a string extra and deserialised it on the other end.
If you're using ServiceStack.Text, which I like, you can do something like this:
intent.PutExtra("SelectedItemId", JsonSerializer.SerializeToString(selectedInsurance));
And on the other end:
var insurance = JsonSerializer.DeserializeFromString<InsuranceReminderBO>(Intent.GetStringExtra("SelectedItemId"))
No need to implement Java.Lang.Object, Java.IO.ISerializable
After doing a lot of search on Google finally i found a solution here.
Basically I used StartActivity(intent);
Given the benefits of composable events as offered by the Reactive Extensions (Rx) framework, I'm wondering whether my classes should stop pushing .NET events, and instead expose Rx observables.
For instance, take the following class using standard .NET events:
public class Foo
{
private int progress;
public event EventHandler ProgressChanged;
public int Progress
{
get { return this.progress; }
set
{
if (this.progress != value)
{
this.progress = value;
// Raise the event while checking for no subscribers and preventing unsubscription race condition.
var progressChanged = this.ProgressChanged;
if (progressChanged != null)
{
progressChanged(this, new EventArgs());
}
}
}
}
}
Lot of monotonous plumbing.
This class could instead use some sort of observable to replace this functionality:
public class Foo
{
public Foo()
{
this.Progress = some new observable;
}
public IObservable<int> Progress { get; private set; }
}
Far less plumbing. Intention is no longer obscured by plumbing details. This seems beneficial.
My questions for you fine StackOverflow folks are:
Would it good/worthwhile to replace standard .NET events with IObservable<T> values?
If I were to use an observable, what kind would I use here? Obviously I need to push values to it (e.g. Progress.UpdateValue(...) or something).
For #2, the most straightforward way is via a Subject:
Subject<int> _Progress;
IObservable<int> Progress {
get { return _Progress; }
}
private void setProgress(int new_value) {
_Progress.OnNext(new_value);
}
private void wereDoneWithWorking() {
_Progress.OnCompleted();
}
private void somethingBadHappened(Exception ex) {
_Progress.OnError(ex);
}
With this, now your "Progress" can not only notify when the progress has changed, but when the operation has completed, and whether it was successful. Keep in mind though, that once an IObservable has completed either via OnCompleted or OnError, it's "dead" - you can't post anything further to it.
I don't recommend managing your own subscriber list when there are built in subjects that can do that for you. It also removes the need for carrying your own mutable copy of T.
Below is my (commentless) version of your solution:
public class Observable<T> : IObservable<T>, INotifyPropertyChanged
{
private readonly BehaviorSubject<T> values;
private PropertyChangedEventHandler propertyChanged;
public Observable() : this(default(T))
{
}
public Observable(T initalValue)
{
this.values = new BehaviorSubject<T>(initalValue);
values.DistinctUntilChanged().Subscribe(FirePropertyChanged);
}
public T Value
{
get { return this.values.First(); }
set { values.OnNext(value); }
}
private void FirePropertyChanged(T value)
{
var handler = this.propertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Value"));
}
public override string ToString()
{
return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
}
public static implicit operator T(Observable<T> input)
{
return input.Value;
}
public IDisposable Subscribe(IObserver<T> observer)
{
return values.Subscribe(observer);
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { this.propertyChanged += value; }
remove { this.propertyChanged -= value; }
}
}
I'll keep it short and simple:
yes
BehaviorSubject
:)
Ok guys, seeing as how I think it's at least worth a shot to try this, and seeing as how RX's Subject<T> isn't quite what I'm looking for, I've created a new observable that fits my needs:
Implements IObservable<T>
Implements INotifyPropertyChange to work with WPF/Silverlight binding.
Provides easy get/set semantics.
I call the class Observable<T>.
Declaration:
/// <summary>
/// Represents a value whose changes can be observed.
/// </summary>
/// <typeparam name="T">The type of value.</typeparam>
public class Observable<T> : IObservable<T>, INotifyPropertyChanged
{
private T value;
private readonly List<AnonymousObserver> observers = new List<AnonymousObserver>(2);
private PropertyChangedEventHandler propertyChanged;
/// <summary>
/// Constructs a new observable with a default value.
/// </summary>
public Observable()
{
}
public Observable(T initalValue)
{
this.value = initialValue;
}
/// <summary>
/// Gets the underlying value of the observable.
/// </summary>
public T Value
{
get { return this.value; }
set
{
var valueHasChanged = !EqualityComparer<T>.Default.Equals(this.value, value);
this.value = value;
// Notify the observers of the value.
this.observers
.Select(o => o.Observer)
.Where(o => o != null)
.Do(o => o.OnNext(value))
.Run();
// For INotifyPropertyChange support, useful in WPF and Silverlight.
if (valueHasChanged && propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
/// <summary>
/// Converts the observable to a string. If the value isn't null, this will return
/// the value string.
/// </summary>
/// <returns>The value .ToString'd, or the default string value of the observable class.</returns>
public override string ToString()
{
return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
}
/// <summary>
/// Implicitly converts an Observable to its underlying value.
/// </summary>
/// <param name="input">The observable.</param>
/// <returns>The observable's value.</returns>
public static implicit operator T(Observable<T> input)
{
return input.Value;
}
/// <summary>
/// Subscribes to changes in the observable.
/// </summary>
/// <param name="observer">The subscriber.</param>
/// <returns>A disposable object. When disposed, the observer will stop receiving events.</returns>
public IDisposable Subscribe(IObserver<T> observer)
{
var disposableObserver = new AnonymousObserver(observer);
this.observers.Add(disposableObserver);
return disposableObserver;
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { this.propertyChanged += value; }
remove { this.propertyChanged -= value; }
}
class AnonymousObserver : IDisposable
{
public IObserver<T> Observer { get; private set; }
public AnonymousObserver(IObserver<T> observer)
{
this.Observer = observer;
}
public void Dispose()
{
this.Observer = null;
}
}
}
Usage:
Consuming is nice and easy. No plumbing!
public class Foo
{
public Foo()
{
Progress = new Observable<T>();
}
public Observable<T> Progress { get; private set; }
}
Usage is simple:
// Getting the value works just like normal, thanks to implicit conversion.
int someValue = foo.Progress;
// Setting the value is easy, too:
foo.Progress.Value = 42;
You can databind to it in WPF or Silverlight, just bind to the Value property.
<ProgressBar Value={Binding Progress.Value} />
Most importantly, you can compose, filter, project, and do all the sexy things RX lets you do with IObservables:
Filtering events:
foo.Progress
.Where(val => val == 100)
.Subscribe(_ => MyProgressFinishedHandler());
Automatic unsubscribe after N invocations:
foo.Progress
.Take(1)
.Subscribe(_ => OnProgressChangedOnce());
Composing events:
// Pretend we have an IObservable<bool> called IsClosed:
foo.Progress
.TakeUntil(IsClosed.Where(v => v == true))
.Subscribe(_ => ProgressChangedWithWindowOpened());
Nifty stuff!
Apart from the fact that your existing eventing code could be terser:
public event EventHandler ProgressChanged = delegate {};
...
set {
...
// no need for null check anymore
ProgressChanged(this, new EventArgs());
}
I think by switching to Observable<int> you are just moving complexity from the callee to the caller. What if I just want to read the int?
-Oisin