ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

MFC画刷绘图(想模仿Microsoft的画图)

2022-08-15 12:32:14  阅读:268  来源: 互联网

标签:MFC 画刷 point CMy0727MfcTestAppView bitmap nFlags 绘制 Microsoft 左键


CBrush类,创建画刷对象,通常用于填充一块区域。(此处缺gif,术业有专攻,东西也有专用。。。日后一定补上来,痛哭流涕)


 1.创建一个红色画刷绘图:(鼠标左键按下,这个消息响应OnLButtonDown

 1 void CMy0727MfcTestAppView::OnLButtonDown(UINT nFlags, CPoint point)
 2 {
 3     // TODO: 在此添加消息处理程序代码和/或调用默认值
 4     // 创建一个红色画刷
 5     CBrush brush(RGB(255, 0, 0));
 6     // 创建并获得设备描述表
 7     CClientDC dc(this);
 8     // 利用红色画刷填充鼠标拖拽过程中形成的矩形区域
 9     dc.FillRect(CRect(m_ptOrigin, point), &brush); // m_ptOrigin 初始点(0,0)  
10     CView::OnLButtonDown(nFlags, point);
11 }
这样做的问题在于,点击一下左键就会直接绘制出来一个矩形颜色块,看起来很不顺眼,另外,这里的m_ptOrigin设置的初始值为(0,0),也就是从左上角到鼠标点击处构成一个红色块。如下图:


 2.那我现在想按下左键后拖拽鼠标,然后等左键弹起,在这个拖拽区域(鼠标左键按下,和鼠标左键弹起,这两点构成的区域)绘制该怎么实现?
:记录鼠标左键按下的位置,然后在OnLButtonUp(左键弹起)中绘制就可以了
void CMy0727MfcTestAppView::OnLButtonDown(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    m_ptOrigin = point;    // 记录初始点坐标
    CView::OnLButtonDown(nFlags, point);
}

void CMy0727MfcTestAppView::OnLButtonUp(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    CBrush brush(RGB(255, 0, 0));
    CClientDC dc(this);
    dc.FillRect(CRect(m_ptOrigin, point), &brush);
    CView::OnLButtonUp(nFlags, point);
}

在OnLButtonDown中记录左键按下的位置,在OnLButtonUp进行绘制,(在鼠标按下后,要一直按着拖拽,如果直接在同一点按下然后马上抬起,虽然会绘制,不过太小了你根本看不到的,或者说,这两个点重合,导致矩形坍缩为一个点,所以需要拖一点距离)


 (穿插一点)(2.5).使用位图画刷,即矩形块使用一个位图来填充,不使用这个颜色块
首先需要去创建一个位图,资源视图中的Bitmap文件,右键,添加资源(这个是已有,然后添加进去)/插入资源(没有,自己画一个),然后就可以愉快的使用了(ID什么的自己改一改,符合规范就行),代码都差不多

 1 void CMy0727MfcTestAppView::OnLButtonDown(UINT nFlags, CPoint point)
 2 {
 3     // TODO: 在此添加消息处理程序代码和/或调用默认值
 4     m_ptOrigin = point;
 5     CView::OnLButtonDown(nFlags, point);
 6 }
 7 
 8 
 9 
10 void CMy0727MfcTestAppView::OnLButtonUp(UINT nFlags, CPoint point)
11 {
12     // TODO: 在此添加消息处理程序代码和/或调用默认值
13     // 创建位图对象
14     CBitmap bitmap;
15     // 加载位图资源
16     bitmap.LoadBitmap(IDB_BITMAP1);
17     // 创建位图画刷
18     CBrush brush(&bitmap);
19     // 创建并获得设备描述表
20     CClientDC dc(this);
21     // 利用位图画刷填充鼠标拖曳过程中形成的矩形区域
22     dc.FillRect(CRect(m_ptOrigin, point), &brush);
23 
24     CView::OnLButtonUp(nFlags, point);
25 }


3.上一次实现的是鼠标抬起后才会出现图案,在拖拽过程不会出现,这显然不符合我们干活的要求,再次改进,要求在拖拽时也能看到这个绘制的图形
:这里首先还是需要记录起始点(鼠标左键按下点),然后在鼠标移动的过程中进行绘制,可以有消息响应OnMouseMove函数实现,最后抬起左键,结束绘制
 1 void CMy0727MfcTestAppView::OnMouseMove(UINT nFlags, CPoint point)
 2 {
 3     // TODO: 在此添加消息处理程序代码和/或调用默认值
 4     if (m_bDraw == true)    // 在鼠标拖拽过程中进行绘制
 5     {
 6         CBitmap bitmap;
 7         bitmap.LoadBitmap(IDB_BITMAP1);
 8         CBrush brush(&bitmap);
 9         CClientDC dc(this);
10 
11         dc.FillRect(CRect(m_ptOrigin, point), &brush);
12     }
13     CView::OnMouseMove(nFlags, point);
14 }
15 
16 
17 void CMy0727MfcTestAppView::OnLButtonDown(UINT nFlags, CPoint point)
18 {
19     // TODO: 在此添加消息处理程序代码和/或调用默认值
20     // 表示左键已经按下
21     m_bDraw = true;
22     m_ptOrigin = point;
23     CView::OnLButtonDown(nFlags, point);
24 }
25 
26 
27 
28 void CMy0727MfcTestAppView::OnLButtonUp(UINT nFlags, CPoint point)
29 {
30     // TODO: 在此添加消息处理程序代码和/或调用默认值
31     m_bDraw = false;     // 左键抬起,绘制结束
32     CView::OnLButtonUp(nFlags, point);
33 }

确实能够实现,在拖拽过程中就可以显示,但是这不是有bug,这个矩形块变大出现图案,但是在拖曳过程中,当矩形块缩小的时候,图案又不会变小,还是保持最大的情形,这和我们使用的画图软件画矩形块的功能比不上啊。。。。。怎么解决这个问题?

4.如果在鼠标移动过程中,把上一次绘制的矩形块给清除掉,然后再根据这次得到的最新的矩形块进行填充那问题不就解决了,

:利用函数RedrawWindow实现指定区域重绘?(也就是干掉之前绘制的图案)
:解决方案,在OnMouseMove中绘制时,绘制完后就利用RedrawWindow函数清空刚刚绘制区域,这样在鼠标连续拖曳过程中就可以看到随着鼠标的移动,绘制出来的矩形区域可变大变小,最后在OnLButtonUp函数中最后绘制一次(这次是最终的矩形块,因为之前在OnMouseMove中绘制时,每绘制一次就会清空一次,所以不在OnLButtonUp再绘制一次,界面上不会存在矩形块的)问题解决,
(当然,你也可以尝试先清除,再绘制,我试了一下,这不太符合要求,主要是鼠标在移动中,这次的清除无法消除上次绘制的痕迹)
 1 void CMy0727MfcTestAppView::OnMouseMove(UINT nFlags, CPoint point)
 2 {
 3     // TODO: 在此添加消息处理程序代码和/或调用默认值
 4     if (m_bDraw == true)
 5     {
 6         CBitmap bitmap;
 7         bitmap.LoadBitmap(IDB_BITMAP1);
 8         CBrush brush(&bitmap);
 9         CClientDC dc(this);
10 
11         dc.FillRect(CRect(m_ptOrigin, point), &brush);// 绘制
12         Sleep(5);        // 暂停一会,不然一绘制就清空,肉眼根本看不到,可以删掉试试
13         RedrawWindow(CRect(m_ptOrigin, point));        // 重绘指定区域
14     }
15     CView::OnMouseMove(nFlags, point);
16 }
17 
18 void CMy0727MfcTestAppView::OnLButtonDown(UINT nFlags, CPoint point)
19 {
20     // TODO: 在此添加消息处理程序代码和/或调用默认值
21     // 表示左键已经按下
22     m_bDraw = true;
23     m_ptOrigin = point;
24     CView::OnLButtonDown(nFlags, point);
25 }
26 
27 void CMy0727MfcTestAppView::OnLButtonUp(UINT nFlags, CPoint point)
28 {
29     // TODO: 在此添加消息处理程序代码和/或调用默认值
30     if (m_bDraw == true)                        // 最后再次绘制一次
31     {
32         CBitmap bitmap;
33         bitmap.LoadBitmap(IDB_BITMAP1);
34         CBrush brush(&bitmap);
35         CClientDC dc(this);
36         dc.FillRect(CRect(m_ptOrigin, point), &brush);
37     }
38     m_bDraw = false;        // 停止绘制
39     CView::OnLButtonUp(nFlags, point);
40 }                    

问题解决,新问题又来了,如何解决这个绘制过程中的闪烁问题?这显示是绘制和清空之间出现的闪烁,这看起来也太费眼了,怎么解决这个问题?


 5.观察上面的绘制过程,找到了几个主要的问题:

一,绘制过程中闪烁问题;
二,已绘制的地方,当我再次拖曳时,会把他给覆盖,然后会因为拖曳而清空(RedrawWindow重绘所致),最后导致清除了之前绘制的图案;
三,在绘制过程中,按下左键后的拖拽过程,鼠标出界面后,怎么修改m_Draw,否则鼠标再次回界面无需按下左键随意可绘制,无法约束;

5.1双缓冲解决绘制过程中的闪烁:参考博文:MFC中的双缓冲技术(解决绘图闪烁问题),  【MFC】双缓存 背景刷新 ,MFC VC 双缓冲绘图基本原理与实现 ,MFC中如何实现指定区域的重绘 ,VC++双缓冲保持背景不擦除之实现
全部看完,不会也会了,大致操作就是,先在内存DC里直接画图,然后再把这个画好的进行显示,
(抄的一段话:)(在图形图象处理编程过程中,双缓冲是一种基本的技术。我们知道,如果窗体在响应WM_PAINT消息的时候要进行复杂的图形处理,那么窗体在重绘时由于过频的刷新而引起闪烁现象。解决这一问题的有效方法就是双缓冲技术。因为窗体在刷新时,总要有一个擦除原来图象的过程OnEraseBkgnd,它利用背景色填充窗体绘图区,然后在调用新的绘图代码进行重绘,这样一擦一写造成了图象颜色的反差。当WM_PAINT的响应很频繁的时候,这种反差也就越发明显。于是我们就看到了闪烁现象。)
原理明白了,,现在就上代码喽:
 1 void CMy0727MfcTestAppView::OnLButtonDown(UINT nFlags, CPoint point)
 2 {
 3     // TODO: 在此添加消息处理程序代码和/或调用默认值
 4     // 表示左键已经按下
 5     m_bDraw = true;
 6     m_ptOrigin = point;      // 记录初始点
 7     CView::OnLButtonDown(nFlags, point);
 8 }
 9 
10 void CMy0727MfcTestAppView::OnLButtonUp(UINT nFlags, CPoint point)
11 {
12     // TODO: 在此添加消息处理程序代码和/或调用默认值
13     m_bDraw = false;
14     CView::OnLButtonUp(nFlags, point);
15 }
16 
17 
18 void CMy0727MfcTestAppView::OnMouseMove(UINT nFlags, CPoint point)
19 {
20     // TODO: 在此添加消息处理程序代码和/或调用默认值
21     // 点我画刷
22     if (m_bDraw == true)
23     {
24         CDC * pDC = GetDC();        // 获取DC
25 
26         CRect rect;                    // 用于存储客户区大小
27         GetClientRect(&rect);
28 
29         CDC dcMem;                  // 用于缓冲作图的内存DC
30         CBitmap bmp;                // 内存中承载临时图象的位图
31                                                                             
32         bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());    // 创建兼容位图
33         dcMem.CreateCompatibleDC(pDC);             // 依附窗口DC创建兼容内存DC
34         dcMem.SelectObject(bmp);                   // 将位图选择进内存DC
35         dcMem.FillSolidRect(rect, pDC->GetBkColor());    // 按原来背景填充客户区,不然会是黑色RGB(255,255,255)
36                             
37             // 这里是选择了位图画刷,和前面一样
38         CBitmap bitmap;
39         bitmap.LoadBitmap(IDB_BITMAP1);
40         CBrush brush(&bitmap);
41 
42         dcMem.FillRect(CRect(m_ptOrigin, point), &brush);    // 使用位图画刷在内存DC绘制
43         pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcMem, 0, 0, SRCCOPY); // 将内存DC上的图象拷贝到前台
44 
45             // 释放空间
46         dcMem.DeleteDC();                 
47         bmp.DeleteObject();
48         ReleaseDC(pDC);
49     }
50     CView::OnMouseMove(nFlags, point);
51 }

