Question

I'm working on an application in Visual Studio 2010 and I'm coding in C++/CLI.
In my previous questions I had issue with data series from a serial port. Now it seems ok and now I'm trying to plot them.
I don't want to use Chart, so I would like to use handmade functions that use the class Graphics. In order to test my code, I created two arrays: the first one is filled with values coming from a Gaussian. The second one is filled with random numbers.
When I plot the values, I would like to see my plot growing and updating just like an oscilloscope. The second Do_Plot manages to "remove point" plotting them by the BackColor. All the code works as intended but I'm experiencing problem with performances. If I run the code on my Pc, my series is plotted every 500/700 ms.
Sometimes it slows down to 1500ms and then it comes back faster.
I tried to run the code on my coworkers' Pc and I noticed the series is plotted every 170ms on the first one, whereas the series is plotted every 950ms on the second one. This is the code:

System::Void Form1::button1_Click(System::Object^  sender, System::EventArgs^  e) {
    button1->Enabled = false;

    array<float,1>^ Gauss = gcnew array<float,1>(1001);

    array<float,1>^ Rumore = gcnew array<float,1>(1001);


     /*Some useful variables*/
     Random^ generatore = gcnew Random;
     float a = safe_cast<float>(Math::Round(5/(SIGMA*Math::Sqrt(2*PI)), 2));
     float b = safe_cast<float>(2*(SIGMA*SIGMA));

      /*Start */


      float portante; 
      float r;
      float s;

      int convX =1000/1000;
      int convY =500/2;  

      /*time variables */  

      int bias = 50;
      int dif =600;  

      /*Gap between drawing and removing*/  

      int k = 3; 
      int e1 = 0;


     for ( ; ; ) {

          /*Start*/
          clock_t Start = clock();

          if(textBox1->Text==""){
            portn = 5;
          }
          else
            portn = float::Parse(textBox1->Text);

          /*temp variables to go out the for cycle */
          portante = portn;
          r = rand;
          s = sig;

          /ckeck state is OK */
          check = 0;


            for(int i = 1; i<=1000; i++) {

                Gauss[i] = safe_cast<float>(Math::Round( a*s*Math::Exp(-Math::Pow(((0.01*1*(i))-portante), 2)/b), 2));

                Rumore[i] = safe_cast<float>(Math::Round(r*generatore->NextDouble(), 2));
                bool clipSup = ClipSup(2, Gauss[i]+Rumore[i]);

                if(clipSup==true) {
                    Gauss[i] = 1.99f;
                    Rumore[i] = 0;
                }
                Do_Plot(g, disegna, i-1, Gauss[i-1]+Rumore[i-1], i, Gauss[i]+Rumore[i], convX, convY);


                e1 =(k+i)%1000;
                Do_Plot(g, rimuovi, e1, Gauss[e1]+Rumore[e1], e1+1, Gauss[e1+1]+Rumore[e1+1], convX, convY);




                /*Ckeck if go out for cycle*/
                if(check == CODE_1 ) {
                    portante = portn;
                    break;
                }
                if(check == CODE_2 ) {
                    r = rand;
                    break;
                }
                if(check == CODE_3 ) {
                    s = sig;
                    break;
                }

            }

             clock_t Stop = clock();
             int Diff = Stop-Start;
             label8->Text = Convert::ToString(Diff);
             int tempDiff = (Stop-Start)+bias;
             if(tempDiff>dif)
             {

                //Do_Axes(g);           /*Do Axes*/
                //Do_Grid(g);           /*Do Grid */
                Application::DoEvents();
                dif = 600;
                bias = 0;
            }
            else
                bias +=50; //Else bias grows
      }


}

Where Do_Plot is:

void Do_Plot(Graphics^ g, Pen^ penna, int Xi, float Yi, int Xf, float Yf, int convX, int convY) {


    g->DrawLine(penna, (convX*Xi+50), safe_cast<int>(500-(Yi*convY)+50), 
        (convX*Xf+50), safe_cast<int>(500-(Yf*convY)+50)); 
}

I have declared Graphics^ g here:

public ref class Form1 : public System::Windows::Forms::Form
{
Graphics^ g; 
public:
    Form1(void) 
    {
        InitializeComponent();
        //
        //TODO: aggiungere qui il codice del costruttore.
        //
        g = pictureBox1->CreateGraphics();

    }

Onestly I don't know why my code works so differently when it runs on another Pc. I think the problem is g = pictureBox1->CreateGraphics(); but I'm just doing some hypothesis. Any kind of help would be really appreciated cause I'm stuck on this one since the previous week!!
Thanks a lot!
Emiliano

Was it helpful?

Solution

I am going to give you the code in c/c++. Will then find how to do it in c++/cli

First initialize the objects we will use (begining of button_click):

RECT rt = {0, 0, 1200, 600};
HBITMAP hBitmap = NULL;
HPEN hLinePen = NULL;
HDC hdcPctrBox = NULL, hdc = NULL, hdcMemTempImage = NULL;
HBRUSH hBrush = NULL;
POINT arrayPnt[1000]; //array of points

hdc = CreateIC(TEXT("DISPLAY"), NULL, NULL, NULL);
hdcPctrBox = GetWindowDC(hwndPctrBox); //hwndPctrBox is the handle of picturebox
hdcMemTempImage = CreateCompatibleDC(hdc);

hBitmap = CreateCompatibleBitmap(hdc, 1200, 600);  
SelectObject(hdcMemTempImage, hBitmap);  

hLinePen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0)); //width of pen = 1,and color (255, 0, 0)
SelectObject(hdcMemTempImage, hLinePen);

