Create a ListView with LoadMoreItemsAsync on end of scroll - c#

I have a ListView in my Windows Phone 8.1 application and I can have something like 1000 or more results, so I need to implement a Load More feature each time the scroll hits bottom, or some other logic and natural way of triggering the adding of more items to the List.
I found that the ListView has support for an ISupportIncrementalLoading, and found this implementation: https://marcominerva.wordpress.com/2013/05/22/implementing-the-isupportincrementalloading-interface-in-a-window-store-app/
This was the better solution I found, since it does not specify a type, i.e., it's generic.
My problem with this solution is that when the ListView is Loaded, the LoadMoreItemsAsync runs all the times needed until it got all the results, meaning that the Load More is not triggered by the user. I'm not sure what make the LoadMoreItemsAsync trigger, but something is not right, because it assumes that happens when I open the page and loads all items on the spot, without me doing anything, or any scrolling. Here's the implementation:
IncrementalLoadingCollection.cs
public interface IIncrementalSource<T> {
Task<IEnumerable<T>> GetPagedItems(int pageIndex, int pageSize);
void SetType(int type);
}
public class IncrementalLoadingCollection<T, I> : ObservableCollection<I>, ISupportIncrementalLoading where T : IIncrementalSource<I>, new() {
private T source;
private int itemsPerPage;
private bool hasMoreItems;
private int currentPage;
public IncrementalLoadingCollection(int type, int itemsPerPage = 10) {
this.source = new T();
this.source.SetType(type);
this.itemsPerPage = itemsPerPage;
this.hasMoreItems = true;
}
public bool HasMoreItems {
get { return hasMoreItems; }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) {
var dispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(
async () => {
uint resultCount = 0;
var result = await source.GetPagedItems(currentPage++, itemsPerPage);
if(result == null || result.Count() == 0) {
hasMoreItems = false;
}
else {
resultCount = (uint)result.Count();
await dispatcher.RunAsync(
CoreDispatcherPriority.Normal,
() => {
foreach(I item in result)
this.Add(item);
});
}
return new LoadMoreItemsResult() { Count = resultCount };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
Here's the PersonModelSource.cs
public class DatabaseNotificationModelSource : IIncrementalSource<DatabaseNotificationModel> {
private ObservableCollection<DatabaseNotificationModel> notifications;
private int _type = "";
public DatabaseNotificationModelSource() {
//
}
public void SetType(int type) {
_type = type;
}
public async Task<IEnumerable<DatabaseNotificationModel>> GetPagedItems(int pageIndex, int pageSize) {
if(notifications == null) {
notifications = new ObservableCollection<DatabaseNotificationModel>();
notifications = await DatabaseService.GetNotifications(_type);
}
return await Task.Run<IEnumerable<DatabaseNotificationModel>>(() => {
var result = (from p in notifications select p).Skip(pageIndex * pageSize).Take(pageSize);
return result;
});
}
}
I changed it a bit, because the call to my Database is Asynchronous and it was the only way I found to make sure I could wait for the query before filling the collection.
And in my DatabaseNotificationViewModel.cs
IncrementalNotificationsList = new IncrementalLoadingCollection<DatabaseNotificationModelSource, DatabaseNotificationModel>(type);
Everything works fine, apart from the not so normal "Load More". What's wrong in my code?

I created a very simplified example of this issue here, and raised this issue on the MSDN forums here. Honestly, I don't know why this weird behavior is happening.
What I observed
The ListView will call LoadMoreItemsAsync first with a count of 1. I assume this is to determine the size of a single item so that it can work out the number of items to request for the next call.
If the ListView is behaving nicely, the second call to LoadMoreItemsAsync should happen immediately after the first call, but with the correct number of items (count > 1), and then no more calls to LoadMoreItemsAsync will occur unless you scroll down. In your example, however, it may incorrectly call LoadMoreItemsAsync with a count of 1 again.
In the worst case, which actually occurs quite frequently in your example, is that the ListView will continue to call LoadMoreItemsAsync with a count of 1 over and over, in order, until HasMoreItems becomes false, in which case it has loaded all of the items one at a time. When this happens, there is a noticeable UI delay while the ListView loads the items. The UI thread isn't blocked, though. The ListView is just hogging the UI thread with sequential calls to LoadMoreItemsAsync.
The ListView won't always exhaust all of the items though. Sometimes it will load 100, or 200, or 500 items. In each case, the pattern is: many calls of LoadMoreItemsAsync(1) followed by a single call to LoadMoreItemsAsync(> 1) if not all of the items have been loaded by the prior calls.
It only seems to occur on page load.
The issue is persistent on Windows Phone 8.1 as well as Windows 8.1.
What causes the problem
The issue seems to be very short lived awaited tasks in the LoadMoreItemsAsync method before you've added the items to the list (awaiting tasks after you've added the items to the list is fine).
The issue doesn't occur if you remove all awaits inside LoadMoreItemsAsync, thus forcing it to execute synchronously. Specifically, if you remove the dispatcher.RunAsync wrapper and await source.GetPagedItems (just mock the items instead), then the ListView will behave nicely.
Having removed all awaits, the issue will reappear even if all you add is a seemingly harmless await Task.Run(() => {}). How bizarre!
How to fix the problem
If most of the time spent in a LoadMoreItemsAsync call is waiting for a HTTP request for the next page of items, as I expect most apps are, then the issue won't occur. So, we can extend the time spent in the method by awaiting a Task.Delay(10), like this maybe:
await Task.WhenAll(Task.Delay(10), dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (I item in result)
this.Add(item);
}).AsTask());
I've merely provided a (hacky) workaround for your example, but not an explanation why. If anyone knows why this is happening, please let me know.

This is not the only thing that can cause this issue. If your ListView is inside a ScrollViewer, it will continue loading all of the items and ALSO will not virtualize properly, negatively impacting performance. The solution is to give your ListView a specific height.

Related

How to build a custom loop activity in Elsa workflows

I'm trying to make a custom activity that will eventually do a complicated database query or API call to get a bunch of records and loop over them. I'm sure it could be done with the built in flow control activities, but I want to make this usable by non-programmers who don't know or care what a foreach loop is, so putting a lot of functionality into one box is good.
My first attempt was to inherit from ForEach and do some initialization before letting OnExecute do its thing, but the result feels somewhat hacky.
public class FancyForEach : ForEach
{
private bool? Initialized
{
get
{
return GetState<bool?>("Initialized");
}
set
{
SetState(value, "Initialized");
}
}
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
{
if (Initialized != true)
{
Items = GetThingsFromDatabase();
Initialized = true;
}
return base.OnExecute(context);
}
protected List<DatabaseThings> GetThingsFromDatabase()
{
// Fancy stuff here, including paging eventually.
}
}
It seems like it would be a little cleaner to instantiate a ForEach somewhere within the activity rather than inherit from it, but I can't puzzle out a way to make that work. I imagine a decent solution would be to trigger another workflow for each record, but I'd rather not do that, again to make this easy to digest for people who aren't programmers.
Can anyone offer a suggestion on the best way to make this work? This is my first project using Elsa, so maybe I'm approaching it from an entirely wrong direction!
If I understand correctly, your activity is responsible for loading in the data and looping over it, while the user of the activity should be able to specify what happens in each iteration.
If so, then you might implement something like this:
[Activity(
Category = "Control Flow",
Description = "Iterate over a collection.",
Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done }
)]
public class FancyForEach : Activity
{
private bool? Initialized
{
get => GetState<bool?>();
set => SetState(value);
}
private IList<DatabaseThings>? Items
{
get => GetState<IList<DatabaseThings>?>();
set => SetState(value);
}
private int? CurrentIndex
{
get => GetState<int?>();
set => SetState(value);
}
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
{
if (Initialized != true)
{
Items = GetThingsFromDatabase();
Initialized = true;
}
var collection = Items.ToList();
var currentIndex = CurrentIndex ?? 0;
if (currentIndex < collection.Count)
{
var currentValue = collection[currentIndex];
var scope = context.CreateScope();
scope.Variables.Set("CurrentIndex", currentIndex);
scope.Variables.Set("CurrentValue", currentValue);
CurrentIndex = currentIndex + 1;
context.JournalData.Add("Current Index", currentIndex);
// For each iteration, return an outcome to which the user can connect activities to.
return Outcome(OutcomeNames.Iterate, currentValue);
}
CurrentIndex = null;
return Done();
}
protected List<DatabaseThings> GetThingsFromDatabase()
{
// Fancy stuff here, including paging eventually.
}
}
This example loads the database items into memory once and then stores this list in workflow state (via Items) - which may or may not be desirable, since this has the potential of increasing the size of the workflow instance significantly depending on the size of each record and number of records.
A more scalable approach would be to load just one item per iteration, keeping track of the current index that was loaded, incrementing it (i.e. pagination with a page size of 1).

