Question

I'm using VisualStudio 2010, coding in C++/CLI, and doing all the graphics by GDI. I have a little app that plot continuously a Gaussian curve with some noise added. Every point is added real-time just like I pointed in this post.

Now, my task is to create a little colored area that I can shrink and increase to select a portion of the plot and do some math. This kind of task is managed by a MouseMove event just like that:

System::Void Form1::pictureBox1_MouseMove(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {
      //Recalculate the position of the area,   
      //clean up the old one and redraw a new.
}

It works actually but I'm experiencing a bit graphic "bug". enter image description here

As you can see, while I'm moving the area, everything under it is been deleted. The grid is here simply because it is static and I'm refreshing it everytime the green area is redrawn. Actually it is not a bug, for sure it must go like that. To me, it is kinda obvious. I called it like that because it is not what I'm expecting.
I'm asking if there is a way to the green area as if it is upon a different layer. In this way, I would be able to move the green area while the plot is running without being erased. I tried handling 2 HDC variables and plot the graph and the grid on the first one and the green area on the second one, but it seems not working.
Do you have some nice idea to get through this bad ( to me ) behaviour - maybe with some multilayer thing or some other fancy solutions - or should I give up and waiting for replotting?
Thanks everyone will give me an answer! :)

EDIT: Here is how I draw my dataseries:

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

          SelectObject(hdcPictureBox, LinePen);
          MoveToEx(hdcPictureBox, i-1+50, 500-convY*(Gauss[i-1]+Rumore[i-1])+50, NULL);
          LineTo(hdcPictureBox, i+50, 500-convY*(Gauss[i]+Rumore[i])+50);

          e1 = (i+k)%1000; //Buffer

          if(i>DXX-54 && i<SXX-54) {
              //ErasePen1 = CreatePen(PS_SOLID, 1, RGB(216,191,216));
              label1->Text = Convert::ToString(i);
              label1->Refresh();
              SelectObject(hdcPictureBox, ErasePen1);
          }
          else {
              SelectObject(hdcPictureBox, ErasePen);
          }

                //HPEN ErasePen1 = CreatePen(PS_SOLID, 1, RGB(216,191,216));

        MoveToEx(hdcPictureBox, e1+50, 500-convY*(Gauss[e1]+Rumore[e1])+50, NULL);
        LineTo(hdcPictureBox, e1+1+50, 500-convY*(Gauss[e1+1]+Rumore[e1+1])+50);
}

where DXX and SXX are the X-coordinates of areas - DXX starting, SXX ending.

This is how I'm handling the MouseMove. Do_Chan and Do_Clean are essentially the same thing. Do_Clean draws a bigger area with the background color to erase the old area and allowing Do_Chan to draw a new one.