问题一解决,能够正常画图,没闪烁;

5.2,是解决了闪烁问题,那怎么保存上一次画的图,毕竟现在画完一次后,再次画上次就消失了,(毕竟这是在OnMouseMove里面定义的局部变量,当然会消失)
解决方案:这里要考虑画图中我们到底把画 ,画在了什么地方,只有先搞清楚这个才能把画保存下来。   参考博文:如何将CDC上的绘图直接反映在他的BITMAP内?
我们所做的画,其实就保存在一个CBitmap中,经过dcMem.SelectObject(bmp); 语句之后,所画的图就保存在bmp之中了,那么问题就变得简单了。
显示画布1,绘图画布2,(显示画布1用来存储整个绘制的画)操作过程:
0、绘图画布2复制显示画布1      ----------   这样在绘制画布2 绘制时,还可以看到之前绘制的图形
1、在绘图画布2上进行绘画,当此前绘画结束,把绘图画布2的作品再次画一份到显示画布1,显示画布1保存 ---------  注意,是把这次的绘画操作,重新画一份到显示画布1,而不是把绘图画布2整个复制过去(----,好像复制过去也没什么问题。。。。反正前面说的做)
2、回到第0步骤。重复此过程,
这样一个大致的绘画过程就实现了,能够保存当前的绘画,还是利用前面提到的双缓冲技术,不过此处需要增加一个全局的画布,用来存绘制的画 
代码如下:  (主要是设计显示画布1,绘图画布2在5.1中基本解决) ---------- 变量名没取好,
View.h
1 // 首先去定义成员变量,作为全局的变量
2 public:
3     CDC * dcMem;                                   //用于缓冲作图的内存DC
4     CBitmap * bmp;                                 //内存中承载临时图象的位图
5         // 用于复制CBitmap
6     bool CopyCBitmapFromSrc(CBitmap* pBitmapDest, CBitmap* pBitmapSrc);
7     // 记录矩形绘制的终点
8     CPoint end_point;
View.cpp  
 1 // 构造函数,析构函数
 2 CMy0727MfcTestAppView::CMy0727MfcTestAppView()
 3 {
 4     // TODO: 在此处添加构造代码
 5     m_bDraw = false;
 6 
 7     dcMem = new CDC();
 8     bmp = new CBitmap();
 9 
10 }
11 
12 CMy0727MfcTestAppView::~CMy0727MfcTestAppView()
13 {
14     delete dcMem;
15     delete bmp;
16 }
 1 int CMy0727MfcTestAppView::OnCreate(LPCREATESTRUCT lpCreateStruct)
 2 {
 3     if (CView::OnCreate(lpCreateStruct) == -1)
 4         return -1;
 5 
 6     // TODO:  在此添加您专用的创建代码
 7     int x = GetSystemMetrics(SM_CXSCREEN);
 8     int y = GetSystemMetrics(SM_CYSCREEN);
 9 
10     CDC* pDC = GetDC();
11 
12     bmp->CreateCompatibleBitmap(pDC, x, y);        // 创建兼容位图
13     dcMem->CreateCompatibleDC(pDC);                // 依附窗口DC创建兼容内存DC
14     dcMem->SelectObject(bmp);                    // 将位图选择进内存DC
15     dcMem->FillSolidRect(CRect(0, 0, x, y), RGB(255,255,255));    // 按原来背景填充客户区,不然会是黑色 
16     ReleaseDC(pDC);
17 
18     return 0;
19 }
 1 BOOL CMy0727MfcTestAppView::OnEraseBkgnd(CDC* pDC)
 2 {
 3     // TODO: 在此添加消息处理程序代码和/或调用默认值
 4 // 这里说的是截断干掉默认返回,然后写return true;语句,但是好像没发现有什么影响,欸,就先不管了,出了问题再来考虑
 5 //    CRect rc;
 6 //    GetClientRect(rc);
 7 //    pDC->FillSolidRect(rc, RGB(255, 255, 255)); // 避免 出现黑色View,刷白
 8 //    return true;
 9     return CView::OnEraseBkgnd(pDC);
10 }
 1 // 用来复制CBitmap,即把显示画布1复制给绘图画布2的过程
 2 bool CMy0727MfcTestAppView::CopyCBitmapFromSrc(CBitmap* pBitmapDest, CBitmap* pBitmapSrc)
 3 {
 4     BOOL bFlag = false;
 5 
 6     BITMAP bmpInfo;
 7     // 获取源图信息
 8     pBitmapSrc->GetBitmap(&bmpInfo);
 9     // 求取每一个像素所占的字节
10     long sizeBits = bmpInfo.bmWidth * bmpInfo.bmHeight * (bmpInfo.bmWidthBytes / bmpInfo.bmWidth);
11     // 分配内存
12     PBYTE pBits = new BYTE[sizeBits];
13     ZeroMemory(pBits, sizeBits);
14     // 保存源图像素信息
15     pBitmapSrc->GetBitmapBits(sizeBits, pBits);
16     // 创建新图
17     bFlag = pBitmapDest->CreateBitmap(bmpInfo.bmWidth, bmpInfo.bmHeight, bmpInfo.bmPlanes, bmpInfo.bmBitsPixel, pBits);
18     // 回收资源
19     delete[]pBits;
20 
21     return bFlag;
22 }
 1 void CMy0727MfcTestAppView::OnMouseMove(UINT nFlags, CPoint point)
 2 {
 3     // TODO: 在此添加消息处理程序代码和/或调用默认值
 4     if (m_bDraw == true)
 5     {
 6         end_point = point;                // 记录绘制终点
 7         CDC * pDC1 = GetDC();
 8         CRect rect1;
 9         GetClientRect(&rect1);
10         CDC dcMem1;                                   //用于缓冲作图的内存DC
11         CBitmap * bmp1 = new CBitmap();                                 //内存中承载临时图象的位图
12 // 复制一份bmp,然后作图,
13         CopyCBitmapFromSrc(bmp1, bmp);
14         dcMem1.CreateCompatibleDC(pDC1);               //依附窗口DC创建兼容内存DC
15         dcMem1.SelectObject(bmp1);                    //将位图选择进内存DC
16         CBitmap bitmap;
17         bitmap.LoadBitmap(IDB_BITMAP1);
18         CBrush brush(&bitmap);
19 
20         dcMem1.FillRect(CRect(m_ptOrigin, end_point), &brush);        // 绘制
21 
22         pDC1->BitBlt(0, 0, rect1.Width(), rect1.Height(), &dcMem1, 0, 0, SRCCOPY);//将内存DC上的图象拷贝到前台
23 
24         dcMem1.DeleteDC();                    // 删除DC
25         delete bmp1;                          // 删除位图
26         ReleaseDC(pDC1);
27     }
28     CView::OnMouseMove(nFlags, point);
29 }
30 
31 void CMy0727MfcTestAppView::OnLButtonDown(UINT nFlags, CPoint point)
32 {
33     // TODO: 在此添加消息处理程序代码和/或调用默认值
34     // 表示左键已经按下
35     m_bDraw = true;
36     m_ptOrigin = point;
37     CView::OnLButtonDown(nFlags, point);
38 }
39 
40 void CMy0727MfcTestAppView::OnLButtonUp(UINT nFlags, CPoint point)
41 {
42     // TODO: 在此添加消息处理程序代码和/或调用默认值
43     if (m_bDraw == true)                  // 此处就是把绘图画布中新绘制的图形保存到显示画布,用于存储记录刚刚绘制的图形
44     {
45         CBitmap bitmap;
46         bitmap.LoadBitmap(IDB_BITMAP1);
47         CBrush brush(&bitmap);
48 
49         dcMem->FillRect(CRect(m_ptOrigin, point), &brush);        // 绘制
50     }
51     m_bDraw = false;
52     CView::OnLButtonUp(nFlags, point);
53 }    

