别在MFC了,先分析下,上图
我们以左上角为坐标原点,用position_width和position_height来保存当前显示坐标。
根据msdn说明,滚动条默认情况下的值在0~100之间。
根据图可以知道positon_width的活动范围是0到canvas_width-screen-width,另一边类似。
所以有恒等式1:position_width/(canvas_width-screen_width) = hb_pos/100,其中hb_pos是水平方向滚动条当前值。
滚动块长度公式2:screen_width/canvas_width = 滚动块长度/滚动条可滚动区域长度,滚动条可滚动区域长度大概是screen_width-40差不多,可以设置大写留余地。
下面直接上完整代码,可以运行的,只实现了拖动滚动块事件,其他事件自己补充吧
#include <windows.h> #define IDC_CANVAS 200 HWND hwnd_screen = NULL;//屏幕句柄,这里的屏幕既是我们创建的顶级窗口 HWND hwnd_canvas = NULL;//画布句柄 HINSTANCE Ghinstance = NULL;//程序实例 //注意:以下提到的“屏幕”指的都是我们创建的模拟屏幕,也就是顶级窗口,而不是我们计算机的屏幕 int canvas_width = 2000;//画布长度 int canvas_height = 1500;//画布宽度 int screen_width = 0;//屏幕长度 int screen_height = 0;//屏幕宽度 int position_width = 0;//当前位置的横坐标 int position_height = 0;//当前位置的纵坐标 int hb_pos = 0;//竖直方向滚动条当前位置 int vb_pos = 0;//水平方向滚动条当前位置 LRESULT CALLBACK ScreenProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);//屏幕事件处理函数 LRESULT CALLBACK CanvasProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);//画布事件处理函数 //入口函数 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { //========================================创建屏幕begin================================================== WNDCLASSEX wc; MSG Msg; memset(&wc,0,sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = ScreenProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.lpszClassName = "WindowClass"; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); if(!RegisterClassEx(&wc)) { MessageBox(NULL, "Window Registration Failed!","Error!",MB_ICONEXCLAMATION|MB_OK); return 0; } //程序实例和屏幕句柄放到全局变量里 Ghinstance = hInstance; hwnd_screen = CreateWindowEx(WS_EX_CLIENTEDGE,"WindowClass","Caption",WS_VISIBLE|WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL,NULL,hInstance,NULL); if(hwnd_screen == NULL) { MessageBox(NULL, "Screen Creation Failed!","Error!",MB_ICONEXCLAMATION|MB_OK); return 0; } //========================================创建屏幕end================================================== //你也可以把创建画布的过程放到屏幕的WM_CREATE事件中,放这里是使读者思路能清晰些 //========================================创建画布begin================================================== wc; memset(&wc,0,sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.lpszClassName = "Canvas"; wc.lpfnWndProc = CanvasProc; wc.hInstance = Ghinstance;//这里可以直接使用全局变量了 wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND); if(!RegisterClassEx(&wc)) { MessageBox(NULL, "Canvas Registration Failed!","Error!",MB_ICONEXCLAMATION|MB_OK); return 0; } hwnd_canvas= CreateWindow( "Canvas", "", WS_CHILD | WS_VISIBLE | WS_BORDER, 0, 0, canvas_width, canvas_height, hwnd_screen,//这里可以直接使用全局变量了,注意,如果是放屏幕的WM_CREATE里面,这时候是还不能使用这个全局变量的,WM_CREATE事件结束后CreateWindow方法才会返回创建窗口的句柄 (HMENU)IDC_CANVAS, Ghinstance,//这里可以直接使用全局变量了 0 ); if(hwnd_canvas == NULL) { MessageBox(NULL, "Canvas Creation Failed!","Error!",MB_ICONEXCLAMATION|MB_OK); return 0; } //========================================创建画布end================================================== //该显示的显示 ShowWindow(hwnd_screen, nCmdShow); UpdateWindow(hwnd_screen); //该显示的显示 ShowWindow(hwnd_canvas, SW_SHOW); UpdateWindow(hwnd_canvas); //消息循环 while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam; } //屏幕的事件 LRESULT CALLBACK ScreenProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) { //只需要处理WM_SIZE、WM_HSCROLL、WM_VSCROLL三个消息 switch(Message) { //窗口大小改变时,更新全局变量中的屏幕大小,更新滚动条上滚动块的位置 case WM_SIZE: { //更新屏幕大小begin----------------------------- screen_width = LOWORD (lParam); screen_height = HIWORD (lParam); //更新屏幕大小end----------------------------- //更新滚动条上滚动块的位置begin---------------------- hb_pos = position_width * 100 / (canvas_width - screen_width);//根据恒等式1 vb_pos = position_height * 100 / (canvas_height - screen_height); SCROLLINFO si; si.cbSize=sizeof(SCROLLINFO); si.fMask=SIF_POS; si.nPos = vb_pos; SetScrollInfo(hwnd_screen,SB_VERT,&si,true); si.nPos = hb_pos; SetScrollInfo(hwnd_screen,SB_HORZ,&si,true); //更新滚动条上滚动块的位置end---------------------- //其实还应该更新滚动条上滚动块的长度,这里先忽略吧 //int hb_length = GValue::s_width * (GValue::s_width - 40) / GValue::c_width;//根据恒等式2 //int vb_length = GValue::s_height * (GValue::s_height - 40) / GValue::c_height; break; } //水平方向滚动条事件 case WM_HSCROLL : { SCROLLINFO si; si.cbSize=sizeof(SCROLLINFO); si.fMask=SIF_ALL; GetScrollInfo(hwnd_screen,SB_HORZ,&si);//先拿滚动条信息 switch(LOWORD(wParam)){//这里只处理按下滚动条拖动的事件,其他滚动条事件自己实现吧 //分析可知按住滚动条拖动过程中,需要修改当前位置、然后基于当前位置移动画布,最后修改滚动条位置(你不修改的话视觉效果上会弹回去的)、 case SB_THUMBTRACK: { position_width = si.nTrackPos * (canvas_width - screen_width) / 100;//更改当前位置 MoveWindow(hwnd_canvas, 0 - position_width, 0 - position_height, canvas_width, canvas_height, true);//移动画布 si.nPos=si.nTrackPos; break; } //TODO 滚动条的其他事件 default: { break; } } //回写滚动条滚动块的位置,时视觉上正常 si.fMask=SIF_POS; SetScrollInfo(hwnd_screen, SB_HORZ, &si, true); break; } //竖直方向滚动条事件,与上面相似不解释了 case WM_VSCROLL : { SCROLLINFO si; si.cbSize=sizeof(SCROLLINFO); si.fMask=SIF_ALL; GetScrollInfo(hwnd_screen, SB_VERT, &si); switch(LOWORD(wParam)){ case SB_THUMBTRACK: { position_height = si.nTrackPos * (canvas_height - screen_height) / 100; MoveWindow(hwnd_canvas, 0 - position_width, 0 - position_height, canvas_width, canvas_height, true); si.nPos=si.nTrackPos; break; } default: { break; } } si.fMask=SIF_POS; SetScrollInfo(hwnd_screen, SB_VERT, &si, true); break; } //鼠标滚轮 /* case WM_MOUSEWHEEL : { //向下 if(HIWORD(wParam) < 0) { vb_pos = vb_pos + 10; if(vb_pos > 100) { vb_pos = 0; } } else { vb_pos = vb_pos - 10; if(vb_pos < 0) { vb_pos = 0; } } break; } */ case WM_CLOSE: { DestroyWindow(hwnd); break; } case WM_DESTROY: { PostQuitMessage(0); break; } default: return DefWindowProc(hwnd, Message, wParam, lParam); } return 0; } //窗口的事件 LRESULT CALLBACK CanvasProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) { switch(Message) { //从画布左上角到右下角画一条线,以便观察滚动效果 case WM_PAINT: { PAINTSTRUCT ps; HDC hdc; RECT rc; GetClientRect(hwnd_canvas, &rc); hdc = BeginPaint(hwnd_canvas, &ps); MoveToEx(hdc, 0 , 0 , NULL); LineTo(hdc, rc.right, rc.bottom); EndPaint(hwnd_canvas, &ps); break; } case WM_CLOSE: { DestroyWindow(hwnd); break; } case WM_DESTROY: { PostQuitMessage(0); break; } default: return DefWindowProc(hwnd, Message, wParam, lParam); } }