(10):绘图(C)

最后更新于:2022-04-01 20:07:26

今天我们来欣赏一下用于填充图形的函数,当然我不会逐个去介绍,因为我们参考MSDN直接套参数就可以了。 SetDCBrushColor函数有必要扯一下,它的声明如下: ~~~ COLORREF SetDCBrushColor( __in HDC hdc, __in COLORREF crColor ); ~~~ 第二个参数,通过RGB宏产生COLORREF传进去就可以了,比如这样: ~~~ SetDCBrushColor(ps.hdc,RGB(211,254,41)); ~~~ 但是,如果只是调用这个函数,你会发现在绘图的时候,画刷的颜色还是没有变化,因为我们还没有将HBRUSH的默认画刷DC_BRUSH选到DC中去。所以,在调用SetDCBrushColor之前,要把默认的画刷先放到设备上下文,默认画刷可以通过GetStockObject(DC_BRUSH)获得。 ~~~ SelectObject(ps.hdc,GetStockObject(DC_BRUSH)); ~~~ 接下来我们可以尝试填充几个图形试试,如矩形、椭圆、饼图等。 ~~~ case WM_PAINT: { BeginPaint(hwnd,&ps); SelectObject(ps.hdc,GetStockObject(DC_BRUSH)); SetDCBrushColor(ps.hdc,RGB(0,0,255)); Rectangle(ps.hdc,20,18,68,50); SetDCBrushColor(ps.hdc,RGB(220,32,70)); Rectangle(ps.hdc,125,100,230,300); SetDCBrushColor(ps.hdc,RGB(30,235,12)); Ellipse(ps.hdc,270,80,390,223); SetDCBrushColor(ps.hdc,RGB(35,160,242)); Chord(ps.hdc,185,260,420,480,190,260,405,479); SetDCBrushColor(ps.hdc,RGB(211,254,41)); Pie(ps.hdc,35,320,300,600,56,470,60,360); EndPaint(hwnd,&ps); } return 0; ~~~ 每一次调用SetDCBrushColor都会改变画刷的颜色,所以,比如你希望绘制蓝色的矩形,在调用Rectangle之前就要调用SetDCBrushColor修改画刷颜色,然后再画矩形。我们可以看看上面代码的最终效果。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-14_575fd2d120fc0.PNG) 下面,我们做一个人类历史上最简单的画图程序。 我们为程序提供几种可选的线条风格,通过菜单来选择,如实线,虚线等,鼠标按下左键后开始,鼠标左键弹起就完成一条直线的绘制。为了简化,我们把相应菜单的ID设置的值与CreatePen中的线型的宏的值一致。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-14_575fd2d1548c5.PNG) 这样一来,选择了哪个菜单就直接用这个菜单的ID来创建画笔,就省去了许多代码。 在响应WM_CREATE消息时,创建菜单。 ~~~ case WM_CREATE: { // 创建菜单 HMENU menubar = CreateMenu(); HMENU menuPop = CreatePopupMenu(); AppendMenu(menuPop,MF_STRING,(UINT_PTR)PS_SOLID,L"实线"); AppendMenu(menuPop,MF_STRING,(UINT_PTR)PS_DASH,L"虚线"); AppendMenu(menuPop,MF_STRING,(UINT_PTR)PS_DOT,L"点线"); AppendMenu(menuPop,MF_STRING,(UINT_PTR)PS_DASHDOT,L"点虚线"); AppendMenu(menubar, MF_STRING | MF_POPUP, (UINT_PTR)menuPop, L"选择线型"); SetMenu(hwnd, menubar); } return 0; ~~~ 现在我们来想一下,绘制直线的大概思路。 1、鼠标左键按下,记录线条的起点。 2、鼠标左键弹起时,记录线条的终点,并画出整条线。 3、当窗口发生重绘时,前面画的所有线条被清除,要希望保留前面画的线条,就要响应WM_PAINT消息,把所有线条重新画一次。 4、由于我们会在窗口上画出多条线,程序需要定义一个结构体用来保存线条的起点、终点和所使用的线型。 5、正因为需要保存多条线的数据,故可以把每一条线的相关数据放到一个vector中。 根据上面的分析,完成程序的代码如下: ~~~ #include #include #include using namespace std; typedef struct tagData { int ptBeginX, ptBeginY;//起点 int ptEndX, ptEndY;//终点 int penStyle;//画笔的线型 } PAINTDATA; LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain( HINSTANCE hThisApp, HINSTANCE hPrevApp, LPSTR lpsCmd, int nShow) { WNDCLASS wc = {}; wc.hbrBackground = CreateSolidBrush(RGB(0,0,0)); wc.hInstance = hThisApp; wc.lpfnWndProc = WindowProc; wc.lpszClassName = L"My"; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); HWND hwnd = CreateWindow( L"My", L"应用程序", WS_OVERLAPPEDWINDOW, 50, 20, 600, 480, NULL, NULL, hThisApp, NULL); if(hwnd == NULL) return -1; ShowWindow(hwnd, nShow); UpdateWindow(hwnd); MSG msg; while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static vector datas; static int penStyle = PS_SOLID; static PAINTDATA *pCurrentData = NULL;//指向当前PAINTDATA的指针 switch(msg) { case WM_CREATE: { // 创建菜单 HMENU menubar = CreateMenu(); HMENU menuPop = CreatePopupMenu(); AppendMenu(menuPop,MF_STRING,(UINT_PTR)PS_SOLID,L"实线"); AppendMenu(menuPop,MF_STRING,(UINT_PTR)PS_DASH,L"虚线"); AppendMenu(menuPop,MF_STRING,(UINT_PTR)PS_DOT,L"点线"); AppendMenu(menuPop,MF_STRING,(UINT_PTR)PS_DASHDOT,L"点虚线"); AppendMenu(menubar, MF_STRING | MF_POPUP, (UINT_PTR)menuPop, L"选择线型"); SetMenu(hwnd, menubar); } return 0; case WM_COMMAND: { //为选中的菜单加上单选标记 HMENU mnbar = GetMenu(hwnd); HMENU hmnPop = GetSubMenu(mnbar, 0); CheckMenuRadioItem(hmnPop, PS_SOLID, PS_DASHDOT, LOWORD(wParam), MF_BYCOMMAND); //记录用户选择的线型 penStyle = (int)LOWORD(wParam); } return 0; case WM_LBUTTONDOWN: { pCurrentData = new PAINTDATA; //获取起点 pCurrentData ->penStyle = penStyle; pCurrentData->ptBeginX = GET_X_LPARAM(lParam); pCurrentData->ptBeginY = GET_Y_LPARAM(lParam); } return 0; case WM_LBUTTONUP: { if(pCurrentData != NULL) { //获取终点 pCurrentData->ptEndX = GET_X_LPARAM(lParam); pCurrentData->ptEndY = GET_Y_LPARAM(lParam); //画出线条 HDC hdc = GetDC(hwnd); HPEN pen = CreatePen(pCurrentData->penStyle,1,RGB(0,255,0)); HPEN oldpen = (HPEN)SelectObject(hdc,pen); MoveToEx(hdc,pCurrentData->ptBeginX,pCurrentData->ptBeginY,NULL); LineTo(hdc,pCurrentData->ptEndX,pCurrentData->ptEndY); SelectObject(hdc,oldpen); DeleteObject(pen); ReleaseDC(hwnd,hdc); //把当前数据添加到vector中 datas.push_back(*pCurrentData); } } return 0; case WM_PAINT: { PAINTSTRUCT ps; BeginPaint(hwnd,&ps); //将所有线条重新画一遍 vector::const_iterator item; for(item = datas.begin(); item != datas.end(); item++) { HPEN pen = CreatePen(item->penStyle, 1, RGB(0,255,0)); SelectObject(ps.hdc, pen); MoveToEx(ps.hdc, item->ptBeginX, item->ptBeginY, NULL); LineTo(ps.hdc, item->ptEndX, item->ptEndY); DeleteObject(pen); } EndPaint(hwnd,&ps); } return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd,msg,wParam,lParam); } return 0; } ~~~ 结构体PAINTDATA用来保存每一条线的起点坐标、终点坐标、线型。为了避免在跳出WindowProc后所有数据被回收,可以使用static关键字来声明变量,这样这些变量的生命周期就与整个应用程序相同了。 运行程序后,在菜单中选择一种线型,然后在窗口上画线,效果如图所示。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-14_575fd2d168df8.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-14_575fd2d17cd18.PNG)
';