Refreshing a UI to reflect items added to a list - c#

While my UI is displayed, data is being passed in the back end and added to a List<string> that I would in turn like to display on my UI.
I've seen several examples using background workers however I don't have access to the actual component due to how I layout my User Controls and programmatically build them.
Question: How can I run this method repeatedly behind my UI without locking up my UI in a loop?
public void UpdatePanel()
{
foreach (var item in list)
{
AddMethod(item);
}
}

Instead of using a loop or time intervals to monitor a list, as an option when possible, you can use a BindingList<T> or ObservableCollection<T> and receive notification when list changes.
Then you can update user interface in the event handler which you attaced to ListChanged event ofBindingList<T> or CollectionChanged event of ObservableCOllection<T>.
Example
Here is an example based on ObservableCollection<string>.
ObservableCollection<string> list;
private void Form1_Load(object sender, EventArgs e)
{
list = new ObservableCollection<string>();
list.CollectionChanged += list_CollectionChanged;
list.Add("Item 1");
list.Add("Item 2");
list.RemoveAt(0);
list[0] = "New Item";
}
void list_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var items = string.Join(",", e.NewItems.Cast<String>());
MessageBox.Show(string.Format("'{0}' Added", items));
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
var items = string.Join(",", e.OldItems.Cast<String>());
MessageBox.Show(string.Format("'{0}' Removed", items));
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
var oldItems = string.Join(",", e.OldItems.Cast<String>());
var newItems = string.Join(",", e.NewItems.Cast<String>());
MessageBox.Show(string.Format("'{0}' replaced by '{1}'", oldItems, newItems));
}
else
{
MessageBox.Show("Reset or Move");
}
}

You can use Task, Async and await, where is some code which insert an element in a listbox each second without blocking UI.
In your case, you have to return the data from backend asynchronously.
public async void LoadItemsAsync()
{
for (int i = 0; i < 10; i++)
listBox1.Items.Add(await GetItem());
}
public Task<string> GetItem()
{
return Task<string>.Factory.StartNew(() =>
{
Thread.Sleep(1000);
return "Item";
});
}

Related

Add many items to ListBox while keeping the UI resposive

I am trying to update a ListBox with a large amount of data in a way that keeps the user interface (UI) responsive.
To do this, I am using the following code to collect the data into batches of 100 items, and then insert these batches into the ListBox in one go, rather than inserting each item individually. This should prevent the UI from being updated each time an item is added, but unfortunately, the code does not work as expected and the UI is only updated after all of the items have been added to the ListBox.
public partial class Form1 : Form
{
private SynchronizationContext synchronizationContext;
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
synchronizationContext = SynchronizationContext.Current;
await Task.Run(() =>
{
ConcurrentDictionary<int, int> batch = new ConcurrentDictionary<int, int>();
int count = 0;
for (var i = 0; i <= 10000; i++)
{
batch[i] = i;
count++;
if (count == 100)
{
count = 0;
UpdateUI(batch);
batch = new ConcurrentDictionary<int, int>();
}
}
});
}
private void UpdateUI(ConcurrentDictionary<int, int> items)
{
synchronizationContext.Post(o =>
{
listBox1.SuspendLayout();
foreach (var item in items)
{
listBox1.Items.Add(item.Value);
}
listBox1.ResumeLayout();
}, null);
}
}
You don't need a multithreading approach in order to update the UI. All you need is to suspend the painting of the ListBox during the mass insert, by using the ListBox.BeginUpdate and ListBox.EndUpdate methods:
private void button1_Click(object sender, EventArgs e)
{
listBox1.BeginUpdate();
for (var i = 1; i <= 10000; i++)
{
listBox1.Items.Add(i);
}
listBox1.EndUpdate();
}
The Control.SuspendLayout and Control.ResumeLayout methods are used when you add controls dynamically in a flexible container, and you want to prevent the controls from jumping around when each new control is added.

How to replace previous item of listbox with next item using backgroundworker c#?

I am adding items to the listbox using backgroundworker. It is showing all the items present in the list one by one after some interval of time. I want to show only current item in the list and not all the items.
This is what I have tried.
private void backgroundWorker3_DoWork(object sender, DoWorkEventArgs e)
{
List<string> result = new List<string>();
var found = obj.getFiles();//List<strings>
if (found.Count != 0)
{
for (int i = 0; i < found.Count; i++)
{
int progress = (int)(((float)(i + 1) / found.Count) * 100);
if (found[i].Contains("SFTP"))
{
result.Add(found[i]);
(sender as BackgroundWorker).ReportProgress(progress, found[i]);
}
System.Threading.Thread.Sleep(500);
}
e.Result = result;
}
}
private void backgroundWorker3_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.UserState != null)
listBox3.Items.Add(e.UserState);
}
I want to show only current item in the list and not all the items.
Although this is a bit of a hack, I think it should work for what you describe:
// just call clear first
listBox3.Items.Clear();
listBox3.Items.Add(e.UserState);
Truth be told, are you sure you want a ListBox in this circumstance? After all, it's not really a list, it's only an item.

