win32分割窗口的一个简易做法,非常好的编程思维,但是在实践中还是有问题的:需要不断的刷新父窗口,导致子窗口响应消息受到影响
使用win32写一个分割窗口为若干子窗口,随着鼠标在“分割条”上拖动,可以动态的改变子窗口的大小,从而让程序的界面可以显示不同模块的内容,这个需求是编程当中经常遇到的。
本人在写了一个CTP交易程序,就遇到登录成功以后一个页面显示今日委托、今日成交、今日持仓三大模块。于是,我在这个页面建立了三个STATIC控件子窗口,分别显示不同的内容。
话不多说,上图看:
图中,分割条是虚的,并没有真实的绘制
废话少说,上代码:
// SplitWnd.cpp : 定义应用程序的入口点。
//第一步,建立主窗口和子窗口
//在WinMain()里hwndMain = CreateWindow();
//在WindowProc()里的WM_CREATE里,分别hwndEdit / hwndPreview / hwndTitleimg = CreateWindow();
// 第二步,确定分割线的位置,建立几条可以拖动的分割线(实际上是矩形)
// 通过全局变量初始化分割线的位置和子窗口的位置
//在WM_SIZE里,MoveWindow()到预定的位置。
//之后你就会看到左侧的hwndEdit窗口,右侧上方的hwndPreview窗口,下方的hwndTitleimg窗口。
//第三步,鼠标按在子窗口空隙形成的分割线(条)的时候拖动,子窗口能任意调整彼此之间的大小。怎么做呢?简单的一种做法就是,在鼠标拖动的时候画出一条矩形阴影来模拟,
// 等鼠标释放时重新调整大小,也就是WM_SIZE里根据鼠标的位置设置3个子窗口的大小。复杂点的做法,就是分割条也当作一个子窗口CreateWindow()出来,不过有点麻烦,暂且不提。
//下面给出完整代码:
#include "SplitWnd.h"//头文件里啥也没有,主要是需要#include <windows.h>
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
WCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
POINT pt = {100,100 };//横竖分割线的交叉的,三个窗口的大小都和这个点有密切的关系,都是根据这个点的坐标来绘制的
int LineWidth =50; //分割线的厚度(宽度),为了效果我给了50像素宽的分割条,实际应用当中改为5像素宽就美观了!!!
INT WidthOfWnd = 0;//子窗口的宽度,每次使用把子窗口的宽度保存下来方便使用,能够让程序简单流畅
INT HeighthOfWnd = 0;//子窗口的高度,每次使用把子窗口的高度度保存下来方便使用,能够让程序简单流畅
bool isOnVLine = false;
bool isOnHLine = false;
HWND MainWnd = 0; //主框架窗口句柄
HWND ChildOrder=0; //委托单窗口句柄,保含已成交未成交的单子信息
HWND ChildTrade = 0; //成交单窗口句柄,目前持仓信息全部统计在这里
HWND ChildNowTrade = 0; //交易持仓,目前持仓信息全部统计在这里
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
//判断鼠标落在竖线上
//参数是鼠标落点的两个坐标
bool OnVerticalRect(HWND hWnd, int x, int y);
//判断鼠标落在横线上
//参数是鼠标落点的两个坐标
bool OnHorirontleRect(HWND hWnd, int x, int y);
//废弃的函数:
//绘制分割线
void DrawSplitRect(HDC hdc, HPEN mPen, POINT mPt, BOOL IsVertOrHori);
//废弃的函数:
//绘制分割线
void DrawLine(HWND hwnd);
//下面需要一个函数来解决问题:改变所有需要改变的窗口
void ChangeWnd();
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR lpCmdLine,_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此处放置代码。
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_SPLITWND, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SPLITWND));
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
// 函数: MyRegisterClass()
// 目标: 注册窗口类。
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SPLITWND));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_SPLITWND);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
// 函数: InitInstance(HINSTANCE, int)
// 目标: 保存实例句柄并创建主窗口
// 注释:
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
MainWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!MainWnd)
{
return FALSE;
}
ShowWindow(MainWnd, nCmdShow);
UpdateWindow(MainWnd);
return TRUE;
}
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
// 目标: 处理主窗口的消息。
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SIZE:
{
pt.x =LOWORD(lParam)/2; //窗口的宽
pt.y = HIWORD(lParam)/2 ; //窗口的高
WidthOfWnd = LOWORD(lParam);//把窗口的宽度保存下来,使用较为方便
HeighthOfWnd = HIWORD(lParam);//把窗口的高度保存下来,使用较为方便
ChangeWnd();
// DrawLine(hWnd);
}
break;
case WM_CREATE:
{
//正确:
//char tmpt[124];
//wsprintfA(tmpt, "窗口的大小:\n宽:%d\n高:%d\n", LOWORD(lParam), HIWORD(lParam));
//MessageBoxA(NULL, tmpt, "初始化窗口", MB_OK);
//RECT rect = { 0 };
//GetClientRect(hWnd, &rect);
//pt.x = rect.right / 2;
//pt.y = rect.bottom / 2;
//委托单窗口
ChildOrder = CreateWindowEx(0, L"STATIC", L"委托单子窗口,全部成交未成交都在这里", WS_CHILD | WS_VISIBLE | WS_BORDER | SS_NOTIFY,0, 0, pt.x-4, pt.y-4, hWnd, (HMENU)1001, hInst, NULL);
//成交单窗口
ChildTrade = CreateWindowEx(0, L"STATIC", L"成交单窗口,全部成交单都在这里", WS_CHILD | WS_VISIBLE | WS_BORDER | SS_NOTIFY, pt.x + 4, 0, WidthOfWnd, pt.y - 4, hWnd, (HMENU)1002, hInst, NULL);; //交易持仓,目前持仓信息全部统计在这里
//持仓单窗口
ChildNowTrade = CreateWindowEx(0, L"STATIC", L"持仓单窗口,全部持仓单都在这里", WS_CHILD | WS_VISIBLE | WS_BORDER | SS_NOTIFY, pt.x + 4, 0, WidthOfWnd, pt.y - 4, hWnd, (HMENU)1003, hInst, NULL);; //交易持仓,目前持仓信息全部统计在这里
}
break;
case WM_LBUTTONDOWN:
{
//SetCapture函数功能:该函数在属于当前线程的指定窗口里设置鼠标捕获。
//一旦窗口捕获了鼠标,所有鼠标输入都针对该窗口,无论光标是否在窗口的边界内。
//同一时刻只能有一个窗口捕获鼠标。
//如果鼠标光标在另一个线程创建的窗口上,只有当鼠标键按下时系统才将鼠标输入指向指定的窗口。
//返回值:返回值是上次捕获鼠标的窗口句柄。如果不存在那样的句柄,返回值是NULL。
//备注:只有前台窗口才能捕获鼠标。
//如果一个后台窗口想捕获鼠标,则该窗口仅为其光标热点在该窗口可见部份的鼠标事件接收消息。
//另外,即使前台窗口已捕获了鼠标,用户也可点击该窗口,将其调入前台。
//当一个窗日不再需要所有的鼠标输入时,创建该窗口的线程应当调用函数ReleaseCapture来释放鼠标。此函数不能被用来捕获另一进程的鼠标输入。
if (OnVerticalRect(hWnd, LOWORD(lParam), HIWORD(lParam)))
{
//MessageBox(NULL, L"点击在竖线上了!", L"竖线", MB_OK);
isOnVLine = true;
SetCursor(LoadCursor(NULL, IDC_CROSS));//当鼠标点击到竖向分割条时光标变形为十字
}
if (OnHorirontleRect(hWnd, LOWORD(lParam), HIWORD(lParam)))
{
// MessageBox(NULL, L"点击在横线上了!", L"横线", MB_OK);
isOnHLine = true;
SetCursor(LoadCursor(NULL, IDC_CROSS));//当鼠标点击到横向分割条时光标变形为十字
}
// SetCapture(hWnd);
}
break;
case WM_LBUTTONUP://按下鼠标,拖过来:,
{
if (isOnVLine)//点击在竖线上了!
{
//MessageBox(NULL, L"点击在竖线上了!", L"竖线", MB_OK);
pt.x = LOWORD(lParam);//把竖的分割线拖过来,那就只改变横坐标
}
if (isOnHLine)//点击在横线上了!
{
//MessageBox(NULL, L"点击在横线上了!", L"横线", MB_OK);
pt.y = HIWORD(lParam);//把横的分割线拖过来,那就只改变纵坐标
}
//此函数调用已经被废弃
//此函数调用已经被废弃
//此函数调用已经被废弃
// DrawLine(hWnd);//此函数调用已经被废弃
ReleaseCapture();
//下面需要一个函数来解决问题:改变所有需要改变的窗口
ChangeWnd();
}
break;
case WM_COMMAND:
{
// 分析菜单选择:
switch (LOWORD(wParam))
{
case 1001:
{
switch (HIWORD(wParam))
{
case STN_DBLCLK:
{
MessageBox(NULL, L"1001被双击\n委托单子窗口", L"委托", MB_OK);
}
break;
//case STN_CLICKED:
//{
// MessageBox(NULL, L"1001被单击", L"Infor", MB_OK);
//}
//break;
default:
break;
}
}
break;
case 1002:
{
switch (HIWORD(wParam))
{
case STN_CLICKED:
//MessageBox(NULL, L"1002被点击", L"Infor", MB_OK);
break;
case STN_DBLCLK:
MessageBox(NULL, L"1002被双击点击\n今日成交记录", L"成交", MB_OK);
break;
default:
break;
}
}
break;
case 1003:
{
switch (HIWORD(wParam))
{
case STN_CLICKED:
//MessageBox(NULL, L"1002被点击", L"Infor", MB_OK);
break;
case STN_DBLCLK:
MessageBox(NULL, L"1003被双击点击\n持仓信息子窗口", L"持仓", MB_OK);
break;
default:
break;
}
}
break;
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TextOutA(hdc, 50, 150, "我是裴英轩,裴英轩裴英轩,裴英轩", 33);
//遵循出栈的规则,依次清理
// ReleaseDC(hWnd, hdc);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
//判断鼠标是否落在竖线分割条上
//参数是鼠标落点的两个坐标
bool OnVerticalRect(HWND hwnd, int x, int y)
{
bool isok = false;
if (x >= pt.x - LineWidth / 2 && x <= pt.x + LineWidth/2&&y<=pt.y + LineWidth / 2&& y >=0)//目标点与竖线相近
{
isok = true;
// //SetCapture函数功能:该函数在属于当前线程的指定窗口里设置鼠标捕获。
// //一旦窗口捕获了鼠标,所有鼠标输入都针对该窗口,无论光标是否在窗口的边界内。
// //同一时刻只能有一个窗口捕获鼠标。
// //如果鼠标光标在另一个线程创建的窗口上,只有当鼠标键按下时系统才将鼠标输入指向指定的窗口。
// //返回值:返回值是上次捕获鼠标的窗口句柄。如果不存在那样的句柄,返回值是NULL。
// //备注:只有前台窗口才能捕获鼠标。
// //如果一个后台窗口想捕获鼠标,则该窗口仅为其光标热点在该窗口可见部份的鼠标事件接收消息。
// //另外,即使前台窗口已捕获了鼠标,用户也可点击该窗口,将其调入前台。
// //当一个窗日不再需要所有的鼠标输入时,创建该窗口的线程应当调用函数ReleaseCapture来释放鼠标。此函数不能被用来捕获另一进程的鼠标输入。
SetCapture(hwnd);
}
return isok;
}
//判断鼠标是否落在横线分割条上
//参数是鼠标落点的两个坐标
bool OnHorirontleRect(HWND hwnd, int x, int y)
{
bool isok = false;
if (x>0&&x< WidthOfWnd &&y >= pt.y - LineWidth / 2 && y <= pt.y + LineWidth/2)//目标点与竖线相近
{
isok = true;
//SetCapture函数功能:该函数在属于当前线程的指定窗口里设置鼠标捕获。
//一旦窗口捕获了鼠标,所有鼠标输入都针对该窗口,无论光标是否在窗口的边界内。
//同一时刻只能有一个窗口捕获鼠标。
//如果鼠标光标在另一个线程创建的窗口上,只有当鼠标键按下时系统才将鼠标输入指向指定的窗口。
//返回值:返回值是上次捕获鼠标的窗口句柄。如果不存在那样的句柄,返回值是NULL。
//备注:只有前台窗口才能捕获鼠标。
//如果一个后台窗口想捕获鼠标,则该窗口仅为其光标热点在该窗口可见部份的鼠标事件接收消息。
//另外,即使前台窗口已捕获了鼠标,用户也可点击该窗口,将其调入前台。
//当一个窗日不再需要所有的鼠标输入时,创建该窗口的线程应当调用函数ReleaseCapture来释放鼠标。此函数不能被用来捕获另一进程的鼠标输入。
SetCapture(hwnd);
}
return isok;
}
//废弃的函数:
//绘制分割条
void DrawSplitRect(HDC hdc, HPEN mPen, POINT mPt,BOOL IsVertOrHori)
{
//BOOL IsVertOrHori为真绘制横线,为假绘制垂直线
SelectObject(hdc,mPen);
if (IsVertOrHori)//绘制竖线分割线
{
//绘制竖线分割线
//MoveToEx(hdc, mPt.x, 0, NULL);//横坐标一样,垂直线
//LineTo(hdc, mPt.x, mPt.y);//横坐标一样,垂直线,起点纵坐标加上线的长度就是线的终点纵坐标
Rectangle(hdc, mPt.x - LineWidth / 2, 0, mPt.x + LineWidth / 2, mPt.y);
isOnVLine = false;//复位
}
else//绘制横线分割线
{
//绘制横线分割线,水平分割线的长度为客户区窗口的宽度
//MoveToEx(hdc, 0, mPt.y, NULL);//纵坐标一样,水平线
//LineTo(hdc, WidethOfWnd,mPt.y);//纵坐标一样,水平线,起点横坐标加上线的长度就是线的终点横坐标
//不在绘制直线,而是根据pt点绘制横竖两个矩形
Rectangle(hdc, 0,mPt.y - LineWidth / 2, WidthOfWnd, mPt.y+ LineWidth / 2);
isOnHLine = false;//复位
}
}
//废弃的函数:
//绘制分割线,实际上是绘制一个rectangle,
//因为直线很容易被新建立的窗口遮挡,从而影响分割窗口的逻辑
void DrawLine(HWND hwnd)
{
HDC hdc = GetDC(hwnd);
HPEN MyPen = CreatePen(PS_SOLID, LineWidth, RGB(180, 225, 150));
SelectObject(hdc, MyPen);
// SendMessageW(hwnd, WM_PAINT, 0, 0);
RECT rect1 = { 0 };
GetClientRect(hwnd, &rect1);
InvalidateRect(hwnd, &rect1, TRUE);
DrawSplitRect(hdc, MyPen, pt, 0); //水平分割线
DrawSplitRect(hdc, MyPen, pt, 1); //垂直分割线
DeleteObject(MyPen);
ReleaseDC(hwnd, hdc);
}
//下面需要一个函数来解决问题:改变所有需要改变的窗口
void ChangeWnd()
{
MoveWindow(ChildOrder, 0, 0, pt.x - LineWidth / 2, pt.y - LineWidth / 2, TRUE);//改变委托单子窗口的大小
MoveWindow(ChildTrade, pt.x + LineWidth / 2, 0, WidthOfWnd, pt.y - LineWidth / 2, TRUE);//改变成交子窗口的大小
MoveWindow(ChildNowTrade, 0, pt.y + LineWidth / 2, WidthOfWnd, HeighthOfWnd, TRUE);//改变持仓子窗口的大小
isOnHLine = false;//复位,等待下一次点击分割条改变窗口
isOnVLine = false;//复位,等待下一次点击分割条改变窗口
}
看看实际的效果:
看看实际的效果