Why don't delays work when I'm attempting to flash an error message?

Problem: I'm working on a calculator as my first MVVM application and have come across an interesting problem that I would like to understand better. My noob problem is that I'm trying to flash an error message for an invalid input--in this case I don't want the user to use the negate operator in an invalid location. In order to flash the message across the screen, I'm saving the display in another variable, setting the display to say "Invalid Operation", then I'd like to delay for half a second and reset the display to what it was before (from the temp variable). My problem is that the display variable gets set but the actual display doesn't update to show the error message, no matter how long the delay is.
I've tried both blocking (Thread.Sleep) and non-blocking delays (Task.Delay) within the function, writing separate functions to set and reset the display, and delaying within the Negate function instead, but none of these attempts allow the display to update. The display works as expected when adding and deleting characters in other parts of the code, so I don't think there's an issue with that.
Is this some sort of piping issue (the delay function actually starts before it can call the Display property) or something else entirely? I've checked other posts on here and those solutions don't seem to solve my issue. I'd love feedback on why this doesn't work as I'd expect it to as well as more efficient/effective ways to code this. Here are the relevant code blocks:
public void Negate()
{
if (Display.Length > 0)
{
if (Display[Display.Length - 1].Equals('-'))
{
Display = Display.Substring(0, Display.Length - 1);
}
else if (Display[Display.Length - 1].Equals(' ') || Display[Display.Length - 1].Equals('(') ||
Display[Display.Length - 1].Equals('E') || Display[Display.Length - 1].Equals('^'))
{
Display += '-';
}
else
{
InvalidOperation();
}
}
else
{
Display = "-";
}
}
public void InvalidOperation()
{
tempDisplay = Display;
Display = "Invalid Operation";
Thread.Sleep(500);
Display = tempDisplay;
}
public string Display
{
get
{
return _display;
}
set
{
_display = value;
OnPropertyChanged();
}
}
UI will be updated only after method InvalidOperation execution is complete, so because in last line of the method you set value back to original - there are no updates in UI.
Asynchronous approach should work, because await operator will "pause" InvalidOperation method and return execution to the message loop which will update UI controls.
public async Task InvalidOperation()
{
tempDisplay = Display;
Display = "Invalid Operation";
await Task.Delay(2000);
Display = tempDisplay;
}

