注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

jasonyang9的博客

随便写写

 
 
 

日志

 
 

(怀旧系列)VC程序设计(孙鑫老师)听课笔记:10 图形、位图  

2013-02-05 19:48:10|  分类: programming |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
(怀旧系列)VC程序设计(孙鑫老师)听课笔记:10 图形、位图 - jasonyang9 - jasonyang9的博客
 
==========
图形、位图
==========

新建一个单文档工程,增加一个“绘图”弹出菜单,4个菜单项:分别是“点”(IDM_DOT)、“直线”(IDM_LINE)、“矩形”(IDM_RECTANGLE)和“椭圆”(IDM_ELLIPSE)。分别给这4个菜单项在View类中增加对应的命令响应函数(OnDot、OnLine、OnRectangle和OnEllipse)。
在这4个命令响应函数中将用户的选择保存起来,为此,在View类中增加一个私有的成员变量UINT m_nDrawType。

CXXXView::CXXXView()
{
m_nDrawType = 0; // 初始化为0
}

void CXXXView::OnDot()
{
m_nDrawType = 1; // “点”为1(其实应该定义几个对应的宏来表示绘图的类型)
}

void CXXXView::OnLine()
{
m_nDrawType = 2; // “直线”为2
}

void CXXXView::OnRectangle()
{
m_nDrawType = 3; // “矩形”为3
}

void CXXXView::OnEllipse()
{
m_nDrawType = 4; // “椭圆”为4
}


增加对WM_LBUTTONDOWN和WM_LBUTTONUP的命令响应函数(为了捕获鼠标左键按下时和放开时的坐标点,用于绘制“直线”、“矩形”和“椭圆”)。同时,为了将坐标点保存下来,增加私有成员变量CPoint m_ptOrigin。

CXXXView::CXXXView()
{
m_nDrawType = 0;
m_ptOrigin = 0; // 初始化为0
}

void CXXXView::OnLButtonDown(UINT nflags, CPoint point)
{
m_ptOrigin = point; // 保存鼠标按下时的坐标点
CView::OnLButtonDown(nflags, point);
}

void CXXXView::OnLButtonUp(UINT nflags, CPoint point) // 在鼠标放开时进行绘图
{
CClientDC dc(this); // 创建DC
CPen pen(PS_SOLID, 1, RGB(255, 0, 0)); // 创建红色的画笔
dc.SelectObject(&pen); // 将画笔选入设备表述表
CBrush* pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); // 创建一个指向空画刷的指针
dc.SelectObject(pBrush); // 将空画刷选入设备描述表(使“矩形”和“椭圆”为空心效果)

switch (m_nDrawType)
{
case 1: // 画“点”
dc.SetPixel(point, RGB(255, 0, 0)); // 一个红色的点
break;
case 2: // 画“直线”
dc.MoveTo(m_ptOrigin); // 移动到起点(即鼠标按下时保存的点)
dc.LineTo(point); // 画一条直线到鼠标放开时的点
break;
case 3: // 画“矩形”
dc.Rectangle(CRect(m_ptOrigin, point)); // 用CRect组合2个点为一个矩形对象(注意:这里CRect会自动将CRect转换为LPCRECT,因为CRect重载了一个操作符LPCRECT,当CDC::Rectangle发现传入的参数是一个类对象时,会调用重载的带有LPCRECT的函数,让CRect可以自动将CRect转换为LPCRECT?)
break;
case 4: // 画“椭圆”
dc.Ellipse(CRect(m_ptOrigin, point)); // 类似“矩形”
break;
default:
}

CView::OnLButtonUp(nflags, point);
}

增加线宽的选择
--------------

新建一个对话框(标题:Setting;ID号:IDD_DLG_SETTING;字体:宋体)。增加静态控件(标题:线宽);增加编辑框(ID号:IDC_LINE_WIDTH)。双击对话框创建对应的类(类名:CSettingDlg;父类:CDialog)。为编辑框IDC_LINE_WIDTH增加成员变量(名称:m_nLineWidth;类别:Value;类型:UINT)。
增加一个菜单项(ID号:IDM_SETTING;标题:设置);在View类中增加菜单项的命令响应函数OnSetting。
为了保存设置的值,在View类中定义UINT m_nLineWidth;在构造函数中对其初始化为0。

