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 }
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 }
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,否则鼠标再次回界面无需按下左键随意可绘制,无法约束;
全部看完,不会也会了,大致操作就是,先在内存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;
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画图的撤销重做是如何实现的? --------- 要是直接保存每一步绘制,这个我还可以做一下,不过想一下就很残暴,
类似于一个50长度的数组,存下近50步的画图结果,然后根据你的要求显示,可前,可后;
另外一种保存命令,然后重绘的方案,也感觉是可以的,即记录绘制命令,撤销一步,就是去掉最后的一个绘制命令,然后全部重绘一次,再显示,我个人自然偏向存储每一步结果;
有机会再来传代码。。。
如有错误,请留言联系,必回,改正,谢谢!
2022-08-15