|   一派掌門 二十級 | 
              【C++代码】#include <tchar.h>
 #include <Windows.h>
 #include "resource.h"
 
 #define SCROLL_LINE 20 // 点击滚动条的箭头时, 位图滚动多少像素
 //#define SCROLL_MAX(si) (si.nMax - (int)si.nPage + 1) // 获取滑块左端最大位置的宏
 
 HBITMAP hbmp;
 BITMAP bmp;
 
 LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
 HDC hdc, hdcMem;
 HINSTANCE hInstance = (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE);
 int nLines, nNewPos, nOldPos, nOldHorzPos, nOldVertPos;
 PAINTSTRUCT ps;
 RECT rect, rcClient;
 SCROLLBARINFO sbi;
 SCROLLINFO si;
 ULONG ulScrollLines;
 
 switch (uMsg)
 {
 case WM_CREATE:
 hbmp = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); // 加载位图资源
 GetObject(hbmp, sizeof(bmp), &bmp); // 获取位图尺寸
 
 // 为了方便测试, 可以手动将bmWidth和bmHeight改小, 模拟显示小图片
 //bmp.bmWidth = 800;
 //bmp.bmHeight = 600;
 
 // 设置滚动条的滚动范围
 // 只要位图的大小不变, 那么滚动范围就不会发生变化
 // 由于滚动位置的最小值为0, 而bmWidth和bmHeight的最小值却为1, 因此在指定滚动位置的最大值时必须减1
 // 比如一张10x10的图片显示到窗口中,图片最右下角点的显示坐标为(9, 9)
 // 因此此时滚动条滑块最右端只能滚动到9,不能滚动到10
 SetScrollRange(hWnd, SB_HORZ, 0, bmp.bmWidth - 1, FALSE);
 SetScrollRange(hWnd, SB_VERT, 0, bmp.bmHeight - 1, FALSE);
 break;
 case WM_DESTROY:
 DeleteObject(hbmp);
 PostQuitMessage(0);
 break;
 
 // 水平滚动条消息的处理
 case WM_HSCROLL:
 si.cbSize = sizeof(SCROLLINFO);
 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
 GetScrollInfo(hWnd, SB_HORZ, &si);
 // 下面的这些值无需作任何判断处理, 系统会自动纠正无效的值
 switch (LOWORD(wParam))
 {
 case SB_LEFT:
 nNewPos = si.nMin;
 break;
 case SB_LINELEFT:
 nNewPos = si.nPos - SCROLL_LINE;
 break;
 case SB_LINERIGHT:
 nNewPos = si.nPos + SCROLL_LINE;
 break;
 case SB_PAGELEFT:
 nNewPos = si.nPos - si.nPage;
 break;
 case SB_PAGERIGHT:
 nNewPos = si.nPos + si.nPage;
 break;
 case SB_RIGHT:
 nNewPos = si.nMax; // 执行SetScrollPos函数后会由系统自动调整为SCROLL_MAX
 break;
 
 // 下面两个case要二选一
 //case SB_THUMBPOSITION: // 仅当用户拖动滚动条完成并释放鼠标左键后才更新内容
 case SB_THUMBTRACK: // 只要用户拖动了滚动条就立即更新内容
 nNewPos = HIWORD(wParam);
 break;
 
 // 忽略其他滚动条消息
 default:
 return FALSE;
 }
 nOldHorzPos = SetScrollPos(hWnd, SB_HORZ, nNewPos, TRUE); // 这里设置的是滑块左端的位置
 nNewPos = GetScrollPos(hWnd, SB_HORZ);
 // 若设置后滚动条位置有变化就更新窗口内容
 if (nNewPos != nOldHorzPos)
 InvalidateRect(hWnd, NULL, FALSE);
 break;
 
 // 鼠标滚轮消息处理
 case WM_MOUSEWHEEL:
 SystemParametersInfo(SPI_GETWHEELSCROLLLINES, NULL, &ulScrollLines, NULL); // 获取用户在控制面板中设置的滚动速度
 nLines = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA * ulScrollLines; // 计算要滚动多少单位
 
 sbi.cbSize = sizeof(SCROLLBARINFO);
 GetScrollBarInfo(hWnd, OBJID_VSCROLL, &sbi); // 获取垂直滚动条的状态信息
 if (sbi.rgstate[0] == STATE_SYSTEM_INVISIBLE || sbi.rgstate[0] == STATE_SYSTEM_OFFSCREEN)
 {
 // 如果没有垂直滚动条,就滚动水平滚动条
 nNewPos = GetScrollPos(hWnd, SB_HORZ) - nLines * SCROLL_LINE;
 nOldPos = SetScrollPos(hWnd, SB_HORZ, nNewPos, TRUE);
 nNewPos = GetScrollPos(hWnd, SB_HORZ);
 }
 else
 {
 // 默认滚动垂直滚动条
 nNewPos = GetScrollPos(hWnd, SB_VERT) - nLines * SCROLL_LINE;
 nOldPos = SetScrollPos(hWnd, SB_VERT, nNewPos, TRUE);
 nNewPos = GetScrollPos(hWnd, SB_VERT);
 }
 // 如果滚动条位置发生变化就刷新窗口
 if (nOldPos != nNewPos)
 InvalidateRect(hWnd, NULL, FALSE);
 break;
 
 case WM_PAINT:
 hdc = BeginPaint(hWnd, &ps);
 SetWindowOrgEx(hdc, GetScrollPos(hWnd, SB_HORZ), GetScrollPos(hWnd, SB_VERT), NULL); // 告诉hdc现在滚动到什么位置上了
 
 // 将位图显示到(0, 0)处
 // 其中的坐标都无需根据滚动条的位置改变,该往哪儿画就往哪儿画
 hdcMem = CreateCompatibleDC(hdc);
 SelectObject(hdcMem, hbmp);
 BitBlt(hdc, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcMem, 0, 0, SRCCOPY);
 DeleteDC(hdcMem);
 EndPaint(hWnd, &ps);
 break;
 case WM_SIZE:
 // 记录重设nPage值之前的滚动条位置
 nOldHorzPos = GetScrollPos(hWnd, SB_HORZ);
 nOldVertPos = GetScrollPos(hWnd, SB_VERT);
 
 // 根据新的窗口尺寸重新设置nPage值的大小
 // 如果设置后某个滚动条消失或重新出现,那么系统会自动调整滚动条的位置
 SetRect(&rcClient, 0, 0, LOWORD(lParam), HIWORD(lParam));
 do
 {
 rect = rcClient;
 si.cbSize = sizeof(SCROLLINFO);
 si.fMask = SIF_PAGE;
 si.nPage = rect.right - rect.left;
 SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
 si.nPage = rect.bottom - rect.top;
 SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
 GetClientRect(hWnd, &rcClient);
 } while (memcmp(&rcClient, &rect, sizeof(RECT)) != 0); // 如果滚动条消失或重新出现导致客户区尺寸发生变化,则需要重新设置nPage
 
 // 设置了nPage的值后,若滚动条位置已经自动发生了变化,则必须刷新窗口内容
 // 如果在注册窗口类时指定了CS_HREDRAW | CS_VREDRAW,则可以删除下面这两行
 if (nOldHorzPos != GetScrollPos(hWnd, SB_HORZ) || nOldVertPos != GetScrollPos(hWnd, SB_VERT))
 InvalidateRect(hWnd, NULL, FALSE);
 // 注意: InvalidateRect并不会立即刷新窗口, 而是要等到消息队列中没有消息了才执行WM_PAINT
 // 所以执行两次InvalidateRect并不会影响性能
 break;
 
 // 垂直滚动条消息的处理,这个和上面的水平滚动条差不多
 case WM_VSCROLL:
 si.cbSize = sizeof(SCROLLINFO);
 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
 GetScrollInfo(hWnd, SB_VERT, &si);
 switch (LOWORD(wParam))
 {
 case SB_TOP:
 nNewPos = si.nMin;
 break;
 case SB_LINEUP:
 nNewPos = si.nPos - SCROLL_LINE;
 break;
 case SB_LINEDOWN:
 nNewPos = si.nPos + SCROLL_LINE;
 break;
 case SB_PAGEUP:
 nNewPos = si.nPos - si.nPage;
 break;
 case SB_PAGEDOWN:
 nNewPos = si.nPos + si.nPage;
 break;
 case SB_BOTTOM:
 nNewPos = si.nMax;
 break;
 case SB_THUMBTRACK:
 nNewPos = HIWORD(wParam);
 break;
 default:
 return FALSE;
 }
 nOldVertPos = SetScrollPos(hWnd, SB_VERT, nNewPos, TRUE);
 nNewPos = GetScrollPos(hWnd, SB_VERT);
 if (nNewPos != nOldVertPos)
 InvalidateRect(hWnd, NULL, FALSE);
 break;
 default:
 return DefWindowProc(hWnd, uMsg, wParam, lParam);
 }
 return FALSE;
 }
 
 int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
 {
 WNDCLASS wc;
 wc.cbClsExtra = wc.cbWndExtra = 0;
 wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 wc.hInstance = hInstance;
 wc.lpfnWndProc = WndProc;
 wc.lpszClassName = TEXT("ImageViewer");
 wc.lpszMenuName = NULL;
 wc.style = NULL;
 RegisterClass(&wc);
 
 // 创建窗口时,指定WS_HSCROLL或WS_VSCROLL就可以开启窗口滚动条
 HWND hWnd = CreateWindow(wc.lpszClassName, TEXT("Image Viewer"), WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
 if (!hWnd)
 return 1;
 ShowWindow(hWnd, nCmdShow);
 UpdateWindow(hWnd);
 
 MSG msg;
 while (GetMessage(&msg, NULL, 0, 0))
 {
 TranslateMessage(&msg);
 DispatchMessage(&msg);
 }
 return msg.wParam;
 }
 
 |