#include "SettingDlg.h" // 在View类中包含对话框的头文件,以获得其声明

void CXXXView::OnSetting()
{
CSettingDlg dlg; // 定义CSettingDlg对话框类的对象dlg

dlg.m_nLineWidth = m_nLineWidth; // 将保存在View类中的m_nLineWidth传回给对话框(否则每次定义新的dlg对象时m_nLineWidth都会被CSettingDlg的构造函数初始化为0)

if (IDOK == dlg.DoModal()) // 调用DoModal显示模态对话框
{
m_nLineWidth = dlg.m_nLineWidth; // 如果用户点击OK按钮返回,则读取选择的值
}
}


修改OnLButtonUp,根据用户选择的线宽创建画笔。

void CXXXView::OnLButtonUp(UINT nflags, CPoint point) // 在鼠标放开时进行绘图
{
...
CPen pen(PS_SOLID, m_nLineWidth, RGB(255, 0, 0)); // 创建红色的画笔,线宽为用户设定
...
}


增加线型的选择
--------------
在对话框上增加一个组框(标题:线型);增加3个单选按钮(“实线”,IDC_RADIO1;“虚线”,IDC_RADIO2;“点线”,IDC_RADIO3),并设为一组(将第一个单选按钮属性的Group复选框选中)。为第一个单选按钮创建(关联)一个成员变量int m_nLineStyle。
在View类中增加变量int m_nLineStyle,在构造函数中对其初始化为0。
修改OnSetting。

void CXXXView::OnSetting()
{
CSettingDlg dlg; // 定义CSettingDlg对话框类的对象dlg

dlg.m_nLineWidth = m_nLineWidth; // 将保存在View类中的m_nLineWidth传回给对话框(否则每次定义新的dlg对象时m_nLineWidth都会被CSettingDlg的构造函数初始化为0)
dlg.m_nLineStyle = m_nLineStyle; // 将保存在View类中的m_nLineStyle传回给对话框(否则每次定义新的dlg对象时m_nLineStyle都会被CSettingDlg的构造函数初始化为-1,即3个单选按钮全都不选中的状态)

if (IDOK == dlg.DoModal()) // 调用DoModal显示模态对话框
{
m_nLineWidth = dlg.m_nLineWidth; // 如果用户点击OK按钮返回,则读取选择的值
m_nLineStyle = dlg.m_nLineStyle; // 同上
}
}

修改OnLButtonUp。

void CXXXView::OnLButtonUp(UINT nflags, CPoint point) // 在鼠标放开时进行绘图
{
...
CPen pen(m_nLineStyle, m_nLineWidth, RGB(255, 0, 0)); // 由于“实线”、“虚线”和“点线”的值和pen的参数正好一致,所以可以直接传参
...
}


增加颜色的选择
--------------
用MFC提供的CColorDialog类可以方便的创建颜色选择对话框,CColorDialog派生于CCommonDialog,CCommonDialog派生于CDialog。
增加一个菜单项(ID号:IDM_COLOR;标题:颜色),在View类中增加命令响应函数OnColor。
在View类中增加一个COLORREF m_clr成员变量,在构造函数中对其初始化为RGB(255, 0, 0)(红色)。

void CXXXView::OnColor()
{
CColorDialog dlg;

dlg.m_cc.flags |= CC_RGBINIT; // 增加CC_RGBINIT到flags使得赋给rgbResult的值为颜色对话框的初始颜色值(必须用“|=”操作,表示增加一个类型;而不是“=”操作,会将其他类型覆盖)(m_cc是CColorDlg的一个成员变量,是CHOOSECOLOR类型的一个结构,其中包括了很多关于颜色的成员)(如果再“|”上一个CC_FULLOPEN类型,则对话框初始为带有自定义面板的完全展开状态)
dlg.m_cc.rgbResult = m_clr; // 将保存在View类中的m_clr传回给对话框

if (IDOK == dlg.DoModal())
{
m_clr = dlg.m_cc.rgbResult; // 如果用户点击OK按钮返回,则读取选择的颜色
}
}


