Link to home
Start Free TrialLog in
Avatar of judico
judico

asked on

Passing of values between functions

I am trying to plot the incoming data from a multimeter as a function of time and for that purpose I’m trying to use the following code:



#pragma once

#include "MultiMeter.h"

static int evtCounter;
static double dMeterReading ;

using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;


namespace Q307398
{
      /// <summary>
      /// Summary for Form1
      ///
      /// WARNING: If you change the name of this class, you will need to change the
      ///          'Resource File Name' property for the managed resource compiler tool
      ///          associated with all .resx files this class depends on.  Otherwise,
      ///          the designers will not be able to interact properly with localized
      ///          resources associated with this form.
      /// </summary>
      public __gc class temperature : public System::Windows::Forms::Form
      {      
      public:
            temperature(void)
            {
                  InitializeComponent();
            }
 

      protected:
            void Dispose(Boolean disposing)
            {
                  if (disposing && components)
                  {
                        components->Dispose();
                  }
                  __super::Dispose(disposing);
            }


    private: System::Windows::Forms::TextBox *  textBox1;
    private: System::Timers::Timer* aTimer;
    //private: int evtCounter;
             MultiMeter *mmeter;

   private: System::ComponentModel::IContainer *  components;

   private:
            /// <summary>
            /// Required designer variable.
            /// </summary>


            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            void InitializeComponent(void)
            {
                  this->textBox1 = new System::Windows::Forms::TextBox();
                  this->SuspendLayout();
                  //
                  // textBox1
                  //
                  this->textBox1->Location = System::Drawing::Point(520, 8);
                  this->textBox1->Name = S"textBox1";
                  this->textBox1->ReadOnly = true;
                  this->textBox1->TabIndex = 3;
                  this->textBox1->Text = S"";
                  this->textBox1->TextAlign = System::Windows::Forms::HorizontalAlignment::Center;
                  //
                  // temperature
                  //
                  this->AutoScaleBaseSize = System::Drawing::Size(5, 13);
                  this->ClientSize = System::Drawing::Size(632, 466);
                  this->Controls->Add(this->textBox1);
                  this->Name = S"temperature";
                  this->Text = S"Form1";
                  this->ResumeLayout(false);

            }      
   
     
      protected:
      void OnPaint( PaintEventArgs *paintEvent )      
      {
         
              // call base OnPaint method
              __super::OnPaint( paintEvent );
         
             // get graphics object
         Graphics *graphicsObject = paintEvent->Graphics;
         SolidBrush *brush = new SolidBrush( Color::Blue );
         Pen *pen = new Pen( Color::Black );
 



                mmeter = new MultiMeter;
            mmeter->ConfigureSerialInterface();
            aTimer = new System::Timers::Timer(500);
            aTimer->Elapsed+=new System::Timers::ElapsedEventHandler(this, OnTimedEvent);
            // Only raise the event the first time Interval elapses.
            aTimer->AutoReset = false;
            aTimer->Enabled = true;

textBox1->Text = System::Convert::ToString(dMeterReading);
graphicsObject->DrawEllipse( pen,evtCounter, dMeterReading * 100, 7, 7 );
                  
}


private:
      void OnTimedEvent(Object* source, System::Timers::ElapsedEventArgs* e)
       {
         try
            {
                        evtCounter += 1;
                //textBox1->Text = System::Convert::ToString(evtCounter,10);

                BYTE *pBuf = NULL;
                        dMeterReading = mmeter->ReadData();
                //textBox1->Text = System::Convert::ToString(dMeterReading);
            
                // Restart timer
                aTimer->Start();
            }
            catch (System::Exception *ex)
            {
                System::Console::WriteLine(ex->Message);
            }  
        }

     };
}



Unfortunately, The values of evtCounter and dMeterReading created in the 'void OnTimedEvent' function cannot be seen in the 'void OnPaint' despite the fact that both variables are declared as static and supposedly should be seen globally. How can these variables be made visible in the part of the program where the OnPaint method is applied?


P.S. Also, is there a way to avoid the above timer code and instead use the timer1 object from the toolbox and just refresh its clicks -- I tried that but it didn't work in this case.
Avatar of drichards
drichards

