WIN32滚动条的创建和使用

论坛 期权论坛 脚本     
匿名技术用户   2020-12-30 21:20   114   0

转自:https://blog.csdn.net/hd770c/article/details/17975927

https://www.cnblogs.com/javawebsoa/p/3226173.html

一、不可变长的滚动条

所需四个重要函数:

  1. 设置滚动条的范围:SetScrollRange
  2. 设置滚动滑块的位置:SetScrollPos
  3. 获取滚动条的范围:GetScrollRange
  4. 获取滚动滑块的位置:GetScrollPos

图片是最终的窗口效果,下面用一张图来讲解滚动条的作用和区域设置。

黄线矩形为窗口区域,红线区域为图片大小,只有当图片的宽或者高大于客户区的时候才需要相应的滚动条,滚动条的作用即为显示图片在客户区以外的部分,将滚动条位置和范围与像素关联,水平滚动条的最大范围即为cxBitmap-cxClient,最小范围当然是0啦,当滚动条位置发生变化时,图片进行相应的偏移,窗口的大小发生变化时,即cxClient发生变化,需要重新设置水平滚动条的范围,并判断当前滚动条位置是否在新的cxBitmap-cxClient范围内,如果超出范围,需要进行纠正;垂直滚动条的调整原理和水平是一样的。

实例整体很简单,定义一个典型的WINDOWS窗体:1.定义窗体属性;2.定义窗体处理程序;3.循环处理进程消息队列。详见代码注释。

注意:图片显示移动的方向和滚动条的滚动方向应该是相反的,所以代码中你会看到贴位图的位置是一个负值

BitBlt(hdc, -iHScrollBarPos, -iVScrollBarPos, cxBitmap, cyBitmap, hdcSrc, 0, 0, SRCCOPY);

