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的实现)(抄代码)

CBitmap拷贝函数(根据一个CBitmap对象创建另一个相同的CBitmap对象)

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

当然,这里还差了一点问题,发送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的画图,假设这个拖拽矩形块做完了,那其他形状的绘制不就是换个形状(中间绘制那里两行代码的差距),然后是颜色选择,(虽然不知道他怎么实现的,哦,好像MFC提供了颜色选择的对话框来着的,实在不行,我设计多个按钮选颜色也不是不可以。。虽然很丑,)(然后是线条粗细什么的,这个我看的那本MFC书上也教过一点了,虽然我没记下来。。。不过还是知道能够做这个操作,到时候再去查资料吧)(然后文件保存什么的,应该就是把那个位图给存储成为.BMP照片?)(理论上感觉确实是可以实现这个Windows画图了)(我太懒了。。还是没有实现。。。)
 windows画图的撤销重做是如何实现的?      ---------     要是直接保存每一步绘制,这个我还可以做一下,不过想一下就很残暴,

(2022-08-22)画图撤销这里我试过了,Microsoft的画图只能后撤50步,当超过这个极限,就无法继续撤销了,这和链接中说的一致,是可行方案;
类似于一个50长度的数组,存下近50步的画图结果,然后根据你的要求显示,可前,可后;
另外一种保存命令,然后重绘的方案,也感觉是可以的,即记录绘制命令,撤销一步,就是去掉最后的一个绘制命令,然后全部重绘一次,再显示,我个人自然偏向存储每一步结果;

有机会再来传代码。。。
 
如有错误,请留言联系,必回,改正,谢谢!

 2022-08-15

posted on 2022-08-15 12:21  夜_归_人  阅读(554)  评论(0编辑  收藏  举报