Selecting Multiple Items From ListBox causes Collection was modified; enumeration operation may not execute

This is a simple code in which i am Transferring Items of one listBox to one Another on btnAdd_Click Event and btnRemove_Click Event resp.
This was working Fine when The selectionMode="Single" and at that point of time i had no need to use foreach inbtn_Add_Click. But Now i've changed selectionMode="Multiple" and used foreach in btnAdd_Click and when i am selecting multiple items from ListBox it is creating the following Error:-
Collection was modified; enumeration operation may not execute
class Movies
{
private Int32 _Id;
private string _movieName;
public Int32 Id
{
get { return _Id; }
set { _Id = value; }
}
public string movieName
{
get { return _movieName; }
set { _movieName = value; }
}
public Movies(Int32 ID, string MovieName)
{
Id = ID;
movieName = MovieName;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
List<Movies> list = new List<Movies>();
list.Add(new Movies(1, "Movie1"));
list.Add(new Movies(2, "Movie2"));
list.Add(new Movies(3, "Movie3"));
list.Add(new Movies(4, "Movie4"));
list.Add(new Movies(5, "Movie5"));
list.Add(new Movies(6, "Movie6"));
list.Add(new Movies(7, "Movie7"));
lstMain.DataSource = list;
lstMain.DataBind();
}
}
protected void btnAdd_Click(object sender, EventArgs e)
{
foreach (ListItem list in lstMain.Items)// Here the error comes
{
if(list.Selected)
{
lstFAvourite.ClearSelection();
lstFAvourite.Items.Add(list);
lstMain.Items.Remove(list);
}
}
}
protected void btnRemove_Click(object sender, EventArgs e)
{
ListItem list = lstFAvourite.SelectedItem;
lstMain.ClearSelection();
lstMain.Items.Add(list);
lstFAvourite.Items.Remove(list);
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
foreach (ListItem item in lstFAvourite.Items)
{
lbl1.Text += "<li>" + item.Text;
}
}
Please tell me what is going wrong with the foreach loop in btnAdd_Click Event....
Thanks..
You can't remove items from an enumeration while you are looping through it using a foreach, which is what the error message is trying to tell you.
Switching to a straight for loop will get your code to execute:
protected void btnAdd_Click(object sender, EventArgs e)
{
for(var i=0; i<lstMain.Items.Count; i++)
{
var list = lstMain.Items[i];
lstFAvourite.ClearSelection();
lstFAvourite.Items.Add(list);
lstMain.Items.Remove(list);
i--;
}
}
However, I believe this code will add all items from lstMain to lstFAvourite. I think perhaps that we should be looking at the Selected property of the ListItem as well in the for loop. For example:
protected void btnAdd_Click(object sender, EventArgs e)
{
for (var i = 0; i < lstMain.Items.Count; i++)
{
var list = lstMain.Items[i];
if(!list.Selected) continue;
lstFAvourite.ClearSelection();
lstFAvourite.Items.Add(list);
lstMain.Items.Remove(list);
//Decrement counter since we just removed an item
i--;
}
}
Another method.
lstMain.Items
.Cast<ListItem>()
.ToList()
.ForEach( item =>
{
if(item.Selected)
{
lstFAvourite.Items.Add(item);
lstMain.Items.Remove(item);
}
}
);
lstFAvourite.ClearSelection();
N.B: much slower than the for loop method.
If you try to remove list box items using a for loop using an increment (++) statement, eventually that loop will throw out of bounds error failing to finish removing all the items you want. Use decrement (--) instead.
The error Collection was modified; enumeration operation may not execute is because, while you are deleting the items from the list its affecting the sequence. You need to iterate the items in reverse order for this.
for (int i = lstMain.Items.Count; i > 0; i--)
{
ListItem list = lstMain.Items[i];
lstFAvourite.ClearSelection();
lstFAvourite.Items.Add(list);
lstMain.Items.Remove(list);
}

What control should I use for multiple selection of item in winforms C#?