#include <Windows.h>
 
 
//WINDOWS程序的标准范式:定义WINDOWS入口函数WinMain
//注册一个窗口对象
//定义窗口的属性,即WNDCLASS结构的队员
//定义窗口对应的处理函数,即WndProc回调函数
//WinMain中循环处理进程消息队列
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
 static TCHAR szAppName[] = TEXT("AppName");
 HWND   hwnd;
 MSG    msg;
 WNDCLASS   wndclass;
 int  cxclient, cyclient;
 
 wndclass.hInstance = hInstance;
 wndclass.lpszMenuName = NULL;
 wndclass.lpszClassName = szAppName;
 wndclass.cbClsExtra = NULL;
 wndclass.cbWndExtra = NULL;
 wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 wndclass.lpfnWndProc = WndProc;
 wndclass.style = CS_HREDRAW|CS_VREDRAW;
 
 //窗口在创建之前需要先注册窗口类
 if(!RegisterClass(&wndclass))
 {
  MessageBox(NULL, TEXT("Need Windows NT"), TEXT(""),MB_HELP); 
  return 0;
 }
 
 
 //获取屏幕属性的一个很有用的函数,F12可以看到详细用法,这里是获取屏幕的宽和高
 cxclient = GetSystemMetrics(SM_CXSCREEN);
 cyclient = GetSystemMetrics(SM_CYSCREEN);
 
 
 hwnd = CreateWindow(szAppName,
  TEXT(""),                                //窗口标题,可以设置为空,TEXT()为微软提供的字节处理宏,当定了_UNICODE的时候即代表了UNICODE字符,否则为ASC11字符
  WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,        //第一个宏为窗口有标题栏,有最大化,有最小化,有退出,后面两个指定了窗口具有垂直和水平滚动条
  cxclient/3,            //设定窗口起点为屏幕三分之一高和三分之一宽
  cyclient/3,
  cxclient/3,          //设定窗口宽和高分别为屏幕的三分之一
  cyclient/3,
  NULL,
  NULL,
  NULL,       //hInstance
  NULL);
 
 //创建窗口以后显示窗口
 ShowWindow(hwnd, TRUE);
 UpdateWindow(hwnd);
 //GetMessage消息循环从进程消息队列中匹配消息并转换后分发给对应窗口,窗口收到消息后调用注册的窗口处理函数处理翻译后的消息。
 //需要注意的是,如果想要从进程消息队列中获取所有消息,需要第二个参数设置成NULL,否则GetMessage无法获取WM_QUIT消息,即无法退出程序。
 //WM_QUIT属于进程但不属于进程的任意一个窗口,只有第二个参数NULL可以获取,GetMessage获取WM_QUIT后进程退出
 while(GetMessage(&msg, NULL, NULL, NULL))
 {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }
 return msg.wParam;
}
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
 static int cxClient, cyClient, cxBitmap, cyBitmap;
 static HBITMAP bitmap;
 static int iHScrollBarPos, iVScrollBarPos;
 
 
 switch(message)
 {
 case WM_CREATE:
  BITMAP bmpinfo;
  bitmap = (HBITMAP)LoadImage(NULL, TEXT("view.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
  if(!bitmap)
  {
   MessageBox(hwnd, TEXT("Load Image Error"), TEXT("My Demo"), MB_ICONERROR);
  }
  GetObject(bitmap, sizeof(BITMAP), &bmpinfo);
  cxBitmap = bmpinfo.bmWidth;
  cyBitmap = bmpinfo.bmHeight;
  break;
 case WM_PAINT:
  HDC hdc, hdcSrc;
  PAINTSTRUCT ps;
  hdc = BeginPaint(hwnd, &ps);
  hdcSrc = CreateCompatibleDC(hdc);
  SelectObject(hdcSrc, bitmap);
  BitBlt(hdc, -iHScrollBarPos, -iVScrollBarPos, cxBitmap, cyBitmap, hdcSrc, 0, 0, SRCCOPY);
  EndPaint(hwnd, &ps);
  DeleteDC(hdcSrc);
  break;
 case WM_SIZE:
  //窗口大小改变时收到此消息,F12可查看消息参数意思,msdn查到获取新窗口宽和高的方法
  cxClient = LOWORD(lparam);
  cyClient = HIWORD(lparam);
  //设定滚动条的范围
  SetScrollRange(hwnd, SB_HORZ, 0, cxBitmap - cxClient, FALSE);
  SetScrollRange(hwnd, SB_VERT, 0, cyBitmap - cyClient, FALSE);
  //获取滚动条的新位置,即判断窗口大小改变以后滚动条是否超出了应有的最大范围
  iHScrollBarPos = min(cxBitmap - cxClient, max(0, iHScrollBarPos));
  iVScrollBarPos = min(cyBitmap - cyClient, max(0, iHScrollBarPos));
  //如果滚动条超过了最大范围,则重设滚动条范围并刷新窗口
  if(iHScrollBarPos != GetScrollPos(hwnd, SB_HORZ))
  {
   SetScrollPos(hwnd, SB_HORZ, iHScrollBarPos, TRUE);
   InvalidateRect(hwnd, NULL, FALSE);
  }
  if(iVScrollBarPos != GetScrollPos(hwnd, SB_VERT))
  {
   SetScrollPos(hwnd, SB_VERT, iVScrollBarPos, SB_VERT);
   InvalidateRect(hwnd, NULL, FALSE);
  }
  break;
 case WM_VSCROLL:
  //垂直滚动条消息,F12查看消息可以从MSDN获取消息详细参数
  switch(LOWORD(wparam))
  {
  case SB_LINEUP:
   //每次滚动图片变化10个像素
   iVScrollBarPos -= 10;
   break;
  case SB_LINEDOWN:
   iVScrollBarPos += 10;
   break;
  case SB_PAGEUP:
   //每次翻页都滚动一整个客户区的大小
   iVScrollBarPos -= cyClient;
   break;
  case SB_PAGEDOWN:
   iVScrollBarPos += cyClient;
   break;
  case SB_THUMBTRACK:
   iVScrollBarPos = HIWORD(wparam);
   break;
  default :
   break;
  }
  //判断滚动后的滚动条是否超过最大值或最小值,如果超过最大值或者最小值,则取最大值或0,否则等于当前值
  iVScrollBarPos = min(cyBitmap - cyClient, max(0, iVScrollBarPos));
  //如果滚动条位置发生变化,则设置滚动条位置和刷新屏幕
  if(iVScrollBarPos != GetScrollPos(hwnd, SB_VERT))
  {
   SetScrollPos(hwnd, SB_VERT, iVScrollBarPos, TRUE);
   //最后参数设置为FALSE可以大幅度减少屏幕闪烁,可以尝试一下。
   InvalidateRect(hwnd, NULL, FALSE);
  }
  break;
 case WM_HSCROLL:
  switch(LOWORD(wparam))
  {
  case SB_LINEUP:
   iHScrollBarPos -= 10;
   break;
  case SB_LINEDOWN:
   iHScrollBarPos += 10;
   break;
  case SB_PAGEUP:
   iHScrollBarPos -= cxClient;
   break;
  case SB_PAGEDOWN:
   iHScrollBarPos += cxClient;
   break;
  case SB_THUMBTRACK:
   iHScrollBarPos = HIWORD(wparam);
   break;
  default :
   break;
  }
  iHScrollBarPos = min(cxBitmap - cxClient, max(0, iHScrollBarPos));
  if(iHScrollBarPos != GetScrollPos(hwnd, SB_HORZ))
  {
   SetScrollPos(hwnd, SB_HORZ, iHScrollBarPos, TRUE);
   InvalidateRect(hwnd, NULL, FALSE);
  }
  break;
 case WM_DESTROY:
  //点击窗口右上角的X,系统会向窗口发出WM_DESTROY消息,该消息并不能退出窗口,真正退出窗口的是WM_QUIT消息,调用PostQuitMessage向进程消息队列发送WM_QUIT消息
  PostQuitMessage(1);
  DeleteObject(bitmap);
  return 0;
  break;
 default :
  break;
 }
 return DefWindowProc(hwnd, message, wparam, lparam);
}

二、可变长的滚动条

Win32的标准风格
它要稍微复杂点儿,它同样有一套API来维护,以下是相关的API函数:

设定滚动条信息

int SetScrollInfo(
  HWND hwnd,           // 窗口句柄
  int fnBar,           // 滚动条类型
  LPCSCROLLINFO lpsi,  // 滚动条信息结构体(稍后详解)
  BOOL fRedraw         // 是否重绘滚动条方块
);

获取滚动条信息

BOOL GetScrollInfo(
  HWND hwnd,         // handle to window
  int fnBar,         // scroll bar type
  LPSCROLLINFO lpsi  // scroll bar parameters
);

滚动窗口(这里是指窗口的客户区)

BOOL ScrollWindow(
  HWND hWnd,              // handle to window
  int XAmount,            // 水平滚动距离
  int YAmount,            // 垂直滚动距离
  CONST RECT *lpRect,     // 滚动区域范围(一般就是设为NULL,指客户区)
  CONST RECT*lpClipRect  // 剪裁区域(今天用不到,设定为NULL)
);

SCROLLINFO结构体

typedef struct tagSCROLLINFO { 
    UINT cbSize;//SCROLLINFO类型大小(主要是windows为了以后能兼容),也就是sizeof(SCROLLINFO)
    UINT fMask; //设定滚动条需要设置的参数
    int  nMin; //滚动条上边界
    int  nMax;  //滚动条下边界
    UINT nPage; //每一页的大小(主要用于计算滚动条块的大小)
    int  nPos;  //滚动条的位置
    int  nTrackPos; //滚动条滚动的位置
}   SCROLLINFO, *LPSCROLLINFO; 
typedef SCROLLINFO CONST *LPCSCROLLINFO;

我们先来看看它的效果:

你会发现随着窗口大小的变化,滚动条方块也随着变化了,它变化的依据如下:

有一点需要注意的是:windows帮你做了以下一个设计 —— 滚动条实际滚动的范围是:nMax - nPage + 1,这主要是避免过多的滚动,当显示内容在后一行在客户区最后一行就行了,所以我们只需要从我们的视角来设定nMin和nMax,不要自己去考虑滚动条滚动的实际范围,windows都为我们做好了。

主要代码详解:

初始化滚动条参数

case WM_SIZE:
  cxClient = LOWORD(lParam);
  cyClient = HIWORD(lParam);

  //设定垂直滚动条范围和页面大小
  si.cbSize = sizeof(si);
  si.fMask = SIF_RANGE | SIF_PAGE;
  si.nMin  = 0;
  si.nMax  = cyBitmap;
  si.nPage = cyClient;
  SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

  //设定水平滚动条范围和页面大小
  si.cbSize = sizeof(si);
  si.fMask = SIF_RANGE | SIF_PAGE;
  si.nMin  = 0;
  si.nMax  = cxBitmap;
  si.nPage = cxClient;
  SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);

  return 0;