修改OnLButtonUp。

void CXXXView::OnLButtonUp(UINT nflags, CPoint point) // 在鼠标放开时进行绘图
{
...
CPen pen(m_nLineStyle, m_nLineWidth, m_clr);
...
dc.SetPixel(point, m_clr);
...
}


创建字体对话框
--------------
用CFontDialog,也是从CCommonDialog派生。
增加一个菜单项(ID号:IDM_FONT;标题:字体),在View类中增加命令响应函数OnFont。
在View类中增加成员变量CFont m_font;CString m_strFontName(初始化为""(空))。

void CXXXView::OnFont()
{
CFontDialog dlg;
if (IDOK == dlg.DoModal())
{
// CFontDialog有CHOOSEFONT结构体类型的成员变量m_cf,其中的lpLogFont是一个LPLOGFONT类型的成员变量,LOGFONT是一个逻辑字体的结构体,其中的lfFaceName是一个TCHAR(null-terminated)字符串,是字体的名字。
if (m_font.m_hObject) // CGdiObject类对象都有一个m_hObject成员变量保存了相关联的Windows GDI对象的句柄,可以用来判断是否已经创建了对象资源(不可重复创建)
m_font.DeleteObject(); // 释放相关联的Windows GDI对象资源(CGdiObject::DeleteObject删除Windows GDI对象,释放占用的资源,但和该Windows GDI对象相关联的CGdiObject类对象不受影响(这跟窗口和窗口类对象之间的关系类似)。不可对处在设备描述表中的CGdiObject类对象使用DeleteObject调用。)
m_font.CreateFontIndirect(dlg.m_cf.lpLogFont); // 用m_cf中的lpLogFont结构体创建字体
m_strFontName = dlg.m_cf.lpLogFont->lpfaceName; // 获取字体名字
Invalidate(); // 产生WM_PAINT消息,使窗口重绘,在OnDraw函数中输出字体名
}
}

void CXXXView::OnDraw(CDC* pDC)
{
CXXXDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

CFont *pOldFont = pDC->SelectObject(&m_font); // 将字体选入设备描述表
pDC->TextOut(0, 0, m_strFontName); // 输出字体名
pDC->SelectObject(pOldFont); // 恢复原字体
}


在Setting对话框中增加预览功能
-----------------------------
增加一个组框(ID号:IDC_SAMPLE;标题:示例)。
在CSettingDlg类中捕获编辑框(IDC_LINE_WIDTH)的EN_CHANGE消息(该消息表示用户改变了编辑框的内容);捕获3个单选按钮的BN_CLICKED消息(表示用户点选了它们)。

void CSettingDlg::OnChangeLineWidth()
{
Invalidate(); // 使窗口重绘,在OnPaint中实现预览功能
}

void CSettingDlg::OnRadio1()
{
Invalidate();
}

void CSettingDlg::OnRadio2()
{
Invalidate();
}

void CSettingDlg::OnRadio3()
{
Invalidate();
}

增加WM_PAINT消息响应函数OnPaint。

void CSettingDlg::OnPaint()
{
CPaintDC dc(this);

UpdateData(TRUE); // 读取控件的值(否则与控件关联的变量中保存的是默认值)

CPen pen(m_nLineStyle, m_nLineWidth, RGB(255, 0, 0)); // 根据用户的选择,创建画笔
dc.SelectObject(&pen); // 将画笔选入设备描述表

CRect rect;
GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect); // 获取组框(示例)的矩形区域(注意:GetWindowRect返回的是针对屏幕左上角的坐标,还需要转换)
ScreenToClient(&rect); // 将屏幕坐标系转换为客户端坐标系(这里传递rect也是可以的,因为CRect重载了LPRECT操作符,能自动类型转换)
dc.MoveTo(rect.left + 20, rect.top + rect.Height() / 2);
dc.LineTo(rect.right - 20, rect.top + rect.Height() / 2); 
}