I am really confused on thinking which control to use for my purpose.
I am having list of items say item1 to item10. User can select 4 or 5 items in any order.
Now user selected items has to be separated in same order.
For example, if the user selected the items in following order, item4, item8, item3 and item2.
I want it in the same order. item4,item8,item3,item2.
How do I achieve this in winforms control?
It is not a very nice solution but I wrote it as you asked.
Set the SelectionMode of your ListBox to MultiExtended or MultiSimple as you need.
Then write this code in SelectedIndexChanged event of your ListBox:
List<string> orderedSelection = new List<string>();
bool flag = true;
private void listBox3_SelectedIndexChanged(object sender, EventArgs e)
{
if (flag)
{
flag = false;
var list1 = listBox3.SelectedItems.Cast<string>().ToList();
if (listBox3.SelectedItems.Count > orderedSelection.Count)
{
orderedSelection.Add(list1.Except(orderedSelection).First());
}
else if (listBox3.SelectedItems.Count < orderedSelection.Count)
{
orderedSelection.Remove(orderedSelection.Except(list1).First());
}
var list2 = listBox3.Items.Cast<string>().Except(list1).ToList();
listBox3.Items.Clear();
for (int i = 0; i < list1.Count; i++)
{
listBox3.Items.Add(list1[i]);
listBox3.SelectedIndex = i;
}
foreach (string s in list2)
{
listBox3.Items.Add(s);
}
flag = true;
}
}
When user selects an item, It comes to first of the list and the rest of the items comes next.
Also, there is an alternative way. You can use a CheckedListBox with two extra button for moving selected items up and down. So user can change the order of selected items.
This solution uses CheckListBox's ItemCheck event along with a private List to keep track of the click items order.
protected List<string> clickOrderList = new List<string>();
private void Form1_Load(object sender, EventArgs e)
{
// Populate the checked ListBox
this.checkedListBox1.Items.Add("Row1");
this.checkedListBox1.Items.Add("Row2");
this.checkedListBox1.Items.Add("Row3");
this.checkedListBox1.Items.Add("Row4");
}
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (sender != null && e != null)
{
// Get the checkListBox selected time and it's CheckState
CheckedListBox checkListBox = (CheckedListBox)sender;
string selectedItem = checkListBox.SelectedItem.ToString();
// If curent value was checked, then remove from list
if (e.CurrentValue == CheckState.Checked &&
clickOrderList.Contains(selectedItem))
{
clickOrderList.Remove(selectedItem);
}
// else if new value is checked, then add to list
else if (e.NewValue == CheckState.Checked &&
!clickOrderList.Contains(selectedItem))
{
clickOrderList.Insert(0, selectedItem);
}
}
}
private void ShowClickOrderButton_Click(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach (string s in clickOrderList)
{
sb.AppendLine(s);
}
MessageBox.Show(sb.ToString());
}

SelectedIndices Changed Listbox

Is there some event I can use to tell when the SelectedIndices property changes for a listbox? I want to deselect items in a listbox based on a certain property value of the item. I've hooked up an event that works for when a SelectedIndex is changed, but not sure how to do it for when the SelectedIndices property changes for multiselection.
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Curve curItem = (Curve)listBox1.SelectedItem;
int index = listBox1.Items.IndexOf(curItem);
if (curItem.newName == null)
{
listBox1.SetSelected(index, false);
}
}
You could use ListBox.SelectedItems and LINQ to find all Curves with newName==null to deselect them:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
var nullNameCurves = listBox1.SelectedItems
.Cast<Curve>()
.Where(c => c.newName == null)
.ToList();
listBox1.SelectedIndexChanged -= listBox1_SelectedIndexChanged;
foreach (Curve curve in nullNameCurves)
listBox1.SetSelected(listBox1.Items.IndexOf(curve), false);
listBox1.SelectedIndexChanged += listBox1_SelectedIndexChanged;
}
According to MSDN, this event will be fired every time the selection changes:
If the SelectionMode property is set to SelectionMode.MultiSimple or SelectionMode.MultiExtended, any change to the SelectedIndices collection, including removing an item from the selection, will raise this event.
So basically, you can use it the same way as using it with single selection.
Sample:
For example if you want to deselect all items with null as newName:
foreach (var item in listBox1.SelectedItems)
{
if ((item as Curve).newName == null)
{
int index = listBox1.SelectedItems.IndexOf(item);
listBox1.SetSelected(index, false);
}
}
(I'm not sure if you can deselect items inside a foreach loop since it changes the SelectedItems object itself. If it does not work, you can still make a temporary list of those items and deselect them after the loop.)
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Curve curItem = null;
for (int i = 0; i < listBox1.SelectedItems.Count; i++)
{
curItem = (Curve)listBox1.SelectedItems[i];
if (curItem != null)
{
int index = listBox1.Items.IndexOf(curItem);
if (curItem.newName == null)
{
listBox1.SetSelected(index, false);
}
}
}
}

Categories

Resources