处理滚动条消息(同样也实现垂直的)

//处理垂直滚动条消息
 case WM_VSCROLL:
  si.cbSize = sizeof(si);
  si.fMask = SIF_ALL;
  GetScrollInfo(hwnd, SB_VERT, &si);

  iVertPos = si.nPos;

  switch(LOWORD(wParam))
  {
          case SB_TOP: //置顶(先按下Shift键不放,然后点击滚动条方块上侧区域就能置顶)
               si.nPos = si.nMin ;
               break ;
               
          case SB_BOTTOM://置底(同置顶)
               si.nPos = si.nMax ;
               break ;

          case SB_LINEUP:
               si.nPos -= 10 ;//每一行滚动10个像素
               break ;
     
          case SB_LINEDOWN:
               si.nPos += 10 ;
               break ;
     
          case SB_PAGEUP://翻页就是一个客户区大小
               si.nPos -= cyClient ;
               break ;
     
          case SB_PAGEDOWN:
               si.nPos += cyClient ;
               break ;
     
          case SB_THUMBTRACK:
               si.nPos = HIWORD (wParam) ;
               break ;
     
          default :
               break ;
  }

  si.fMask = SIF_POS;
  SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
  GetScrollInfo(hwnd, SB_VERT, &si);

  if(iVertPos != si.nPos)
  {
   //这里InvalidateRect、ScrollWindow,效果相同
   InvalidateRect(hwnd, NULL, FALSE);
   //ScrollWindow(hwnd, 0, iVertPos - si.nPos, NULL, NULL);
  }
  return 0;

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP