界面控件 - 滚动条ScrollBar(对滚动条消息和鼠标消息结合讲的不错)
界面是人机交互的门户,对产品至关重要。在界面开发中只有想不到没有做不到的,有好的想法,当然要尝试着做出来。对滚动条的扩展,现在有很多类是的例子。
VS2015的代码编辑是非常强大的,其中有一个功能可以把滚动态变成MinMap,可以通过Options->Text Editor->C/C++->Scroll Bars中的Behavior
选项分类进行打开。
sublime也有这个功能,但没有VS的好用。变成MinMap后整个代码文档变成一个完整的缩微图,在你对代码比较熟悉的情况下,可以非常容易判断并定位到大致的函数位置。同时对特殊状态,如:错误信息,更新信息等进行标识。只要看一眼滚动条就能知道当前文档的情况,可以快速定位到相关文档位置。
优点多多: 当一个文件内容比较长时,在上面标注相关重要内容是非常方便的。用于只要一看滚动条上的相关标示,就能知道相关信息。不用再开辟窗口告诉用户,他想得到的有关信息。
有VS可作参考,做这个就比较容易。不知道微软有没有注册版权-_-!!。因此想到扩展Scroll Bar是个不错的主意,但windows系统并没有给这个机会,一般的窗口类都自带系统滚动条。系统自带的完全无法做出这种效果,只能自己做一个覆盖系统默认滚动条。
一、滚动条结构SCROLLINFO
typedef struct tagSCROLLINFO {
UINT cbSize; // 结构尺寸
UINT fMask; // 需要处理或返回的相关选项参数
int nMin; // 滚动条的最小值
int nMax; // 最大滚动范围
UINT nPage; // 一页有效行数
int nPos; // 当前滚动条
int nTrackPos; // 拖拽滚筒条时的位置
} SCROLLINFO, *LPCSCROLLINFO;
正常我们用到的的滚动条有下面一些元素,画了张图更容易理解点。
可以看到如果我们默认最小值=0时,实际需要的只有三个滚动条参数,最大可以滚动的范围nMax,一页可显示的行数nPage和 当前行的显示位置nPos 这三个参数。
1、滚动数量计算
如滚动条能正常滚动到最后一行的位置。有100行记录,滚动最下面界面显示最后一行。为方便计算没条记录的行高固定。
- nPage = ClientHeight / rowHeight
- nMax = rowCount - 1 + nPage - 1
正常情况计算是总行数-1, 就是 nMax的值,应为要允许滚动到记录最后一条,因此需要多加一页的滚动数量。就产生了上面的nMax的公式。
// 只要超过1行就能滚动,因此需要多加一页的行数。
si.nMax = rowCount - 1 + yClient / rowHeight - 1;
si.nPage = yClient / rowHeight;
2、滑块尺寸计算
滑块的尺寸的尺寸按照上图的定义,滚动条的高度除以滚动范围再乘上每页数量可以得到。
滑块尺寸 = 滚动条高度 / 有效范围 * 每页数量
上述计算公式存在另外一个问题,如果记录量很大时就杯具,滑块会非常小,用户很难用鼠标抓到滑块。眼神犀利和伸手敏捷的才能完成这种艰巨任务。因此为滑容易定位的最小值。
s = max((float)((r.bottom - r.top) / (si.nMax - si.nMin) * si.nPage), 20.0f);
3、滑块位置计算
在图上看滑块有效的滑动位置是需要扣除滑块的大小的。
实际滑动位置 = (滚动条高度 - 滑块尺寸) / (有效范围 - 每页数量 + 1) * 当前行位置
有除法,就会存一些精度问题,所以算出位置后要检查一下位置是否超出客户区。
v = 0;
if (si.nPos > 0)
v = (int)((r.bottom - r.top - s) / (float)(si.nMax - si.nMin + 1 - si.nPage) * si.nPos);
// 由于精度问题,可能滑块位置会超界。超界就取最大值
if (v && v + (int)s > r.bottom)
v = r.bottom - (int)s;
二、滚动条信息
做滚动条肯定需要记录上述说的相关信息。为简易期间直接使用这个结构体记录static SCROLLINFO si;
。在设置和获取时直接使用。
自定义滚动条不能使用SetScrollInfo 或 GetScrollInfo ,这两个是给系统提供的。需要另外增加2个消息,系统已经提供了两个消息,可以直接使用。SBM_SETSCROLLINFO 和 SBM_GETSCROLLINFO 处理滚动信息。
参数:
- wParam --- SBM_SETSCROLLINFO 是否需要刷新, SBM_GETSCROLLINFO 无效。
- lParam --- *SCROLLINFO 结构体指针。
前面在SCROLLINFO 结构中有提到fMask,在设置和获取中需要使用。
Mask 值:
- SIF_RANGE --- 设置行数范围参数 nMin ~ nMax 这两个参数值有效。
- SIF_PAGE --- 每页行数 nPage 参数值有效
- SIF_POS --- 位置值 nPos 参数值有效
- SIF_ALL --- 所有参数值都有效。
其他对应值可以参考MSDN文档。
case SBM_SETSCROLLINFO:
if (!lParam) return 0;
// 设置滚动条信息
psrcsi = (SCROLLINFO *)lParam;
if (psrcsi->fMask & SIF_RANGE) { // 设置内容行数
si.nMax = psrcsi->nMax;
si.nMin = psrcsi->nMin;
}
if (psrcsi->fMask & SIF_PAGE) // 每页能显示多少行
si.nPage = psrcsi->nPage;
if (psrcsi->fMask & SIF_POS) // 行显示位置
si.nPos = psrcsi->nPos;
// wParam = true 刷新滚动区域
if (wParam) InvalidateRect(hwnd, NULL, false);
return 0;
case SBM_GETSCROLLINFO:
//这里简单处理,应该和SBM_SETSCROLLINFO一样判断获取的信息。
if (lParam) *(SCROLLINFO *)lParam = si;
return 0;
三、滚动条绘制
有基本的滚动条结构信息后,要绘制出自己的滚动条就很简单了。需要主意的一点,当滚动条尺寸小到比滑块尺寸还小时,就不要再绘制滑块了。
技巧: 在填充色块时可以使用ExtTextOut函数进行填充,在不使用双缓存的情况下,能减少闪烁,而且不需要申请画刷。
static void mlCGFillColor(HDC hdc, RECT *r, unsigned int color)
{
// 实际使用ExtTextOut要比FillRect填充颜色效果好,能减少绘制中闪烁问题。而且不要申请画刷。
SetBkColor(hdc, color);
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, r, 0, 0, 0);
}
// WM_PAINT Code
// 绘制背景色
GetClientRect(hwnd, &r);
mlCGFillColor(hdc, &r, 0xcccccc);
// 计算滑块大小,过小时不绘制
if (r.bottom - r.top > 30 && si.nMax && (si.nMax - si.nMin) >= (int)si.nPage) {
// 滑块计算
// 大小 = 滚动条高度 / 有效范围 * 每页数量
// 最小20, 内容比较多,降低用于鼠标定位到滚动条拖动的难度。
s = max((float)((r.bottom - r.top) / (si.nMax - si.nMin) * si.nPage), 20.0f);
// 实际滑动位置
// = (滚动条高度 - 滑块尺寸) / (有效范围 - 每页数量 + 1) * 当前行位置
// 实际滚动的位置会比实际少一页的数量。
//
v = 0;
if (si.nPos > 0)
v = (int)((r.bottom - r.top - s) / (float)(si.nMax - si.nMin + 1 - si.nPage) * si.nPos);
// 由于精度问题,可能滑块位置会超界。超界就取最大值
if (v && v + (int)s > r.bottom)
v = r.bottom - (int)s;
// 绘制滑块
r.left++;
r.right--;
r.top = v ;
r.bottom = r.top + (int)s;
// 拖拽时滑块颜色反一下
mlCGFillColor(hdc, &r, dragState ? 0x999999 : 0x666666e);
InflateRect(&r, -1, -1);
mlCGFillColor(hdc, &r, dragState ? 0x666666e : 0x999999);
}
四、响应鼠标消息
鼠标消息,主要是左键点击定位、拖拽和滚轮滚动翻页。
1、点击定位响应
鼠标左击滚动条位置,可以直接定位到对应行。这个功能就是第一个需求,根据标识快速定位到记录行。实际的核心就是,鼠标所在滚动条的实际位置,计算出对应的行数。
计算方法:
nPos = (鼠标位置 - 半滑块尺寸) / 每象素滑动量
- 每象素滑动量 = 有效高度 / 可滑动数量
- 有效高度 = 总高度 - 滑块尺寸
- 可滑动数量 = 总量 - 每页数量
根据上述公式提取出一个通用方法,计算像素位置位置对应行数的关系函数。
// 计算滚动条位置
static int calcVertThumPos(int postion, SCROLLINFO *si, int s)
{
float thumsize; // 滑块的大小
float pxSize; // 每象素可滑动量
int scrollCnt; // 可滑动数量
// 滑块尺寸 = 滚动条高度 / 有效范围 * 每页数量
// 最小 20
thumsize = max((float)((s) / (si->nMax - si->nMin) * si->nPage), 20.0f);
if (postion <= (int)(thumsize / 2))
return 0;
if (postion >= s - (thumsize / 2))
return (si->nMax - si->nMin + 1) - si->nPage;
// 计算方法:
// 每象素滑动量 = 有效高度 / 可滑动数量
// 有效高度 = 总高度 - 滑块尺寸
// 可滑动数量 = 总量 - 每页数量
scrollCnt = (si->nMax - si->nMin + 1) - si->nPage;
pxSize = (float)(s - thumsize) / (float)scrollCnt;
//
// 计算方法:
// 位置 = (鼠标位置 - 半滑块尺寸) / 每象素滑动量
return (int)((postion - thumsize / 2.0) / pxSize);
}
鼠标左击事件响应WM_LBUTTONDOWN消息。
si.nTrackPos = calcVertThumPos(GET_Y_LPARAM(lParam), &si, height);
if (si.nPos != si.nTrackPos)
PostMessage(GetParent(hwnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hwnd);
使用calcVertThumPos 函数计算出滚动位置,并通过WM_VSCROLL消息请求滚动。
注意:MSDN中 WM_VSCROLL的lParam参数约定,当自定义控件是通常传自定义控件的hWnd。用于区分系统默认的还是自定义控件的。
点击定位就搞定了。
2、拖拽滑块滚响应
拖拽滚动需要同时用到3消息WM_LBUTTONDOWN、WM_MOUSEMOVE 和WM_LBUTTONUP。当鼠标按下时准备拖拽,拖拽中滚动记录,鼠标释放结束拖拽。
正常情况鼠标按下就需要锁定鼠标SetCapture,但有时只是点击一下,并没有需要拖拽。或内部处理更多的控制时,直接锁定不太好。因此在拖拽时加了个dragState的状态,只有准备拖拽了才进行锁定鼠标操作。
另外一种情况,某些异常情况,锁定的鼠标会丢失。如断点调试一定会打断,这时候会出现不想要的情况。没在拖拽状态,鼠标已经松开了,滑块还是会跟着鼠标移动。
case WM_LBUTTONDOWN:
...
dragState = 1; // 准备拖动滚动条
InvalidateRect(hwnd, NULL, false);
return 0;
case WM_MOUSEMOVE:
if (dragState == 1) {
SetCapture(hwnd); // 有拖拽准备,锁定鼠标鼠标
dragState = 2;
}
else if (dragState == 2) {
if (!(wParam & MK_LBUTTON)) {
dragState = 0; // 防止中间中断,导致出现无效拖拽
if (GetCapture() == hwnd)
ReleaseCapture();
}
else {
// 在锁定状态下拖动滚动条定位。
si.nTrackPos = calcVertThumPos(GET_Y_LPARAM(lParam), &si, height);
if (si.nTrackPos != si.nPos)
PostMessage(GetParent(hwnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hwnd);
}
}
return 0;
case WM_LBUTTONUP:
if (dragState == 2)
ReleaseCapture(); // 释放鼠标锁定
if (dragState) {
dragState = 0; // 清除状态
InvalidateRect(hwnd, NULL, false);
}
return 0;
鼠标滚轮响应
滚动事件WM_MOUSEWHEEL,鼠标滚动翻页时会有一个精度。慢慢滚动滚轮会上移或下移3行左右,当快速滚动时,会很容易到文档末尾。这个就是一般说的精度。
这里默认处理,滚动一次滚轮移动三行。获得滚动行数量后就可以直接在当前数量nPos上累加。注意一下,不要超界。
// 鼠标滚动支持
accumDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA; // 滚动精度120一个单位
si.nTrackPos = si.nPos - accumDelta * 3; // 每滚一次3行
if (si.nTrackPos < 0)
si.nTrackPos = 0;
else if (si.nTrackPos > (int)((si.nMax - si.nMin + 1) - si.nPage))
si.nTrackPos = (si.nMax - si.nMin + 1) - si.nPage;
if (si.nPos != si.nTrackPos)
PostMessage(GetParent(hwnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hwnd);
~OK 啦
简单的滚动条就完成,然后后面的标识之类的就可以在这个基础上尽情发挥。随你完全由你掌控滚动条。
最终的效果:
完整代码:
相关消息和API:
- SBM_SETSCROLLINFO --- 设置滚动条信息
- SBM_GETSCROLLINFO --- 获取滚动条信息
- WM_VSCROLL --- 滚动消息
- WM_LBUTTONDOWN --- 鼠标左击按下消息
- WM_LBUTTONUP --- 鼠标左击弹起消息
- WM_MOUSEMOVE --- 鼠标移动消息
- WM_MOUSEWHEEL --- 鼠标滚轮消息
- SetCapture --- 鼠标捕获
- ReleaseCapture -- 释放鼠标
- ExtTextOut --- 文本绘制,有用于底色填充。
http://www.cnblogs.com/gleam/p/5433719.html
界面控件 - 滚动条ScrollBar(对滚动条消息和鼠标消息结合讲的不错)的更多相关文章
- MFC 控件编程之水平滚动条跟垂直滚动条
MFC 控件编程之水平滚动条跟垂直滚动条 一点水平滚动条的操作 首先在操作滚动条的时候.我们要知道滚动条的一些属性. 比如我们要设置 最大值 最小值. 以及每次递增的值是多少.都要设置. 所有就有一个 ...
- div或其他html控件的overflow使用滚动条
在编写html代码时, 有时候不想把控件撑大,滚动条就是个不错的选择 如下代码 <div style="height:auto !important;max-height:58px;o ...
- CheckedListBoxControl 或CheckedListBox 控件中显示水平滚动条 z
public partial class Form1 : Form { public Form1() { InitializeComponent(); DisplayHScroll(); } /// ...
- WPF中获取TreeView以及ListView获取其本身滚动条的方法,可实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条)
原文:WPF中获取TreeView以及ListView获取其本身滚动条的方法,可实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条) 对于TreeView而言: TreeViewAut ...
- C#多线程操作界面控件的解决方案(转)
C#中利用委托实现多线程跨线程操作 - 张小鱼 2010-10-22 08:38 在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常.这是微软为了 ...
- 基于MVC4+EasyUI的Web开发框架形成之旅--界面控件的使用
在前面介绍了两篇关于我的基于MVC4+EasyUI技术的Web开发框架的随笔,本篇继续介绍其中界面部分的一些使用知识,包括控件的赋值.取值.清空,以及相关的使用. 我们知道,一般Web界面包括的界面控 ...
- 转--基于MVC4+EasyUI的Web开发框架形成之旅--界面控件的使用
原文 http://www.cnblogs.com/wuhuacong/p/3317223.html 基于MVC4+EasyUI的Web开发框架形成之旅--界面控件的使用 在前面介绍了两篇关于我的基 ...
- 【转】VC 多线程中控制界面控件的几种方法
原文网址:https://software.intel.com/zh-cn/blogs/2010/11/30/vc-3 为了保证界面的用户体验经常要把数据处理等放到子线程中进行,然后把结果更新到主界面 ...
- (转)基于MVC4+EasyUI的Web开发框架形成之旅--界面控件的使用
原文地址:http://www.cnblogs.com/wuhuacong/p/3317223.html 在前面介绍了两篇关于我的基于MVC4+EasyUI技术的Web开发框架的随笔,本篇继续介绍其中 ...
- 解析大型.NET ERP系统 灵活复杂的界面控件Infragistics WinForms
Infragistics 是.NET平台优秀的控件供应商,囊括了WinForms,ASP.NET,Silverlight,WPF,Windows Phone等所有关于微软.NET技术的界面控件.借助于 ...
随机推荐
- 激活Windows 10 正式版
原文 http://jingyan.baidu.com/article/27fa732684b5f646f8271ff4.html Windows 10只提供为期一年的免费升级.因此,不要无限拖延期自 ...
- TCP/IP笔记 三.运输层(3)——TCP超时重传算法
TCP 每发送一个报文段,就对这个报文段设置一次计时器.只要计时器设置的重传时间到但还没有收到确认,就要重传这一报文段 1. 平均往返时延RTT 往返时延:一个报文段发出的时间,以及收到相应的确认报文 ...
- HDU 1568 Fibonacci
题解:首先,对于小于10000的斐波那契数,我们直接计算,当大于10000时,用公式,由于只要输出前四位,所以不用考虑浮点数的问题,算出其取log的结果: tmp=(log(sq5/5)+n*log( ...
- WeasyPrint - Converts HTML + CSS to PDF - WeasyPrint converts HTML/CSS documents to PDF
WeasyPrint - Converts HTML + CSS to PDF - WeasyPrint converts HTML/CSS documents to PDF WeasyPrint c ...
- Uber 叫车时,弹出以下代码导致无法打车(An email confirmation has been sent to...),解决办法
”鄙人用了虚拟信用卡+广西的手机号码+163邮箱申请了Uber的新帐号...然后输入mastercn优惠码,上网查询只有这个优惠码,应该就能免费的使用一次用车,限额200元.但在点用车时 弹出窗口提 ...
- OC学习那些事:点语法
1.使用自定义的方法创建get/set方法 Person.h文件: #import <Foundation/Foundation.h> @interface Person : NSObje ...
- python下异常处理
1.python下异常如何处理: #encoding=utf-8 """ python遇到异常,程序直接运行 try: "判断有可能抛出异常的代码" ...
- BFG
"/"应用程序中的服务器错误. 配置错误 说明: 在处理向该请求提供服务所需的配置文件时出错.请检查下面的特定错误详细信息并适当地修改配置文件. 分析器错误消息: 提供程序集合中不 ...
- 【JAVA】使用Eclipse依赖生成jar包时,避免最外层同时生成资源文件的配置。
使用Eclipse依赖生成jar包时,如果做配置,生成的jar包文件会全部生成在外面,这并不是我们需要的,下面我们一起来修改下配置,使生成的jar包符合我们的需求吧. 1.如果不做任何配置生成的jar ...
- Codeforces 492B B. Vanya and Lanterns
Codeforces 492B B. Vanya and Lanterns 题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid= ...