Loop through selected listview items in background worker - c#

I am having a hard time trying to loop through all my selected items from my listview in a background worker. I did some research and found this; Can't get items in a ListView cross-thread But it does not seem to work as I only want to send to selected items could someone give an example and an explanation as to what I should do? this is all very new and confusing.
Visual example of what i tired;

Your function getListViewItems expects ListView as an argument. You are trying to call it with ListViewItemCollection that is why it fails.
just modify it to
foreach(ListViewItem item in getListViewItems(listView2))

Defint a function like this:
public static List<ListViewItem> GetSelectedListViewItems(ListView lv)
{
if (!lv.InvokeRequired)
return lv.SelectedItems.Cast<ListViewItem>().ToList();
else
return (List<ListViewItem>)lv.Invoke(
new Func<ListView, List<ListViewItem>>(GetSelectedListViewItems),
lv);
}
Usage:
private void SendMSG_DoWork(object sender, DoWorkEventArgs e)
{
foreach (var item in GetListViewSelectedItems(listView2))
{
// ...
}
}

Related

C# WPF React to a clicked button in another method

Sorry if this is a basic question but somehow I have not been able to find a good answer to what I thought would be something very simple.
In one window I have a textblock, a textbox and a button (and some other items). The textblock has a question and you are supposed to enter the answer in the textbox. After entering the text you press the button to check if the answer is correct. The questions are handled in a foreach loop in method A. In "the middle" of the loop I need to check if the answer was correct. After searching I only found the following solution. Is this really the "correct way" of doing this?
I am quite new to C# so maybe I am overseeing something. The method seems to work but I would have expected something like a "wait for button pressed".
Any ideas or links to information would be very welcome ... Thanks a lot
TaskCompletionSource<bool> tcs = null;
private async void goingThroughTheList()
{
foreach member of the list
do something
tcs = new TaskCompletionSource<bool>();
await tcs.Task;
do something
}
private void button_method(object sender, RoutedEventArgs e)
{
do something
tcs?.TrySetResult(true); // this triggers that the other method
// knows that the button has been pressed
}
Wouldn't it be easier for the button to invoke the second part of your "goingThroughTheList()" if the first part has been completed?
bool FirstPartDone = false;
private void FirstPart()
{
foreach (var item in list)
{
// do something
}
FirstPartDone = true;
}
private void SecondPart()
{
foreach (var item in list)
{
// do something
}
}
private void button_method(object sender, RoutedEventArgs e)
{
// do something
if (FirstPartDone)
{
SecondPart();
}
}

Reading a ListView SelectedItems collection from another thread

I'm trying to perform some actions on the selected items in a ListView on a Windows Forms application from a background thread. I've got a delegate method in my code like so:
private delegate ListView.SelectedListViewItemCollection dlgGetSelectedJobs();
private ListView.SelectedListViewItemCollection GetSelectedJobs()
{
if(listViewJobViewer.InvokeRequired)
{
var dlg = new dlgGetSelectedJobs(GetSelectedJobs);
return listViewJobViewer.Invoke(dlg) as ListView.SelectedListViewItemCollection;
}
return listViewJobViewer.SelectedItems;
}
This is being called elsewhere on a background thread using the following:
foreach(ListViewItem job in GetSelectedJobs())
{
// Do stuff
}
However whenever the code enters the foreach loop I get a cross-thread exception and I'm not sure why... Any assistance gratefully received!
Thanks to MongZhu for the assists!
I've managed to work around this issue by altering my delegate method to use Linq to return a List based on the selected items in the ListView:
private delegate List<ListViewItem> dlgGetSelectedJobs();
private List<ListViewItem> GetSelectedJobs()
{
if(listViewJobViewer.InvokeRequired)
{
var dlg = new dlgGetSelectedJobs(GetSelectedJobs);
return listViewJobViewer.Invoke(dlg) as List<ListViewItem>;
}
return (from ListViewItem i in listViewJobViewer.SelectedItems select i).ToList();
}
I'm still not sure why it wouldn't work when I was trying to return the collection, but this seems to work correctly.

Winforms Datagridview can't be refreshed from a delegate

