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.
Related
I have an ObservavebleColection bound to a listView. Basically, this collection must keep up with every change in the server and receives updates in string format.
My code parses the string and adds elements to the collection, but I'm having trouble finding a way to remove elements. How can I update the collection when an element is removed or changed on the server?
Here's my code:
public static ObservableCollection<TransactionDetails> offerList = new ObservableCollection<TransactionDetails>();
public async static Task<ObservableCollection<TransactionDetails>> getOfferList()
{
// Start getting Offers
string Offer = await BedpAPI_V1.getOfferList();
string[] splitedResponse = Offer.Split(new[] { "####" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string response in splitedResponse) {
string[] splitedMessage = response.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
offer.TransactionID = Convert.ToInt32(splitedMessage[0]);
offer.Seller = splitedMessage[1];
offer.Cost = Convert.ToDouble(splitedMessage[2]);
offer.Duration = Convert.ToInt16(splitedMessage[3]);
offer.Delay = Convert.ToInt16(splitedMessage[4]);
offer.Capacity = Convert.ToDouble(splitedMessage[5]);
offer.Availability = Convert.ToDouble(splitedMessage[6]);
if (currentOffer <= offer.TransactionID)
{
offerList.Add(new TransactionDetails() { TransactionID = offer.TransactionID, Seller = offer.Seller, Cost = offer.Cost, Duration = offer.Duration, Delay = offer.Delay, Capacity = offer.Capacity, Availability = offer.Availability });
currentOffer++;
}
}
return offerList;
}
If your ListView bind with a ObservavebleColection<>, when you modify an element, you may fire a OnCollectionChanged event yourself.
Assume you modify an element using []:
public class MyCollection : ObservableCollection<MyClass>
{
public new MyClass this[int index]
{
get { return base[index]; }
set
{
base[index] = value;
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
UPDATE:
Firstly, you may want to bind your collection like:
myCollection = MyFunction();
MyListView.ItemsSource = myCollection;
If you change an item in your collection like:
myCollection[index] = someNewItem;
Then you could update your listview like:
MyListView.Refresh();
But it is recommended to use some dynamic binding.
I solved the problem by clearing the collection on every call before parsing splitedResponse - as Clear() was throwing an unheld exception, had to handle it inside a throw catch block:
try
{
offerList.Clear();
}
catch (Exception ex) {
offerList.Clear();
Console.WriteLine(ex.Message);
}
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());
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.
I'm currently looking for a way to monitor a list for an item to be added (although it may already be in the list) with a certain ID. The below example demonstrates what I want to do, however I was hoping there would be a neater approach to this.
var list = new List<string>();
var task = new Task<bool>(() =>
{
for (int i = 0; i < 10; i++)
{
if (list.Contains("Woft"))
return true;
Thread.Sleep(1000);
}
return false;
});
I would appreciate any suggestions, and can elaborate if required.
EDIT: I'm not sure how I would use a CollectionChanged handler for this, still not sure I'm describing the problem well enough, here is a more complete example.
class Program
{
private static List<string> receivedList = new List<string>();
static void Main(string[] args)
{
var thing = new item() { val = 1234, text = "Test" };
var message = new message() { id = "12", item = thing };
//This would be a message send
//in the receiver we would add the message id to the received list.
var task2 = new Task(() =>
{
Thread.Sleep(2000);
receivedList.Add(message.id);
});
task2.Start();
var result = EnsureReceived(thing, message.id);
if (result == null)
{
Console.WriteLine("Message not received!");
}
else
{
Console.WriteLine(result.text + " " + result.val);
}
Console.ReadLine();
}
//This checks if the message has been received
private static item EnsureReceived(item thing, string id)
{
var task = new Task<bool>(() =>
{
for (int i = 0; i < 10; i++)
{
if (receivedList.Contains(id))
return true;
Thread.Sleep(1000);
}
return false;
});
task.Start();
var result = task.Result;
return result ? thing : null;
}
}
class message
{
public string id { get; set; }
public item item { get; set; }
}
class item
{
public string text { get; set; }
public int val { get; set; }
}
It sounds like what you want to do is use an ObservableCollection and subscribe to the CollectionChanged event. That way, you'll have an event fire whenever the collection is modified. At that point, you can check if the collection has the element you're expecting.
Even better, the CollectionChanged event handler will receive NotifyCollectionChangedEventArgs, which contains a NewItems property.
See MSDN for more information.
You can implement own type, what intercept all operations to add items and exposes ReadOnlyList. Then create an event ItemAdded with added item value (you will have to rise up multiple events when adding a collection obviously). Subscribe to it, check, do whatever.
I have a WinForms application which uses a list box to display list of items. My application hangs whent the number of items in the listbox exceeds some 150 items. Is this the property of ListBox control that it can hold only so many items? If so, I would request you to provide a solution to this problem.
Thanks,
Rakesh.
It all depends on what you are binding, if you are binding simple key value pairs you can instantly bind 10k easy. You might want to try adding the items in a loop instead of binding to see if there is a certain item it hangs on.
for (int i = 0; i < 10000; i++)
{
listBox1.Items.Add("item:" + i.ToString());
}
You can back your listbox by a larger dataset and use a paging mechanism or you can add an event listener for SizeChanged and disable adding when it reaches your max.
First tip, always..
SuspendLayout();
// fill your lists
ResumeLayout();
Second tip, use AddRange when possible.
Third, and it may be overkill, create your own ListBox...
public class LimitedListBox : ListBox
{
private int _maxItems = 100;
public LimitedListBox()
{
SetItems(new LimitedObjectCollection(this, _maxItems));
}
public int MaxItems
{
get { return _maxItems; }
set { _maxItems = value; }
}
/// <summary>
/// This is the only 'bug' - no design time support for Items unless
/// you create an editor.
/// </summary>
public new LimitedObjectCollection Items
{
get
{
if (base.Items == null)
{
SetItems(new LimitedObjectCollection(this, _maxItems));
}
return (LimitedObjectCollection) base.Items;
}
}
private void SetItems(ObjectCollection items)
{
FieldInfo info = typeof (ListBox).GetField("itemsCollection",
BindingFlags.NonPublic | BindingFlags.Instance |
BindingFlags.GetField);
info.SetValue(this, items);
}
#region Nested type: LimitedObjectCollection
public class LimitedObjectCollection : ObjectCollection
{
private int _maxItems;
public LimitedObjectCollection(ListBox owner, int maxItems)
: base(owner)
{
_maxItems = maxItems;
}
public LimitedObjectCollection(ListBox owner, ObjectCollection value, int maxItems)
: base(owner)
{
_maxItems = maxItems;
AddRange(value);
}
public LimitedObjectCollection(ListBox owner, object[] value, int maxItems)
: base(owner)
{
_maxItems = maxItems;
AddRange(value);
}
public int MaxItems
{
get { return _maxItems; }
set { _maxItems = value; }
}
public new int Add(object item)
{
if (base.Count >= _maxItems)
{
return -1;
}
return base.Add(item);
}
public new void AddRange(object[] items)
{
int allowed = _maxItems - Count;
if (allowed < 1)
{
return;
}
int length = allowed <= items.Length ? allowed : items.Length;
var toAdd = new object[length];
Array.Copy(items, 0, toAdd, 0, length);
base.AddRange(toAdd);
}
public new void AddRange(ObjectCollection value)
{
var items = new object[value.Count];
value.CopyTo(items, 0);
base.AddRange(items);
}
}
#endregion
}
Why don't you just parse your database something like this.
int Total = yourarray.GetLength(0);
Then all you have to do is this in your new array.
double [] new = double[Total];
array.copy(Total,new);
Now you have an array thats dynamic. Anytime your database grows it automatically populates
the new array.
Or if you can do a select count statement on your database you can get the total number or rows and then pipe that to a string. Then you use the string to make control the array. Hope this helps
I just got an "out of memory" error message when filling a list box. The problem was not anything to do with too many items. There was a bug in my code and the items in the list box were returning null in the ToString() method. So the Dot Net error message was wrong and confusing.