Easy things first.  You can easily use the toolbox timer.  Just move the code from 'OnTimedEvent' into the auto-generated 'timer1_Tick' method.  The only difference is that you need to add a timer1->Stop() at the beginning of the method so you are not generating extra events while you are processing a previous event.  They stack up.

What didn't work when you tried it?  Worked OK for me.
Next thing is that All the initialization code should not be in OnPaint which is called MANY times.  If you want it started automatically and not by the button click, it should go in the constructor right after the call to InitializComponent.  If you switch to the other timer, you can remove all the timer code shown here.  Constructor like this:

          temperature(void)
          {
               InitializeComponent();
              mmeter = new MultiMeter;
            mmeter->ConfigureSerialInterface();
            aTimer = new System::Timers::Timer(500);
            aTimer->Elapsed+=new System::Timers::ElapsedEventHandler(this, OnTimedEvent);
            // Only raise the event the first time Interval elapses.
            aTimer->AutoReset = false;
            aTimer->Enabled = true;
          }

and OnPaint like this:

     void OnPaint( PaintEventArgs *paintEvent )    
      {
         
            // call base OnPaint method
            __super::OnPaint( paintEvent );
         
           // get graphics object
         Graphics *graphicsObject = paintEvent->Graphics;
         SolidBrush *brush = new SolidBrush( Color::Blue );
         Pen *pen = new Pen( Color::Black );

         textBox1->Text = System::Convert::ToString(dMeterReading);
         graphicsObject->DrawEllipse( pen,evtCounter, dMeterReading * 100, 7, 7 );      
}

If yo put it anywhere but the constructor, do this:

     if ( mmeter == NULL )
     {
            mmeter = new MultiMeter;
            mmeter->ConfigureSerialInterface();
            aTimer = new System::Timers::Timer(500);
            aTimer->Elapsed+=new System::Timers::ElapsedEventHandler(this, OnTimedEvent);
            // Only raise the event the first time Interval elapses.
            aTimer->AutoReset = false;
            aTimer->Enabled = true;
     }

That will at least protect you from reinitializing over and over.  Now on to the other issues...
Put 'evtCounter' and 'dMeterReading' inside the Form1 class (right under or above 'mmeter'.  There's a commented-out version of evtCounter there.  Also, don't make them static.  They will be member variables of Form1.  That should work just fine as all your methods using these values are defined in Form1.
Sorry, for you it's the 'temperature' class.  Code will look like:

    private: System::Windows::Forms::TextBox *  textBox1;
    private: System::Timers::Timer* aTimer;
                int evtCounter;
                double dMeterReading;
                MultiMeter *mmeter;

They should have worked the way you had them, however, was there an error or did something just not work right?

You should also initialize evtCounter in the constructor.

Also normalize dMeterReading prior to drawing circle so it won't go off the drawing area.
Avatar of judico

ASKER

Unfortunately, the main problem remains -- no matter where and how one declares  'evtCounter' and 'dMeterReading' they remain invisible for the OnPaint method -- the value in textBox3 is zero and the ellipse doesn't move on the screen (I'm not doing normalization here because this is only an example to see if anything happens at all.)

Otherwise 'evtCounter' and 'dMeterReading' are properly created and their values can be seen in textBox1 and textBox2, respectively, which are in the void OnTimedEvent.

Here is the current code, if this can help:



#pragma once

#include "MultiMeter.h"

//static int evtCounter;
//static double dMeterReading;

using namespace System;
using namespace System::IO;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;