I'm trying to load data from file to list and show that data immediately on Winforms' Datagridview. For that I've made the reading in another thread using Backgroundworker. The problem is, it only updates once and I can't get it to show more data. Not only that, when clicked, it tries to access element with -1 index, which of course doesn't exist, resulting in a crash.
Usually, from what I've seen, simply adding again same data to data source dataGridView1.DataSource = samelist; should work, but not in this case.
BackgroundWorker's work
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//lotsofCode...
while (readData != null)
{
fooLists.Add(readData);
//someCalculations...
worker.ReportProgress();
}
}
BackgroundWorker's progressChanged
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Invoke((MethodInvoker)delegate { UpdateGridView(); });
}
UpdateGridView Method
private void UpdateGridView()
{
if (fooLists.GetListById(1).calculatedList != null)
dataGridView1.DataSource = fooLists.GetListById(1).calculatedList;
}
Later on I've read some threads on stack, where one suggested using BindingSource as a "middleman", so now I have dataGridView1.DataSource = MyBindingSource; in the component initialization and tab1source.DataSource = fooLists.GetListById(1).calculatedList; instead of dataGridView1.DataSource. It certainly helped, as the list is now clickable as it should be, but still there are only few records on a list.
None of dataGridView1.Refresh(), dataGridView1.RefreshEdit() or dataGridView1.Update() helped, though made the list loading slightly fancier (probably due to the delay they introduced :) ).
I tried making some "protections" (semaphores, so the delegate isn't called again, while working; try-catches, though no exceptions are thrown there; data clearing before re-writing...) but the "better version" worked as poor as this one and it only darkened the code.
Am I missing a way to update the Datagridview control? Thanks in advance.
Although you didn't write it, but I think the reason that the items that you add to your dataSource are added to a collection that does not implement interface IBindingList. You probably use a simple list to hold your read data.
If your 'DataSourceimplements this interface, then after adding an item to your collection an event is raised. The class that holds theDataSource, whether it is aDataGridViewor aBindingSource` get notified about the changes in the list and update their contents accordingly.
Your solution would be to store your elements in an object of class System.ComponentModel.BindingList<T>.
Suppose the items you want to show are of class MyReadData
class MyForm : Form
{
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingList<MyReadData>();
this.MyBindingSource.DataSource = this.myReadItems;
// if not already done in InitializeComponents
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingList<MyReadData> myReadItems;
// whenever needed, start the BackGroundWorker.
private void OnButtonReadFile_Click(object send, EventArgs e)
{
// create and start the backgroundworker
BackGroundWorkdr worker = ...
MyBackGroundWorkerParams params = ...
worker.RunWorkerAsync(params);
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// I am certain the sender is my BackGroundWorker:
BackgroundWorker worker = (BackGroundWorker)sender;
MyBackGroundWorkerParams params = (MyBackGroundWorkerParams)e.Argument;
// do some work using the params
while (readData != null)
{
// some data read.
// dont't add the data to the list, just report the data that must been added to the list:
// someCalculations...
int percentProgress = ...
MyReadData dataToAddToGrid = ...
worker.ReportProgress(percentProgress, dataToAddToGrid);
}
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
// no need to call invoke, this is already the context of your forms thread
Debug.Assert(!This.InvokeReguired);
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
}
}
The main difference is that you should not let your BackgroundWorker to add data to the list of displayed data. The task of the BackGroundWorker is to read the data and to report to everyone who is interested what data has been read.
As it is the task of MyForm to display the read data, let MyForm decide which read data to display and in what format. This enhances reusage of both MyForm and MyBackGroundWorker: MyForm could display that that has been fetched in a different way, and MyBackGroundWorker could be used to inform others than MyForm to notify about read data.
Furthermore the display context of the progress changed event handler is the context of 'MyForm`, so an invoke is not needed.
You could also assign the IBindingList directly to the DataGridView, so without the use of a BindingSource. The only reason to keep a BindingSource is if you want access to the Current item, or if you want the freedom to fill your DataGridView with other items than the contents of your BindingList.
Finally: the most important part of the solution was that the items were added to an IBindingList.
System.Components.BindingList<T> is a class with limited functionality. If you want to order the rows in your DataGridView, or only show items that match some predicate, or combine items from several sources into one DataGridView, consider using Equin.ApplicationFramework.BindingListView
using Equin.ApplicationFramework;
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingListView<MyReadData>(this.components);
this.MyBindingSource.DataSource = this.myReadItems;
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingListView<MyReadData> myReadItems;
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
// finished updating the list, DataGridView can be updated:
this.myReadItems.Refresh();
// this Refresh function allows you to change several items in the list
// without unnecessary intermediate updates of your BindingSource and DataGridView
}
Presto, that is all: free sorting or your columns by clicking on the column header. Consider examining their example project to see how filtering works and how to use several sources.
// Show only Brummies
this.myReadData.ApplyFilter(person => person.Address.City == "Birmingham");
// Remove the filter, show everyone again
this.myReadData.RemoveFilter();

