Chapter 4 An Exercise in Text Output

以下全是本人自学Windows编程第5版的见解,不一定全对

WM_PAINT

当你用一个窗口覆盖另一个窗口,再移开那个窗口,原来被覆盖的窗口就会被重绘,被重绘意味着需要接受到WM_PAINT消息。

那么产生WM_PAINT消息的方式有:

(1)一个先前隐藏的区域被重新出现(2)一个窗口被resize(3)调用ScrollWindow滚动窗口 (4)调用InvalidateRect or InvalidateRgn无效化区域来产生WM_PAINT

这里注意下:光标问题,光标(那个箭头吧)盖住了Client区域的一个文字,再移开,那个文字再出现这个过程呢是没WM_PAINT消息的,Windows总是保存这样的被覆盖的区域被自动恢复它:Windows always saves the area of the display it overwrites and then restores it.

 

产生WM_PAINT消息的本质:无效区域的存在

上面提到的产生的各种方式其实都会产生无效区域,例如被覆盖的区域重新出现,那么被覆盖的区域就是一个无效区了,InvalidateRect函数就是产生无效矩形了。

每个窗口都维护一个paint information struct来保存重绘客户区所需要的信息,这个paint information struct就是那个PAINTSTRUCT结构体了,在SYSMETS3程序里面就用到了这个结构体,这个结构体保存了窗口目前的无效矩形的坐标,要是消息队列里有一个WM_PAINT消息还没来得及处理就又改变了无效矩形,那么Windows会计算出一个新矩形来包含新旧的无效区域,所以不会产生新的WM_PAINT消息,只是把PAINTSTRUCT结构体的无效矩形的坐标改一改。

InvalidateRect函数会看下消息队列有没WM_PAINT,有的话就修改一下PAINTSTRUCT结构体的无效矩形坐标,没就插入新的WM_PAINT消息。在BeginPaint函数会把无效矩形有效化,之后再运行后面的程序代码(我个人觉得这里把无效矩形有效化了重绘的时候仍然没把内容画到无效矩形以外的原因就是存在Clipping 区域的原因了)。

 

GDI函数与DC

To paint the client area of your window, you use Windows' Graphics Device Interface (GDI) functions. Windows provides several GDI functions for writing text strings to the client area of the window. 由此可见paint somethings in the client area,we need GDI functions.

The device context (also called simply the "DC") is really just a data structure maintained internally by GDI. DC又叫设备描述表,里面有很多piant时用到的资料,例如文字的颜色,字体等等的参数。

获取DC的方式:

(1)hdc=BeginPaint(hwnd,&ps); EndPaint(hwnd,&ps);      (2)hdc=GetDC(hwnd);    ReleaseDC(hwnd,hdc);  

注意:BeginPaint会用窗口类注册时提供的画刷把无效区域刷一遍,再把无效区域有效化。而GetDC单纯是获取DC,不会把区域有效化的,也因此它能在没有WM_PAINT消息的情况下就画东西在Client上


SCROLL BAR:

添加scroll bar:

在CreateWindow函数定义windows stycle的参数加上WS_VSCROLL(加入竖直滚动条)WS_HSCROLL(加入水平滚动条);

scroll bar的范围:

默认情况是0到100,同时修改也很容易:SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;
参数解释:The iBar argument is either SB_VERT or SB_HORZ, iMin and iMax are the new minimum and maximum positions of the range,SetScrollRange函数能改变scroll bar的范围,最后一个参数为true就会重绘scroll bar(更新thumb的位置吧),如果这个函数后面还有影响scroll bar位置函数的话最后一个参数就应该为false,避免重复的重绘。

thumb的位置:

 修改thumb的位置,SetScrollPos (hwnd, iBar, iPos, bRedraw) ;
The iPos argument is the new position and must be within the range of iMin and iMax . Windows provides similar functions (GetScrollRange and GetScrollPos ) to obtain the current range and position of a scroll bar.

Scroll Bar Message:

当你点击或者拖动thumb的时候,Windows就会发送 WM_VSCROLL    WM_HSCROLL到消息队列中。
wparam参数中low word指示了具体对滚动条做了什么,例如有SB_LINEUP     SB_LINEDOWN      SB_PAGEUP      SB_PAGEDOWN      SB_THUMBPOSTION     SB_THUMBTACK 等等。
特别的:当你按住左键拖动thumb时会产生WM_VSCROLL(以竖直滚动条为例),该信息的wparam参数的low word==SB_THUMBTRACK,high word指示了当前thumb的position,当你松开左键的时候应该又有一条WM_VSCROLL信息,它的wparam参数low word==SB_THUMBPOSITION,high word就是最终thumb的位置,除了这2种情况,其他wparam参数的high word都可以无视。

SCROLLINFO结构体:

