目录
路径
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CString str;
str = _T("风灵");
pDC->TextOut(100, 100, str);
str.LoadString(IDS_STRINGVC);
pDC -> TextOut(0, 200, str);
// TODO: 在此处为本机数据添加绘制代码
}
在设备描述表中还有一个路径层(path bracket)的概念。什么是路径层呢?路径层的概念就像当年军阀割据时圈地那样,在地域上划定了界线,界线之内的是各自的地盘,别人不能侵犯。
在MFC中,创建路径层是利用CDC类提供的BeginPath和EndPath这两个函数来实现的,首先调用前者BeginPath,该函数的作用是在设备描述表中打开一个路径层;然后利用图形设备接口(GDI)提供的绘图函数进行绘图操作,例如绘制一些点、矩形、椭圆等;最后,在绘图操作完成之后,应用程序通过调用EndPath函数关闭这个路径层。
下面我们在路径层中绘制一个矩形,将先前输出的“风灵01”字符串框起来。如果要用一个矩形把字符串框起来,就需要知道这个字符串在窗口中的坐标值。在上述代码中,字符串“风灵01”是在坐标(100,100)处输出的,于是我们可以确定矩形的左上角坐标为(100,100),但是如何确定矩形的右下角坐标呢?对一个字符串来说,如果能够知道它的宽度和高度,再加上它的左上角坐标就能够得到包围这个字符串的矩形的右下角坐标了。我们能不能利用C 语言中的strlen 函数来获得字符串的宽度呢?strlen 这个函数获得的是字符串中字符的个数,而字符串在窗口中显示时占据的宽度并不是由其字符数来决定的。例如“w”和“i”同样都是一个字符,但它们所占据的宽度是不一样的。同时,字体的大小也会影响字符串在窗口中显示的宽度。另外,我们在使用Word时,经常会根据需要调整字间距,也就是说,字符和字符之间实际上是有间距的。由此可见,一个字符串在屏幕上显示的宽度是由多个方面的因素决定的,因此,希望利用strlen函数来获得字符串的宽度是根本无法做到的。CDC类为我们提供了一个GetTextExtent函数,利用这个函数可以获得一个字符串在屏幕上显示的宽度和高度,这个函数的一种声明形式如下所示:
CSize GetTextExtent(
const CString& str
) const;
从上述声明中可以得知,我们需要给这个函数传递一个字符串,它会返回一个CSize类型的对象。CSize类类似于Windows的SIZE结构体。Windows的SIZE结构体的定义如下所示:
typedef struct tagSIZE
{
int cx;
int cy;
} SIZE;
该结构体有两个成员变量:cx和cy,分别表示宽度和高度。
GetTextExtent函数需要一个CString对象的引用作为其参数,前面刚刚说过,不同的字符在窗口中显示时,其宽度可能也是不同的,因此,想要得到字符串在窗口中的显示宽度,必须针对特定的字符串调用 GetTextExtent 函数。不要把这个函数和前面讲过的GetTextMetrics函数混淆了,对GetTextMetrics函数来说,它获得的是设备描述表中当前字体的度量信息。而GetTextExtent函数则是获得某个特定的字符串在窗口中显示时所占据的宽度和高度。一定要注意区分这两个函数的作用。
如下所示代码是使用路径层的具体实现代码。
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CString str;
str = _T("风灵");
pDC->TextOut(100, 100, str);
CSize sz = pDC->GetTextExtent(str);
str.LoadString(IDS_STRINGVC);
pDC -> TextOut(0, 200, str);
pDC->BeginPath();
pDC->Rectangle(100, 100, 100 + sz.cx, 100 + sz.cy);
pDC->EndPath();
// TODO: 在此处为本机数据添加绘制代码
}
Build 并运行 Text 程序,将会发现程序窗口与前面的程序结果没有什么不同。但是当我们把上述代码中打开和关闭路径层的两行代码注释起来,也就是不在路径中绘制矩形时,看看程序运行结果会是怎样的。这时的程序运行界面如图所示。
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CString str;
str = _T("风灵");
pDC->TextOut(100, 100, str);
CSize sz = pDC->GetTextExtent(str);
str.LoadString(IDS_STRINGVC);
pDC -> TextOut(0, 200, str);
//pDC->BeginPath();
pDC->Rectangle(100, 100, 100 + sz.cx, 100 + sz.cy);
//pDC->EndPath();
// TODO: 在此处为本机数据添加绘制代码
}
在设备描述表中有一个默认的白色画刷,当绘制矩形时,它会用这个画刷来填充矩形内部,因此在本例中,调用 Rectangle 函数后,就把先前绘制的文字给覆盖了。前面我们已经看到,如果是在路径层中绘制矩形,那么它对先前输出的文字是没有影响的。既然没有影响,那么路径层有什么作用呢?
下面我们先在窗口中绘制一些网格状线条,这些线条将覆盖已输出的文字。具体代码如例所示。
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CString str;
str = _T("风灵");
pDC->TextOut(100, 100, str);
CSize sz = pDC->GetTextExtent(str);
str.LoadString(IDS_STRINGVC);
pDC -> TextOut(0, 200, str);
pDC->BeginPath();
pDC->Rectangle(100, 100, 100 + sz.cx, 100 + sz.cy);
pDC->EndPath();
for (int i = 0; i < 300; i += 10)
{
pDC->MoveTo(0, i);
pDC->LineTo(300, i);
pDC->MoveTo(i, 0);
pDC->LineTo(i, 300);
}
// TODO: 在此处为本机数据添加绘制代码
}
上述代码中利用一个循环来实现网格状线条的绘制,线条与线条之间的间距为10个逻辑单位。在这个循环中,首先是纵坐标不断变化,绘制网格的横线;然后是横坐标不断变化,绘制网格的竖线。编译并运行Text程序,结果如图所示,我们发现多了些网格线把先前输出的文字遮盖住了。此时,我们还是没有看出路径层到底有什么好处。
这里,先介绍一下裁剪区域(clipping region)的概念。可以把它理解为一个绘图区域,其大小可以由我们来控制。我们知道对单文档应用程序来说,除了标题栏、菜单栏以外,剩余的就是客户区。通常可以把客户区看作一个大的裁剪区域,但裁剪区域也可以局限于客户区中一个很小的范围之内。例如,可以限制一个矩形区域作为裁剪区域,把后面的绘图操作仅限于这个矩形之内。
CDC类提供了一个SelectClipPath函数,该函数的作用是把当前设置的路径层和设备描述表中已有的裁剪区域按照一种指定的模式进行一个互操作。该函数的声明形式如下所示:
BOOL SelectClipPath(
int nMode
);
其中,参数nMode用来指定互操作的模式,它可以有多种取值,例如RGN_DIFF,该模式的含义是新的裁剪区域包含当前裁剪区域,但排除当前路径层区域。我们可以看看这个模式产生的效果。修改上述代码,在EndPath函数调用之后,添加SelectClipPath函数的调用,结果如例所示。
void CTextView::OnDraw(CDC* pDC)
{
CTextDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CString str;
str = _T("风灵");
pDC->TextOut(100, 100, str);
CSize sz = pDC->GetTextExtent(str);
str.LoadString(IDS_STRINGVC);
pDC -> TextOut(0, 200, str);
pDC->BeginPath();
pDC->Rectangle(100, 100, 100 + sz.cx, 100 + sz.cy);
pDC->EndPath();
pDC->SelectClipPath(RGN_DIFF);
for (int i = 0; i < 300; i += 10)
{
pDC->MoveTo(0, i);
pDC->LineTo(300, i);
pDC->MoveTo(i, 0);
pDC->LineTo(i, 300);
}
// TODO: 在此处为本机数据添加绘制代码
}
编译并运行Text程序,结果如图所示。可以发现在窗口中绘制的线条到了程序设置的矩形路径部分就断开了。这正是RGN_DIFF模式的效果,它使新的裁剪区域包含了当前裁剪区域,但把当前路径层的范围排除在外。因此,在程序窗口中就看不到有线条穿过路径范围内的文字,到了这个路径范围线条就终止了。
我们再看看另一种裁剪区域操作模式:RGN_AND的效果。该模式的作用是,新的裁剪区域是当前裁剪区域和当前路径层的交集。把上述例6-9所示代码中的裁剪区域操作模式(即加灰显示的那行代码中的参数:RGN_DIFF)换成RGN_AND,然后编译并运行Text程序,结果如下图所示。可以发现,这时只有矩形路径中显示有线条,其他部分均没有线条。
至此,就可以理解路径层的作用了。以后在绘图时,就可以利用路径层这一特点来实现特殊的效果。例如,如果希望整幅图形中某一部分与其他部分有所区别,就可以把这部分的图形放置到一个路径层中,然后利用 SelectClipPath 函数设置一种模式,让路径层和裁剪区域进行互操作以达到一种特殊的效果。