c# async property call

I have a ListBox, where my SelectedValue is set to a class DefaultStrediska which has IEditableObject implemented. What I am doing every time user selects a new item under this particular ListBox (SelectedValue changes), I first check if any change has been made, and if yes; then I ask user if he wants to save temporary changes (otherwise I discard them and return back to the original values).
I am using Mahapps.Metro async method for displaying a message (rather than using traditional System.Windows.MessageBox) and getting the result. The problem is, that this is an asynchronous method that I have to call from my property. Here it is how I do it:
private async Task<bool> GetResult()
{
if (await Window.ShowMessageAsync("Zmena v údajoch", "Pozor! Nastala zmena v údajoch. Prajete si ich dočasne uložiť zmeny?", MessageDialogStyle.AffirmativeAndNegative) == MessageDialogResult.Affirmative)
_SelectedStredisko.EndEdit();
return true;
}
private DefaultStrediska _SelectedStredisko;
public DefaultStrediska SelectedStredisko
{
get { return _SelectedStredisko; }
set
{
//check if any changes have been made
if (value != null && _SelectedStredisko != null)
{
if (_SelectedStredisko.WasChangeMade())
{
var x = GetResult().Result;
}
}
_SelectedStredisko = value;
//create backup of current data
_SelectedStredisko.BeginEdit();
OnPropertyChanged("SelectedStredisko");
}
}
However the problem is, that now my var x = GetResult().Result completely blocks the UI thread and I neither get the messagebox, nor can do anything else. If I remove .Result, then the code first goes to _SelectedStredisko = value and only afterwards calls the GetResult() method, which is unacceptable.
What am I doing wrong in here?
There are a number of ways to avoid the deadlock, I go through a few of them here. I think in your case it might be best to use ConfigureAwait(false) when you are showing the message, but I haven't used that API myself.
await Window.ShowMessageAsync(..).ConfigureAwait(false)

