c# text box, different class, backgroundWorker - c#

I'm new to c# and have a simple project where I have one Form1, which has a button to open Form2 (Diagnostics page to be password protected later on).
I have created a SerialPortClass which as you can guess handles all the serial port methods, such as openport, sendline, isPortOpen etc.
What I want to do is receive a serial string from the serial port in the SerialPortClass, then display this string in a text box in Form2. I have tried to achieve this in several ways after reading many posts on this site and others.
From what I read, using the BackGroundWorker is the best way of doing this. So I copied the example Microsoft Thread safe example, and have a button on Form2 to use the BackGroundWorker, to display text in the TextBox successfully. However when I try to run the BackGroundWorker from SerialPortClass I get a:
Exception thrown: 'System.NullReferenceException' in SerialTest.exe
Additional information: Object reference not set to an instance of an object.
Can anyone point me in the right direction please?
I know I'm actually passing the string yet, but just trying to start the background working in the other class as a test
Full SerialPortClass:
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;
using System.Collections;
using System.Threading;
using System.Reflection;
using UsbLibrary;
namespace SerialTest
{
public class SerialPortClass : Form
{
private static SerialPortClass instance;
private System.IO.Ports.SerialPort serialPort1 = new SerialPort(); // Initilises an instance of COM port
private System.IO.Ports.SerialPort serialPort2 = new SerialPort(); // and another
Form1 form1;
Form2 form2;
internal delegate void SerialDataReceivedEventHandlerDelegate(
object sender, SerialDataReceivedEventArgs e);
delegate void SetTextCallback(string text);
string InputData = String.Empty;
private static SerialPort port;
public SerialPortClass()
{
serialPort1.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
public static readonly SerialPortClass _instance = new SerialPortClass();
public ThreadStart ThreadProcSafe { get; private set; }
public bool serialOpen(int port)
{
if (port == 1)
{
if (serialPort1.IsOpen) return true;
else return false;
}
else if (port == 2)
{
if (serialPort2.IsOpen) return true;
else return false;
}
else return false;
}
public void serialSendString(int port, string command)
{
if (port == 1)
{
// If the port is closed, don't try to send a character.
if (!serialPort1.IsOpen) return;
serialPort1.WriteLine(command);
}
else if (port == 2)
{
if (!serialPort2.IsOpen) return;
serialPort2.WriteLine(command);
}
else
{
MessageBox.Show("Invalid port no");
}
}
public void serialSendString1(string command)
{
// If the port is closed, don't try to send a character.
if (serialPort1.IsOpen)
{
serialPort1.WriteLine(command);
}
else
{
MessageBox.Show("port not opening at connect..");
return;
}
}
public void serialSendString2(string command)
{
// If the port is closed, don't try to send a character.
if (serialPort2.IsOpen)
{
serialPort2.WriteLine(command);
}
else
{
MessageBox.Show("port not opening at connect..");
return;
}
}
public void Connect() //SerialTest.Form1 form) //string comPortNo, int baud)
{
serialPort1.PortName = "COM38"; // comPortNo;
serialPort1.BaudRate = 9600; // baud;
if (serialOpen(1))
{
MessageBox.Show("Serial port already open");
return;
}
try
{
serialPort1.Open();
serialPort1.NewLine = "\r";
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
if (serialOpen(1))
{
Console.WriteLine("port open");
}
else
{
MessageBox.Show("port not opening at connect..");
}
}
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
Console.WriteLine("Data recieved");
InputData = serialPort1.ReadExisting();
if (InputData != String.Empty)
{
SetText(InputData);
}
}
public void SetText(string text)
{
Console.WriteLine("set text");
form2.backgroundWorker1.RunWorkerAsync();
}
public void disconnect()
{
//serialPort1.PortName = "COM38";
//serialPort1.BaudRate = 9600;
if (serialOpen(1))
{
serialPort1.Close();
Console.WriteLine("Port closed");
}
else
{
MessageBox.Show("Port not open to close");
}
}
public class SerialErrorReceivedEventArgs : EventArgs
{
//Data to pass to the event
public string LineData { get; private set; }
public SerialErrorReceivedEventArgs(string lineData)
{
this.LineData = lineData;
}
}
}
}
and Form2:
using System;
using System.Threading;
using UsbLibrary;
using log4net;
using SensorTestApp;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using SerialTest;
using System.IO.Ports;
namespace SerialTest
{
public partial class Form2 : Form
{
string comPortNo;
// This delegate enables asynchronous calls for setting
// the text property on a TextBox control.
delegate void SetTextCallback(string text);
// This thread is used to demonstrate both thread-safe and
// unsafe ways to call a Windows Forms control.
public Thread demoThread = null;
// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
public BackgroundWorker backgroundWorker1;
//private TextBox tBQuery;
private Button setTextUnsafeBtn;
private Button setTextSafeBtn;
private Button setTextBackgroundWorkerBtn;
private System.ComponentModel.IContainer components1 = null;
public static Form2 _instance = new Form2();
public Form2()
{
InitializeComponent();
this.backgroundWorker1 = new BackgroundWorker();
// here you have also to implement the necessary events
// this event will define what the worker is actually supposed to do
this.backgroundWorker1.DoWork += backgroundWorker1_DoWork;
//this.backgroundWorker1.RunWorkerAsync += backgroundWorker1_RunWorkerAsync;
// this event will define what the worker will do when finished
this.backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
btnRelay1On.Enabled = false;
btnRelay2On.Enabled = false;
btnRelay3On.Enabled = false;
btnRelay4On.Enabled = false;
btnRelay5On.Enabled = false;
btnRelay1Off.Enabled = false;
btnRelay2Off.Enabled = false;
btnRelay3Off.Enabled = false;
btnRelay4Off.Enabled = false;
btnRelay5Off.Enabled = false;
}
// This event handler creates a thread that calls a
// Windows Forms control in a thread-safe way.
private void button2_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
// This method is executed on the worker thread and makes
// a thread-safe call on the TextBox control.
public void ThreadProcSafe()
{
this.SetText("This text was set safely.");
}
// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextCallback and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
public void AppendText(String text)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<string>(AppendText), new object[] { text });
return;
}
this.richTextBox1.Text += text;
}
public void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.tBQuery.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
Console.WriteLine("different thread, text callback");
}
else
{
Console.WriteLine("same thread, string: %s", text);
this.tBQuery.Text = text;
}
}
// This event handler starts the form's
// BackgroundWorker by calling RunWorkerAsync.
//
// The Text property of the TextBox control is set
// when the BackgroundWorker raises the RunWorkerCompleted
// event.
public void button1_Click(
object sender,
EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
public void backgroundWorker1_DoWork(object sender,
DoWorkEventArgs e)
{
Console.WriteLine("BackgroundWorker1_Do Work");
}
// This event handler sets the Text property of the TextBox
// control. It is called on the thread that created the
// TextBox control, so the call is thread-safe.
//
// BackgroundWorker is the preferred way to perform asynchronous
// operations.
public void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
this.tBQuery.Text =
"This text was set safely by BackgroundWorker.";
}
private void cbComPort_SelectedIndexChanged(object sender, EventArgs e)
{
comPortNo = cbComPort.Text.ToString();
btnOpenCom.Enabled = true;
}
private void btnOpenCom_Click(object sender, EventArgs e)
{
//SerialPortClass.GetInstance().Connect(comPortNo, 9600);
try
{
SerialPortClass._instance.Connect();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
if (SerialPortClass._instance.serialOpen(1))
{
btnOpenCom.Enabled = false;
btnCloseCom.Enabled = true;
btnRelay1On.Enabled = true;
btnRelay2On.Enabled = true;
btnRelay3On.Enabled = true;
btnRelay4On.Enabled = true;
btnRelay5On.Enabled = true;
btnRelay1Off.Enabled = true;
btnRelay2Off.Enabled = true;
btnRelay3Off.Enabled = true;
btnRelay4Off.Enabled = true;
btnRelay5Off.Enabled = true;
}
else MessageBox.Show("port not open btnOpenCom");
}
private void btnCloseCom_Click(object sender, EventArgs e)
{
try
{
SerialPortClass._instance.disconnect();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
if (!SerialPortClass._instance.serialOpen(1))
{
btnOpenCom.Enabled = true;
btnCloseCom.Enabled = false;
btnRelay1On.Enabled = false;
btnRelay2On.Enabled = false;
btnRelay3On.Enabled = false;
btnRelay4On.Enabled = false;
btnRelay5On.Enabled = false;
btnRelay1Off.Enabled = false;
btnRelay2Off.Enabled = false;
btnRelay3Off.Enabled = false;
btnRelay4Off.Enabled = false;
btnRelay5Off.Enabled = false;
}
}
private void btnRelay1On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH1");
}
private void btnRelay1Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL1");
}
private void btnRelay2On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH2");
}
private void btnRelay2Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL2");
}
private void btnRelay3On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH3");
}
private void btnRelay3Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL3");
}
private void btnRelay4On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH4");
}
private void btnRelay4Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL4");
}
private void btnRelay5On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH5");
}
private void btnRelay5Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL5");
}
private void btnQuery_Click(object sender, EventArgs e)
{
//this.BeginInvoke(new SetTextCallback(SetText), new object[] { "hjdfdsfj" });
SerialPortClass._instance.serialSendString(1, "?");
Console.WriteLine("?");
}
}
}
Also there's references to BackgroundWorker in the Designer file, so I've included it here too:
this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
//
// backgroundWorker1
//
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
//private System.ComponentModel.BackgroundWorker backgroundWorker1;

