专栏导航
下一篇:无
本专栏课件
关于本专栏课件的获取方法,请参考下述课节。
参考课节:MFC笔记:本专栏课件
本节前言
对于本节所讲解的知识,有可能,你会需要时不时地参考本专栏的其它文章。真的遇到了需要参考之前的文章的知识点,请你自行查阅。
我呢,也会提到一部分的参考课节。但是呢,你不应该依赖于我的主动提及。最好呢,你自己能够多去了解和查看本专栏目录。
编程中需要用到的三角函数知识,我们大体上算是复习完了。
为了到时候,能够顺利地讲解 SineWave 程序,我们还需要去讲解一下平面直角坐标系的绘制与描点方法。从平面直角坐标系的描点方法中,我们来掌握一个编程技巧。
本节,我们要去做的,是绘制一个平面直角坐标系。本节,我们只绘制坐标系,而不去描点。下一节,我们来描点。
一. 运行结果展示与绘制思路讲解
图1,程序运行结果,坐标系
图1 所示,就是我们本节程序的运行结果。
图1 的绘制,我全是用 Windows 编程方式来绘制的,没有用 PhotoShop 来修饰。
我们来分析一下绘制思路。
除了绘制原点 O 以外,其余的部分,基本上,都是画线函数 MoveToEx 和 LineTo 的使用。
在构思的时候,我首先想到的是原点的定位。原点在客户区的横纵坐标,我将其取名为 xCenter 和 yCenter 。
(一)X 轴与 Y 轴的绘制思路
接下来,是 X 轴与 Y 轴的线段部分。X 轴的左右两端点与原点的距离是一样的,Y 轴也是。并且呢,OX 与 OY 的长度也是一样的。我定义了一个变量,nRadius,n 是数据类型 int 的标识,Radius 是英语 "半径" 的意思。
原点的横坐标减去这个半径值,就是 X 轴左端点的横坐标;原点的横坐标加上这个半径值,就是 X 轴右端点的横坐标。至于两个端点的纵坐标,它们与 yCenter 是相等的。X 轴的左右端点的横坐标,由变量 xLeft 和 xRight 来表示。
原点的纵坐标减去这个半径值,就是 Y 轴上端点的纵坐标;原点的纵坐标加上这个半径值,就是 Y 轴下端点的纵坐标。至于两个端点的横坐标,它们与 xCenter 是相等的。Y 轴的上下端点的纵坐标,由变量 yTop 和 yBottom 来表示。
有了这些个构思与计算代码以后,接下来,用 MoveToEx 和 LineTo 函数对来绘制直线,也就可以了。
(二)坐标轴的指示箭头的绘制
首先来说一说 X 轴的指示箭头。箭头有三个端点。我们可以将其称为上端点,中端点和下端点。中端点,就是(xRight, yCenter),就是 X 轴的右端点。在箭头之中,中端点上面的那个端点,也就是 X 轴箭头的上端点,我将其表示为 (xXArrow1, yXArrow1);中端点下面的那个端点,也就是 X 轴箭头的下端点,我将其表示为(xXArrow2, yXArrow2)。
X 轴指示箭头的上端点和下端点,它们的横坐标的值是一样的,它们的纵坐标与 X 轴右端点的纵坐标的距离是一样的。
我设置了一个变量,nLen1 。在 WM_CREATE 消息的初始化代码中,我将其赋值为 10 。X 轴的右端点的横坐标减去 nLen1,作为上下端点的横坐标。X 轴右端点的纵坐标减去 nLen1,作为箭头上端点的纵坐标;X 轴右端点的纵坐标加上 nLen1,作为箭头下端点的纵坐标。
然后呢,我们来说 Y 轴的指示箭头。箭头也有三个端点。我们可以将其称为左端点,中端点和右端点。箭头的中端点,就是(xCenter, yTop),就是 Y 轴的上端点。在箭头之中,中端点左面的那个端点,也就是 Y 轴箭头的左端点,我将其表示为 (xYArrow1, yYArrow1);中端点右面的那个端点,也就是 Y 轴箭头的右端点,我将其表示为(xYArrow2, yYArrow2)。
Y 轴指示箭头的左端点和右端点,它们的纵坐标的值是一样的,它们的横坐标与 Y 轴上端点的横坐标的距离是一样的。
我设置了一个变量,nLen1 。在 WM_CREATE 消息的初始化代码中,我将其赋值为 10 。Y 轴的上端点的纵坐标加上 nLen1,作为箭头左右端点的纵坐标。Y 轴上端点的横坐标减去 nLen1,作为箭头左端点的横坐标;Y 轴上端点的横坐标加上 nLen1,作为箭头右端点的横坐标。
有了这些个构思与计算代码以后,接下来,用 MoveToEx 和 LineTo 函数对来绘制直线,也就可以了。
(三)原点的绘制
这部分,我们用椭圆函数 Ellipse 来绘制。绘制的时候,我们还需要获取备用画笔 NULL_PEN 和新建一个黑色的画刷 hBrush 。
我没有单独设置变量来表示原点的左右上下四个坐标。我设置了一个变量,nLen2 。原点的横坐标减去 nLen2,作为原点所在椭圆的左侧横坐标;原点的横坐标加上 nLen2,作为原点所在椭圆的右侧横坐标;原点的纵坐标减去 nLen2,作为原点所在椭圆的上侧纵坐标;原点的纵坐标加上 nLen2,作为原点所在椭圆的下侧纵坐标。
(四)文字标识的绘制
在图1 中,还有 "X 轴"、"Y 轴" 和 "O" 这样的文字标识。对于其字符串,我分别用三个字符数组来标识,如下面的代码块所示。
static TCHAR strXAxisText[10], strYAxisText[10];
static TCHAR strOrigin[10];
既然是用字符数组来存储字符串,那么,对于字符串的赋值,就得是采用特殊的方法。在本节程序中,我们所用的赋值函数,是 lstrcpy 函数。对于这个函数,如果你忘了或者不熟悉,可以参考下述课节。
参考课节:第2章 :Windows 的字符串函数
上述参考课节中的 一.(二)里面,就有讲解 lstrcpy 函数的使用方法。
赋值代码如下。
lstrcpy(strXAxisText, _T("X 轴"));
lstrcpy(strYAxisText, _T("Y 轴"));
lstrcpy(strOrigin, _T("O"));
在 Windows API 程序设计中,想要使用【_T】这个宏,我们还需要在代码开头包含着 tchar.h 头文件。所以呢,本节程序开头部分的头文件如下图所示。
图2
绘制文字时,我们使用了 TextOut 函数,TextOut 函数中,有一个字符数的参数。我们专门地给三个字符数组的字符串的最后一个有效字符的下一个位置,设置为 0,并且,还调用 lstrlen 函数来获取了三个字符串的长度。代码如下。
图3
去进行这种查询字符数与设置结尾的 NULL 字符的事情,其实是不必要的。因为,lstrcpy 函数,本身就会复制字符串结尾的 NULL 字符。
不过呢,我当时写程序的时候,忘了说,lstrcpy,是否会复制字符串结尾的 NULL 字符了,所以呢,就写了图3 所示的代码。这属于是一种偷懒,不愿意去查信息。
实际上,在下述的两个课节中,是可以了解到 lstrcpy 的复制字符串结尾的 NULL 字符的行为的。
参考课节:第2章 :Windows 的字符串函数
参考课节:编程技能:字符串函数02,strcpy
然后呢,在绘制文字的时候,文字的位置,也需要去设置一下的。
X 轴文字的坐标是(xXAxisText, yXAxisText),Y 轴坐标的文字是(xYAxisText, yYAxisText),原点 O 的文字坐标是(xOriginText, yOriginText)。
将 X 轴箭头的下端点的横坐标,设置为 xXAxisText 。将 X 轴箭头的下端点的纵坐标加上 nLen1,设置为 yXAxisText 。
将 Y 轴箭头的右端点的纵坐标,设置为 yYAxisText 。将 Y 轴箭头的右端点的横坐标加上 nLen1,设置为 xYAxisText 。
将原点的横坐标加上 nLen1,设置为 xOriginText 。将原点的纵坐标加上 nLen1,设置为 yOriginText 。
三个文字的位置设置代码,如下图所示。
图4
到了这里,本节程序代码的思路,基本上,我也就说完了。
剩下的,你自己研究吧,你应该能看懂。在下一分节,我们来展示完整代码。
二. 本节代码
代码如下。
/*------------------------------------------------------------
绘制平面直角坐标系,不绘制刻度线
水饺编程,2026
------------------------------------------------------------*/
#include <windows.h>
#include <tchar.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("CoordinateSystem");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("注册窗口类失败!"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, // window class name
TEXT("绘制平面直角坐标系"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL); // creation parameters
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
HBRUSH hBrush, hOldBrush;
COLORREF color;
TEXTMETRIC tm;
static int xLeft, xRight, yTop, yBottom;
static int xXArrow1, yXArrow1, xXArrow2, yXArrow2;
static int xYArrow1, yYArrow1, xYArrow2, yYArrow2;
static int nRadius, xCenter, yCenter;
static int nLen1, nLen2;
static int xXAxisText, yXAxisText;
static int xYAxisText, yYAxisText;
static int xOriginText, yOriginText;
static int nLenTextX, nLenTextY, nLenTextO;
static TCHAR strXAxisText[10], strYAxisText[10];
static TCHAR strOrigin[10];
switch (message)
{
case WM_CREATE:
nRadius = 150;
xCenter = 300;
yCenter = 300;
nLen1 = 10;
nLen2 = 4;
xLeft = xCenter - nRadius;
xRight = xCenter + nRadius;;
yTop = yCenter - nRadius;
yBottom = yCenter + nRadius;
xXArrow1 = xRight - nLen1;
yXArrow1 = yCenter - nLen1;
xXArrow2 = xRight - nLen1;
yXArrow2 = yCenter + nLen1;
xYArrow1 = xCenter - nLen1;
yYArrow1 = yTop + nLen1;
xYArrow2 = xCenter + nLen1;
yYArrow2 = yTop + nLen1;
xXAxisText = xXArrow2;
yXAxisText = yXArrow2 + nLen1;
xYAxisText = xYArrow2 + nLen1;
yYAxisText = yYArrow2;
xOriginText = xCenter + nLen1;
yOriginText = yCenter + nLen1;
lstrcpy(strXAxisText, _T("X 轴"));
lstrcpy(strYAxisText, _T("Y 轴"));
lstrcpy(strOrigin, _T("O"));
nLenTextX = lstrlen(strXAxisText);
nLenTextY = lstrlen(strYAxisText);
nLenTextO = lstrlen(strOrigin);
strXAxisText[nLenTextX] = 0;
strYAxisText[nLenTextY] = 0;
strOrigin[nLenTextO] = 0;
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
// 绘制 x 轴和 y 轴的长线。
MoveToEx(hdc, xLeft, yCenter, NULL);
LineTo(hdc, xRight, yCenter);
MoveToEx(hdc, xCenter, yBottom, NULL);
LineTo(hdc, xCenter, yTop);
// 绘制 x 轴的箭头
MoveToEx(hdc, xXArrow1, yXArrow1, NULL);
LineTo(hdc, xRight, yCenter);
MoveToEx(hdc, xRight, yCenter, NULL);
LineTo(hdc, xXArrow2, yXArrow2);
// 绘制 y 轴的箭头
MoveToEx(hdc, xYArrow1, yYArrow1, NULL);
LineTo(hdc, xCenter, yTop);
MoveToEx(hdc, xCenter, yTop, NULL);
LineTo(hdc, xYArrow2, yYArrow2);
// 描原点
color = RGB(0, 0, 0);
hBrush = CreateSolidBrush(color);
hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
Ellipse(hdc, xCenter - nLen2, yCenter - nLen2,
xCenter + nLen2, yCenter + nLen2);
SelectObject(hdc, hOldBrush);
DeleteObject(hBrush);
// 写 x 轴、y 轴和原点的标识文字
TextOut(hdc, xXAxisText, yXAxisText, strXAxisText, nLenTextX);
TextOut(hdc, xYAxisText, yYAxisText, strYAxisText, nLenTextY);
TextOut(hdc, xOriginText, yOriginText, strOrigin, nLenTextO);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
程序的编译方法,如果你忘了,可以参考下面的课节。
参考课节:使用 VS2010 编写 C语言程序
参考课节:用 VS2019 编写C语言程序
图1 就是运行结果。
结束语
本节程序本身不难。不过,我讲解编程思路的文字,还是多了些。
专栏导航
下一篇:无