但这个示例的线条颜色不是用户指定的颜色,要获取用户指定的颜色,在CSettingDlg中增加一个public的COLORREF m_clr(以便从外部将颜色传入),在CSettingDlg的构造函数中将其初始化为0。
修改View类的OnSetting函数,传入用户选择的颜色。

void CXXXView::OnSetting()
{
...
dlg.m_clr = m_clr;
...
}


不要忘记修改CSettingDlg::OnPaint()。

void CSettingDlg::OnPaint()
{
...
CPen pen(m_nLineStyle, m_nLineWidth, m_clr); // 将第3个参数修改为传入的颜色
...
}



改变对话框和控件的背景色,文字颜色
----------------------------------
CWnd::OnCtlColor函数响应WM_CTLCOLOR消息,OnCtlColor会返回一个绘制控件背景的画刷,对话框中每个控件的绘制都要发送WM_CTLCOLOR消息,OnCtlColor会被调用多次,每个控件的DC作为第一个参数传给它,pWnd指针作为第二个参数,第三个参数标识当前绘制的控件类型(CTLCOLOR_BTN、CTLCOLOR_DLG等)。
在对话框类CSettingDlg增加WM_CTLCOLOR消息处理OnCtlColor。增加成员变量CBrush m_brush,在构造函数中初始化m_brush.CreateSolidBrush(RGB(0, 0, 255))。
增加一个静态控件(ID号:IDC_TEXT;标题:程序员)。增加成员变量CFont m_font,在构造函数中初始化m_font.CreatePointFont(200, "华文行楷")。

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

if (pWnd->GetDlgCtrlID() == IDC_LINE_STYLE) // 用GetDlgCtrlID获取传入控件的ID号来判断是否一致(要先将组框(标题:线型)的ID号改为IDC_LINE_STYLE)
{
pDC->SetTextColor(RGB(255, 0, 0)); // 设定文字颜色(由于每个对话框控件在绘制时都会为该控件准备一个pDC和pWnd,在调用OnCtlColor时会将它们传入,因此这里只要判断出控件的ID号,就是在对该控件的pDC和pWnd进行操作)
pDC->SetBkMode(TRANSPARENT); // 设定文字背景为透明模式
return m_brush; // 用自定义的画刷(虽然返回的是一个CBrush的
}

if (pWnd->GetDlgCtrlID() == IDC_LINE_WIDTH) // 编辑框
{
pDC->SetTextColor(RGB(255, 0, 0));
pDC->SetBkColor(RGB(0, 0, 255)); // 编辑框的背景色设置需要调用SetBkColor
return m_brush;
}

if (pWnd->GetDlgCtrlID() == IDC_TEXT) // 静态控件(ID号:IDC_TEXT;标题:程序员)
{
pDC->SelectObject(&m_font); // 将字体选入设备描述表
}

if (pWnd->GetDlgCtrlID() == IDOK) // OK按钮
{
return m_brush; // 注意:这种做法不能改变按钮的背景!
}

return hbr; // 默认的画刷
}


要改变按钮的外观,必须派生一个按钮类,然后重载其虚函数DrawItem。新建一个MFC类(CTestBtn,基类CButton),增加虚函数DrawItem。

void CTestBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
UINT uStyle = DFCS_BUTTONPUSH; // 这段代码为MSDN中的示例代码

// This code only works with buttons.
ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);

// If drawing selected, add the pushed style to DrawFrameControl.
if (lpDrawItemStruct->itemState & ODS_SELECTED)
uStyle |= DFCS_PUSHED;

// Draw the button frame.
::DrawFrameControl(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, 
DFC_BUTTON, uStyle);

// Get the button's text.
CString strText;
GetWindowText(strText);

