I have been trying to implement ISupportIncrementalLoading interface for an ObservableCollection. My implementation works for the most part but it behaves strangely under uncertain circumstances. Here is my code to IncrementalCollection class.
public class IncrementalCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
{
private bool hasMoreItems;
private int currentPage;
private string filter;
private Func<string, int, Task<IList<T>>> func;
Action onLoadingStarts;
Action onLoadingEnds;
public IncrementalCollection(Func<string, int, Task<IList<T>>> func, Action onLoadingStarts, Action onLoadingEnds)
{
this.func = func;
this.hasMoreItems = true;
this.onLoadingStarts = onLoadingStarts;
this.onLoadingEnds = onLoadingEnds;
}
public void ResetCollection(string filter)
{
currentPage = 0;
this.filter = filter;
this.Clear();
}
public bool HasMoreItems
{
get { return hasMoreItems; }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
return DoLoadMoreItemsAsync(count).AsAsyncOperation<LoadMoreItemsResult>();
}
private async Task<LoadMoreItemsResult> DoLoadMoreItemsAsync(uint count)
{
onLoadingStarts();
var result = await func(this.filter, ++this.currentPage);
if (result == null || result.Count == 0)
{
hasMoreItems = false;
}
else
{
foreach (T item in result)
this.Add(item);
}
onLoadingEnds();
return new LoadMoreItemsResult() { Count = result == null ? 0 : (uint)result.Count };
}
}
The first strange behavior occurs when page loads, LoadMoreItemsAsync function is sometimes called once, generally twice and sometimes more than twice. This is strange as one call is enough to add enough items to the collection. I even tried to pull more data (2-3 times) but the behavior continues. There might be problem about the place of initialization of the IncrementalCollection object. As it seems the longer it takes to load the page the more calls are made to LoadMoreItemsAsync function. I am creating the collection in NavigationHelper_LoadState function like this.
_users = new IncrementalCollection<User>((filter, page) => _dataService.GetUserList(url, filter, null, page), onLoadingStarts, onLoadingEnds);
Second strange behavior is about caching, although I have added
this.NavigationCacheMode = NavigationCacheMode.Disabled;
to every page constructor and also changed NavigationHelper not to save pageState on back navigation. It feels like web requests are cached as it is very hard to return a response in that amount of time.
public void OnNavigatedFrom(NavigationEventArgs e)
{
if (e.NavigationMode == NavigationMode.Back)
return;
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
var pageState = new Dictionary<String, Object>();
if (this.SaveState != null)
{
this.SaveState(this, new SaveStateEventArgs(pageState));
}
frameState[_pageKey] = pageState;
}
Any help about these strange behaviors is appreciated.
Also is there any good tutorial about ISupportIncrementalLoading interface that explains LoadMoreItemsAsync firing conditions. I am trying to modify a WrapPanel implementation but don't know where to start as I don't know what it looks for. This is probably about ItemHeight but still concrete information is better.
Thanks in advance.
There seems to be a bug in ISupportIncrementalLoading interface. Solved the multiple request problem by applying the solution here Create a ListView with LoadMoreItemsAsync on end of scroll.
I wrapped the foreach loop inside Task.WhenAll call.
await Task.WhenAll(Task.Delay(50), Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (T item in result)
this.Add(item);
}).AsTask());
Related
Let's say you have the following method:
public void Install()
{
CreateItem(Item1)
CreateItem(Item2)
CreateItem(Item3)
}
where CreateItem will throw a ItemAlreadyExistsException if the item already exists, but you want execution to continue to the other two even if that exception is thrown, so you'd probably do something similar to this:
public void Install()
{
var itemsColl = new[] {Item1, Item2, Item3};
foreach (var item in itemsColl)
{
try
{
CreateItem(item);
}
catch (ItemAlreadyExistsException e)
{
// Do nothing or handle exception
}
}
}
Now let's say you also have an Uninstall() method that does the opposite - deletes the items - and that you also have a Repair() method that just calls to Uninstall() and then Install() - with one small difference: on Repair() because you just had a call to Uninstall() before you calling Install() you know that the items should never exist (if they did, they would have gotten deleted by the Uninstall() call) which means now you do care about the ItemAlreadyExistsExceptions and you no longer want to catch them.
In this particular simple example the body of Install() is small/simple enough that it could just be copied to Repair() (although this would create duplication of code), but how would one go about creating something that works for a more complex example without creating duplication of code?
One thing I can think of is creating something like:
public void Install()
{
InternalInstall(false);
}
private void InternalInstall(bool throwOnError)
{
var itemsColl = new[] {Item1, Item2, Item3};
foreach (var item in itemsColl)
{
try
{
CreateItem(item);
}
catch (ItemAlreadyExistsException e)
{
if (throwOnError)
throw;
}
}
}
public void Repair()
{
Uninstall();
InternalInstall(true);
}
But having a parameter decide whether to throw or not is not a very good idea as stated here (even if this is a private method): Throw/do-not-throw an exception based on a parameter - why is this not a good idea?
Any other thoughts?
I think, I'd do it like this
IEnumerable<Item> Install(){
var failedItems = new List<Item>();
// assuming items is a list of Items on class level
foreach( var item in items )
{
try{ CreateItem(item); }
catch(ItemAlreadyExistsException ){
failedItems.Add(item);
}
}
return failedItems;
}
Something similar for Uninstall, and finally
IEnumerable<Item> Repair(){
var failedItems = new List<Item>();
// assuming items is a list of Items on class level
foreach( var item in items )
{
try
{
DeleteItem(item);
CreateItem(item);
}
catch(ItemAlreadyExistsException ){
failedItems.Add(item);
}
}
return failedItems;
}
Of course you immediately see where the code duplication is now and how you could improve from there on.
Maybe a little like this:
private IEnumerable<Item> GuardedIterationOfItems( Action<Item> action )
{
var failedItems = new List<Item>();
// assuming items is a list of Items on class level
foreach( var item in items )
{
try{
action(item);
}
catch(ItemAlreadyExistsException ){
failedItems.Add(item);
}
}
return failedItems;
}
IEnumerable<Item> Install()
{
return GuardedIterationOfItems( CreateItem );
}
IEnumerable<Item> UnInstall()
{
return GuardedIterationOfItems( DeleteItem );
}
IEnumerable<Item> Repair()
{
return GuardedIterationOfItems( x => {
DeleteItem(x);
CreateItem(x);
});
}
Disclaimer: Untested and maybe in need of threadsafety measures if it should be.
On my view I have a GridView. As the number of items can be very high I'm trying to implement ISupportIncrementalLoading as new ItemsSource.
public class IncrementalCollection : ObservableCollection<Object>, ISupportIncrementalLoading
{
private int _addedItems = 0;
private const int _PAGESIZE = 20;
private IList<BookingDTO> _bookings;
public IncrementalCollection(Guid guid)
{
LoadBookings(guid);
}
private async Task LoadBookings(Guid guid)
{
var data = await IoC.Resolve<IMBAMService>().GetOrderedBookingsForAccountAsync(guid);
_bookings = data.Data;
}
public bool HasMoreItems
{
get { return (this.Count < _bookings.Count); }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
var coreDispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(async () =>
{
await coreDispatcher.RunAsync(CoreDispatcherPriority.High,
() =>
{
foreach (var item in _bookings.Skip(_addedItems).Take((int)count))
{
_addedItems++;
this.Add(item);
}
});
return new LoadMoreItemsResult() { Count = count };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
In the navigation function I create a new instance.
BookingList.ItemsSource = new IncrementalCollection(ViewModel.Account.Id);BookingList.ItemsSource = new IncrementalCollection(Guid);
My problem is now that LoadMoreItemsAsync is called so many times that the hole list will be displayed and not as expected after scrolling.
What do I need to change that it loads only the first 50 items and the rest when it's needed after scrolling?
I've tried to implement it like here:
http://blogs.msdn.com/b/devosaure/archive/2012/10/15/isupportincrementalloading-loading-a-subsets-of-data.aspx
It's possible that the GridView is trying to make use of infinite space instead of limiting its size to that of the visible screen. This would cause it to keep querying to load items to fill up the available space, even if those items are not visible. Read this page for more info on this, specifically where it mentions ScrollViewer: http://msdn.microsoft.com/en-us/library/windows/apps/hh994637.aspx.
In my first view model (renamed to MainViewModel) I have a list of ActionViewModels.
In my xaml i have a listbox which is bound to the list, in the listbox i have a template which binds to properties from the ActionViewModel.
So far so good and everything works.
When selecting one of the listitems i navigate to an ActionViewModel and pass the id with it.
The ActionViewModel retrieves information from a static list in memory from which the MainViewModel also retrieved the information to create the list of actionviewmodels.
So far still so good, i can edit the properties, all the bindings do work fine and i'm all happy.
By clicking the save button the information is gathered and stored in the static list.
When i hit the back button i go back to the list, but unfortunately the values showing there are still the same, is there some way to send a command to reload the items in the list? To pass a complete viewmodel as reference to a new ActionViewModel? Or some property which tells the parent 'this viewmodel in your list has been updated'?
I am sure the above text is a bit confusing, so here is some code to clarify it a bit (hopefully)
MainViewModel.cs
private List<ActionViewModel> _actionViewModels;
public List<ActionViewModel> ActionViewModels
{
get { return _actionViewModels; }
set { _actionViewModels = value; RaisePropertyChanged(() => ActionViewModels); }
}
private Cirrious.MvvmCross.ViewModels.MvxCommand<int> _navigateToAction;
public System.Windows.Input.ICommand NavigateToAction
{
get
{
_navigateToAction = _navigateToAction ?? new Cirrious.MvvmCross.ViewModels.MvxCommand<int>((action) => NavigateToTheDesiredAction(action));
return _navigateToAction;
}
}
private void NavigateToTheDesiredAction(int action)
{
ShowViewModel<ActionViewModel>(new { id = action });
}
// Get DTOs from server or from cache and fill the list of ActionViewModels
public async Task Load()
{
ActionService actionService = new ActionService();
List<ActionViewModel> actionViewModels = new List<ActionViewModel>();
MyActions = await actionService.GetMyActions();
foreach (ActionDTO action in MyActions)
{
ActionViewModel actionViewModel = new ActionViewModel();
await actionViewModel.Load(action.id);
actionViewModels.Add(actionViewModel);
}
ActionViewModels = actionViewModels;
}
ActionViewModel.cs
public int ID
{
get { return TheAction.id; }
set { TheAction.id = value; RaisePropertyChanged(() => ID); }
}
public string Title
{
get { return TheAction.Title; }
set { TheAction.Title = value; RaisePropertyChanged(() => Title); }
}
public async Task Load(int actionId)
{
ActionDTO TheAction = await actionService.GetAction(actionId);
this.ID = TheAction.id;
this.Title = TheAction.Title;
}
private Cirrious.MvvmCross.ViewModels.MvxCommand _save;
public System.Windows.Input.ICommand Save
{
get
{
_save = _save ?? new Cirrious.MvvmCross.ViewModels.MvxCommand(PreSaveModel);
return _save;
}
}
private void PreSaveModel()
{
SaveModel();
}
private async Task SaveModel()
{
ValidationDTO result = await actionService.SaveAction(TheAction);
}
ActionService.cs
public static List<ActionDTO> AllActions = new List<ActionDTO>();
public async Task<ActionDTO> GetAction(int actionId)
{
ActionDTO action = AllActions.FirstOrDefault(a => a.id == actionId);
if (action == null)
{
int tempActionId = await LoadAction(actionId);
if (tempActionId > 0)
return await GetAction(actionId);
else
return new ActionDTO() { Error = new ValidationDTO(false, "Failed to load the action with id " + actionId, ErrorCode.InvalidActionId) };
}
return action;
}
private async Task<int> LoadAction(int actionId)
{
ActionDTO action = await webservice.GetAction(actionId);
AllActions.Add(action);
return action.id;
}
public async Task<ValidationDTO> SaveAction(ActionDTO action)
{
List<ActionDTO> currentList = AllActions;
ActionDTO removeActionFromList = currentList.FirstOrDefault(a => a.id == action.id);
if (removeActionFromList != null)
currentList.Remove(removeActionFromList);
currentList.Add(action);
AllActions = currentList;
return await webservice.SaveAction(action);
}
There are 3 ways I can think of that would allow you to do this.
The ActionService could send out some sort of notification when data changes. One easy way to do this is to use the MvvmCross Messenger plugin. This is the way the CollectABull service works in CollectionService.cs in the N+1 days of mvvmcross videos (for more info watch N=13 in http://mvvmcross.wordpress.com)
This is the approach I generally use. It has low overhead, uses WeakReferences (so doesn't leak memory), it is easily extensible (any object can listen for changes), and it encourages loose coupling of the ViewModel and Model objects
You could implement some kind of Refresh API on the list ViewModel and could call this from appropriate View events (e.g. ViewDidAppear, OnNavigatedTo and OnResume).
I don't generally use this approach for Refreshing known data, but I have used it for enabling/disabling resource intensive objects - e.g. timers
For certain shape of model data (and especially how often it changes), then I can imagine scenarios where this approach might be more efficient than the messenger approach.
You could extend the use of INotifyPropertyChanged and INotifyCollectionChanged back into your model layer.
I've done this a few times and it's worked well for me.
If you do choose this approach, be careful to ensure that all Views do subscribe to change events using WeakReference subscriptions such as those used in MvvmCross binding - see WeakSubscription. If you didn't do this, then it could be possible for the Model to cause Views to persist in memory even after the UI itself has removed them.
I have the following code, which basically takes values from a database and populates a listview.
using (IDataReader reader = cmd.ExecuteReader())
{
lvwMyList.Items.Clear();
while (reader.Read())
{
ListViewItem lvi = lvwMyList.Items.Add(reader["Value1"].ToString());
lvi.SubItems.Add(reader["Value2"].ToString());
}
}
The problem that I have is that this is repeatedly executed at short intervals (every second) and results in the items in the listview continually disappearing and re-appearing. Is there some way to stop the listview from refreshing until it’s done with the updates? Something like below:
using (IDataReader reader = cmd.ExecuteReader())
{
lvwMyList.Items.Freeze(); // Stop the listview updating
lvwMyList.Items.Clear();
while (reader.Read())
{
ListViewItem lvi = lvwMyList.Items.Add(reader["Value1"].ToString());
lvi.SubItems.Add(reader["Value2"].ToString());
}
lvwMyList.Items.UnFreeze(); // Refresh the listview
}
Like this:
try
{
lvwMyList.BeginUpdate();
//bla bla bla
}
finally
{
lvwMyList.EndUpdate();
}
Make sure that you invoke lvwMyList.Items.Clear() after BeginUpdate if you want to clear the list before filling it.
This is my first time posting on StackOverflow, so pardon the messy code formatting below.
To prevent locking up the form while updating the ListView, you can use the method below that I've written to solve this issue.
Note: This method should not be used if you expect to populate the ListView with more than about 20,000 items. If you need to add more than 20k items to the ListView, consider running the ListView in virtual mode.
public static async void PopulateListView<T>(ListView listView, Func<T, ListViewItem> func,
IEnumerable<T> objects, IProgress<int> progress) where T : class, new()
{
if (listView != null && listView.IsHandleCreated)
{
var conQue = new ConcurrentQueue<ListViewItem>();
// Clear the list view and refresh it
if (listView.InvokeRequired)
{
listView.BeginInvoke(new MethodInvoker(() =>
{
listView.BeginUpdate();
listView.Items.Clear();
listView.Refresh();
listView.EndUpdate();
}));
}
else
{
listView.BeginUpdate();
listView.Items.Clear();
listView.Refresh();
listView.EndUpdate();
}
// Loop over the objects and call the function to generate the list view items
if (objects != null)
{
int objTotalCount = objects.Count();
foreach (T obj in objects)
{
await Task.Run(() =>
{
ListViewItem item = func.Invoke(obj);
if (item != null)
conQue.Enqueue(item);
if (progress != null)
{
double dProgress = ((double)conQue.Count / objTotalCount) * 100.0;
if(dProgress > 0)
progress.Report(dProgress > int.MaxValue ? int.MaxValue : (int)dProgress);
}
});
}
// Perform a mass-add of all the list view items we created
if (listView.InvokeRequired)
{
listView.BeginInvoke(new MethodInvoker(() =>
{
listView.BeginUpdate();
listView.Items.AddRange(conQue.ToArray());
listView.Sort();
listView.EndUpdate();
}));
}
else
{
listView.BeginUpdate();
listView.Items.AddRange(conQue.ToArray());
listView.Sort();
listView.EndUpdate();
}
}
}
if (progress != null)
progress.Report(100);
}
You don't have to provide an IProgress object, just use null and the method will work just as well.
Below is an example usage of the method.
First, define a class that contains the data for the ListViewItem.
public class TestListViewItemClass
{
public int TestInt { get; set; }
public string TestString { get; set; }
public DateTime TestDateTime { get; set; }
public TimeSpan TestTimeSpan { get; set; }
public decimal TestDecimal { get; set; }
}
Then, create a method that returns your data items. This method could query a database, call a web service API, or whatever, as long as it returns an IEnumerable of your class type.
public IEnumerable<TestListViewItemClass> GetItems()
{
for (int x = 0; x < 15000; x++)
{
yield return new TestListViewItemClass()
{
TestDateTime = DateTime.Now,
TestTimeSpan = TimeSpan.FromDays(x),
TestInt = new Random(DateTime.Now.Millisecond).Next(),
TestDecimal = (decimal)x + new Random(DateTime.Now.Millisecond).Next(),
TestString = "Test string " + x,
};
}
}
Finally, on the form where your ListView resides, you can populate the ListView. For demonstration purposes, I'm using the form's Load event to populate the ListView. More than likely, you'll want to do this elsewhere on the form.
I've included the function that generates a ListViewItem from an instance of my class, TestListViewItemClass. In a production scenario, you will likely want to define the function elsewhere.
private async void TestListViewForm_Load(object sender, EventArgs e)
{
var function = new Func<TestListViewItemClass, ListViewItem>((TestListViewItemClass x) =>
{
var item = new ListViewItem();
if (x != null)
{
item.Text = x.TestString;
item.SubItems.Add(x.TestDecimal.ToString("F4"));
item.SubItems.Add(x.TestDateTime.ToString("G"));
item.SubItems.Add(x.TestTimeSpan.ToString());
item.SubItems.Add(x.TestInt.ToString());
item.Tag = x;
return item;
}
return null;
});
PopulateListView<TestListViewItemClass>(this.listView1, function, GetItems(), progress);
}
In the above example, I created an IProgress object in the form's constructor like this:
progress = new Progress<int>(value =>
{
toolStripProgressBar1.Visible = true;
if (value >= 100)
{
toolStripProgressBar1.Visible = false;
toolStripProgressBar1.Value = 0;
}
else if (value > 0)
{
toolStripProgressBar1.Value = value;
}
});
I've used this method of populating a ListView many times in projects where we were populating up to 12,000 items in the ListView, and it is extremely fast. The main thing is you need to have your object fully built from the database before you even touch the ListView for updates.
Hopefully this is helpful.
I've included below an async version of the method, which calls the main method shown at the top of this post.
public static Task PopulateListViewAsync<T>(ListView listView, Func<T, ListViewItem> func,
IEnumerable<T> objects, IProgress<int> progress) where T : class, new()
{
return Task.Run(() => PopulateListView<T>(listView, func, objects, progress));
}
You can also try setting the visible or enabled properties to false during the update and see if you like those results any better.
Of course, reset the values to true when the update is done.
Another approach is to create a panel to overlay the listbox. Set it's left, right, height, and width properties the same as your listbox and set it's visible property to true during the update, false after you're done.
hi what is the easiest way to implement asynch operations on WPF and MVVM, lets say if user if user hits enter when on a field i want to launch a command and then return back while a thread will do some search operations and then come back and update the properties so notification can update the bindings.
thanks!
Rob Eisenberg showed a really clean implementation of running async operations in MVVM during his MIX10 talk. He has posted the source code on his blog.
The basic idea is that you implement the command as returning an IEnumerable and use the yield keyword to return the results. Here is a snippet of code from his talk, which does a search as a background task:
public IEnumerable<IResult> ExecuteSearch()
{
var search = new SearchGames
{
SearchText = SearchText
}.AsResult();
yield return Show.Busy();
yield return search;
var resultCount = search.Response.Count();
if (resultCount == 0)
SearchResults = _noResults.WithTitle(SearchText);
else if (resultCount == 1 && search.Response.First().Title == SearchText)
{
var getGame = new GetGame
{
Id = search.Response.First().Id
}.AsResult();
yield return getGame;
yield return Show.Screen<ExploreGameViewModel>()
.Configured(x => x.WithGame(getGame.Response));
}
else SearchResults = _results.With(search.Response);
yield return Show.NotBusy();
}
Hope that helps.
How about a BackgroundWorker instance to call your command on the VM ?
Update:
Scratch the above suggestion.. There's an online video on MVVM by Jason Dolinger.. I recommend you take a look at that. It's a cleaner way where the view is thin/ does not hold any threading code.
To summarize:
the VM ctor caches the Dispatcher.CurrentDispatcher object (main thread).
when updating the backing store (results), use
_dispatcher.BeginInvoke( () => _results.AddRange( entries) ) so that the UI is updated correctly.
In Shawn Wildermuth's MSDN Article he did something like this:
check out the article here:
http://msdn.microsoft.com/en-us/magazine/dd458800.aspx
and his more recent blog post here:
http://wildermuth.com/2009/12/15/Architecting_Silverlight_4_with_RIA_Services_MEF_and_MVVM_-_Part_1
public interface IGameCatalog
{
void GetGames();
void GetGamesByGenre(string genre);
void SaveChanges();
event EventHandler<GameLoadingEventArgs> GameLoadingComplete;
event EventHandler<GameCatalogErrorEventArgs> GameLoadingError;
event EventHandler GameSavingComplete;
event EventHandler<GameCatalogErrorEventArgs> GameSavingError;
}
with an implementation like this:
public class GameCatalog : IGameCatalog
{
Uri theServiceRoot;
GamesEntities theEntities;
const int MAX_RESULTS = 50;
public GameCatalog() : this(new Uri("/Games.svc", UriKind.Relative))
{
}
public GameCatalog(Uri serviceRoot)
{
theServiceRoot = serviceRoot;
}
public event EventHandler<GameLoadingEventArgs> GameLoadingComplete;
public event EventHandler<GameCatalogErrorEventArgs> GameLoadingError;
public event EventHandler GameSavingComplete;
public event EventHandler<GameCatalogErrorEventArgs> GameSavingError;
public void GetGames()
{
// Get all the games ordered by release date
var qry = (from g in Entities.Games
orderby g.ReleaseDate descending
select g).Take(MAX_RESULTS) as DataServiceQuery<Game>;
ExecuteGameQuery(qry);
}
public void GetGamesByGenre(string genre)
{
// Get all the games ordered by release date
var qry = (from g in Entities.Games
where g.Genre.ToLower() == genre.ToLower()
orderby g.ReleaseDate
select g).Take(MAX_RESULTS) as DataServiceQuery<Game>;
ExecuteGameQuery(qry);
}
public void SaveChanges()
{
// Save Not Yet Implemented
throw new NotImplementedException();
}
// Call the query asynchronously and add the results to the collection
void ExecuteGameQuery(DataServiceQuery<Game> qry)
{
// Execute the query
qry.BeginExecute(new AsyncCallback(a =>
{
try
{
IEnumerable<Game> results = qry.EndExecute(a);
if (GameLoadingComplete != null)
{
GameLoadingComplete(this, new GameLoadingEventArgs(results));
}
}
catch (Exception ex)
{
if (GameLoadingError != null)
{
GameLoadingError(this, new GameCatalogErrorEventArgs(ex));
}
}
}), null);
}
GamesEntities Entities
{
get
{
if (theEntities == null)
{
theEntities = new GamesEntities(theServiceRoot);
}
return theEntities;
}
}
}