What you are missing is the initialization of the BackGroundWorker. You should do this in the constructor:
public Form2()
{
InitializeComponent();
this.backgroundWorker1 = new BackGroundWorker();
// here you have also to implement the necessary events
// this event will define what the worker is actually supposed to do
this.backgroundWorker1 .DoWork += backgroundWorker1r_DoWork;
// this event will define what the worker will do when finished
this.backgroundWorker1 .RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
EDIT:
As I read your post a little more clearly. If you want to:
I try to run the BackGroundWorker from SerialPortClass
You have to make sure that you have an instance of the Form2 in your SerialPortClass. Note that if you just use the new keyword it might not be the same instance as is already shown on your monitor.
EDIT 2:
Ok there seems to be a pattern emerging. Please correct me if I am wrong
As I understand you open Form1 where you have a field SerialPortClass sp_class. In Form1 you press a button and this button calls the method:
sp_class.SetText();
Now you have a problem because apparently when you come to the line:
form2.backgroundWorker1.RunWorkerAsync();
form2 is null because it has never been instantiated! Please do the following: Create an instance and open the form from the SerialPortClass like this:
form2 = new Form2();
form2.backgroundWorker1.RunWorkerAsync();
form2.Show();
Now, the event registration of the backgroundWorker1 still belongs into the Form2 class , because this is where you actually let it run! The instance is still in Form2 please don't get mixed up in this triangle of Form1, Form2 and SerialPortClass
EDIT 3:
Since your SerialPort triggers the backgroundWorker1 you should already in the constructor of the SeralPortClass make an instance of the Form2 like this:
public SerialPortClass()
{
form2 = new Form2();
serialPort1.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
But what you really should do to unwind the knot that you created between your 3 classes which seem to depend on each other, is to pass the instance of the Form2 to the constructor of the SerialPortClass like this:
public SerialPortClass(Form2 f2)
{
form2 = f2
serialPort1.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
and I guess in Form1 you make an instance of Form2 which you call and the SerialPortClass which you use to call SetText. And exactly there you need to pass the instance into the contructor call:
SerialPortClass my_sp_class = new SerialPortClass(form2);
This will make sure that you get the display of text on the desired form

The issue is here.
form2.backgroundWorker1.RunWorkerAsync();
You have created a backgroundwoker but not registered any methods/delegates to it.
Assign it by adding this in constructor of form2,
this.backgroundWorker1.DoWork += backgroundWorker1_DoWork; //not found in snippet
this.backgroundWorker1.RunWorkerCompleted +=backgroundWorker1_RunWorkerCompleted;
Also, as mentioned by Mong-Zhu, you need to initialize Backgroundworker like this in form2.
this.backgroundWorker1 = new BackGroundWorker();
Also, I personally think it is not a good idea to call backgroundworker of different form.

After reading the comments and such, I can see that you're not referencing the same form2, and you're also not initializing a new one, meaning the said null reference is invalid.
When you do the code
Form2 form2;
This is simply preparing a variable to be assigned, and since you haven't done this you can't access it's objects. If you wish to reference your form2, initialise it globally where you're able to access it, and from there you can use it's objects.
If you need any more guidance just reply :)

It seems that your use of BackgroundWorker is incomplete. You can use it with the Designer, which is easier, or in the code-behind.
To use with the designer, drag and drop a backgroundWorker object onto the component tray. Then click on the newly created BackgroundWorker1 object. Go over to the properties tray and select the lightning bolt for events. There are 3 events, the two that you will need are the DoWork and RunWorkerCompleted. Double-click on both to generate the methods for you.
It appears in your code snippet that you are missing the DoWork part, as well as not actually instantiating your BackGroundWorker object. The way it flows is this: The BackgroundWorker is declared and initialized. When you need to make an asynchronous call you raise the .RunWorkerAsync event. That RunWorkerAsync event will make its way to your DoWork event handler. This is where you place the code for the work you wish to do asynchronously. Once this DoWork event expires, the RunWorkercompleted event is called. Once this RunWorkerCompleted event expires, so does the new thread.
If you try to change designer components while in a different thread than the thread it was created on (i.e. setting button text from inside the DoWork event handler for the BackgroundWorker), you will have to ensure to use the .InvokeRequired, as it appears you have already started.
If you are calling this backgroundworker from outside of form2 make sure that you are giving the handle to form2 to that class. It is unclear from the snippet whether you instantiated a new one or not. An easy way is to make a private field for Form2 in your Serialization class, as well as a method, say AttachForm(Form f) and then call that method from Form2, passing this as a parameter.

Related

How to run a windows form from a backgroundworker runnig through another backgroundworker?

this is the simplified plan for a solution:
for some reasons i need to run a windows form through a backgroundworker that is runnig by another backgroundworker, when the new windows form loads, the older backgroundworker must pause. i write the code like this :
creating a class with name : temp
public class temp
{
static public BackgroundWorker backgroundWorker1 = new BackgroundWorker() { WorkerSupportsCancellation = true };
static public EventWaitHandle ew = new EventWaitHandle(false, EventResetMode.ManualReset);
static public BackgroundWorker back = new BackgroundWorker() { WorkerSupportsCancellation = true };
}
the codes for form1 are :
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
temp.backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
temp.back.DoWork += new DoWorkEventHandler(back_DoWork);
}
void back_DoWork(object sender, DoWorkEventArgs e)
{
Form2 f = new Form2();
f.Show();
}
private void button1_Click(object sender, EventArgs e)
{
temp.backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
temp.back.RunWorkerAsync();
if (temp.backgroundWorker1.CancellationPending)
temp.ew.WaitOne();
}
}
}
and the codes of form2 goes here :
namespace WindowsFormsApplication1
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e)
{
temp.backgroundWorker1.CancelAsync();
temp.ew.Reset();
}
}
}
by clicking the button1 from form1 the temp.backgroundworker1 runs and then in the DoWork of temp.backgroundworker1, the temp.back runs and then FORM2 LOADS BUT THE FORM2 HANGS AND BECOMES USELESS AND YOU CANNOT USE THAT ANY MORE.
where did i wrong ?
the whole plan that i'm going to execute is :
we have a For loop that processes every row of a DataGridView.
each time in a certain point, another windowsform opens
and it stops the loop until the user inserts the information and then click on OK button, the windowsform closes and the loop keep on working. i dont know what to do.......
even if i dont cancel working of the temp.backgroundworker in form2load like the code below, the Form2 is useless
private void Form2_Load(object sender, EventArgs e)
{
}
Do not use any UI operation in the work thread (DoWork method). Maybe that's why you set the CheckForIllegalCrossThreadCalls property, but your app will not work properly just suppresses the error when the debugger is attached.
See my answer here for the correct usage of the BackgroundWorker (that is about canceling but you can see the operations in UI and worker thread).
In this particular case what you can use a similar volatile bool to sign the UI thread that the form can be shown. Or, if you want to send different messages between the threads, use a ConcurrentQueue<T> to write and read messages:
public partial class Form1 : Form
{
private enum Message
{
ShowForm2,
SuspendWork,
ResumeWork,
FinishWorker1
// ... and whatever you want
}
private Timer timer;
private ConcurrentQueue<Message> messagesToUI = new ConcurrentQueue<Message>();
private ConcurrentQueue<Message> messagesToWorker = new ConcurrentQueue<Message>();
public Form1()
{
InitializeComponent();
timer = new Timer(this);
timer.Interval = 10;
timer.Tick += PollUIMessages;
timer.Enabled = true;
}
void PollUIMessages(object sender, EventArgs e)
{
// do we have a new message?
Message message;
if (messagesToUI.TryDequeue(out message))
{
switch (message)
{
case Message.ShowForm2:
Form2 f = new Form2();
f.Show();
// todo: in Form2.Close add a Resume message to the messagesToWorker
break;
// ... process other messages
}
}
}
void back_DoWork(object sender, DoWorkEventArgs e)
{
// Here you are in the worker thread. You can send a message to the
// UI thread like this:
messagesToUI.Enqueue(Message.ShowForm2);
bool isWorking = true;
// and here you can poll the messages to the worker thread
while (true)
{
Message message;
if (!messagesToWorker.TryDequeue(out message))
{
// no message: idle or work
if (isWorking)
DoSomeWork(); // do whatever you want
else
Thread.CurrentThread.Sleep(10);
continue;
}
switch (message)
{
case Message.FinishWorker1:
// finishing the worker: jumping out
return;
case Message.SuspendWork:
isWorking = false;
break;
case Message.ResumeWork:
isWorking = true;
break;
}
}
}

BackgroundWorker: InvalidOperationException in RunWorkerCompleted

I have a WinForm with a backgroundWorker:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SeoTools.Utils;
namespace SeoTools.UI
{
public partial class UIProgress : Form
{
public UIProgress(DoWorkEventHandler doWorkEventHandler, RunWorkerCompletedEventHandler runWorkerCompletedEventHandler)
{
InitializeComponent();
this.backgroundWorker.WorkerReportsProgress = true;
this.backgroundWorker.WorkerSupportsCancellation = true;
this.backgroundWorker.DoWork += doWorkEventHandler;
this.backgroundWorker.RunWorkerCompleted += runWorkerCompletedEventHandler;
}
public void Start()
{
var foo = SynchronizationContext.Current;
backgroundWorker.RunWorkerAsync();
}
private void btnStop_Click(object sender, EventArgs e)
{
btnStop.Enabled = false;
btnStop.Text = "Stopping...";
backgroundWorker.CancelAsync();
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
try
{
wdgProgressBar.Value = e.ProgressPercentage;
if (this.Visible == false)
{
this.ShowDialog();
this.Update();
}
}
catch (InvalidOperationException) {}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.Hide(); //Here I get a InvalidOperationException
this.Dispose();
}
}
}
First time I run this it works fine. But second time I get InvalidOperationException when calling this.Hide().
"Additional information: Cross-thread operation not valid: Control 'UIProgress' accessed from a thread other than the thread it was created on."
The weird thing is on first run foo in Start() is a WindowsFormsSyncronizationContext but on the second try it's a System.Threading.SyncronizationContext.
The application I'm writing is a ExcelDna plugin.
EDIT
Start() is called like this:
UIProgress uiProgress = new UIProgress(
delegate(object sender, DoWorkEventArgs args)
{
....
},
delegate(object sender, RunWorkerCompletedEventArgs args)
{
...
}
);
uiProgress.Start();
Your Start() method must be called from code that runs on the UI thread to allow the BackgroundWorker to operate correctly. It was not when you get this exception. Add protective code to your method so you can diagnose this mishap:
public void Start()
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) {
throw new InvalidOperationException("Bug! Code called from a worker thread");
}
backgroundWorker.RunWorkerAsync();
}
Now you can set a breakpoint on the throw statement and use the debugger's Call Stack window to find out why this happened.
You are calling UI operation on background thread. This is the reason for that exception. I would use entirely different method to make the progress form the best one is to use Task with IProgress. The other way it to use this:
private void backgroundWorker_ProgressChanged( object sender , ProgressChangedEventArgs e )
{
this.UpdateOnMainThread(
( ) =>
{
wdgProgressBar.Value = e.ProgressPercentage;
if ( this.Visible == false )
{
this.ShowDialog( );
this.Update( );
}
} );
}
private void UpdateOnMainThread( Action action )
{
if ( this.InvokeRequired )
{
this.BeginInvoke( ( MethodInvoker ) action.Invoke);
}
else
{
action.Invoke( );
}
}
private void backgroundWorker_RunWorkerCompleted( object sender , RunWorkerCompletedEventArgs e )
{
this.UpdateOnMainThread(
( ) =>
{
this.Hide( ); //Here I get a InvalidOperationException
this.Dispose( );
} );
}
Use the BeginInvoke() method on the form:
//http://msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.110).aspx
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.BeginInvoke(new InvokeDelegate(InvokeMethod));
}
public delegate void InvokeDelegate();
public void InvokeMethod()
{
this.Hide();
this.Dispose();
}
I think you can find some help here: BackgroundWorker hide form window upon completion .
However don't forget to detach BackgroundWorker events and stop BackgroundWorker it self like explained in here: Proper way to Dispose of a BackGroundWorker .
The problem can be in the
this.Dispose();
in the backgroundWorker_RunWorkerCompleted event. With that you are disposing form page. Is that what you want to do? Or you want to dispose BackgroundWorker? Disposing form page all resources are released so doing this.Hide(); a second time can be a mistake.
For more info, you can see this links: C# Form.Close vs Form.Dispose and Form.Dispose Method
You must check this link
How to update the GUI from another thread in C#?
Probably have all the possible answers
Hope this Help
You're running a call to the main thread from a thread that can't manipulate UI.
The simplest way is to use anonymous delegate invoke.
Change this:
if (this.Visible == false)
{
this.ShowDialog();
this.Update();
}
For this:
this.Invoke((MethodInvoker) delegate {
if (this.Visible == false)
{
this.ShowDialog();
this.Update();
}
});
It's not the most optimized way but does the job awesomely fast without much recode. :)