hBrush = CreateSolidBrush(RGB(100, 100, 255)); //color of picturebox

Then:

for ( ; ; ) {
    /*Start*/
    clock_t Start = clock();

    FillRect(hdcMemTempImage, &rt, hBrush); //clear the hdcMemTempImage with the picturebox color
    //your code continues...

In the for(int i = 1; i<=1000; i++) add one point at a time:

arrayPnt[i - 1].x = X; //calculate X
arrayPnt[i - 1].x = Y; ////calculate Y

And finaly we draw it, after the for loop and before the clock_t Stop (we dont need the Do_Plot()):

Polyline(hdcMemTempImage, &arrayPnt, 1000);
BitBlt(hdcPctrBox, 0, 0, 1200, 600, hdcMemTempImage, 0, 0, SRCCOPY);

clock_t Stop = clock();
//your code continues...

EDIT: use your original code(you dont need rt, hBitmap, hBrush, hdcMemTempImage, arrayPnt and hdc) and instead of calling Do_Plot() add this code:

//create two pens, one with the color you want hLinePen, and another with the color of picturebox hLinePenErase
//inside for(int i = 1; i<=1000; i++)
(some code...)
if(clipSup==true) {
    Gauss[i] = 1.99f;
    Rumore[i] = 0;
}

SelectObject(hdcPctrBox, hLinePen); //the pen to draw the line
MoveToEx(hdcPctrBox, xStart, yStart, NULL); //start point of line
LineTo(hdcPctrBox, xEnd, yEnd); //end point of line

SelectObject(hdcPctrBox, hLinePenErase); //the pen to erase the line
MoveToEx(hdcPctrBox, xStartErase, yStartErase, NULL); //start point of line to erase
LineTo(hdcPctrBox, xEndErase, yEndErase); //end point of line to erase
(code continues...)

To delete your resources:

HPEN hLinePen = NULL, hLinePenErase = NULL, originalPen = NULL;
HDC hdcPctrBox = NULL;

hdcPctrBox = GetWindowDC(hwndPctrBox); //hwndPctrBox is the handle of picturebox
originalPen = SelectObject(hdcPctrBox, GetStockObject(DC_PEN));
//create the two pens

//in the end release resources
SelectObject(hdcPctrBox, originalPen);
DeleteObject(hLinePen);
DeleteObject(hLinePenErase);
ReleaseDC(hwndPctrBox, hdcPctrBox);

valter

OTHER TIPS

I suggest to implement your own painting class. Derive your own class from "Panel" or "UserControl" and do the following in the constructor, which activates double buffering and optimized drawing:

public ref class MyPaintControl : public System::Windows::Forms::UserControl
{
public:
    MyPaintControl()
    {
        this->SetStyle(System::Windows::Forms::ControlStyles::AllPaintingInWmPaint, true);
        this->SetStyle(System::Windows::Forms::ControlStyles::DoubleBuffer, true);
        this->SetStyle(System::Windows::Forms::ControlStyles::ResizeRedraw, true);
        this->SetStyle(System::Windows::Forms::ControlStyles::UserPaint, true);
    }

 // And then implement your painting in OnPaint:

protected: 
    virtual void OnPaint(PaintEventArgs^ e)
    {
      // Do painting...
      System::Drawing::Brush^ br = gcnew System::Drawing::SolidBrush(this->BackColor);
      e->Graphics->FillRectangle(br, this->ClientRectangle);
      delete br;

     // Do other paintings...
    }

    // This function might be called if new data arrived...
    public:
      void UpdateArea(int daten)  // pass data here, if you want...
      {
        // Store the data in a member variable...

        // force a redraw:
        this->InvalidateRect(); // this will force a "OnPaint"
      }
}

Then embedd this control in your UI. This should improve your painting very much!

Also this works, even if you move or resize your window. Be aware, that painting should always be done in "OnPaint"!

I would like to update the state of my coding.
I used the method posted by Jochen. It is very fast but it does not suit my application or maybe I'm not able to adapt it. What I need to do, is to update constantly my plot. At time t0, I should draw a line. At time t1, when I store a new line, I should draw the new line and add this to the previous one. The plot must be "alive". I tried to derive a panel class and override the OnPaintevent but my plot refreshing only if all lines are stored and displayed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top