本文为了从C语言的面向过程说明窗口创建、操作的过程。
1.窗口创建过程
每种窗口是一种类,例如form类型的窗口类、文本窗口类等等,不同的类包含的变量和消息处理方法不一样。要创建一个窗口,首先需要把类名和消息处理函数注册到WINDOWS操作系统中,然后使用已经注册的类名来创建窗口。
第一步,注册窗口类,实际上就是对结构体WNDCLASSEX初始化,然后用该结构体作为ATOM RegisterClassEx(&WNDCLASSEX)的参数注册窗口类,是将窗口类的数据放在User32.dll维护的一个原子表中,自定义注册窗口函数:
// 注册应用程序窗口类
ATOM _RegisterClass()
{
int i = 0;
WNDCLASSEX wc;
::ZeroMemory(&wc, sizeof(wc)); // 作为一步清空,是为了让未赋值的字段的默认值为(或NULL)
wc.cbSize = sizeof(wc);
wc.style = CS_HREDRAW | CS_VREDRAW; // 指定当窗口横向和纵向的尺寸发生变化时都会重绘窗口
wc.hInstance = _HInstance;
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1); // 指定主窗口背景为“工作区域”系统颜色
wc.lpszClassName = _WindowClass; // 此为要注册的类名,创建窗口时要以此类名为标识符
wc.lpfnWndProc = _TextBoxWndProc; // 此为处理窗口消息的函数
return ::RegisterClassEx(&wc); // 调用API函数注册窗口类
}
第二部,创建窗口对象,用CreateWindow创建窗口对象。函数原型为:
HWND WINAPI CreateWindow(
In_opt LPCTSTR lpClassName, // 窗口类名称
In_opt LPCTSTR lpWindowName, // 窗口标题
In DWORD dwStyle, // 窗口风格,或称窗口格式
In int x, // 初始 x 坐标
In int y, // 初始 y 坐标
In int nWidth, // 初始 x 方向尺寸
In int nHeight, // 初始 y 方向尺寸
In_opt HWND hWndParent, // 父窗口句柄
In_opt HMENU hMenu, // 窗口菜单句柄
In_opt HINSTANCE hInstance, // 程序实例句柄
In_opt LPVOID lpParam // 创建参数
);
CreateWindow详细参数说明可以参考:
http://www.tuicool.com/articles/6RvqIrv
然后用系统的API函数显示窗口和更新窗口:
::ShowWindow;
::UpdateWindow;
HWND _CreateWindow(int nCmdShow)
{
HWND hWnd = ::CreateWindow(_WindowClass, _Title, WS_POPUP,
500, 500, 200, 20, NULL, NULL, _HInstance, NULL);
if (hWnd == NULL)
return NULL;
::ShowWindow(hWnd, nCmdShow);
::UpdateWindow(hWnd);
return hWnd;
}
2.窗口操作过程
WINDOWS操作系统通过消息与应用程序进行交互,每个窗口都有自己的消息处理函数,该函数在窗口注册时就和窗口绑定了,既窗口对一系列的事件,如鼠标事件、键盘事件等等,然后调用绑定的消息处理函数处理自己的用户操作,然后进行界面重绘。
// 创建文本框
HWND _CreateTextBoxWindow(HWND hParentWnd)
{
// 之下代码是为了让文本框显示在父窗口中央,而计算位置
RECT parentWndRect;
::GetClientRect(hParentWnd, &parentWndRect); // 获取父窗口客户区的位置
int left = (parentWndRect.right - TEXTBOX_WIDTH) / 2, top = (parentWndRect.bottom - TEXTBOX_HEIGHT) / 2;
// 创建文本框
HWND hWnd = ::CreateWindow(_TextBoxClass, NULL, WS_CHILDWINDOW | WS_VISIBLE,
left, top, TEXTBOX_WIDTH, TEXTBOX_HEIGHT,
hParentWnd, NULL, _HInstance, NULL);
return hWnd;
}
// 文本框消息的处理过程
LRESULT CALLBACK _TextBoxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT: { // 绘制这里之所以加一对大括号,是为了让之下定义的变量局部化
static PAINTSTRUCT ps;
static RECT rect;
HDC hDC = ::BeginPaint(hWnd, &ps); // 开始绘制操作
::GetClientRect(hWnd, &rect); // 获取客户区的尺寸
::DrawEdge(hDC, &rect, EDGE_SUNKEN, BF_RECT); // 绘制边框,EDGE_SUNKEN表示绘制样式为内嵌样式,BF_RECT表示绘制矩形边框
_DrawText(hDC); // 绘制文本
::EndPaint(hWnd, &ps); // 结束绘制操作
} break;
case WM_SETFOCUS: { // 获得焦点
::CreateCaret(hWnd, (HBITMAP)NULL, 1, TEXTBOX_HEIGHT - 5); // 创建光标
_SetCaretPos(hWnd); // 设置光标位置
::ShowCaret(hWnd); // 显示光标
} break;
case WM_KILLFOCUS: // 失去焦点
::HideCaret(hWnd); // 隐藏光标
::DestroyCaret(); // 销毁光标
break;
case WM_SETCURSOR: { // 设置光标形状
static HCURSOR hCursor = ::LoadCursor(NULL, IDC_IBEAM);
::SetCursor(hCursor);
} break;
case WM_CHAR: { // 字符消息
TCHAR code = (TCHAR)wParam;
int len = ::_tcslen(_String);
if (code < (TCHAR)' ' || len >= TEXTBOX_MAXLENGTH)
return 0;
::MoveMemory(_String + _StringPosition + 1, _String + _StringPosition, (len - _StringPosition + 1) * sizeof(TCHAR));
_String[_StringPosition++] = code;
_UpdateWindow(hWnd);
_SetCaretPos(hWnd);
} break;
case WM_KEYDOWN: { // 键按下消息
TCHAR code = (TCHAR)wParam;
switch (code)
{
case VK_LEFT: // 左光标键
if (_StringPosition > 0)
_StringPosition--;
break;
case VK_RIGHT: // 右光标键
if (_StringPosition < (int)::_tcslen(_String))
_StringPosition++;
break;
case VK_HOME: // HOME 键
_StringPosition = 0;
break;
case VK_END: // END 键
_StringPosition = ::_tcslen(_String);
break;
case VK_BACK: // 退格键
if (_StringPosition > 0)
{
::MoveMemory(_String + _StringPosition - 1, _String + _StringPosition, (::_tcslen(_String) - _StringPosition + 1) * sizeof(TCHAR));
_StringPosition--;
_UpdateWindow(hWnd);
}
break;
case VK_DELETE: { // 删除键
int len = ::_tcslen(_String);
if (_StringPosition < len)
{
::MoveMemory(_String + _StringPosition, _String + _StringPosition + 1, (::_tcslen(_String) - _StringPosition + 1) * sizeof(TCHAR));
_UpdateWindow(hWnd);
}
} break;
}
_SetCaretPos(hWnd);
} break;
case WM_LBUTTONDOWN: { // 鼠标单击,设置光标位置
int x = LOWORD(lParam);
HDC hDc = ::GetDC(hWnd);
int strLen = ::_tcslen(_String), strPos = 0;
SIZE size;
for (strPos = 0; strPos<strLen; strPos++)
{
::GetTextExtentPoint(hDc, _String, strPos, &size);
if (size.cx + 4 >= x)
break;
}
_StringPosition = strPos;
::GetTextExtentPoint(hDc, _String, strPos, &size);
::SetCaretPos(size.cx + 4, 3);
::ReleaseDC(hWnd, hDc);
} break;
default:
return ::DefWindowProc(hWnd, message, wParam, lParam);
}
return (LRESULT)0;
}
然后再定义更新窗口、绘制文本、设置光标位置的函数:
// 更新窗口
void _UpdateWindow(HWND hWnd)
{
RECT rect;
::GetClientRect(hWnd, &rect);
::InvalidateRect(hWnd, &rect, TRUE);
::UpdateWindow(hWnd);
}
// 绘制文本
void _DrawText(HDC hDC)
{
int len = ::_tcslen(_String);
::TextOut(hDC, 4, 2, _String, len);
}
// 设置光标位置
void _SetCaretPos(HWND hWnd)
{
HDC hDC = ::GetDC(hWnd);
SIZE size;
::GetTextExtentPoint(hDC, _String, _StringPosition, &size);
::SetCaretPos(4 + size.cx, 3);
::ReleaseDC(hWnd, hDC);
}
这样就可以生成一个浮现在桌面的文本框,直接输入字符串了。