Reputation: 149
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;
Upvotes: 0
Views: 561
Reputation: 23732
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
Upvotes: 1
Reputation: 449
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.
Upvotes: 0
Reputation: 1724
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 :)
Upvotes: 0
Reputation: 4000
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.
Upvotes: 0