Unable to populate listbox - c#

Im having a bit of trouble with displaying a list in my listbox.
When I had everything in one class, things seemed to work fine but I cant figure out why it doesnt work now. My app, when clicked on the scan button, goes to a different class where there is a new thread created to scan for available bluetooth devices and the a list with those devices is created. Once the list is passed back to a method in Form1 class, it doesnt update the listbox. In debugging mode I could see that there are items on the list but nothing appears in the listbox. The listbox displays items if I did listBox1.Items.Add("Hello World") from the scan button click method. Im sort of stuck here. Ive just started learning C# and if anyone could help me that would be greatly appreciated.
public partial class Form1 : Form
{
int PanelWidth;
bool PanelCalShow;
public Form1()
{
InitializeComponent();
PanelWidth = PanelCal.Width;
PanelCalShow = false;
}
private void button2_Click(object sender, EventArgs e)
{
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
if (PanelCalShow)
{
PanelCal.Width = PanelCal.Width + 10;
if (PanelCal.Width >= PanelWidth)
{
timer1.Stop();
PanelCalShow = false;
this.Refresh();
}
}
else
{
if (PanelCalShow != true)
{
PanelCal.Width = PanelCal.Width - 10;
if (PanelCal.Width <= 0)
{
timer1.Stop();
PanelCalShow = true;
this.Refresh();
}
}
}
}
// Bluetooth connection
private void BtnScan_Click(object sender, EventArgs e)
{
var instance = new BtCom();
instance.Scan();
}
public void LbClientUpdate(List<string> DiscoveredDevices)
{
listBox1.DataSource = DiscoveredDevices;
}
}
and the bluetooth connection class
public class BtCom
{
public List<string> DiscoveredDevices = new List<string>();
Guid mUUID = new Guid("00001101-0000-1000-8000-00805F9B34FB");
public void Scan()
{
Thread bluetoothScanThread = new Thread(new ThreadStart(Scanning));
bluetoothScanThread.Start();
}
BluetoothDeviceInfo[] devices;
public void Scanning()
{
var form1 = new Form1();
BluetoothClient client = new BluetoothClient();
devices = client.DiscoverDevicesInRange();
foreach (BluetoothDeviceInfo d in devices)
{
DiscoveredDevices.Add(d.DeviceName);
}
form1.LbClientUpdate(DiscoveredDevices);
}
}