// Draw the button text using the text color red.
COLORREF crOldColor = ::SetTextColor(lpDrawItemStruct->hDC, RGB(255,0,0));
::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(), 
&lpDrawItemStruct->rcItem, DT_SINGLELINE|DT_VCENTER|DT_CENTER);
::SetTextColor(lpDrawItemStruct->hDC, crOldColor);
}

用ClassWizard为OK按钮新建一个CTestBtn类型的成员变量(名称:m_btnTest;类别:Control;类型CTestBtn)(其实就是用这个办法将标准的CButton类型的按钮换成CTestBtn类型)。要在CSettingDlg中增加include "TestBtn.h"。
最后,在资源编辑器中将按钮的Owner draw类型选上。

利用已有代码的演示。

在窗口中显示位图
================
1、创建位图(CBitmap bitmap; bitmap.LoadBitmap(IDB_BITMAP1);)
2、创建兼容DC(CDC dcCompatible; dcCompatible.CreateCompatibleDC(pDC);)
3、将位图选到兼容DC中(dcCompatible.SelectObject(&bitmap);)
4、将兼容DC中的位图贴到当前DC中(pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), &dcCompatible, 0, 0, SRCCOPY);)

用画图等工具保存位图,在VC中导入该文件(VC可能会提示由于位图深度大于256色,不能被编辑器加载,但已经成功导入)。
窗口的绘制一般有2个步骤,先将窗口背景擦除,然后再绘制内容,因此要在窗口中显示位图,可在负责擦除窗口背景的消息响应函数中处理。
增加WM_ERASEBKGND消息处理函数OnEraseBkgnd。

BOOL CXXXView::OnEraseBkgnd(CDC* pDC)
{
CBitmap bitmap; // 创建位图对象
bitmap.LoadBitmap(IDB_BITMAP1); // 用资源ID号加载位图

BITMAP bmp; // BITMAP结构体
bitmap.GetBitmap(&bmp); // 用于获取位图的尺寸

CDC dcCompatible; // 创建兼容DC
dcCompatible.CreateCompatibleDC(pDC); // 和当前DC兼容

dcCompatible.SelectObject(&bitmap); // 将位图选入兼容DC,以确定显示表的大小

CRect rect;
GetClientRect(&rect);

// pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, 0, 0, SRCCOPY); // 将兼容DC(dcCompatible)中的位图拷贝到目的DC中,前2个0是拷贝目的坐标点,后2个0是位图左上角坐标点
pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, // 用拉伸的方式拷贝位图
0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);

return TRUE; // 直接返回一个非0值
// return CView::OnEraseBkgnd(pDC); // 屏蔽掉基类的OnEraseBkgnd(否则位图被背景擦除)
}

将代码转移到OnDraw中。

BOOL CXXXView::OnDraw(CDC* pDC)
{
...
CBitmap bitmap; // 创建位图对象
bitmap.LoadBitmap(IDB_BITMAP1); // 用资源ID号加载位图

BITMAP bmp; // BITMAP结构体
bitmap.GetBitmap(&bmp); // 用于获取位图的尺寸

CDC dcCompatible; // 创建兼容DC
dcCompatible.CreateCompatibleDC(pDC); // 和当前DC兼容

dcCompatible.SelectObject(&bitmap); // 将位图选入兼容DC,以确定显示表的大小

CRect rect;
GetClientRect(&rect);

// pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, 0, 0, SRCCOPY); // 将兼容DC(dcCompatible)中的位图拷贝到目的DC中,前2个0是拷贝目的坐标点,后2个0是位图左上角坐标点
pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, // 用拉伸的方式拷贝位图
0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
...
}

在OnDraw中显示位图,但这时如果改变窗口大小,会有一些闪烁,原因是窗口发生重绘前需要擦除背景,然后再绘制内容,产生了闪烁。

思考题
======
如何在CDialogBar上设置控件的背景色,在列表框的背景上显示位图。

  评论这张
 
阅读(325)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017