为了更好的熟悉window程序设计,特编写了下面的小示例(参照Windows程序设计第二版),现将程序实现总结如下:
程序运行图:
程序代码如下:
// header files
#include "windows.h"
#include "math.h"
// defines
#define WINDOW_CLASS_NAME "3D Game"
#define IDT_CLOCK 2
#define IDM_TOPMOST 3
#define IDM_HELP 4
// global variable for use
HINSTANCE hInstance_app;
HWND hMainWnd;
// variables for timer use //--------------------------------------------------------
// 上一次Windows通知时的时间
static int s_nPreHour;
static int s_nPreMinute;
static int s_nPreSecond;
// 窗口客户区的大小
static int s_cxClient;
static int s_cyClient;
// 是否位于最顶层
static BOOL s_bTopMost;
// time
SYSTEMTIME time;
// color
COLORREF crfColor;
// ID(Message)
int nID;
// Menu
HMENU hSysMenu;
//-------------------------------------------------------------------------------------
// function for internal use //--------------------------------------------------------
void DrawHandle(HDC hdc,int nLength,int nWidth,int nDegree,COLORREF clrColor); // 绘制Clock指针
void SetIsotropic(HDC hdc, int cx, int cy); // 设置坐标变换
void DrawClockFace(HDC hdc); // 绘制Clock
//-------------------------------------------------------------------------------------
// WinProc
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_CREATE:
::GetLocalTime(&time);
s_nPreHour = time.wHour % 12;
s_nPreMinute = time.wMinute;
s_nPreSecond = time.wSecond;
// 创建定时器
::SetTimer(hWnd,IDT_CLOCK,1000,NULL);
// 设置菜单
hSysMenu = ::GetSystemMenu(hWnd,FALSE);
::AppendMenu(hSysMenu,MF_SEPARATOR,0,NULL);
::AppendMenu(hSysMenu,MF_STRING,IDM_TOPMOST,"总在最前");
::AppendMenu(hSysMenu,MF_STRING,IDM_HELP,"帮助");
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
{
// 设置坐标系
SetIsotropic(hdc,s_cxClient,s_cyClient);
// 绘制钟表外观
DrawClockFace(hdc);
// 绘制钟表指针
DrawHandle(hdc,200,8,s_nPreHour*30 + s_nPreMinute/2,RGB(0,0,0));
DrawHandle(hdc,400,6,s_nPreMinute*6,RGB(0,0,0));
DrawHandle(hdc, 400, 1, s_nPreSecond*6, RGB(0, 0, 0));
}
EndPaint(hWnd, &ps);
break;
case WM_TIMER: // Timer
// 判断窗口是否是最小化
if (IsIconic(hWnd)) return 0;
::GetLocalTime(&time);
// 建立坐标系
hdc = ::GetDC(hWnd);
SetIsotropic(hdc, s_cxClient, s_cyClient);
crfColor = RGB(255,255,255);
// 如果分钟改变的话就擦除时针和分针
if(time.wMinute != s_nPreMinute)
{
// 擦除时针和分针
DrawHandle(hdc, 200, 8, s_nPreHour*30 + s_nPreMinute/2, crfColor);
DrawHandle(hdc, 400, 6, s_nPreMinute*6, crfColor);
s_nPreHour = time.wHour;
s_nPreMinute = time.wMinute;
}
// 如果秒改变的话就擦除秒针,然后重画所有指针
if(time.wSecond != s_nPreSecond)
{
// 擦除秒针
DrawHandle(hdc, 400, 1, s_nPreSecond*6, crfColor);
// 重画所有指针
DrawHandle(hdc, 400, 1, time.wSecond*6, RGB(0, 0, 0));
DrawHandle(hdc, 200, 8, time.wHour*30 + time.wMinute/2, RGB(0, 0, 0));
DrawHandle(hdc, 400, 6, time.wMinute*6, RGB(0, 0, 0));
s_nPreSecond = time.wSecond;
}
break;
case WM_SIZE:
s_cxClient = LOWORD(lParam);
s_cyClient = HIWORD(lParam);
break;
case WM_NCHITTEST: // 在无标题栏的窗口中,响应该消息可以在窗口的任意位置实现窗口的拖动功能
UINT nHitTest;
nHitTest = ::DefWindowProc(hWnd,WM_NCHITTEST,wParam,lParam);
if (nHitTest == HTCLIENT && ::GetAsyncKeyState(MK_LBUTTON) < 0)
{
nHitTest = HTCAPTION;
}
return nHitTest;
case WM_CONTEXTMENU:
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
hSysMenu = ::GetSystemMenu(hWnd,FALSE);
nID = ::TrackPopupMenu(hSysMenu,TPM_LEFTALIGN | TPM_RETURNCMD,pt.x,pt.y,0,hWnd,NULL);
if (nID > 0)
{
::SendMessage(hWnd,WM_SYSCOMMAND,nID,0);
}
break;
case WM_SYSCOMMAND:
nID = wParam;
{
if (nID == IDM_HELP)
{
::MessageBox(hWnd,"一个基于D游戏框架的时钟","时钟",0);
}
else if (nID == IDM_TOPMOST)
{
hSysMenu = ::GetSystemMenu(hWnd,FALSE);
if (s_bTopMost)
{
::CheckMenuItem(hSysMenu,IDM_TOPMOST,MF_UNCHECKED);
::SetWindowPos(hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE);
s_bTopMost = FALSE;
}
else
{
::CheckMenuItem(hSysMenu,IDM_TOPMOST,MF_CHECKED);
::SetWindowPos(hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE);
s_bTopMost = TRUE;
}
}
}
break;
case WM_CLOSE:
::KillTimer(hWnd,IDT_CLOCK);
::DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}// end WindProc
// WINMAIN
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
{
WNDCLASSEX winclass; // this holds the class we create
HWND hWnd; // generic window handle
MSG msg; // generic message
// fill in the window class structure
winclass.cbSize = sizeof(WNDCLASSEX);
winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hInstance;
winclass.hIcon = ::LoadIcon(NULL,IDI_APPLICATION);
winclass.hCursor = ::LoadCursor(NULL,IDC_ARROW);
winclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
winclass.lpszMenuName = NULL;
winclass.lpszClassName = WINDOW_CLASS_NAME;
winclass.hIconSm = ::LoadIcon(NULL,IDI_APPLICATION);
// save the hInstance in global
hInstance_app = hInstance;
// register the window class
if (!RegisterClassEx(&winclass))
return (0);
// create the window
if (!(hWnd = ::CreateWindowEx(NULL, // extended style
WINDOW_CLASS_NAME, // class
"3D Game Console", // title
// WS_OVERLAPPEDWINDOW | WS_VISIBLE, // style
WS_POPUP | WS_SYSMENU | WS_SIZEBOX | WS_VISIBLE,
0,0, // initial x, y
800,600, // initial width, height
NULL, // handle to parent
NULL, // handle to menu
hInstance, // instance of this application
NULL))) // extra creation parms
return (0);
// save main window handle
hMainWnd = hWnd;
// enter main event loop
while (TRUE)
{
// test if there is a message in queue,if so get it
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
// test if this is a quit
if (msg.message == WM_QUIT)
break;
// translate any accelerator keys
TranslateMessage(&msg);
// send the message to the window proc
DispatchMessage(&msg);
}// end if
}// end while
return (msg.wParam);
}// end WinMain
///
// function for internal use /
// Draw the clock handle //-------------------------------------------------------------------------------
void DrawHandle(HDC hdc,int nLength,int nWidth,int nDegree,COLORREF clrColor)
{
// turn angle to radian
double nRadians = (double)nDegree * (2 * 3.1415926 / 360);
// compute the coordinate
POINT pt[2];
pt[0].x = (int)(nLength * sin(nRadians));
pt[0].y = (int)(nLength * cos(nRadians));
pt[1].x = -pt[0].x / 5;
pt[1].y = -pt[0].y / 5;
// create the pen
HPEN hPen = ::CreatePen(PS_SOLID,nWidth,clrColor);
HPEN hOldPen = (HPEN)::SelectObject(hdc,hPen);
// paint the handle
::MoveToEx(hdc,pt[0].x,pt[0].y,NULL);
::LineTo(hdc,pt[1].x,pt[1].y);
// delete the pen
::SelectObject(hdc,hOldPen);
::DeleteObject(hPen);
}
// Set the coordinate //--------------------------------------------------------------------------------
void SetIsotropic(HDC hdc, int cx, int cy)
{
::SetMapMode(hdc,MM_ISOTROPIC); // 设置自定义映射模式
::SetWindowExtEx(hdc,1000,1000,NULL); // 设置窗口的逻辑单位为/1000英寸
::SetViewportExtEx(hdc,cx,-cy,NULL); // 设置视口的映射规则,x坐标不变,y坐标方向相反
::SetViewportOrgEx(hdc,cx/2,cy/2,NULL); // 设置视口的坐标原点
}
// Draw the clock face //-------------------------------------------------------------------------------
void DrawClockFace(HDC hdc)
{
const int SQUARESIZE = 20; // 盘面时刻点圆的半径
// 定义时刻点
static POINT pt[] =
{
0, 450, // 12点
225, 390, // 1点
390, 225, // 2点
450, 0, // 3点
390, -225, //... 下面的坐标是上面的点的对称点(以X轴、Y轴或原点对称)
225, -390,
0, -450,
-225, -390,
-390, -225,
-450, 0,
-390, 225,
-225, 390
};
// 设置画刷
::SelectObject(hdc,::GetStockObject(BLACK_BRUSH));
// 绘制时刻点圆
for (int i = 0; i < 12; i++)
{
::Ellipse(hdc,pt[i].x - SQUARESIZE, pt[i].y - SQUARESIZE, pt[i].x + SQUARESIZE, pt[i].y + SQUARESIZE);
}
}
//------------------------------------------------------------------------------------------
程序设计过程中,主要设计到Windows GDI的使用,计时器的使用,以及右键系统菜单的使用和如何在POPUP风格的窗口实现在窗口的任意区域都响应标题栏的事件等,详细见代码。