typedef struct tagSCROLLINFO
{
UINT cbSize ; // set to sizeof (SCROLLINFO)
UINT fMask ; // values to set or get
int nMin ; // minimum range value
int nMax ; // maximum range value
UINT nPage ; // page size
int nPos ; // current position
int nTrackPos ; // current tracking position
}
SCROLLINFO, * PSCROLLINFO ;
设置nPage项,Windows设置Scroll bar最大值不是用nMax,而是用nMax-nPage+1.
ScrollWIndow函数第二个参数是水平卷动client多少像素,第三个参数是竖直卷动client多少像素,第三第四个参数设为NULL就意味着整个client会被scrolled,Windows会自动地把这些区域无效化,因此不再需要InvalidateRect函数。

最后总结一下scroll bar其实就是3个步骤:第一就是建立,需要设置范围和初始位置(可在WM_CREATE或者WM_SIZE等消息中进行)
第二就是针对WM_VSCROLL或者WM_HSCROLL这些消息适时更新滚动条消息
第三就是针对更新了滚动条就意味着需要重绘窗口了

综合:

#include<Windows.h>
#include<tchar.h>
#include"SYSMET.h"

LRESULT CALLBACK WinProc(HWND hwnd,UINT uimsg,WPARAM wparam,LPARAM lparam)
{
	static int cxChar,cxCaps,cyChar,cxClient,cyClient,iMaxWidth;
	TEXTMETRIC tm;
	PAINTSTRUCT ps;
	SCROLLINFO si;
	HDC hdc;
	if(uimsg==WM_CREATE)//在WM_CREATE消息中保存字体信息
	{
		hdc=GetDC(hwnd);
		GetTextMetrics(hdc,&tm);
		cxChar=tm.tmAveCharWidth;
		cxCaps=(tm.tmPitchAndFamily&1?3:2)*cxChar/2;
		cyChar=tm.tmHeight+tm.tmExternalLeading;
		ReleaseDC(hwnd,hdc);

		iMaxWidth=22*cxCaps+40*cxChar;
		return 0;
	}
	else if(uimsg==WM_SIZE)//在WM_SIZE消息中初始化scroll bar的范围与nPage的大小(根据WM_SIZE消息的lparam参数可以知道Client的大小)
	{
		cxClient=LOWORD(lparam);
		cyClient=HIWORD(lparam);
		//Set Vert Scroll bar
		si.cbSize=sizeof(si);
		si.fMask=SIF_RANGE | SIF_PAGE;
		si.nMin=0;
		si.nMax=NUMLINES-1;
		si.nPage=cyClient/cyChar;
		SetScrollInfo(hwnd,SB_VERT,&si,true);
		//Set Horz Scroll bar
		si.nMin=0;
		si.nMax=iMaxWidth/cxChar+2;//范围是0到一共有多少个小写字符,+2是为了留一点空白
		si.nPage=cxClient/cxChar;
		SetScrollInfo(hwnd,SB_HORZ,&si,true);
		
		return 0;
	}
	else if(uimsg==WM_VSCROLL)
	{
		si.cbSize=sizeof(si);
		si.fMask=SIF_ALL;
		GetScrollInfo(hwnd,SB_VERT,&si);
		int iPrevPos=si.nPos;
		switch(LOWORD(wparam))
		{
		case SB_LINEUP: si.nPos-=1;break;
		case SB_LINEDOWN: si.nPos+=1;break;
		case SB_PAGEUP: si.nPos-=si.nPage;break;
		case SB_PAGEDOWN: si.nPos+=si.nPage;break;
		case SB_THUMBTRACK: si.nPos=si.nTrackPos;break;
		default: break;
		}
		si.fMask=SIF_POS;
		SetScrollInfo(hwnd,SB_VERT,&si,true);
		GetScrollInfo(hwnd,SB_VERT,&si);
		if(si.nPos!=iPrevPos)
		{
			ScrollWindow(hwnd,0,(iPrevPos-si.nPos)*cyChar,NULL,NULL);//ScrollWindow函数所产生的无效区域一定要理解好才能,例如向上移一行,那么无效区域就是需要更新的Client最后的一行了,之前的行数直接把原有的上移一行就行。
			UpdateWindow(hwnd);
		}
		return 0;
	}
	else if(uimsg==WM_HSCROLL)
	{
		si.cbSize=sizeof(si);
		si.fMask=SIF_ALL;
		GetScrollInfo(hwnd,SB_HORZ,&si);
		int iPrevPos=si.nPos;
		switch(LOWORD(wparam))
		{
		case SB_LINELEFT: si.nPos-=1;break;
		case SB_LINERIGHT: si.nPos+=1;break;
		case SB_PAGELEFT: si.nPos-=si.nPage;break;
		case SB_PAGERIGHT: si.nPos+=si.nPage;break;
		case SB_THUMBTRACK: si.nPos=si.nTrackPos;break;
		default:break;
		}
		si.fMask=SIF_POS;
		SetScrollInfo(hwnd,SB_HORZ,&si,true);
		GetScrollInfo(hwnd,SB_HORZ,&si);
		if(si.nPos!=iPrevPos)
		{
			ScrollWindow(hwnd,(iPrevPos-si.nPos)*cxChar,0,NULL,NULL);
			UpdateWindow(hwnd);
		}
		return 0;
	}
	else if(uimsg==WM_PAINT)
	{
		TCHAR szbuf[1024];
		hdc=BeginPaint(hwnd,&ps);
		int iVPos,iHPos;
		si.cbSize=sizeof(si);
		si.fMask=SIF_POS;
		GetScrollInfo(hwnd,SB_VERT,&si);
		iVPos=si.nPos;
		GetScrollInfo(hwnd,SB_HORZ,&si);
		iHPos=si.nPos;
		//这里是把整个Client重绘的方式
		/*for(int i=iVPos;i<NUMLINES;i++)
		{
			int x=(0-iHPos)*cxChar;
			TextOut(hdc,x,(i-iVPos)*cyChar,Sysmets[i].szLabel,wcslen(Sysmets[i].szLabel));
			TextOut(hdc,x+22*cxCaps,(i-iVPos)*cyChar,Sysmets[i].szDesc,wcslen(Sysmets[i].szDesc));
			SetTextAlign(hdc,TA_RIGHT | TA_TOP);
			TextOut(hdc,x+iMaxWidth,(i-iVPos)*cyChar,szbuf,wsprintf(szbuf,TEXT("%5d"),
				GetSystemMetrics(Sysmets[i].index)));
			SetTextAlign(hdc,TA_TOP | TA_LEFT);
		}*/
		//这里是只把无效区重绘的方式
		int iPaintBeg=max(0,iVPos+ps.rcPaint.top/cyChar);//计算出无效区top坐标下一行是打印第几个
		int iPaintEnd=min(NUMLINES-1,iVPos+ps.rcPaint.bottom/cyChar););//计算出无效区bottom坐标下一行是打印第几个
		for(int i=iPaintBeg;i<=iPaintEnd;i++)//打印多一行是很重要的,因为ps.rcPaint.top/cyChar求出来的可能是取整之后的结果,会留有一点空白在最后那里,之后把新的一行打印Client的时候,原本留有的那丁点空白却不在Clipping区域,没法画上去,新的无效区又没新的内容画上去,导致空白会越来越大的
		{
			int y=(i-iVPos)*cyChar;
			int x=(0-iHPos)*cxChar;
			TextOut(hdc,x,y,Sysmets[i].szLabel,wcslen(Sysmets[i].szLabel));
			TextOut(hdc,x+22*cxCaps,y,Sysmets[i].szDesc,wcslen(Sysmets[i].szDesc));
			SetTextAlign(hdc,TA_RIGHT | TA_TOP);
			TextOut(hdc,x+iMaxWidth,y,szbuf,wsprintf(szbuf,TEXT("%5d"),
				GetSystemMetrics(Sysmets[i].index)));
			SetTextAlign(hdc,TA_TOP | TA_LEFT);
		}
	}
	else if(uimsg==WM_DESTROY)
	{
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd,uimsg,wparam,lparam);
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR szCmdLine,int iCmdShow)
{//还是设计,注册,创建与更新窗口,还有就是消息循环
	TCHAR szAPPNAME[]=TEXT("SYSMETS3");
	TCHAR szbuf[1024];
	MSG msg;
	WNDCLASS wndcls;
	wndcls.cbClsExtra=0;
	wndcls.cbWndExtra=0;
	wndcls.hbrBackground=CreateSolidBrush(RGB(255,255,255));
	wndcls.hCursor=LoadCursor(NULL,IDC_ARROW);
	wndcls.hIcon=LoadIcon(NULL,IDI_APPLICATION);
	wndcls.hInstance=hInstance;
	wndcls.lpfnWndProc=WinProc;
	wndcls.lpszClassName=szAPPNAME;
	wndcls.lpszMenuName=NULL;
	wndcls.style=CS_HREDRAW | CS_VREDRAW;

	RegisterClass(&wndcls);//创建窗口处指定要求增加水平与竖直的scroll bar
	HWND hwnd=CreateWindow(szAPPNAME,TEXT("title"),WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
		CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
	ShowWindow(hwnd,iCmdShow);
	UpdateWindow(hwnd);

	while(1)
	{
		bool bGetmsg=GetMessage(&msg,NULL,0,0);
		if(bGetmsg)
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
			break;
	}
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值