0

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;
chasher
  • 149
  • 11

4 Answers4

1

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

Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
  • Thanks Mong Zhu, I've not added the missing initialisation in the constructor and still get the exception thrown. I have an instance of Form2: "Form2 form2;" where do I use the new keyword please? – chasher Dec 05 '16 at 13:47
  • @chasher you have **not** added [..] and still get the error ? So you did not change anything and the error persists? How do you get the `form2` instance into your `SerialPortClass` ? could you post the entire class? Could you also post the call-site of `SerialPortClass.SetText()` ? this is decisive to understand your problem. I guess `form2` is `null` in `form2.backgroundWorker1.RunWorkerAsync();` am I right? what does the debugger tell you, when stepping through? – Mong Zhu Dec 05 '16 at 13:53
  • sorry "now" added. form2 instance 'Form2 form2;' in 'SerialPortClass' posting code in a minute or two thanks! – chasher Dec 05 '16 at 14:03
  • @chasher I edited my response have a look. You seem to be confused how the data within this triangle is passed around. Important is the call of this method: `SerialPortClass.SetText()` this you have not shown . Where does this happen? in `Form1` ? – Mong Zhu Dec 05 '16 at 14:12
  • the method SetText is called from port_DataRecieved_1 – chasher Dec 05 '16 at 14:22
  • @chasher ahh ok. I thought you want to use the `BackGroundWorker` for the communication with the serial port and not the other way around. Ok you serial port device is actually triggering the `BackGroundWorker`. I will edit my answer a last time. But you need to change the entire structure and data flow of your project. It is a knot that is difficult to untie in here. – Mong Zhu Dec 05 '16 at 14:25
  • thanks I kinda understand what you mean, trying to implement now, getting errors at Form2 = new Form2();, must declare a body because it is not marked abstract, extern of partial – chasher Dec 05 '16 at 15:07
  • @chasher you cannot call `Form2 = new Form2();` because `Form2` is a type. This is wrong syntax. You can only call something like `Form2 myform2 = new Form2();` you need a name for the variable that you want to create an instance of. – Mong Zhu Dec 05 '16 at 15:36
  • 1
    Thanks for your help, I'm using what I've learned from here and starting a new solution, with one form1, with a button to open form2 and gradually build up from there – chasher Dec 05 '16 at 15:56
  • @chasher good luck. Have a look on [this post](http://stackoverflow.com/questions/38871128/one-class-data-use-in-another-class-data-c-sharp/38871234#38871234) and [this](http://stackoverflow.com/questions/38792677/how-to-link-combobox-data-source-from-form1-to-form2-combobox/38793132#38793132), it might help – Mong Zhu Dec 05 '16 at 16:03
0

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.

Prajwal
  • 3,930
  • 5
  • 24
  • 50
  • Thanks Prajwal, I've tried what you and Mong Zhu have suggested, but still get exception when calling backgroundworker, what would be a better way of achieving what I want please? – chasher Dec 05 '16 at 13:50
  • What's the exception? the same? – Prajwal Dec 05 '16 at 13:52
  • We all have assumed that you have a reference to `Form2` as`form2` in `SerialPortClass`. Do you have it? – Prajwal Dec 05 '16 at 13:55
  • @ Prajwal yes reference : 'Form2 form2;' in 'SerialPortClass' – chasher Dec 05 '16 at 14:00
  • That is not a reference to old instance of `Form2`. you are creating a new `Form2` which has nothing to do with old `Form2` instance. Unless `form2` is child of `SerialPortClass`. – Prajwal Dec 05 '16 at 14:03
  • @Prajwal `Form2 form2;` does not create any instance. It is simply a variable declaration. I think OP is not yet clear about the difference. Although the point with the original Form is still valid – Mong Zhu Dec 05 '16 at 15:38
  • Yea. Just poor choice of words, that's it. :) – Prajwal Dec 05 '16 at 16:12
0

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 :)

Sasha
  • 1,674
  • 1
  • 16
  • 23
0

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.

Rhurac
  • 439
  • 4
  • 16