How can i load items back to the listView control from text file and also add checkBoxes near each item faster?

In this method i'm reading a text file from my hard disk and add the items to the listView.
I also changed in the form1 designer on the listView propeties the property CheckBoxes to true.
Now when i'm running my program it's taking like 10-15 seconds to load it up all.
The form1 constructor:
LoadtoListView();
And the method LoadtoListView:
private void LoadtoListView()
{
int countit = 0;
using (StreamReader sr = new StreamReader(#"c:\listviewfile\databaseEN.txt"))
{
while (-1 < sr.Peek())
{
try
{
string name = sr.ReadLine();
string email = sr.ReadLine();
var lvi = new ListViewItem(name.Substring(name.IndexOf(":") + 1));
lvi.SubItems.Add(email.Substring(email.IndexOf(":") + 1));
listView1.Items.Add(lvi);
countit++;
}
catch (Exception) { }
}
sr.Close();
numberofforums = countit;
}
}
There are 547 items to load and 547 checkBoxes.
I tested now if i change in the designer the listView property of the CheckBoxes to false again it will load fast about 1-2 seconds.
But once i'm turning this property of the CheckBoxes to true it's tkaing more then 10-15 seconds to load.
I guess the problem is that it's taking time to draw all the CheckBoxes.
Is there any way to make it all faster ?
There exist a couple of ways you could make that faster. Actually you could make it blazingly fast, checkboxes or no checkboxes.
Your code requires a few tweaks here and there and you must think about reusability, concern separation and parameterisation.
For instance, the method name LoadToListView is already doing too much work. It loads stuff, and it also populates the listview.
There are 3 ingredients that can get you to Nirvana.
First of all
consider creating an up front materialised list of ListViewItem instances, more particularly, create a primitive array, such as this (by the way, I will also sprinkle some other good practices along the way, even though it is not the lacking of those practices which causes your delays):
public ListViewItem[] LoadItems(string filePath) {
// not hardcoding the filePath is a good idea
List<ListViewItem> accumulator = new List<ListViewItem>();
int countit = 0;
using (StreamReader sr = new StreamReader(filePath)) {
while (-1 < sr.Peek()) {
try
string name = sr.ReadLine();
string email = sr.ReadLine();
var lvi = new ListViewItem(name.Substring(name.IndexOf(":") + 1));
lvi.SubItems.Add(email.Substring(email.IndexOf(":") + 1));
// instead of adding this item to the list
// --> no more this:: listView1.Items.Add(lvi);
// just "accumulate" it
accumulator.Add(lvi);
countit++;
}
catch (Exception) { }
}
// no need to manually close the reader
// sr.Close();
// the using clause will close it for you
numberofforums = countit;
}
return accumulator.ToArray();
}
Okay.. So we've created an array of ListViewItem.
"So what?" you might think.
Well, for each and every Add invocation on your ListView, the ListView will try to react graphically (even if the GUI thread is occupied it will still try). What this reaction is is not for this answer to be concerned. What you must understand is that instead of Add you could call AddRange which takes a primitive array of ListViewItem as its parameter. That will cause just one graphical reaction for all the set of ListViewItem instances which means it will speed up your app a lot.
So here's ingredient number 2
Make another method which calls LoadItems and then calls AddRange on the ListView:
public void SomeOtherPlace() {
string filePath = #"....";
ListViewItem[] items = LoadItems(filePath);
this.listView1.Items.AddRange( items );
}
This will have already given your app the extra speed you were looking for, but even if the next step will not make your app elegant, it will surely help.
And ingredient number 3 :: Asynchrony
It would be grand if your UI didn't freeze while the LoadItems method was being called.
This "non freezing" ability can be achieved in many ways, but the most modern and coolest way is to use Task<T> and the async and await operators introduced in .NET 4.5 and C# 5.0.
If you don't have a clue about what these things are then, just enjoy the first two ingredients but don't hesitate to learn about the entities I've mentioned.
Basically what you need to do is:
make sure you can't possibly call SomeOtherPlace() twice, since what we're about to do is to make this a possibility. So if you have a button's event handler, for instance, which is calling SomeOtherPlace then we should disable that button and reenable it once we're done
we will make the SomeOtherPlace() method be an async method, which allows it to await tasks
we will run the LoadItems code on a separate thread all nicely wrapped in a Task<ListViewItem[]> and await it on the GUI thread
Let's go. The first change is this:
public void SomeOtherPlace() {
becomes
public async void SomeOtherPlace() {
Secondly, we disable the button I talked about:
public async void SomeOtherPlace() {
this.button1.Enabled = false;
...
this.button1.Enabled = true;
}
Third, we turn this line:
ListViewItem[] items = LoadItems(filePath);
into this:
ListViewItem[] items = await Task.Factory.StartNew(() => LoadItems(filePath));
Now your method should look something like this:
public async void SomeOtherPlace() {
string filePath = #"....";
ListViewItem[] items = await Task.Factory.StartNew(() => LoadItems(filePath));
this.listView1.Items.AddRange( items );
}
Hope I didn't forget anything.
Good luck and don't settle for not understanding how things work under the hood!
Use listView1.Items.AddRange instead of adding one ListViewItem at a time. This should improve your load time.

Problem While receiving events from a thread

I'm Doing a project on FileTransfer in which i have a listview , i will get events from one of my class file for updating the percentage of the file sent so far,after receiving it i will place the percentage in my listview ,while doing that the listview got
a flickering effect how to avoid it.i used application.doevents() but it doesnt works. i have seen in torrents while updating the percent the list doesnt get flickered
how to achieve this .
void Sender_Progress(int CurrentValue, string Ip) // here im receiving Events
{
try
{
//if (CurrentValue == 1)
// UpdateTimer.Enabled = true;
//list_send.Items[CurrentValue].SubItems[4].Text = Ip.ToString();
//Application.DoEvents();
obj = new object[] {CurrentValue, Ip };
list_send.Invoke(new UpdateList(UpList), obj);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void UpList(int Val, string ind) // here im updating the listview
{
Application.DoEvents();
int index = 0;
index = Convert.ToInt32(ind);
index = index - 1;
list_send.Items[index].SubItems[4].Text = Val.ToString();
if (Val == 100)
{
list_send.Items[index].SubItems[2].Text = "Completed.";
//UpdateTimer.Enabled = false;
}
//Application.DoEvents();
}
Firstly, you don't need the DoEvents, since you are already correctly working on two threads. Remove that. After that, I expect the problem is simply doing too much too quickly. Is it possible to batch updates, and only send an update, say, every 20? 50? times? It isn't clear what the control is, but many have multiple-update modes; for example with ListView:
theList.BeginUpdate();
try {
// make multiple updates here...
} finally {
theList.EndUpdate();
}
I would then see about passing over a list of updates, say, every 20 times (unless each takes a considerable time) [note it must be a different list per Invoke, and you need to remember to send any remaining items at the end, too].
Use worker thread - it's available from the toolbox and has two events that are invoked in the main (UI) thread.
The Progress event can be used to signal the listbox that it need to refresh or that the task was completed.
i overcome the flickering effect succesfully,im getting events frequently ,i will get an integer everytime, i will store it in a variable and compare it with next variable received by the event if it matches i wont invoke the listview,otherwise i will invoke it.now the flickering goes away. thanks all.

Categories

Resources