准备:确定自己已经在Vs上安装好了MFC的配件:
(最后的连接错误让我很难受~~~~~)
首先是创建单文档的MFC程序应用。(这里MFC应用命名为:OOPEx)
前提工作:
在类视图中,找到:程序名+View类(这里为COOPExView[也被称为CView])
右键给该类添加一个CPoint m_point变量(获取点击的点的位置)
然后继续在该类点击,右键--->类向导中为该类添加左键点击函数(WM_LBUTTONDOWN)和左键释放函数(WM_LBUTTONUP),还有光标移动消息函数(WM_MOUSEMOVE)
(一定要点击第二步添加,直接确定是没有添加响应函数的~~~~)
好了,函数添加完后,就是响应函数的代码了,~~~~~
壹:直线绘制的四种方法:
一、API函数方式添加
void COOPExView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_point = point;//保存点的位置
CView::OnLButtonDown(nFlags, point);
}
void COOPExView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
HDC hdc;
hdc = ::GetDC(m_hWnd);//移动到线条的起始位置
MoveToEx(hdc, m_point.x, m_point.y, NULL);//画线
LineTo(hdc, point.x, point.y);
::ReleaseDC(m_hWnd, hdc);
CView::OnLButtonUp(nFlags, point);
}
二、MFC绘制(CDC类)
void COOPExView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_point = point;
CView::OnLButtonDown(nFlags, point);
}
void COOPExView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CDC *pDC = GetDC();//CDC类中的GetDC()正好返回了一个CDC类的对象指针
pDC->MoveTo(m_point);//利用CDC的成员函数画线
pDC->LineTo(point);
ReleaseDC(pDC);//牢记的是,在这里我们依然不要忘记释放DC
CView::OnLButtonUp(nFlags, point);
}
三、CClientDC类绘制
void COOPExView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_point = point;//保存点的位置
CView::OnLButtonDown(nFlags, point);
}
void COOPExView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//CClientDC dc(GetDesktopWindow());//整个屏幕,NULL也可以
//CClientDC dc(GetParent());//获取父类的窗口句柄,也就是CMainFrame的窗口句柄,在父类的客户区内画线
CClientDC dc(this);
dc.MoveTo(m_point); //CClientDC类型的变量dc是一个对象,采用点操作符来调用该对象的函数
dc.LineTo(point);
CView::OnLButtonUp(nFlags, point);
}
四、CWindowDC类绘制
void COOPExView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_point = point;//保存点的位置
CView::OnLButtonDown(nFlags, point);
}
void COOPExView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//CWindowDC dc(GetDesktopWindow());//整个屏幕,NULL也可以
//CWindowDC dc(GetParent());//获取父类的窗口句柄,也就是CMainFrame的窗口句柄,在父类的客户区内画线
CWindowDC dc(this); //视图客户区内作图,结束时会自释放DC
dc.MoveTo(m_point);
dc.LineTo(point);
//CWindowDC类是从CDC继承的。它在构造的时候调用Windows函数GetWindowDC,在销毁的时候调用ReleaseDC。
CView::OnLButtonUp(nFlags, point);
}
贰:绘制红色实线:
void COOPExView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_point = point;//保存点的位置
CView::OnLButtonDown(nFlags, point);
}
void COOPExView::OnLButtonUp(UINT nFlags, CPoint point)
{
CPen pen(PS_SOLID, 5, RGB(255, 0, 0));//中间的数字可以调整画笔大小
CClientDC dc(this);
CPen *pOldPen = dc.SelectObject(&pen);
dc.MoveTo(m_point);
dc.LineTo(point);
dc.SelectObject(pOldPen);
CView::OnLButtonUp(nFlags, point);
}
绘制(填充)矩阵:
void COOPExView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_point = point;//保存点的位置
CView::OnLButtonDown(nFlags, point);
}
void COOPExView::OnLButtonUp(UINT nFlags, CPoint point)
{
CBrush brush(RGB(255, 0, 0)); //画刷填充,可空心(这个自己查)
CPen pen(PS_SOLID, 5, RGB(255, 0, 0));
CClientDC dc(this);
CPen *pOldPen = dc.SelectObject(&pen);
dc.FillRect(CRect(m_point, point), &brush);
dc.MoveTo(m_point);
dc.LineTo(point);
dc.SelectObject(pOldPen);
CView::OnLButtonUp(nFlags, point);
}
绘制(非填充)矩阵:
void CMFCApplication1View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_point = point;
CView::OnLButtonDown(nFlags, point);
}
void CMFCApplication1View::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CClientDC dc(this);
CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); // CBrush::FromHandle是静态成员函数
//FromHandle给出一个Windows HBRUSH对象句柄时,返回一个指向CBrush对象的指针。即将画刷的句柄转换为对象的指针。
CBrush *pOldBrush = dc.SelectObject(pBrush);
dc.Rectangle(CRect(m_point, point));
dc.SelectObject(pOldBrush);
CView::OnLButtonUp(nFlags, point);
}
叁:绘制国际象棋棋盘:
void COOPExView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_point = point;//保存点的位置
CView::OnLButtonDown(nFlags, point);
}
void COOPExView::OnLButtonUp(UINT nFlags, CPoint point)
{
int hight = 20, width = 20, flag = 0;//参数可自己调大小
CClientDC dc(this);
CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); // CBrush::FromHandle是静态成员函数
CBrush *pOldBrush = dc.SelectObject(pBrush);
dc.SelectObject(pOldBrush);
//CClientDC dc(this);
p1.x = m_point.x - 15;
p1.y = m_point.y - 15;
p2.x = m_point.x + 8 * hight + 16;
p2.y = m_point.y + 8 * width + 16;
dc.Rectangle(CRect(p1, p2));
p1.x = m_point.x - 1;
p1.y = m_point.y - 1;
p2.x = m_point.x + 8 * hight + 1;
p2.y = m_point.y + 8 * width + 1;
dc.Rectangle(CRect(p1, p2));
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
point1.x = m_point.x + i * hight;
point1.y = m_point.y + j * width;
point2.x = m_point.x+(i + 1)*hight;
point2.y = m_point.y+(j + 1)*width;
if ((i + j) % 2 == 0)
{
CBrush brush(RGB(0, 0, 0));
dc.FillRect(CRect(point1, point2), &brush);
}
else
{
CBrush brush(RGB(255, 255, 255));
dc.FillRect(CRect(point1, point2), &brush);
}
}
}
CView::OnLButtonUp(nFlags, point);
}
肆:双缓冲实现国际象棋:
参考博客:MFC双缓冲解决图象闪烁[转]
在CView类的类向导中虚函数里,有一个OnDraw虚函数,对其进行修改;
添加代码为:
先在构造函数中确定初始棋盘的位置:
CMFCApplication2View::CMFCApplication2View() noexcept
{
// TODO: 在此处添加构造代码
m_point.x = 200;//自己看情况
m_point.y = 200;
}
或者定义一个数字,让棋盘不显现:
CMFCApplication2View::CMFCApplication2View() noexcept
{
// TODO: 在此处添加构造代码
count = 0;
}
然后在上面绘制棋盘的基础上给OnDraw函数添加代码:
void CMFCApplication2View::OnDraw(CDC* pDC)
{
int nWidth = 1, nHeight = 1;
CMFCApplication2Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CDC MemDC; //首先定义一个显示设备对象
CBitmap MemBitmap;//定义一个位图对象
//随后建立与屏幕显示兼容的内存显示设备
MemDC.CreateCompatibleDC(NULL);
//这时还不能绘图,因为没有地方画 ^_^
//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
MemBitmap.CreateCompatibleBitmap(pDC, nWidth, nHeight);
//将位图选入到内存显示设备中
//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);
//先用背景色将位图清除干净,这里我用的是白色作为背景
//你也可以用自己应该用的颜色
MemDC.FillSolidRect(0, 0, nWidth, nHeight, RGB(255, 255, 255));
//绘图
int hight = 20, width = 20, flag = 0;//参数可自己调大小
CClientDC dc(this);
CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); // CBrush::FromHandle是静态成员函数
CBrush *pOldBrush = dc.SelectObject(pBrush);
dc.SelectObject(pOldBrush);
//CClientDC dc(this);
if (count > 0){ //可加可不加。。。。看老师心情
p1.x = m_point.x - 15;
p1.y = m_point.y - 15;
p2.x = m_point.x + 8 * hight + 16;
p2.y = m_point.y + 8 * width + 16;
dc.Rectangle(CRect(p1, p2));
p1.x = m_point.x - 1;
p1.y = m_point.y - 1;
p2.x = m_point.x + 8 * hight + 1;
p2.y = m_point.y + 8 * width + 1;
dc.Rectangle(CRect(p1, p2));
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
point1.x = m_point.x + i * hight;
point1.y = m_point.y + j * width;
point2.x = m_point.x + (i + 1)*hight;
point2.y = m_point.y + (j + 1)*width;
if ((i + j) % 2 == 0)
{
CBrush brush(RGB(0, 0, 0));
dc.FillRect(CRect(point1, point2), &brush);
}
else
{
CBrush brush(RGB(255, 255, 255));
dc.FillRect(CRect(point1, point2), &brush);
}
}
}
}
//将内存中的图拷贝到屏幕上进行显示
pDC->BitBlt(0, 0, nWidth, nHeight, &MemDC, 0, 0, SRCCOPY);
//绘图完成后的清理
MemBitmap.DeleteObject();
MemDC.DeleteDC();
// TODO: 在此处为本机数据添加绘制代码
}
效果图:
(注意:该双缓冲代码只能解决最后一次绘画国际象棋时的闪烁问题,即最大化后只会保留最后绘制的那个国际象棋棋盘)
伍:交互式指定棋盘大小;
方法:
通过绘画标准线的方法界定棋盘的大小
步骤:左键点击到左键释放绘画标准线,双击右键进行棋盘绘画。。
示例:
具体操作:(文件名为:MFCApplication3)
为Cview类添加的变量和响应函数如下:
将.cpp文件中的右键释放函数代码注销:(可在类向导中寻找该响应函数)
继续对各响应函数编写代码为:
void CMFCApplication3View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_point = point;
CView::OnLButtonDown(nFlags, point);
}
void CMFCApplication3View::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
int S = (int)sqrt((m_point.x - point.x)*(m_point.x - point.x) + (m_point.y - point.y)*(m_point.y - point.y));
hight = width = S;
hight /= 9;
width /= 9;
CPen pen(PS_SOLID, 2, RGB(255, 0, 0));//中间的数字可以调整画笔大小
CClientDC dc(this);
CPen *pOldPen = dc.SelectObject(&pen);
dc.MoveTo(m_point);
dc.LineTo(point);
dc.SelectObject(pOldPen);
CView::OnLButtonUp(nFlags, point);
}
void CMFCApplication3View::OnLButtonDblClk(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CView::OnLButtonDblClk(nFlags, point);
}
void CMFCApplication3View::OnRButtonDblClk(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
c_point = point;
count++;
CClientDC dc(this);
CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
// CBrush::FromHandle是静态成员函数
CBrush *pOldBrush = dc.SelectObject(pBrush);
dc.SelectObject(pOldBrush);
//CClientDC dc(this);
p1.x = point.x - hight / 4 * 3-1;
p1.y = point.y - width / 4 * 3-1;
p2.x = point.x + 8 * hight + hight / 4 * 3 + 1;
p2.y = point.y + 8 * width + width / 4 * 3 + 1;
dc.Rectangle(CRect(p1, p2));
p1.x = point.x - 1;
p1.y = point.y - 1;
p2.x = point.x + 8 * hight + 1;
p2.y = point.y + 8 * width + 1;
dc.Rectangle(CRect(p1, p2));
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
point1.x = point.x + i * hight;
point1.y = point.y + j * width;
point2.x = point.x + (i + 1)*hight;
point2.y = point.y + (j + 1)*width;
if ((i + j) % 2 == 0)
{
CBrush brush(RGB(0, 0, 0));
dc.FillRect(CRect(point1, point2), &brush);
}
else
{
CBrush brush(RGB(255, 255, 255));
dc.FillRect(CRect(point1, point2), &brush);
}
}
}
CView::OnRButtonDblClk(nFlags, point);
}
构造函数初始化变量:
CMFCApplication3View::CMFCApplication3View() noexcept
{
// TODO: 在此处添加构造代码
count = 0; //双缓冲刷新限制条件
hight = width = 20; //未画标准线时棋盘的大小
}
双缓冲防刷新(找到OnDraw函数进行编译):
void CMFCApplication3View::OnDraw(CDC* pDC)
{
/*CMFCApplication3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;*/
int nWidth = 1, nHeight = 1;
CMFCApplication3Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CDC MemDC; //首先定义一个显示设备对象
CBitmap MemBitmap;//定义一个位图对象
//随后建立与屏幕显示兼容的内存显示设备
MemDC.CreateCompatibleDC(NULL);
//这时还不能绘图,因为没有地方画 ^_^
//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
MemBitmap.CreateCompatibleBitmap(pDC, nWidth, nHeight);
//将位图选入到内存显示设备中
//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);
//先用背景色将位图清除干净,这里我用的是白色作为背景
//你也可以用自己应该用的颜色
MemDC.FillSolidRect(0, 0, nWidth, nHeight, RGB(255, 255, 255));
//绘图
if (count > 0)
{
CClientDC dc(this);
CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); // CBrush::FromHandle是静态成员函数
CBrush *pOldBrush = dc.SelectObject(pBrush);
dc.SelectObject(pOldBrush);
//CClientDC dc(this);
p1.x = c_point.x - hight / 4 * 3-1;
p1.y = c_point.y - width / 4 * 3-1;
p2.x = c_point.x + 8 * hight + hight / 4 * 3 + 1;
p2.y = c_point.y + 8 * width + width / 4 * 3 + 1;
dc.Rectangle(CRect(p1, p2));
p1.x = c_point.x - 1;
p1.y = c_point.y - 1;
p2.x = c_point.x + 8 * hight + 1;
p2.y = c_point.y + 8 * width + 1;
dc.Rectangle(CRect(p1, p2));
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
point1.x = c_point.x + i * hight;
point1.y = c_point.y + j * width;
point2.x = c_point.x + (i + 1)*hight;
point2.y = c_point.y + (j + 1)*width;
if ((i + j) % 2 == 0)
{
CBrush brush(RGB(0, 0, 0));
dc.FillRect(CRect(point1, point2), &brush);
}
else
{
CBrush brush(RGB(255, 255, 255));
dc.FillRect(CRect(point1, point2), &brush);
}
}
}
}
//将内存中的图拷贝到屏幕上进行显示
pDC->BitBlt(0, 0, nWidth, nHeight, &MemDC, 0, 0, SRCCOPY);
//绘图完成后的清理
MemBitmap.DeleteObject();
MemDC.DeleteDC();
// TODO: 在此处为本机数据添加绘制代码
}
还有一种交互式绘画棋盘大小的方法:分割视图,用对话框传参
(这个,我遇到了一个小小的bug,,就是对话框中获取的数据无法传到视图类中,所以导致失败,
如果有找到如何将对话框中数据传到视图类中的同志们,望私戳告知,不胜感激。)
陆:将棋盘文件保存为BMP格式文件:
(注意:这里仅保存棋盘,而不是整个屏幕,当然,保存棋盘是建立在保存整个屏幕的基础上的。)
前提:在 伍:【交互式指定棋盘大小的基础】上继续进行。
一、BITMAPINFO方式保存:
步骤:
1、切换到资源视图,在Menu文件中双击IDR_MAINFRAME弹出右侧文件框架中,
为“ 文件——>保存 ” 添加 “保存文件为bmp格式” (当然,你开心就好~~~),
2、右键“保存文件为bmp格式”选择“添加事件处理程序”,在弹出的对话框中选择第一个COMMAND,
右边的类一定要选择CView类,这里为“CMFCApplication3View”。
3、对上述COMMAND函数进行编辑,编辑代码为:
void CMFCApplication3View::On32771()
{
// TODO: 在此添加命令处理程序代码
int x = c_point.x - hight / 4 * 3 - 1;//定位棋盘的位置
int y = c_point.y - hight / 4 * 3 - 1;
CDC* pDC = GetWindowDC();
CBitmap bitmap;
CDC memDC;
CRect rect;
GetWindowRect(rect);
memDC.CreateCompatibleDC(pDC);
bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
memDC.SelectObject(&bitmap);
memDC.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, x, y, SRCCOPY);
CFileDialog fDlg(FALSE, _T("bmp"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("位图文件|*.bmp"), this);
if (fDlg.DoModal() == IDOK)
{
CString bmpfile = fDlg.GetPathName();
CFile file(bmpfile, CFile::modeCreate | CFile::modeWrite);
BITMAP bInfo;
bitmap.GetBitmap(&bInfo);
//计算调色板大小
int panelsize = 0;
if (bInfo.bmBitsPixel < 24) //非真彩色
{
panelsize = pow((double)2, bInfo.bmBitsPixel) * sizeof(RGBQUAD);
}
//定义位图信息
int size = hight * 9 + hight/2 + 3;//设置棋盘的大小
BITMAPINFO* bMapInfo = (BITMAPINFO*)LocalAlloc(LPTR, sizeof(BITMAPINFO) + panelsize);
bMapInfo->bmiHeader.biBitCount = bInfo.bmBitsPixel;
bMapInfo->bmiHeader.biClrImportant = 0;
bMapInfo->bmiHeader.biCompression = 0;
bMapInfo->bmiHeader.biHeight = size;
bMapInfo->bmiHeader.biPlanes = bInfo.bmPlanes;
bMapInfo->bmiHeader.biSize = sizeof(BITMAPINFO);
bMapInfo->bmiHeader.biSizeImage = bInfo.bmHeight*bInfo.bmWidthBytes;
bMapInfo->bmiHeader.biWidth = size;
bMapInfo->bmiHeader.biXPelsPerMeter = 0;
bMapInfo->bmiHeader.biYPelsPerMeter = 0;
//获取位图的实际数据
char* pData = new char[bMapInfo->bmiHeader.biSizeImage];
int len = GetDIBits(pDC->m_hDC, bitmap, 0, bInfo.bmHeight, pData, bMapInfo, DIB_RGB_COLORS);
BITMAPFILEHEADER bFileHeader;
bFileHeader.bfType = 0x4D42;
bFileHeader.bfReserved1 = 0;
bFileHeader.bfReserved2 = 0;
bFileHeader.bfSize = sizeof(BITMAPFILEHEADER);
bFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + panelsize;
//向文件中写入位图数据
file.Write(&bFileHeader, sizeof(BITMAPFILEHEADER));
file.Write(&bMapInfo->bmiHeader, sizeof(BITMAPINFOHEADER));
file.Write(pData, bMapInfo->bmiHeader.biSizeImage + panelsize);
file.Close();
delete pData;
LocalFree(bMapInfo);
}
bitmap.DeleteObject();
memDC.DeleteDC();
}
测试:
这里我们选择合适的位置进行保存即可。
二、CImage类保存图片
(这个保存方式等你们慢慢发现一下吧~~~~~)
给出第三步代码:
(这个需要自己先创建文件和写入路径。。。)
void CMainFrame::On32775()
{
// TODO: 在此添加命令处理程序代码
HWND hwnd = this->GetSafeHwnd();
HDC hDC = ::GetWindowDC(hwnd);//获取DC
RECT rect;
::GetWindowRect(hwnd, &rect);//获取屏幕大小
HDC hDCMem = ::CreateCompatibleDC(hDC);//创建兼容DC
HBITMAP hBitMap = ::CreateCompatibleBitmap(hDC, rect.right - rect.left, rect.bottom - rect.top);//创建兼容位图
HBITMAP hOldMap = (HBITMAP)::SelectObject(hDCMem, hBitMap);//将位图选入DC,并保存返回值
::BitBlt(hDCMem, 0, 0, rect.right - rect.left, rect.bottom - rect.top, hDC, 0, 0, SRCCOPY);//将屏幕DC的图象复制到内存DC中
CImage image;//需要#include <atlimage.h>
image.Attach(hBitMap);
image.Save(_T("E://B.bmp"));//如果文件后缀为.bmp,则保存为为bmp格式
//路径//文件名.bmp。。。。
image.Detach();
::SelectObject(hDCMem, hOldMap);//选入上次的返回值
//释放
::DeleteObject(hBitMap);
::DeleteDC(hDCMem);
::DeleteDC(hDC);
}
柒 :后记
至此 单文档的基础内容我们也学习的差不多了 主要还是完成作业啊~~
希望大家可以和我一样,继续加油,再接再厉,
即便孤独、迷茫、愤怒,
也请勇敢向前,
直到星与海的尽头,
直到梦与想的彼岸~~~~~
下面是我学习时借鉴和启发我的一些博客:
有一些没发上去,感觉太多了,给出截图,自己体会: