滚动条(1)
说明窗口中标准配置的滚动条的操作方法。
1. 滚动条的生成
生成窗口时,在样式中添加WS_VSCROLL|WS_HSCROLL的话,如下图所示,画面的底部和右端会显示滚动条。
HWND hwnd = CreateWindowEx(0, "Demo", "GridView",
WS_OVERLAPPEDWINDOW|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL,
100, 100, 250, 200, NULL, NULL, hInst, NULL);
因为设置在滚动条上的信息与部分窗口大小有关,所以在WM_SIZE消息处理中,使用SetScrollInfo函数进行设置。 垂直滚动条的情况,例如SetScrollInfo(hwnd, SB_VERT, &si, TRUE);。 对于水平滚动条,第二个参数是SB_HORZ。 si表示SCROLLINFO结构体变量。 这个定义如下所示。
typedef struct _SCROLLINFO {
UINT cbSize; // sizeof(SCROLLINFO)
UINT fMask;
int nMin;
int nMax;
UINT nPage;
int nPos;
int nTrackPos;
} SCROLLINFO;
在fMask中设定的值是以下组合。 一般用SIF_ALL就可以了。
SIF_RANGE = 1; // 获取和设置滚动范围。 nMin和nMax有效。
SIF_PAGE = 2; // 获取并设置拇指的大小。 nPage有效。
SIF_POS = 4; // 获取和设置拇指的位置。 nPos有效。
SIF_DISABLENOSCROLL = 8; // 设置禁止使用。
SIF_TRACKPOS = 16; // 获取拖曳中的拇指位置。 nTrackPos有效。
SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS);
设定例子如下所示。 这是对应上面的画面,显示NROWS行的表格。 行的编号是0, 1, 2, ..., NROWS-1,所以假设siv.nMin = 0, siv.nMax = NROWS-1。 siv.nPage表示屏幕上可以显示几行。 把画面的高度除以行的高度就可以了。 因为先头行是以列名固定的,所以数据行是这个值减去1的值。
void onSize(HWND hwnd, WPARAM wp, LPARAM lp) {
GetClientRect(hwnd, &rcTable);
nPage = (rcTable.bottom - rcTable.top)/RowHeight - 1;
SCROLLINFO siv = { sizeof(SCROLLINFO), SIF_ALL, 0, NROWS-1, nPage, nTopRow };
SetScrollInfo(hwnd, SB_VERT, &siv, TRUE);
}
在这个例子中,NROWS = 12。 因为画面上显示的是5行,所以滚动条的旋钮(拇指)的宽度是整体的5/12。
当旋钮在最上面时,作为拇指位置得到的值显然是siv.nMin,即0。 但是,如图所示,下旋钮的值不是11(siv.nMax),而是7。 注意不是表示最下行,而是指向最上行。 一般来说这个更感谢。
SetScrollInfo函数的最后一个参数设置为TRUE的话,会重新绘制滚动条,如果是FALSE的话就不会重新绘制。
因为柱子宽度一般不是恒定的,所以水平滚动和垂直滚动完全不一样。 nPage的值不能完全确定。 即使是垂直滚动,如果画面的高度不是行的整数倍的话,也会出现零头,但是水平滚动这个零头的比例比较大,所以很显眼。
经过试错的结果,发现水平滚动时,nMax不是列数,而是相当于全列宽度的像素数,nPage可以显示在画面上的像素数就可以了。
具体的例子如gridview05.c所示。
// gridview05.c 2013-03-29 M.Hatada
#include <stdlib.h>
#include <windows.h>
#define GRAY 0xf0f0f0
#define FontHeight 20
#define RowNumberWidth 50 // 最左边的行号栏的宽度
#define RowHeight 25 // 行的高度
#define NROWS 12
#define NCOLS 4
char *colName[NCOLS] = { "No.", "名前", "続柄", "年齢" };
char *cell[NROWS][NCOLS] = {
{"01","太郎","長男", "15"}, {"02","次郎","次男","11"}, {"03","三郎","三男","9"},
{"04","太郎","長男", "15"}, {"05","次郎","次男","11"}, {"06","三郎","三男","9"},
{"07","太郎","長男", "15"}, {"08","次郎","次男","11"}, {"09","三郎","三男","9"},
{"10","太郎","長男", "15"}, {"11","次郎","次男","11"}, {"12","三郎","三男","9"},
};
int colWidth[NCOLS], colLeft[NCOLS+1];
RECT rcTable;
HFONT hfBold, hfData;
HBRUSH hbBlue, hbGray, hbYellow;
HPEN hpWhite, hpBold, hpGrid;
HBITMAP hBitmap;
HDC hdcMem;
int nTopRow=2, nTopCol=0;
int nCurRow=3, nCurCol=2;
int nPage; // 画面上的显示行数
int nHPage; // 画面上的显示宽度(单位:像素)
void drawLine(HDC hdc, int xFrom, int yFrom, int xTo, int yTo) {
MoveToEx(hdc, xFrom, yFrom, NULL);
LineTo(hdc, xTo, yTo);
}
RECT *getRect(int j, int i, RECT *prc) {
SetRect(prc, RowNumberWidth+colLeft[i]-colLeft[nTopCol], (j+1-nTopRow)*RowHeight,
RowNumberWidth+colLeft[i+1]-colLeft[nTopCol], (j+2-nTopRow)*RowHeight);
return prc;
}
void paint(HDC hdc) {
int i, j;
char buf[32];
RECT rc;
FillRect(hdc, &rcTable, (HBRUSH)GetStockObject(WHITE_BRUSH)); // 屏幕清除
// 列名行及纵划线
SelectObject(hdc, hfBold);
SelectObject(hdc, hpGrid);
SetBkColor(hdc, GRAY);
for (i = nTopCol; i < NCOLS; i++) {
SetRect(&rc, RowNumberWidth+colLeft[i]+1-colLeft[nTopCol], 0,
RowNumberWidth+colLeft[i+1]-colLeft[nTopCol], RowHeight);
FillRect(hdc, &rc, hbGray);
DrawText(hdc, colName[i], -1, &rc, DT_VCENTER|DT_SINGLELINE|DT_CENTER);
drawLine(hdc, rc.right, RowHeight, rc.right, RowHeight*(NROWS+1-nTopRow));
}
SetRect(&rc, RowNumberWidth+colLeft[i]+1-colLeft[nTopCol], 0, rcTable.right, RowHeight);
FillRect(hdc, &rc, hbGray); // 右端的空列
// 行号和横划线
for (j = nTopRow; j <= NROWS; j++) {
SetRect(&rc, 0, (j-nTopRow)*RowHeight, RowNumberWidth-1, (j+1-nTopRow)*RowHeight-1);
FillRect(hdc, &rc, hbGray);
drawLine(hdc, RowNumberWidth, (j+1-nTopRow)*RowHeight-1,
rcTable.right, (j+1-nTopRow)*RowHeight-1);
if (j == nTopRow) continue;
DrawText(hdc, itoa(j,buf,10), -1, &rc, DT_VCENTER|DT_SINGLELINE|DT_CENTER);
}
// 数据单元
SelectObject(hdc, hfData);
for (j = nTopRow; j < NROWS; j++) {
SetBkColor(hdc, 0xffffff);
for (i = nTopCol; i < NCOLS; i++) {
SetRect(&rc, RowNumberWidth+colLeft[i]+6-colLeft[nTopCol],
(j+1-nTopRow)*RowHeight,
RowNumberWidth+colLeft[i+1]-6-colLeft[nTopCol],
(j+2-nTopRow)*RowHeight-1);
DrawText(hdc, cell[j][i], -1, &rc, DT_VCENTER|DT_SINGLELINE|DT_LEFT);
}
}
getRect(nCurRow, nCurCol, &rc);
if (nCurRow >= nTopRow && nCurCol >= nTopCol) {
SelectObject(hdc, hpBold);
SelectObject(hdc, (HBRUSH)GetStockObject(NULL_BRUSH));
Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom); // 用粗框包围选择单元格
}
SelectObject(hdc, hbYellow);
if (nCurRow >= nTopRow)
PatBlt(hdc, 0, rc.top, RowNumberWidth, RowHeight, PATINVERT);
if (nCurCol >= nTopCol)
PatBlt(hdc, rc.left, 0, colWidth[nCurCol], RowHeight, PATINVERT);
}
void update(HWND hWnd) {
HDC hdc = GetDC(hWnd);
paint(hdcMem);
BitBlt(hdc, 0, 0, rcTable.right, rcTable.bottom, hdcMem, 0, 0, SRCCOPY);
ReleaseDC(hWnd, hdc);
}
void onCreate(HWND hWnd) {
HDC hdc;
int iCol, iRow, style;
RECT rc;
LOGFONT lf1 = { FontHeight, 0,0,0,0,0,0,0, SHIFTJIS_CHARSET, 0,0,0,0, "宋体" };
LOGFONT lf2 = { 18, 0,0,0, FW_BOLD, 0,0,0, SHIFTJIS_CHARSET, 0,0,0,0, "宋体" };
hfData = CreateFontIndirect(&lf1); // 制作逻辑字体
hfBold = CreateFontIndirect(&lf2);
hpWhite = CreatePen(PS_SOLID, 2, 0xffffff); // 删除框架用的笔
hpBold = CreatePen(PS_SOLID, 2, 0); // 框架绘图用的笔
hpGrid = CreatePen(PS_SOLID, 1, 0xd0d0d0); // 划线用笔
hbBlue = CreateSolidBrush(0xff0000);
hbGray = CreateSolidBrush(GRAY);
hbYellow = CreateSolidBrush(0x0ff0f0);