namespace Q307398
{
      /// <summary>
      /// Summary for Form1
      ///
      /// WARNING: If you change the name of this class, you will need to change the
      ///          'Resource File Name' property for the managed resource compiler tool
      ///          associated with all .resx files this class depends on.  Otherwise,
      ///          the designers will not be able to interact properly with localized
      ///          resources associated with this form.
      /// </summary>
      public __gc class temperature : public System::Windows::Forms::Form
      {      
      public:
            temperature(void)
            {
                  InitializeComponent();
            }
 
      protected:
            void Dispose(Boolean disposing)
            {
                  if (disposing && components)
                  {
                        components->Dispose();
                  }
                  __super::Dispose(disposing);
            }

    private: System::Windows::Forms::TextBox *  textBox1;
    private: System::Timers::Timer* aTimer;
                int evtCounter;
                double dMeterReading;
                MultiMeter *mmeter;
      private: System::Windows::Forms::Timer *  timer1;
      private: System::Windows::Forms::TextBox *  textBox2;
      private: System::Windows::Forms::TextBox *  textBox3;
      private: System::ComponentModel::IContainer *  components;

      
      private:
            /// <summary>
            /// Required designer variable.
            /// </summary>


            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            void InitializeComponent(void)
            {
                  this->components = new System::ComponentModel::Container();
                  this->textBox1 = new System::Windows::Forms::TextBox();
                  this->timer1 = new System::Windows::Forms::Timer(this->components);
                  this->textBox2 = new System::Windows::Forms::TextBox();
                  this->textBox3 = new System::Windows::Forms::TextBox();
                  this->SuspendLayout();
                  //
                  // textBox1
                  //
                  this->textBox1->Location = System::Drawing::Point(520, 8);
                  this->textBox1->Name = S"textBox1";
                  this->textBox1->ReadOnly = true;
                  this->textBox1->TabIndex = 3;
                  this->textBox1->Text = S"";
                  this->textBox1->TextAlign = System::Windows::Forms::HorizontalAlignment::Center;
                  //
                  // timer1
                  //
                  this->timer1->Enabled = true;
                  this->timer1->Tick += new System::EventHandler(this, timer1_Tick);
                  //
                  // textBox2
                  //
                  this->textBox2->Location = System::Drawing::Point(520, 48);
                  this->textBox2->Multiline = true;
                  this->textBox2->Name = S"textBox2";
                  this->textBox2->ReadOnly = true;
                  this->textBox2->Size = System::Drawing::Size(104, 24);
                  this->textBox2->TabIndex = 4;
                  this->textBox2->Text = S"";
                  this->textBox2->TextAlign = System::Windows::Forms::HorizontalAlignment::Center;
                  //
                  // textBox3
                  //
                  this->textBox3->Location = System::Drawing::Point(520, 136);
                  this->textBox3->Multiline = true;
                  this->textBox3->Name = S"textBox3";
                  this->textBox3->ReadOnly = true;
                  this->textBox3->Size = System::Drawing::Size(104, 24);
                  this->textBox3->TabIndex = 5;
                  this->textBox3->Text = S"";
                  this->textBox3->TextAlign = System::Windows::Forms::HorizontalAlignment::Center;
                  //
                  // temperature
                  //
                  this->AutoScaleBaseSize = System::Drawing::Size(5, 13);
                  this->ClientSize = System::Drawing::Size(632, 466);
                  this->Controls->Add(this->textBox3);
                  this->Controls->Add(this->textBox2);
                  this->Controls->Add(this->textBox1);
                  this->Name = S"temperature";
                  this->StartPosition = System::Windows::Forms::FormStartPosition::CenterScreen;
                  this->Text = S"Form1";
                  this->ResumeLayout(false);

            }      
   
   
private:
            void OnTimedEvent(Object* source, System::Timers::ElapsedEventArgs* e)
                  {
         try
            {
                        evtCounter += 1;
                textBox1->Text = System::Convert::ToString(evtCounter,10);

                BYTE *pBuf = NULL;
                    dMeterReading = mmeter->ReadData();
                textBox2->Text = System::Convert::ToString(dMeterReading);
                        // Restart timer
                aTimer->Start();
            }
            catch (System::Exception *ex)
            {
                System::Console::WriteLine(ex->Message);
            }  
        }




protected:
      void OnPaint( PaintEventArgs *paintEvent )      
                              {
              // call base OnPaint method
              __super::OnPaint( paintEvent );
         
             // get graphics object
         Graphics *graphicsObject = paintEvent->Graphics;
         SolidBrush *brush = new SolidBrush( Color::Blue );
         Pen *pen = new Pen( Color::Black );
 
             textBox3->Text = System::Convert::ToString(dMeterReading);
         graphicsObject->DrawEllipse( pen,dMeterReading, dMeterReading * 100, 7, 7 );
                              }




private: System::Void timer1_Tick(System::Object *  sender, System::EventArgs *  e)
             {
  if ( mmeter == NULL )
                        {
            timer1->Stop();
            mmeter = new MultiMeter;
            mmeter->ConfigureSerialInterface();
            aTimer = new System::Timers::Timer(500);
            aTimer->Elapsed+=new System::Timers::ElapsedEventHandler(this, OnTimedEvent);
            // Only raise the event the first time Interval elapses.
            aTimer->AutoReset = false;
            aTimer->Enabled = true;
            evtCounter = 0;
                        }
             }


};
}


