首先,我们需要一个UINT类型的变量m_nDrawType来记录我们选择的是哪个类型。并在这4个菜单的响应函数中为其赋值:
void CCH_10_GranphicView::OnDot()
{
// TODO: Add your command handler code here
m_nDrawType = 1;
}
void CCH_10_GranphicView::OnLine()
{
// TODO: Add your command handler code here
m_nDrawType = 2;
}
void CCH_10_GranphicView::OnRectangle()
{
// TODO: Add your command handler code here
m_nDrawType = 3;
}
void CCH_10_GranphicView::OnEllipse()
{
// TODO: Add your command handler code here
m_nDrawType = 4;
}
其次,我们需要捕获鼠标按下和鼠标抬起的消息。这两个消息会告诉我们两个点,通过这两个点,我们可以构造直线、矩形、椭圆。
void CCH_10_GranphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc(this);
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(m_ptOrigin,RGB(255,0,0));
break;
case 2:
dc.MoveTo(m_ptOrigin);
dc.LineTo(point.x,point.y);
break;
case 3:
dc.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
dc.Ellipse(CRect(m_ptOrigin,point));
break;
}
CView::OnLButtonUp(nFlags, point);
}
下面我们实现一个功能:增加一个菜单,通过这个菜单,我们可以修改线的线的宽度、线型、颜色、字体等内容。
void CCH_10_GranphicView::OnSetting()
{
// TODO: Add your command handler code here
CSettingDlg dlg;
dlg.m_iLinewidth = m_iLineWidth;//
if(IDOK == dlg.DoModal())
m_iLineWidth = dlg.m_iLinewidth;
}
而在OnLButtonUp中:
CPen pen(PS_SOLID,m_iLineWidth,RGB(255,0,0));//设置画笔颜色
可能有人会觉得奇怪if语句的作用是当点击OK以后,把我们在对话框里设置的颜色传递到view中,可是if之前的那句话是干什么的?它的作用是:确保我们下一次设置时,对话框里显示的是之前设置的颜色。因为我们的dlg对象是一个局部变量,所以每次都会被重新初始化,初始化后的m_iLinewidth在构造函数中被设为0,而view类中的m_iLineWidth的声明周期却是很长的,所以里面记录着上次设置过的值,我们把它传递给新生成的dlg中的m_iLinewidth。
void CCH_10_GranphicView::OnSetting()
{
// TODO: Add your command handler code here
CSettingDlg dlg;
dlg.m_iLinewidth = m_iLineWidth;
dlg.m_nLineStyle = m_iLineStyle;
if(IDOK == dlg.DoModal())
{
m_iLineWidth = dlg.m_iLinewidth;
m_iLineStyle = dlg.m_nLineStyle;
}
}
在OnLButtonUp中,并没有使用复杂的switch case语句来判断线型,而是直接将m_iLineWidth填进去了:
CPen pen(m_iLineStyle,m_iLineWidth,RGB(255,0,0));
这是为什么呢?因为在windows中,线型也是用数字定义的,且与我们这里摆放单选按钮的顺序一致:
/* Pen Styles */
#define PS_SOLID 0
#define PS_DASH 1 /* ------- */
#define PS_DOT 2 /* ....... */
#define PS_DASHDOT 3 /* _._._._ */
#define PS_DASHDOTDOT 4 /* _.._.._ */
#define PS_NULL 5
#define PS_INSIDEFRAME 6
#define PS_USERSTYLE 7
#define PS_ALTERNATE 8
#define PS_STYLE_MASK 0x0000000F
void CCH_10_GranphicView::OnColor()
{
// TODO: Add your command handler code here
CColorDialog dlg;
dlg.m_cc.Flags |= CC_RGBINIT;
dlg.m_cc.rgbResult = m_clr;
if(dlg.DoModal())
{
m_clr = dlg.m_cc.rgbResult;
}
}
CPen pen(m_iLineStyle,m_iLineWidth,m_clr);//设置画笔颜色
这里着重介绍一个CColorDialog 的成员变量m_cc它表明了这个类的很多重要信息,其中的一个成员rgbResult,表示的是颜色,而Flags 则是一些标记,通过它来设定某些功能是否能够使用,其中CC_RGBINIT标记意味着颜色对话框的默认颜色可以使用rgbResult成员。通过设置这个标记,我们可以把上一次设置的颜色保留下来。
void CCH_10_GranphicView::OnFont()
{
// TODO: Add your command handler code here
CFontDialog dlg;
if(IDOK == dlg.DoModal())
{
m_font.CreateFontIndirect(dlg.m_cf.lpLogFont);
m_strFontName = dlg.m_cf.lpLogFont->lfFaceName;
}
Invalidate();
}
通过Invalidate来引起重绘,而在OnDraw中相应:
void CCH_10_GranphicView::OnDraw(CDC* pDC)
{
CCH_10_GranphicDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CFont *pOldFont = pDC->SelectObject(&m_font);
pDC->TextOut(0,0,m_strFontName);
pDC->SelectObject(pOldFont);
}
这里有一个问题,就是如果你多次设置字体,程序就会报错,原因很简单,第一次设置字体后,已经通过CreateFontIndirect 函数初始化字体了,而第二次时,又会试图初始化,所以会报错。所以每次在初始化前,先要判断m_font是否已经被初始化过,如果已经被初始化过,那么得先切断这种关联,释放字体资源,然后与新的字体相关联。首先,如何判断已经被关联了。最简单的办法是通过这个类的句柄来判断,其次,如何切断联系:通过它的基类CGdiObject 的成员函数DeleteObject 来实现:
void CCH_10_GranphicView::OnFont()
{
// TODO: Add your command handler code here
CFontDialog dlg;
if(IDOK == dlg.DoModal())
{
if(m_font.m_hObject)
m_font.DeleteObject();
m_font.CreateFontIndirect(dlg.m_cf.lpLogFont);
m_strFontName = dlg.m_cf.lpLogFont->lfFaceName;
}
Invalidate();
}
下面看如何创建示例对话框。示例对话框,能让我们直观看见我们选择的颜色、线宽、线型等属性的效果。首先,我们先得理顺逻辑,当编辑框的内容改变时,会发出一个EN_CHANGE通告消息;当单选框的内容改变时,会发送BN_CLICKED通告消息。我们没有必要为每个消息写一遍代码,只需要让这些消息的响应函数调用Invalidate函数,而在OnPaint函数中相应就行了:
void CSettingDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
//如果要读取选择的数据,必须调用这个函数
UpdateData();
//设置画笔
CPen pen(m_nLineStyle,m_iLinewidth,m_clr);
dc.SelectObject(&pen);
//获取矩形
CRect rect;
//GetWindowRect获取的是屏幕坐标
GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect);
//转化为客户区坐标
ScreenToClient(&rect);
dc.MoveTo(rect.left + 20, rect.top + rect.Height() / 2);
dc.LineTo(rect.right - 20,rect.top + rect.Height() / 2);
// Do not call CDialog::OnPaint() for painting messages
}
其中为了将颜色也按照用户的需求显示出来,特地在CSettingDlg中增加了成员m_clr,并在OnSetting中将其与视类中保存的颜色关联起来:
dlg.m_clr = m_clr;
HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
// TODO: Return a different brush if the default is not desired
return hbr;
}
可以看到,这个函数先调用基类的OnCtlColor函数,然后返回一个即将被用来画图的刷子。如果想改变背景颜色,只要返回一个我们自己定义好颜色的画刷就行了。我们为CSettingDlg增加一个CBrush类型的成员变量m_brush,在构造函数中初始化它:
m_brush.CreateSolidBrush(RGB(0,0,255));
然后在函数返回时,返回我们自己的画刷:
// return hbr;
return m_brush;
假如我们只想改变某个控件的颜色。那么我们如何判断是哪个控件呢?先把代码列出来再解释:
HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
if(pWnd->GetDlgCtrlID() == IDC_LINE_STYLE)
{
pDC->SetTextColor(RGB(255,0,0));
return m_brush;
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
每个控件创建时,都会调用OnCtlColor函数,而到底是哪个,怎是通过pWnd指针来区分的。所以如果我们想在某个具体的控件中修改,只要判断他的ID就行了。在这里,我们改变了组框的背景色和字体的颜色。但是因为字体本身也有背景,所以看起来不太舒服,我们可以将字体的背景设为透明:
pDC->SetBkMode(TRANSPARENT);
下面,我们改变编辑框的颜色。方法与前面的类似:
if(pWnd->GetDlgCtrlID() == IDC_LINE_WIDTH)
{
pDC->SetTextColor(RGB(255,0,0));
// pDC->SetBkMode(TRANSPARENT);
pDC->SetBkColor(RGB(0,0,255));
return m_brush;
}
注意,这里需要修改的是编辑框的背景颜色,而不是他的背景模式。
最后看看位图的显示。我们按照以下步骤显示一幅位图:
1.创建位图
2.创建兼容DC
3.将位图选进兼容的DC中
4.将兼容DC中的位图贴到当前的DC中。
我们在最前面的几节讲过DC是干什么的,这里再重申以下。DC全称为Device Context,翻译过来是“设备描述表”或者“设备上下文”。显示图片时都会用到,为什么呢?首先,没有一句C语言可以帮助我们在屏幕上显示图像或者在打印机上打印图像,我们要想显示,最终是调用显卡的驱动程序操作硬件的。不同的显卡,不同的显示器肯定会略有不同,而Windows将这些问题在自己的内部处理了,只给我们提供个一个接口,就是DC,它与设备无关,通过它,我们就能调用这些函数画图了。
那么什么是兼容的DC呢?
所谓兼容DC,就是符合某一标准(比如打印机或者显示器)的逻辑设备。它的本质是一段内存,我们在画图时,需要在这段内存上把图形画好,然后一次性拷贝到DC中。为什么不直接拷贝呢?据说是因为MFC的双缓存技术,直接拷贝会导致屏幕反复闪烁。
首先,我们保存一幅位图,这里使用对桌面的截屏。通过插入资源的方式,将它加入进来,此时,它的ID为:IDB_BITMAP1
在程序中,当擦除背景时,程序会发送一个WM_ERASEBKGND消息,对其响应即可:
BOOL CCH_10_GranphicView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
CDC dcCompatible;
dcCompatible.CreateCompatibleDC(pDC);
dcCompatible.SelectObject(&bitmap);
CRect rect;
GetClientRect(&rect);
pDC->BitBlt(0,0,rect.Width(),rect.Height(),
&dcCompatible,0,0,SRCCOPY);
return TRUE;
}
注意,我们删除了MFC默认的return 语句。
BOOL CCH_10_GranphicView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
BITMAP bmp;
bitmap.GetBitmap(&bmp);
CDC dcCompatible;
dcCompatible.CreateCompatibleDC(pDC);
dcCompatible.SelectObject(&bitmap);
CRect rect;
GetClientRect(&rect);
// pDC->BitBlt(0,0,rect.Width(),rect.Height(),
// &dcCompatible,0,0,SRCCOPY);
pDC->StretchBlt(0,0,rect.Width(),rect.Height(),
&dcCompatible,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
return TRUE;
}
其中StretchBlt 与>BitBlt类似,但是多了两个参数,分别是原矩形的宽度和高度。我们通过GetBitmap函数来获得它们,这个函数需要一个BITMAP 结构的地址,然后系统会自动把信息填充到这个地址中,其中就有源位图的宽度和高度。