The reason you aren't seeing any update on your original form is that you are creating a new instance of the Form1 class inside of your BtCom class instead of using the original instance.
One way you could fix this would be to pass the original instance of the form to your BtClass through a parameter to the Scan method, and then pass it along to your Scanning method. This way you will be calling methods on the same instance of the form that's running.
The problem with that is you will be blocking your UI while waiting for the thread to finish (assuming you call bluetoothScanThread.Join() to wait for the thread results).
A slightly different solution would be to use Tasks, with async methods that await results.
To do this, you would have your scan method return a Task<List<string>> (which is a Task that returns a List<string> representing the devices).
Then in form1 you would create an async method (called GetDatasource below) that creates an instance of your BtCom class, awaits the scan method, and returns the Task<List<string>>.
Finally, you would also make the Click method async and have it assign await the GetDatasource method and assign the Datasource when it returns.
By doing it this way, you isolate the BtCon class from having to know any details about the Form1 class, which is a good habit to get into because you end up creating more reusable and independent code.
Here's an example of all those words in code:
Make the scan method return a Task<List<string>> that can be used for the datasource (and have Scanning return a List<string>). Notice that the list is now private to the scanning method. It's a good practice to limit the scope of variables to only the level they are needed.:
public class BtCom
{
Guid mUUID = new Guid("00001101-0000-1000-8000-00805F9B34FB");
public Task<List<string>> Scan()
{
var bluetoothScanTask = Task.Factory.StartNew(Scanning);
bluetoothScanTask.Wait();
return bluetoothScanTask;
}
private List<string> Scanning()
{
BluetoothClient client = new BluetoothClient();
devices = client.DiscoverDevicesInRange();
List<string> discoveredDevices = new List<string>();
foreach (BluetoothDeviceInfo d in devices)
{
discoveredDevices.Add(d.DeviceName);
}
return discoveredDevices;
}
}
Then, write an async method that will get the datasource by creating a new instance of this class, and await for the method to return. Also, make the Click method async so it can await the datasource method:
public partial class Form1 : Form
{
// Other form code omitted...
private async void BtnScan_Click(object sender, EventArgs e)
{
listBox1.DataSource = await Task.Run(GetDatasource);
}
private async Task<List<string>> GetDatasource()
{
var btCom = new BtCom();
List<string> results = await btCom.Scan();
return results;
}
Now your users can click the button and the form will continue to respond while the scanning takes place, and your listbox will populate when the scan method finishes.
For more on async and await, check your favorite search engine (like this page on Asynchronous programming, for example).

Related

Refresh UI from a method running asyncronously on another project

I had a winform using a method on another project thought a DLL, test, count and returns 2 values (good files and bad files) and show up on the winforms those 2 results once done.
Ive been asked to improve that winform to show up results in real time, since the work and the test can take up to 30mins, but ive been struggling since i'm beginning in async programmation.
Ive tried to call function with out or ref, without success. As far i tried, i can refresh in real time a local variable, but not one running in the method out of the winform project.
Winform :
public static int goodfiles { get; set; }
public static int badfiles { get; set; }
Task workControl;
Task refreshControl;
private async void Winform_Load(object sender, EventArgs e)
{
myprogressBar.Style = ProgressBarStyle.Marquee;
workControl = Task.Run(() => WorkMethod());
refreshControl = Task.Run(() => RefreshMethod());
await executerControl.ConfigureAwait(true);
}
private void RefreshMethod()
{
while (!workControl.IsCompleted)
{
label1.Invoke(new MethodInvoker(delegate
{
label1.Text = goodfiles.ToString();
label2.Text = badfiles.ToString();
}
}
}
private void WorkMethod()
{
goodfiles = 0;
badfiles = 0;
var Work = new WorkClass();
Work.ControlFiles(goodfiles, badfiles);
}
Class library project
public class WorkClass
{
public void ControlFiles(int goodfiles, int badfiles)
{
//Do stuff
var Test = new TestClass();
Test.TestFiles(goodfiles, badfiles);
}
}
public class TestClass
{
public void TestFiles(int goodfiles, int badfiles)
{
//Test files
if(stuff) goodfiles++;
else badfiles++;
}
}
I know it's maybe far from being the prefect architecture, but I have to deal with it.
Is it technically possible, difficult or just impossible to do? Or am I missing something obvious ?
You need to use the same fields from the worker thread and UI thread. The best way is to put them in a shared object. This might be the work-class, but you could also create a separate object that is given as a parameter to the actual work-method. I recommend against using any mutable static fields.
public class WorkClass
{
public volatile int GoodFiles;
public volatile int BadFiles;
public void ControlFiles()
{
//Test files
if (stuff) GoodFiles++;
else BadFiles++;
}
}
and call it like
WorkClass myWork;
private async void Winform_Load(object sender, EventArgs e)
{
myWork = new WorkClass();
workControl = Task.Run(() => myWork.ControlFiles());
}
To check the progress I would recommend a timer. Set it to run however often you want, and update the labels from the myWork-object when event handler for the Tick-event. You can await the workControl-task and stop the timer when the task is done.
It depends on how coupled or uncoupled you want your code to be.
In most cases, the Progress class is a good choice.
Here's an article from Stephen Cleary on the subject: Reporting Progress from Async Tasks

Preload a Form in Another Thread

I am trying to preload server form in the constructor of client form, in a separate thread. My reason is that server load is time consuming.
Here's the client form, you can see in the constructor that I am calling Preload(). There's also a button, clicking on it should show the server, which should be fast since the server form is already preloaded:
public partial class Form1 : Form
{
ServerUser server = null;
public Form1()
{
InitializeComponent();
Preload();
}
public async void Preload()
{
await Task.Run(() =>
{
server = new ServerUser();
server.LoadDocument();
server.ShowDialog();
}
);
}
private void button1_Click(object sender, EventArgs e)
{
server.Show();
}
}
Here I try to preload form ServerUser in constructor of Form1 and if I click on button1 Server form show faster
And here's the server form:
public partial class ServerUser : Form
{
public ServerUser()
{
InitializeComponent();
}
public void LoadDocument()
{
ConfigureSource();
}
public void ConfigureSource()
{
InvokeUpdateControls();
}
public void InvokeUpdateControls()
{
UpdateControls();
}
private void UpdateControls()
{
richTextBox1.Rtf = Resource1.ReferatPPC_Bun___Copy;
}
}
You need to rethink your design. You should create all forms from the main UI thread, and offload the heavy lifting(non UI stuff) to the background threads. Calling UI methods from background threads results in undefined behavior or exceptions.
Also, I think you misunderstand what await does. You call Preload() synchronously even though it is an asynchronous method. This means that by the time server.Show(); is called, Server might still be running one of these methods:
server = new ServerUser(); //you should move this outside of Task.Run()
server.LoadDocument(); //implement this method using background threads
server.ShowDialog(); //this will actually throw an exception if called via Task.Run();
From your sample I suppose LoadDocument is the expensive operation. You should rewrite that method to run on a background thread and make ServerUser show a loading screen untill LoadDocument() completes. Make sure that all UI methods from LoadDocument are called via BeginInvoke
or proper async/await.
Send in constructor;
public partial class ServerUser : Form
{
public ServerUser(Form1 form1)
{
InitializeComponent();
form1.Preload();
}
public void LoadDocument()
{
ConfigureSource();
}
public void ConfigureSource()
{
InvokeUpdateControls();
}
public void InvokeUpdateControls()
{
UpdateControls();
}
private void UpdateControls()
{
richTextBox1.Rtf = Resource1.ReferatPPC_Bun___Copy;
}
}
public partial class Form1 : Form
{
ServerUser server = null;
public Form1()
{
InitializeComponent();
Preload();
}
public async void Preload()
{
await Task.Run(() =>
{
server = new ServerUser();
server.LoadDocument();
server.ShowDialog();
}
);
}
private void button1_Click(object sender, EventArgs e)
{
server=new ServerUser(this);// or whatever you want
server.Show();
}
}

C# multithreaded throbber form

Working on a C# project which I would like to implement a "waiting" (throbber) indicator in a separate form. After much research and trial and error it appears as the suggested method of doing this is to load a form using a separate thread from the one from the current form/thread.
The reason I went with this method was because initially using the Show() method on the throbber form produced a transparent form. I cannot use ShowDialog because I need to run some code after the throbber is displayed, after which that completes I would like to close the throbber form.
Anyway .. after trying many different methods to load the throbber form in a separate thread I still get an error about trying to access it from a thread which is different from the one it was created in. Here is a skelton version of the project code that should shed some light on my issue:
the example I was working off of for multithreading was this popular link for creating your own spashscreen in a separate thread ... http://www.codeproject.com/Articles/5454/A-Pretty-Good-Splash-Screen-in-C
public class Main
{
public void CheckData()
{
try
{
ProgressBar pb = new ProgressBar();
pb.ShowProgressBar();
//do data checking here
pb.CloseForm()
}
catch(Exception e)
{
}
}
}
public partial class ProgressBar : Form
{
static Thread ms_oThread = null;
public bool shouldStop = false;
static ProgressBar ms_ProgBar = null;
public ProgressBar()
{
InitializeComponent();
//DoWork();
}
public void ShowForm()
{
ms_ProgBar = new ProgressBar();
Application.Run(ms_ProgBar);
}
public void CloseForm()
{
ms_ProgBar.Close();
}
public void ShowProgressBar()
{
// Make sure it is only launched once.
if (ms_ProgBar != null)
return;
ms_oThread = new Thread(new ThreadStart(ShowForm));
ms_oThread.IsBackground = true;
ms_oThread.SetApartmentState(ApartmentState.STA);
ms_oThread.Start();
while (ms_ProgBar == null || ms_ProgBar.IsHandleCreated == false)
{
System.Threading.Thread.Sleep(1000);
}
}
}
You are creating your ProgressBar twice. Once in your main function, and once in your new thread. You are also calling your CloseWindow method from your main function (and on the window that is never shown), rather than on your new thread window.
You only want to create ProgressBar and show it using your new thread. Make your static ProgressBar field public so you can call close on it directly from Main, but make sure to use Invoke to do it since it's not on that Window's GUI thread.
Also, ShowProgressBar should be static.
Here's a rewrite attempt:
public class Main
{
public void CheckData()
{
try
{
ProgressBar.ShowProgressBar();
//do data checking here
ProgressBar.CloseForm();
}
catch(Exception e)
{
}
}
}
public partial class ProgressBar : Form
{
static ProgressBar _progressBarInstance;
public ProgressBar()
{
InitializeComponent();
//DoWork();
}
static void ShowForm()
{
_progressBarInstance = new ProgressBar();
Application.Run(ms_ProgressBar);
}
public static void CloseForm()
{
_progressBarInstance.Invoke(new Action(_progressBarInstance.Close));
_progressBarInstance= null;
}
public static void ShowProgressBar()
{
// Make sure it is only launched once.
if (_progressBarInstance != null)
return;
var ms_oThread = new Thread(new ThreadStart(ShowForm));
ms_oThread.IsBackground = true;
ms_oThread.SetApartmentState(ApartmentState.STA);
ms_oThread.Start();
}
}

ShowDialog and UI interaction in BackGroundWorker Thread

After 2 hours of researching, i still couldn't find a solution to my problem.
The task I do is process some files in the BackGroundWorker thread. However, sometimes I need to use ShowDialog to let the user choose the SaveFile location but i'm getting the STA/MTA error.
MainForm code:
private void button2_Click(object sender, EventArgs e)
{
button1.Enabled = false;
ProcessReportWorker.RunWorkerAsync();
}
DoWork Code:
void ProcessReportWorker_DoWork(object sender, DoWorkEventArgs e)
{
int ReportCount = Reports.Count();
foreach (string Report in Reports)
{
ProcessReport NewReport = new ProcessReport(Report);
string result = NewReport.Start();
}
}
ProcessReport.Start() Code:
class ProcessReport
{
public string Start()
{
if(File.Exists(ResultPath))
{
SaveFileDialog SaveReport = new SaveFileDialog();
SaveReport.InitialDirectory = "c:\somepath";
SaveReport.CheckPathExists = true;
SaveReport.DefaultExt = ".xls";
SaveReport.OverwritePrompt = true;
SaveReport.ValidateNames = true;
if (SaveReport.ShowDialog() == DialogResult.OK)
{
ResultPath = SaveReport.FileName;
if (File.Exists(ResultPath)) File.Delete(ResultPath);
}
}
}
}
As you can see, the ShowDialog is needed in some cases.
I believe this can be done using delegates but i'm not much familiar with delegates. I did try the solution by Jon in Calling ShowDialog in BackgroundWorker but i couldn't get it to work. (maybe i'm doing something wrong with delegates?)
Someone please help me with this. Please provide me the code for delegates if needed for this. Thanks!
EDIT:
Solution given by PoweredByOrange worked. HOwever, i had to make a small change to it:
this.Invoke((MethodInvoker)delegate{....}); did not work because
- the intention is to refer to the MainForm instance but this code exists in the ProcessReport Class. So the "this" here is referring to the ProcessReport class instance, but it must refer to the GUI instance (MainForm instance) to work.
My Fix:
I sent an instance of the MainForm to the ProcessReport class and made the changes as mentioned below:
IN DoWork:
ProcessReport NewReport = new ProcessReport(Report, this); //CHANGE: Sending 'this'
//this sends reference of MainForm(GUI) to the ProcessReport Class
In ProcessReport Class:
class ProcessReport
{
MainForm MainFormInstance;
public ProcessReport(string report, MainForm x)
{
MainFormInstance = x;
}
public string Start()
{
MainFormInstance.Invoke((MethodInvoker)delegate //changed this.Invoke to MainFormInstance.Invoke
{
SaveFileDialog SaveReport = new SaveFileDialog();
SaveReport.InitialDirectory = "c:\somepath";
SaveReport.CheckPathExists = true;
SaveReport.DefaultExt = ".xls";
SaveReport.OverwritePrompt = true;
SaveReport.ValidateNames = true;
if (SaveReport.ShowDialog() == DialogResult.OK)
{
ResultPath = SaveReport.FileName;
if (File.Exists(ResultPath)) File.Delete(ResultPath);
}
});
}
}
So the above thing finally worked. I understood this pretty well, thanks to PoweredByOrange.
The reason you're getting the exception is because only the thread that owns a control is allowed to modify/access it. In this case, the SaveFileDialog belongs to your main thread, but the Start() method is running in a different (i.e. background) thread. Therefore, the background thread in this case needs to ask the main thread to open up its SaveFileDialog.
public string Start()
{
if(File.Exists(ResultPath))
{
this.Invoke((MethodInvoker)delegate
{
SaveFileDialog SaveReport = new SaveFileDialog();
SaveReport.InitialDirectory = "c:\somepath";
SaveReport.CheckPathExists = true;
SaveReport.DefaultExt = ".xls";
SaveReport.OverwritePrompt = true;
SaveReport.ValidateNames = true;
if (SaveReport.ShowDialog() == DialogResult.OK)
{
ResultPath = SaveReport.FileName;
if (File.Exists(ResultPath)) File.Delete(ResultPath);
}
});
}
}
To make it more clear, assume you want your friend to give you one of his textbooks. You are NOT allowed to go to your friend's room and steal the book. What you could do, is call your friend (invoke) and ask for a favor (delegate).
Unsure if this will help but here is the simplest delegate / event code I can provide you;
public static class CacheManager
{
private static CacheEntryRemovedCallback callback = null;
public delegate void CacheExpiredHandler(string key);
public static event CacheExpiredHandler CacheExpired;
static CacheManager()
{
// create the callback when the cache expires.
callback = new CacheEntryRemovedCallback(MyCachedItemRemovedCallback);
}
private static void MyCachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
{
if (CacheExpired != null)
CacheExpired(arguments.CacheItem.Key);
}
public static class DataManager
{
static DataManager()
{
// when a cached list expires, notify me with the key of the list.
CacheManager.CacheExpired += new CacheManager.CacheExpiredHandler(CacheManager_CacheExpired);
}
/// <summary>
/// When a chached list expires, this is the callback method that is called.
/// </summary>
/// <param name="key">The key of the list that just expired.</param>
static void CacheManager_CacheExpired(string key)
{
// Do something now because the cache manager has raised an event that it has expired.
}

Question on threading in C#

I have a Windows Forms application at the moment, and I want to create a new thread and run a method on another class that accepts an input.
For example
public partial class Form1: Form {
SerialPort serialInput;
// I want to create a new thread that will pass the parameter serialInput into the method
// SMSListener on another class and run the method contionously on the background.
}
class SMS
{
public void SMSListener(SerialPort serial1)
{
serial1.DataReceived += port_DataRecieved;
}
private void port_DataRecieved(object sender, SerialDataReceivedEventArgs e)
{
// Other codes
}
}
How do I perform this in C#? I have seen numerous examples on the web, and most of them run the method on the same class with no parameters, but none that suits my requirements.
Perhaps a Background Worker could help you?
It is a bit hard to understand what you are aiming at.
public class Runner
{
private readonly BackgroundWorker _worker = new BackgroundWorker();
public Runner()
{
_worker.DoWork += WorkerDoWork;
}
public void RunMe(int payload)
{
_worker.RunWorkerAsync(payload);
}
static void WorkerDoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
while (true)
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
// Work
System.Threading.Thread.Sleep((int)e.Argument);
}
}
}
I am not an expert on Multithreading but to the best of my knowledge you can only start threads on methods that accept an object parameter and return void. So in order to achieve that for your problem (don't shoot me down if there is a better approach!) I would do something like
public partial class Form1: Form {
SerialPort serialInput;
// I want to create a new thread that will pass the parameter serialInput into the method
// SMSListener on another class and run the method contionously on the background.
SMS sms = new SMS();
Thread t = new Thread(sms.SMSListenerUntyped);
t.Start(serialInput);
}
class SMS
{
public void SMSListenerUntyped(object serial1) {
if (serial1 is SerialPort) //Check if the parameter is correctly typed.
this.SMSListener(serial1 as SerialPort);
else
throw new ArgumentException();
}
public void SMSListener(SerialPort serial1)
{
serial1.DataReceived += port_DataRecieved;
}
private void port_DataRecieved(object sender, SerialDataReceivedEventArgs e)
{
// Other code.
}
How about just use the ThreadPool directly with a anonymous method allowing you to access your surrounding locals?
public void OnButtonClick(object sender, EventArgs e)
{
SerialPort serialInput = this.SerialInput;
System.Threading.ThreadPool.QueueUserWorkItem(delegate
{
SmsListener listener = new SmsListener(serialInput);
});
}

Categories