CBitmap图像拷贝_恋上豆沙包的博客 (函数CopyCBitmapFromSrc的实现)(抄代码)

运行,发现确实是我们想要的功能,这和画图中拖拽绘制矩形块一样,结果如下图:(成功的成)

当然,这里还差了一点问题,发送WM_PAINT消息重绘后,图像会消失,这主要是因为调用OnDraw,而这个函数里没有代码用来显示画布,所以会出现这样的问题,其实只需要把显示画图加上就可以的,这里暂时就不做了

5.3------有点懒了,先把思路放这里吧,日后再来。。。
Bug1,没能够解决
// 这里的Bug,当我点下左键开始画线之后,然后把鼠标拖出去界面外,
// 再次拖回来,不点下左键也可以画线,因为m_bDraw还满足条件,所以出现了问题
// 同样,画刷那里也会存在这样的问题,。。。。。待解决!!!!
解决方案:鼠标位置捕获的问题,当它离开窗体后! https://bbs.csdn.net/topics/180279
SetCapture函数; https://www.cnblogs.com/zhuluqing/p/8994951.html
OnMouseLeave函数; 当光标离开之前调用 TrackMouseEvent中指定的窗口的工作区时,框架将调用此成员函数。


 ----------------->>>>>>>>>>

至此,问题差不多解决了,没解决基本上也是可以解决的,看看文中给的链接就好,
后面还考虑了撤销绘制操作这个功能,主要是捋了一下思路,并没有去实现,刚开始研究MFC,这个矩形块绘制也做了一天多了,8.12一天加8.15半天,(真的太慢了,主要是有些东西不知道,找资料。。。还需要自己琢磨研究方能领悟。。难搞)从画刷绘制到位图画刷,到拖拽画图,到实时显示图像,到双缓冲显示图像,到存储已绘制图像,
后面还可以继续实现,太懒了。。。。下次一定
 windows画图的撤销重做是如何实现的?      ---------     要是直接保存每一步绘制,这个我还可以做一下,不过想一下就很残暴,
 
如有错误,请留言联系,必回,改正,谢谢!

 2022-08-15

标签:MFC,画刷,point,CMy0727MfcTestAppView,bitmap,nFlags,绘制,Microsoft,左键
来源: https://www.cnblogs.com/2015-16/p/16578944.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有