updating gui from another class c#

hey i am new to c# plz help.
i am writing a program that sorts data in a file and it is a time consuming process so i thought that i should run it in a separate thread and since it has alot of step so i made a new class for it. the problem is that i want to show the progress in the main GUI and i know for that i have to use Invoke function but the problem is that the form control variables are not accessible it this class. what should i do ??????
sample code:
public class Sorter
{
private string _path;
public Sorter(string path)
{
_path = path;
}
public void StartSort()
{
try
{
processFiles(_path, "h4x0r"); // Just kidding
}
catch (Exception e)
{
MessageBox.Show("Error: " + e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void processFiles(string Dir, string[] key)
{
/* sorting program */
}
and it is used as
public partial class Form1 : Form
{
Sorter sort;
public Form1()
{
InitializeComponent();
}
private void browseBtn_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
textBox1.Text = folderBrowserDialog1.SelectedPath;
}
private void startBtn_Click(object sender, EventArgs e)
{
if (startBtn.Text == "Start Sorting")
{
Thread worker = new Thread(new ThreadStart(delegate() {
sort = new Sorter(textBox1.Text);
sort.StartSort(); }));
worker.start();
}
else
MessageBox.Show("Cancel");//TODO: add cancelling code here
}
}
plz help..
Add an Event to your class that is doing the multi-threaded work, that triggers when the progress changes. Have your form subscribe to this event and update the progress bar.
Note ProgressEventArgs is a little class that inherits EventArgs and has an Integer for the progress.
// delegate to update progress
public delegate void ProgressChangedEventHandler(Object sender, ProgressEventArgs e);
// Event added to your worker class.
public event ProgressChangedEventHandler ProgressUpdateEvent
// Method to raise the event
public void UpdateProgress(object sender, ProgressEventArgs e)
{
ProgressChangedEventHandler handler;
lock (progressUpdateEventLock)
{
handler = progressUpdateEvent;
}
if (handler != null)
handler(sender, e);
}
I would recommend you read up on the BackgroundWorker class. It is exactly for the problem you are trying to solve and makes things a lot easier than doing manual threading yourself.
Brief Example
public Form1()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void btnStart_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i < 101; ++i)
{
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
//Sort Logic is in here.
Thread.Sleep(250);
backgroundWorker.ReportProgress(i);
}
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy && backgroundWorker.WorkerSupportsCancellation)
backgroundWorker.CancelAsync();
}
You could do something like this:
public delegate void StatusReporter(double progressPercentage);
public class MainClass
{
public void MainMethod()
{
Worker worker = new Worker(ReportProgress);
ThreadStart start = worker.DoWork;
Thread workThread = new Thread(start);
workThread.Start();
}
private void ReportProgress(double progressPercentage)
{
//Report here!!!
}
}
public class Worker
{
private readonly StatusReporter _reportProgress;
public Worker(StatusReporter reportProgress)
{
_reportProgress = reportProgress;
}
public void DoWork()
{
for (int i = 0; i < 100; i++ )
{
// WORK, WORK, WORK
_reportProgress(i);
}
}
}
There are a few option available to solve this sort of issue. In any case, you will have to fiddle with Invoke to get the UI to update.
You could...
...add an event that fires on your new class which your UI can listen to, and Invoke as applicable - you'd still need to pass the data to your worker class (by constructor, properties, method call, etc)
...keep the method as a method on your form, and pas that to start your new thread from (after all, a new thread doesn't have to be starting in a different class)
...change the access modifiers on your controls to be (say) internal such that any class within the same assembly can Invoke changes to the controls, or read from them.
...make your worker class a child of the form it needs to access - it can then see the privates of its parent, as long as it is passed a reference to the instance.

cross thread call

using System;
using System.Windows.Forms;
using agsXMPP;
using System.Text;
namespace iTalk2
{
public partial class Main : Form
{
agsXMPP.XmppClientConnection objXmpp;
public Main()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Console.WriteLine("Logging in. Please wait...");
Console.ReadLine();
objXmpp = new agsXMPP.XmppClientConnection();
agsXMPP.Jid jid = null;
jid = new agsXMPP.Jid("username" + "#gmail.com");
objXmpp.Password = "password";
objXmpp.Username = jid.User;
objXmpp.Server = jid.Server;
objXmpp.AutoResolveConnectServer = true;
try
{
objXmpp.OnMessage += messageReceived;
objXmpp.OnAuthError += loginFailed;
objXmpp.OnLogin += loggedIn;
objXmpp.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
}
private void messageReceived(object sender, agsXMPP.protocol.client.Message msg)
{
string[] chatMessage = null;
chatMessage = msg.From.ToString().Split('/');
agsXMPP.Jid jid = null;
jid = new agsXMPP.Jid(chatMessage[0]);
agsXMPP.protocol.client.Message autoReply = null;
autoReply = new agsXMPP.protocol.client.Message(jid, agsXMPP.protocol.client.MessageType.chat, "This is a test");
objXmpp.Send(autoReply);
}
private void loginFailed(object o, agsXMPP.Xml.Dom.Element el)
{
Console.WriteLine("Login failed. Please check your details.");
}
private void loggedIn(object o)
{
Console.WriteLine("Logged in and Active.");
lblStatus.Text = "Online";
}
private void txtUsername_TextChanged(object sender, EventArgs e)
{
}
private void label1_Click(object sender, EventArgs e)
{
}
private void label2_Click(object sender, EventArgs e)
{
}
private void txtPassword_TextChanged(object sender, EventArgs e)
{
}
private void btnlogin_Click(object sender, EventArgs e)
{
}
}
}
This code is not working. the function 'loggedIn(object o)' is not working. it says the lblStatus (which is a label) is on another thread. the error window says "Cross-thread operation not valid: Control 'lblStatus' accessed from a thread other than the thread it was created on." thanks in advance.
You need to invoke a call on the UI thread. If you add code as follows at the top of the loggedIn method it should work:-
if(InvokeRequired)
{
Invoke(new Action<object>(loggedIn), o);
return;
}
WinForms is designed such that controls must only be manipulated on the UI-thread, the thread that runs the message-loop that manages the control.
Try this instead:
private void loggedIn(object o)
{
Console.WriteLine("Logged in and Active.");
Action act = () => lblStatus.Text = "Online";
Invoke(act);
}
If your application is such that this method can be called on the UI thread or a separate worker thread, you'd be better off testing forInvokeRequired(simply: am I on the control's UI thread?) and dealing with the result appropriately. For example,
private void loggedIn(object o)
{
if(InvokeRequired)
Invoke(new Action<object>(loggedIn), o);
else
{
Console.WriteLine("Logged in and Active.");
lblStatus.Text = "Online";
}
}
Note that Invokewill block until the UI-update is completed. If you want something more fire-and-forget, use BeginInvokeinstead.
When you start an application it is running from a single thread. This is the main thread, sometimes called the UI thread (since the UI will usually be rendered at startup and as a consequence it will be on that main thread.
Now, when you listen to events, your methods/delegates will get called from new threads. This is a consequence of the event based design. Normally this is not a problem unless you are trying to share data between two threads. This is exactly what happens with your UI elements. In this case your UI elements were created by your first thread but other threads are trying to update its value.
Given your design, you should check for IsInvokeRequired on the control and if so, use Invoke to set the new value. This will marshal your call from the new thread into the main thread that your UI is running on and will allow you to safely change the control.

Listening to Events in Main Form from Another Form in C#

I have an application that has a main form and uses an event handler to process incoming data and reflect the changes in various controls on the main form. This works fine.
I also have another form in the application. There can be multiple instances of this second form running at any given time.
What I'd like to do is have each instance of this second form listen to the event handler in the main form and update controls on its instance of the second form.
How would I do this?
Here's some sample code. I want to information from the_timer_Tick event handler to update each instance of SecondaryForm.
public partial class Form1 : Form
{
Timer the_timer = new Timer();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
the_timer.Tick += new EventHandler(the_timer_Tick);
the_timer.Interval = 2000;
the_timer.Enabled = true;
}
void the_timer_Tick(object sender, EventArgs e)
{
// I would like code in here to update all instances of SecondaryForm
// that happen to be open now.
MessageBox.Show("Timer ticked");
}
private void stop_timer_button_Click(object sender, EventArgs e)
{
the_timer.Enabled = false;
}
private void start_form_button_Click(object sender, EventArgs e)
{
SecondaryForm new_form = new SecondaryForm();
new_form.Show();
}
}
class SecondForm
{
private FirstForm firstForm;
public SecondForm()
{
InitializeComponent();
// this means unregistering on form closing, uncomment if is necessary (anonymous delegate)
//this.Form_Closing += delegate { firstForm.SomeEvent -= SecondForm_SomeMethod; };
}
public SecondaryForm(FirstForm form) : this()
{
this.firstForm = form;
firstForm.Timer.Tick += new EventHandler(Timer_Tick);
}
// make it public in case of external event handlers registration
private void Timer_Tick(object sender, EventArgs e)
{
// now you can access firstForm or it's timer here
}
}
class FirstForm
{
public Timer Timer
{
get
{
return this.the_timerl
}
}
public FirstForm()
{
InitializeComponent();
}
private void Button_Click(object sender, EventArgs e)
{
new SecondForm(this).ShowDialog(); // in case of internal event handlers registration (in constructor)
// or
SecondForm secondForm = new SecondForm(this);
the_timer.Tick += new EventHandler(secondForm.Timer_tick); // that method must be public
}
Consider using loosely coupled events. This will allow you to couple the classes in such a way that they never have to be directly aware of each other. The Unity application block comes with an extension called EventBroker that makes this very simple.
Here's a little lick of the sugar:
public static class EVENTS
{
public const string UPDATE_TICKED = "event://Form1/Ticked";
}
public partial class Form1 : Form
{
[Publishes(EVENTS.UPDATE_TICKED)]
public event EventHandler Ticked;
void the_timer_Tick(object sender, EventArgs e)
{
// I would like code in here to update all instances of SecondaryForm
// that happen to be open now.
MessageBox.Show("Timer ticked");
OnTicked();
}
protected virtual void OnTicked()
{
if (Ticked == null) return;
Ticked(this, e);
}
}
public partial class SecondaryForm : Form
{
[SubscribesTo(EVENTS.UPDATE_TICKED)]
private void Form1_Ticked(object sender, EventHandler e)
{
// code to handle tick in SecondaryForm
}
}
Now if you construct both of these classes using Unity, they will automatically be wired together.
Update
Newer solutions use message bus to handle loosely coupled events. See http://masstransit-project.com/ or http://nimbusapi.github.io/ as examples.
I guess you can make SecondaryForm take in the parent form in the constructor, and the add an event handler in the constructor.
private void start_form_button_Click(object sender, EventArgs e)
{
SecondaryForm new_form = new SecondaryForm(this);
new_form.Show();
}
In SecondaryForm.cs:
public SecondaryForm(ISomeView parentView)
{
parentView.SomeEvent += .....
}

Categories