System::Void Form1::pictureBox1_MouseMove(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {
if(e->Button == System::Windows::Forms::MouseButtons::Left) {

        double span100 = (SXX-DXX)*85/100;
        if (e->X > DXX+((SXX-DXX)/2)-15 && e->X < DXX+((SXX-DXX)/2)+15 && (e->Y >30 && e->Y <50)
            || e->X >DXX+((SXX-DXX)/2)-span100/2 && e->X < DXX+((SXX-DXX)/2)+span100/2 && (e->Y >50 && e->Y <550)) {
        HBRUSH brush = CreateSolidBrush(RGB(245,255,250));
        Do_Clean(hdcPictureBox, DXX, SXX,  brush);
        double spawn = SXX-DXX; 
        DXX = e->X - spawn/2;
        SXX = e->X + spawn/2;
        if(DXX < 50) {
            DXX = 51;

        }
        if(SXX >1050 ) {
            SXX = 1049; 
        }

        spawn = SXX - DXX;
        CXX = DXX + spawn/2;


        HBRUSH brush1 = CreateSolidBrush(RGB(166,251,178));
        Do_Chan(hdcPictureBox2, DXX, SXX, brush1);
        int k = 4;
        int e1 = 0;
        for(int i = 1; i<=1000; i++) {

            SelectObject(hdcPictureBox, LinePen);
            MoveToEx(hdcPictureBox, i-1+50, 500-250*(Gauss[i-1]+Rumore[i-1])+50, NULL);
            LineTo(hdcPictureBox, i+50, 500-250*(Gauss[i]+Rumore[i])+50);
             e1 = (i+k)%1000; //Buffer    
            if(i>DXX-54 && i<SXX-54) {
                    //ErasePen1 = CreatePen(PS_SOLID, 1, RGB(216,191,216));

                    SelectObject(hdcPictureBox, ErasePen1);
                }
                else {
                    SelectObject(hdcPictureBox, ErasePen);
                }

                    //HPEN ErasePen1 = CreatePen(PS_SOLID, 1, RGB(216,191,216));

            MoveToEx(hdcPictureBox, e1+50, 500-250*(Gauss[e1]+Rumore[e1])+50, NULL);
            LineTo(hdcPictureBox, e1+1+50, 500-250*(Gauss[e1+1]+Rumore[e1+1])+50);

        }

    }
}
}

As you can see, after I drew the new area, I redraw all the point of the array Gauss+Rumore. This is how Do_Chan ( Do_Clean is the same ) works:

void Do_Chan(HDC hdc, int dx, int sx, HBRUSH brush) {
//i = 250, y = 50
int y = 50;
int spawn = sx - dx;
  HPEN pen = CreatePen(PS_SOLID, 1, RGB(245, 255, 250));
  HPEN penC = CreatePen(PS_DOT, 1, RGB(0, 0, 0));
  /*Fai il rettangolo */
  SelectObject(hdc, pen);
  SelectObject(hdc, brush);
  POINT punti[4];
  punti[0].x = dx;
  punti[0].y = y;
  punti[1].x = dx +spawn;
  punti[1].y = y;
  punti[2].x = dx + spawn;
  punti[2].y = y+500;
  punti[3].x = dx;
  punti[3].y = y+500;
  Polygon(hdc, punti, 4);

  Ellipse(hdc, dx-10, y-20, dx+10, y);


  SelectObject(hdc, penC);
  MoveToEx(hdc, dx+spawn/2, 50,NULL);
  LineTo(hdc, dx+spawn/2, 550);


  SelectObject(hdc, pen);
  SelectObject(hdc, brush);
  Ellipse(hdc, dx-10+spawn/2, y-20, dx+10+spawn/2, y);

  SelectObject(hdc, pen);
  SelectObject(hdc, brush);

  Ellipse(hdc, dx-10+spawn, y-20, dx+10+spawn, y);
//Plot the axis and the grid 
}
Was it helpful?

Solution

I have thought of a possible way to do it and every solution had a drawback. For example

creating a thread. It has a drawback of drawing to picturebox dc from a different thread than the one handling the message queue. Not recomended

Another one:

using a timer and for every tick(lets say 16msec) draw. The DXX and SXX will be global variables. In picturebox move event you will only calculate these values(no drawing), also use some critical sections to protect them, and do all the drawing inside tick of timer. This works but you probable encounter some delay if your movement in picturebox is faster than 60fps.

The solution that i came finally was:

Inside your infinite loop:

get the mouse position and state(down or up). In this way you know if the user is dragging the green area and calculate DXX and SXX.

draw three rectangles with FillRect(): from 0 to DXX with picturebox back color, from DXX to SXX with green color and from SXX to the end with picturebox back color to an in memory dc eg hdcMemBackground

draw the grid lines to hdcMemBackground

Use the Point array and the polyline method i told you and in every loop move all your 999 points in the array one place to the left and add one point in the end of the array. To achieve this fill the array once before the infinite loop and inside it do the previous method

BitBlt hdcMemBackground to picturebox dc

Application::DoEvents();

EDIT (some code)

Create your resources once, when form loads, and release them in the end. Your Do_Chan()
creates considerable memory leaks. At form load:

HPEN hLinePenRed = NULL, hLinePenBlack = NULL, hLinePenWhite = NULL, hLinePenBlackDotted = NULL hPenOld; //Global
HBRUSH hBrushGreen = NULL, hBrushWhite = NULL, hBrushOld = NULL; //Global
HBITMAP hBitmap = NULL, hBitmapOld = NULL; //Global
HDC hdcMemBackground = NULL, hdcPicBox = NULL; //Global

//in form load event:
hLinePenRed = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
hLinePenBlack = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
hLinePenWhite = CreatePen(PS_SOLID, 1, RGB(245, 255, 250));
hLinePenBlackDotted = CreatePen(PS_DOT, 1, RGB(0, 0, 0));

hPenOld = SelectObject(hdcMemBackground, hLinePenRed);

hBrushGreen = CreateSolidBrush(RGB(166, 251, 178));
hBrushWhite = CreateSolidBrush(RGB(245, 255, 250));

hBrushOld = SelectObject(hdcMemBackground, hBrushGreen);

HDC hdc = CreateIC(TEXT("DISPLAY"), NULL, NULL, NULL);
hdcPicBox = GetWindowDC(hwndPicBox);
hdcMemBackground= CreateCompatibleDC(hdc);
hBitmap = CreateCompatibleBitmap(hdc, 1050, 550); 
hBitmapOld = SelectObject(hdcMemBackground, hBitmap);
DeleteDC(hdc);

In the end when form closes:

SelectObject(hdcMemBackground, hPenOld);
DeleteObject(hLinePenRed);
DeleteObject(hLinePenBlack);
DeleteObject(hLinePenBlackDotted);
DeleteObject(hLinePenWhite);
SelectObject(hdcMemBackground, hBitmapOld);
DeleteObject(hBitmap);
SelectObject(hdcMemBackground, hBrushOld);
DeleteObject(hBrushGreen);
DeleteObject(hBrushWhite);
DeleteDC(hdcMemBackground);
ReleaseDC(hwndPicBox, hdcPicBox);

How to use FillRect and draw ellipses:

RECT rt;
rt.left = 0; rt.top = 0; rt.right = 1050; rt.bottom = 550;
FillRect(hdcMemBackground, &rt, hBrushWhite);

rt.left = DXX; rt.top = 50; rt.right = SXX; rt.bottom = 550;
FillRect(hdcMemBackground, &rt, hBrushGreen);

SelectObject(hdcMemBackground, hBrushGreen);
SelectObject(hdcMemBackground, hLinePenWhite);

Ellipse(hdcMemBackground, dx-10, y-20, dx+10, y);
Ellipse(hdcMemBackground, dx-10+spawn/2, y-20, dx+10+spawn/2, y);
Ellipse(hdcMemBackground, dx-10+spawn, y-20, dx+10+spawn, y); 

//Plot the axis and the grid first and then draw the dotted vertical line

SelectObject(hdcMemBackground, hLinePenBlackDotted);
MoveToEx(hdcMemBackground, dx+spawn/2, 50, NULL);
LineTo(hdcMemBackground, dx+spawn/2, 550);

How to find mouse position and mouse state. This code will be at the beginning of each iteration to see if user dragged the green area and calculate the new DXX, SXX:

/* It is buggy. My mistake 
POINT pt;

GetCursorPos(&pt);
ScreenToClient(hwndPicBox, &pt);

if( pt.x >= 0 && pt.x <= picBoxWidth && pt.y >= 0 && pt.y <= picBoxHeight && (GetAsyncKeyState(VK_LBUTTON) & 0x8000) ){ //the mouse is down and inside picturebox
    //do your staff

}
*/

Use instead picturebox mouse down, mouse up and mouse move events:

int isScrollingLeft = false; //global, the left circle
int isScrollingRight = false; //global, the right circle
int isScrollingMiddle = false; //global, the middle circle

System::Void Form1::pictureBox1_MouseDown(....){
    //check if e.X and e.Y is inside in one of the three circles and set the
    //appropriate isScrolling to true
}

System::Void Form1::pictureBox1_MouseMove(....){
    if(isScrollingLeft){
        //calculate DXX
    }
    else if(isScrollingRight){
        //calculate SXX
    }
    else if(isScrollingMiddle){ //if you dont scroll this you dont need it
        //
    }
    else{;} //do nothing
}

System::Void Form1::pictureBox1_MouseUp(....){
    isScrollingLeft = false;
    isScrollingRight = false;
    isScrollingMiddle = false; //if you dont scroll this you dont need it
}

The way to move the points one place to the left:

POINT arrayPnt[1000]; //the array of points to be drawn by Polyline()

//the movement
memmove(&arrayPnt[0], &arrayPnt[1], 999 * sizeof(POINT));

//set the last one
arrayPnt[999].x = X;
arrayPnt[999].y = Y;

//Draw the lines
Polyline(hdcMemBackground, &arrayPnt, 1000);

To draw the number i into the label:

HDC hdcLabel1 = NULL; //global
HFONT hFont = NULL, hFontOld = NULL; //global
RECT rtLabel = NULL; //global
char strLabel1[5]; //global

Initialize once at the beggining like everything else

hdcLabel1 = GetWindowDC(label1Hwnd);

SetBkColor(hdcLabel1, RGB(?, ?, ?)); //i believe you use the color of your form
SetTextColor(hdcLabel1, RGB(255, 255, 255));

hFont = CreateFont(21, 0, 0, 0, /* Bold or normal*/FW_NORMAL, /*italic*/0, /*underline*/0, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
                     CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, TEXT("Arial")); //21 is i believe the 16 size in word

hFontOld = SelectObject(hdcLabel1, hFont);

rtLabel.top = 0; rtLabel.left = 0;
rtLabel.right = label1.Width; rtLabel.bottom = label1.Height;

and in the for loop draw the string into the label1

sprintf(strLabel1, "%d", i); //it is toooo slow. I have to think something better
DrawTextEx(hdcLabel1, strLabel1, -1, &rtLabel, DT_VCENTER | DT_SINGLELINE | DT_LEFT, NULL);

In the end ofcource release resources

SelectObject(hdcLabel1, hFontOld);
DeleteObject(hFont);
hFont = NULL;
ReleaseDC(label1Hwnd, hdcLabel1);

If you have any problems do comment.

valter

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