【问题标题】:Multilayer graphics using GDI使用 GDI 的多层图形
【发布时间】:2014-04-27 21:25:17
【问题描述】:

我正在使用 VisualStudio 2010,使用 C++/CLI 进行编码,并通过 GDI 完成所有图形。我有一个小应用程序,可以连续绘制添加了一些噪声的高斯曲线。就像我在post 中指出的那样,每个点都是实时添加的。

现在,我的任务是创建一个小的彩色区域,我可以缩小和增加它以选择部分情节并进行一些数学运算。 这种任务由MouseMove 事件管理,就像这样:

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.
}

它确实有效,但我遇到了一些图形“错误”。

如您所见,当我移动该区域时,它下面的所有内容都被删除了。网格在这里只是因为它是静态的,每次重绘绿色区域时我都会刷新它。 实际上它不是一个错误,肯定它必须这样。对我来说,这有点明显。我这样称呼它是因为它不是我所期望的。
我在问是否有通往绿色区域的方法,就好像它在不同的图层上一样。这样,我就可以在情节运行时移动绿色区域而不会被擦除。 我尝试处理 2 个HDC 变量并在第一个变量上绘制图形和网格,在第二个变量上绘制绿色区域,但它似乎不起作用。
你有什么好主意来克服这种(对我来说)糟糕的行为——也许是一些多层的东西或其他一些奇特的解决方案——还是我应该放弃并等待重新绘制?
谢谢大家给我答复! :)

编辑: 这是我绘制数据系列的方式:

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

DXXSXX 是区域的 X 坐标 - DXX 开始,SXX 结束。

这就是我处理MouseMove 的方式。 Do_ChanDo_Clean 本质上是一回事。 Do_Clean 使用背景颜色绘制更大的区域以擦除旧区域并允许 Do_Chan 绘制新区域。

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

        }

    }
}
}

如您所见,在我绘制了新区域之后,我重新绘制了数组 Gauss+Rumore 的所有点。 这就是 Do_Chan(Do_Clean 相同)的工作原理:

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 
}

【问题讨论】:

  • 使用计时器并在那里进行所有绘图。您正在尝试以 2 种不同的方式访问同一个 hdc,这会给您带来这种效果
  • 嗨!感谢您的回答!。你的意思是this计时器吗?
  • 发布您的代码。我们来解决问题
  • 我想我找到了解决方案。每次触发 MouseMove 时,我都会清理旧区域,绘制新区域并重新绘制我的系列。这可能是一个解决方案吗?
  • 视情况而定。你是怎么画这个系列的?

标签: winforms c++-cli gdi


【解决方案1】:

我已经想到了一种可能的方法,并且每个解决方案都有一个缺点。例如

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

另一个:

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.

我最终得到的解决方案是:

在你的无限循环中:

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

编辑(一些代码)

在表单加载时创建一次资源,最后释放它们。您的Do_Chan()
造成相当大的内存泄漏。表单加载时:

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

最后当表单关闭时:

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

如何使用 FillRect 和绘制椭圆:

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

如何找到鼠标位置和鼠标状态。此代码将在每个开头 迭代看用户是否拖动绿色区域并计算新的 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

}
*/

改用图片框mouse downmouse upmouse move 事件:

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
}

将点向左移动一位的方式:

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

将数字i 绘制到标签中:

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

一开始就初始化一次

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;

并在for 循环中将字符串绘制到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);

在课程结束时发布资源

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

如果你有任何问题,请发表评论。

变态

【讨论】:

  • 嗨,瓦尔特,您的解决方案正在测试中。肯定其中之一将符合我的要求。再次感谢您!
  • @Emi987 我确实让它运行(在 vb.net 中)并且它有一个“问题”。如果它太快(因为它会),你会看到噪音。你不会看到它向左移动。但如果它是每秒大约 1000 次绘制,那就太好了!我对光标位置和鼠标状态进行了编辑。谢天谢地,您不需要,因为我查看鼠标状态的方法有问题!我将编辑答案。
  • @Emi987 我目前正在考虑一种更快的方法。准备好后我会发布。
猜你喜欢
  • 2011-02-14
  • 1970-01-01
  • 2012-12-08
  • 2010-09-08
  • 1970-01-01
  • 2012-03-25
  • 1970-01-01
  • 2012-12-14
  • 1970-01-01
相关资源
最近更新 更多