P.S. As far as the timer goes, I found out that the timer had been working all the time. The different variants you propose also work. Because the values of evtCounter are not passed to the  OnPaint function I mistakenly have thought that the timer doesn't work. So, that part is all set.
ASKER CERTIFIED SOLUTION
Avatar of drichards
drichards

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of judico

ASKER

Thnaks very much. That really did it. One more thing -- how can I avoid the interruption when a checksum exception occurs? Can the program overcome the faulty event and then proceed with the next proper one? I tried remming out msg->Append(S"22-812 checksum failure"); but the program complains.

P.S. I increased your points to 500. Please let me know if you have any problems in receiving the increased points. Thanks again.
Just comment out the line that displays the message box:

//            System::Windows::Forms::MessageBox::Show(msg->ToString());

You can add some other processing of the error if you want or just ignore it if you don't care.  I think you can just continue on and hopefully it will recover.

The problem is that if you get in a mode where the whole packet is not read in one chunk, the program will not work.  What you really want is to stream the bytes into a data buffer and remove bytes from the stream as they are processed.   That way you don't care if the whole packet gets received in one chunk.  If you have continued problems with checksum failure, you should look into alternatives like this for processing the data.  If they are just intermittent and the program recovers, you probably don't have to worry about it.
Avatar of judico

ASKER

Thanks. I hope that commenting out that line will be all I need to do now. If the data is not received in one chunk it will be ignored and the next package, hopefully intact, will be read. In this application occasionally ignoring of data will not be a problem. There's another application where it will be but I'll worry about it then. I'd like to finish this project first.

I forgot to ask you something important. When repainting the main form window through  Invalidate(); only the new point is depicted and the previous one is erased. Thus, you have a point moving in time across the screen. I need, however, to see a trace whereby all previous points are retained on the screen. I thought I should probably build an array of the the point values and a for-next loop should redraw it every tiime a new form is repainted. How would you suggext to do that?
You already have the 'evtCounter' counting the data reads.  Just change dMeterReading to an array and index it with evtCounter.  You'll have to decide what do do when you reach the end of the array - either don't read more points or start over at the beginning.

so instead of "double dMeterReading;" you'd have "double dMeterReading __gc[];" and in initialization code "dMeterReading = new double __gc[1000];".  You can replace 100 with a value of your choice.  Then everywhere in the code where you use dMeterReading, replace it with dMeterReading[evtCounter].  And when you increment evtPointer, make sure it doesn't exceed 999 (or whatever value you choose).
Avatar of judico

ASKER

It happened so, I just did that before reading your posting. Works fine but the first point is always zero. Maybe that's where the checksum exception occurs. I don't want that first point but I can't get rid of it.
You can modify the program to ignore points where checksum errors happen.

1) Set dMeterReading to System::Double::MaxValue on error and check this value on return
2)  Set an error flag in multimeter and read it after getting dMeterreading
3) Add a pointer parameter to the read function and pass a pointer to a value that will accept an error code
4) Have the read function return error status and pass dMeterReading to the read function as a pointer.

After the old messagebox line that you removed, do "return System::Double::MaxValue;" instead of "return 0.0;".

Then if the current value of dMeterReading is MaxValue, don't increment evtCounter, don't update textbox, and don't Invalidate().
Avatar of judico

ASKER

I think you deserve points for that so I posted a special question. Please take a look at the link:

https://www.experts-exchange.com/questions/21093154/How-can-data-resulting-from-checksum-exceptions-in-VC-NET-be-ignored.html