C# Removing Submenu Item Image Margins

See the linked screenshot below.
In short, I need those little white boxes to disappear - they're supposed to house an image, but there is no image, and so I'd rather they disappear.
I've accomplished this using the follow code:
foreach (ToolStripMenuItem menuItem in mnuMain.Items)
((ToolStripDropDownMenu)menuItem.DropDown).ShowImageMargin = false;
This works for what I guess are the main items, but not the sub-items, as you can see in the picture.
I've tried a few variations on the above code to try and get it to capture everything instead of just the first level items, but no luck.
What am I doing wrong?
http://i.imgur.com/bst1i4v.png
You should do that for sub items too. To do so, you can use this code:
private void Form1_Load(object sender, EventArgs e)
{
SetValuesOnSubItems(this.menuStrip1.Items.OfType<ToolStripMenuItem>().ToList());
}
private void SetValuesOnSubItems(List<ToolStripMenuItem> items)
{
items.ForEach(item =>
{
var dropdown = (ToolStripDropDownMenu)item.DropDown;
if (dropdown != null)
{
dropdown.ShowImageMargin = false;
SetValuesOnSubItems(item.DropDownItems.OfType<ToolStripMenuItem>().ToList());
}
});
}
This is a modified version of above. Use:
MainMenuStrip.HideImageMargins();
Because the recursive method performs the intended manipulation, I used overloading to make it clearer what is intended. Pattern matching is used because the above sample will throw an exception, not return null.
public static void HideImageMargins([NotNull] this MenuStrip menuStrip)
{
HideImageMargins(menuStrip.Items.OfType<ToolStripMenuItem>().ToList());
}
private static void HideImageMargins([NotNull] this List<ToolStripMenuItem> toolStripMenuItems)
{
toolStripMenuItems.ForEach(item =>
{
if (!(item.DropDown is ToolStripDropDownMenu dropdown))
{
return;
}
dropdown.ShowImageMargin = false;
HideImageMargins(item.DropDownItems.OfType<ToolStripMenuItem>().ToList());
});
}

Adding Item to List Box

I'm having trouble adding items to a list box.
For whatever reason, I can't get the item to display in the list box (nothing is displayed.)
private void btnPressForCandy_Click(object sender, EventArgs e)
{
txtcandyMachine.Text = "";
avalibleCandy = avalibleCandy - 1;
candyDisplay.Items.Add("Candy"); //Key Line
}
Has anyone got any suggestions as to what I'm doing wrong?
Thanks in advance guys.
Joe
I've just realised that there's some other code effecting the list box.
private List <Candy> CollectedCandy;
Which is why possibly it wasn't working.
CollectedCandy = new List<Candy>();
However I'm not quite sure what I need to add to get this to work.
Obviously I need to call UpdateCandyDisplay but beyond that I'm not sure.
private void UpdateCandyDisplay()
{
candyDisplay.Items.Clear();
foreach (Candy candy in CollectedCandy)
{
candyDisplay.Items.Add("Candy");
}
}
try this
namespace WindowsFormsApplication11
{
public partial class Form1 : Form
{
List<string> _items = new List<string>(); // <-- Add this
public Form1()
{
InitializeComponent();
_items.Add("One"); // <-- Add these
_items.Add("Two");
_items.Add("Three");
listBox1.DataSource = _items;
}
}
}
and more information use this link How might I add an item to a ListBox?
I've just realised that there's some other code effecting the list box.
private List <Candy> CollectedCandy;
Which is why possibly it wasn't working.
CollectedCandy = new List<Candy>();
However I'm not quite sure what I need to add to get this to work.
Obviously I need to call UpdateCandyDisplay but beyond that I'm not sure.
private void UpdateCandyDisplay()
{
candyDisplay.Items.Clear();
foreach (Candy candy in CollectedCandy)
{
candyDisplay.Items.Add("Candy");
}
}

Categories

Resources