签到

05月06日
尚未签到

共有回帖数 0

    幻梦如戏

    等级:
    大家看了一之后,不知道有没有做出猜数字的小游戏啊?也没人说一句,搞得我好有挫败感
    其实如果完全掌握了,大家完全可以写出一个计算器的小程序,就是类似windows开始-程序-附件里的那种。大家也完全可以发挥自己的想象,写出更多的东西。

    好了,言归正传。今天我们要学习的是绘图,大家不知道有没有看过吧里精品区的
    用C语言写的一个让桌面下雪的小程序(非屏幕保护),地址是http://tieba.baidu.com/f?kz=263664044
    学完今天的内容之后,大家就也能写出这样有趣的小程序了。当然,我们之后会用另外的技术,写一个更漂亮的用位图作为雪花的小程序,而不是像上面仅仅用一个白色的像素点作为雪花。
    大家完成之后,就可以把这个当做圣诞节的小礼物送给别人了
    (实际上大家也应该看过类似的程序吧,我去年这时候就看到过,不过那时候才开始学,不会写)

    这估计是这个学期这个系列的最后一篇帖子了。最近在被数学分析,财务会计,中级宏观经济学搞得焦头烂额的(从前上课不认真啊!),今天是没在通宵自习室占着座,心中琢磨下,还是回来发这个帖子吧!

    我们就先拿海边的小男孩前辈的用C语言写的一个让桌面下雪的小程序说起吧!
    大家还记的上次程序的入口吗?是int WINAPI WinMain 基本上所有的windows程序都是如此,这个桌面下雪的入口被放在了下面,我们把它提取出来,别看这段程序长,实际上其的效果和DialogBox(xxxxxxxxxxx)是差不多的。不同的地方在于它创建的是一个普通的窗口,而我们创建的是一个对话框。

    这个创建窗口的程序是通用的,大家把它复制过来就可以了,想用窗口的时候直接copy。

    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
    {
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
    MessageBox(NULL, "窗体注册失败!", "错误!",
    MB_ICONEXCLAMATION | MB_OK);
    return 0;
    }

    hwnd = CreateWindowEx(
    WS_EX_CLIENTEDGE,
    g_szClassName,
    "屏幕飘雪",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
    NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
    MessageBox(NULL, "窗体创建失败!", "错误!",
    MB_ICONEXCLAMATION | MB_OK);
    return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while(GetMessage(&Msg, NULL, 0, 0)  0)
    {
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
    }
    return Msg.wParam;
    }

     

    不过依然有几点需要注意,前面一大段是填写WNDCLASS wc这个结构体的结构的。WNDCLASS就是窗口的爸爸,一个WNDCLASS可以有几个儿子,但一个儿子只能有一个爸爸。

    需要注意的是wc.lpfnWndProc = WndProc和wc.lpszClassName = g_szClassName这两个。

    WndProc就相当于对话框中BOOL CALLBACK DlgMain中的DlgMain,当然他们的函数名是可以变的,不过填写结构的时候就记得不要写错了。

    而g_zaClassName就是爸爸的名字。是一个指向字符串的指针。接下来创建窗口的时候就要用到这个来表明你建立的窗口究竟是谁的种。(我爸是李刚!)

    hwnd = CreateWindowEx(
    WS_EX_CLIENTEDGE,
    g_szClassName,
    "屏幕飘雪",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
    NULL, NULL, hInstance, NULL);这就是创建窗口了。还记得吧,hwnd是窗口句柄,标明了这个窗口。240, 120分别是窗口的长和宽。是以像素作为单位的。

    还有其他的一些字段,大家可以去MSDN查RegisterClassEx和CreateWindowEx,看看他们到底是什么意思。
    然后我们看处理消息的函数,不同于对话框的BOOL CALLBACK DlgMain,这里是LRESULT CALLBACK WndProc,你就当他们是一样就行了。

    窗口在创建的时候会受到一个message为WM_CREATE的消息。我们可以在收到这个消息之后完成一些初始化的工作。那些初始化随机数种子之类的,大家都懂,我不多说。首先我要说的是在消息里程序创建了一个计时器。SetTimer(hwnd, ID_TIMER, 10, NULL)

    这是什么东西呢?在创建了计时器以后,windows会每隔一段时间给窗口发送一个WM_TIMER的消息。这个一段时间就是第三个参数,此例中是10,以毫秒做单位。
    那函数的第一个参数自然是窗口咯。(写到这里我突然想到可不可以给别的窗口设置计时器?有时间试试)
    第二个ID_TIMER是一个数字,在开头的时候#DEFINE ID_TIMER 1(或者其他)
    这标志了这个定时器,因为一个程序很可能有多个定时器的。
    实际上我喜欢偷懒,一般直接在函数中用1就是了。
    第四个参数非常重要,不过现在用不着,大家有兴趣去查MSDN。
    然后是重中之重 hDC1 = GetDC(0),hDC是一个设备内容(Device Context)句柄。
    设备内容句柄就是标志一个设备内容的。

    那什么是设备内容呢?比方说你现在打开的浏览器,浏览器给你显示了图案,图案就是在设备内容里面的。你看到浏览器的标题栏,标题栏里面的字的字体啊,颜色啊,都在设备内容里面。你们把设备内容想象成一个画板就好了。设备内容是和一个窗口联系在一起的。

    GetDC(HWND hwnd)这个函数就是得到某个窗口的设备内容句柄。当hwnd是NULL的时候,我们得到的就是屏幕的设备内容句柄。

    我们得到一个窗口的设备内容句柄后,就可以对这个设备内容进行操作,就相当于在画板上画画了。
    剩下的就很简单了,大家看到DrawP()这个函数,里面用到SetPixel( HDC hdc, int X,int Y, COLORREF crColor );GetPixel(HDC hdc,int X,int Y)可能是大家看不明白的地方。

    SetPixel就是画一个像素点,hdc标明在哪个画板上画,而X,Y则标明了画在画板上的位置,实际上就是距离窗口左上角的位置,以像素作单位。crColor就是点的颜色了。这是一个RGB色彩,RGB就是red green blue。我们可以用RGB(xxx,xxx,xxx)来得到这个值。这个xxx就是0到255的一个值。
    为了得到更直观的感觉,大家可以用画图程序的颜色-编辑颜色-规定自定义颜色来看。

    那GetPixel就是得到这个颜色的值啦。
    然后大家就可以看明白这个程序了。在每个10ms收到的WM_TIMER消息的时候,程序擦除上次的雪花点,再画一次移动了位置的雪花点,造成移动的效果。当然,在程序结束的时候,大家别忘记把画板收好,用ReleaseDC。关掉计时器,用KillTimer。还说下,大家可以把WM_CLOSE时做的事放到WM_DESTROY中去做。WM_DESTROY就是窗口玩完的时候收到的消息,但我们还需要用PostQuitMessage去真正的终结它。
    大家注意了,在对话框中,我们用return false就可以把我们不想处理的消息给windows处理,而这里 我们要用DefWindowProc (hwnd, message, wParam, lParam)把 switch语句剩下来的情况抛给windows处理。
    另外说句,还有一些绘图的函数,比方说LineTo,Rectangle等等,大家都可以去MSDN查看的。非常容易学会。

    下面我们要做的就是做一个加强版了,做一个下BMP位图的雪花的程序。先放上我做雪花的位图,这个图不是很好看,是因为我不会PS,就直接到网上找了一个合适但不好看的。会PS的人可以依照我下面说的方法,做出更漂亮的雪花。


    http://u.115.com/file/f7b07247e3
    我们先得把位图加载到我们的工程中去。和加载DialogBox一样,这次我们选择BITMAP 不过我们不新建(NEW),我们选择导入(IMPORT),然后在对话框中找到你要的位图,注意,一定得是位图,即以.bmp结尾的图。因为windows只支持位图。
    然后我们看资源里,就多了个IDC_BITMAP1,你可以修改它的名字。很有可能你双击位图打不开,这是很正常了,因为现在大部分位图都是24位位图,vc6是看不了的,所以不要以为是出错了。

    先把代码复制上,很短。
    这段是定义全局变量和写一个窗口。大家注意ShowWindow(hwnd,SW_SHOWMINIMIZED),这说明当创建时是最小化的窗口。
    #include windows.h
    #include stdlib.h
    #include time.h
    #include "resource.h"



    LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM) ;


    HINSTANCE hInst;
    HDC hScrnDC;
    HDC hCoverScrnDC;
    HDC hMaskDC;
    HDC hSnowDC;
    HBITMAP hCoverScrnBmp;
    HBITMAP hMaskBmp;
    HBITMAP hSnowBmp;
    int Position[300][2] = {0};
    int Snow[300] = {0};
    int xScrn;
    int yScrn;
    int Vx;
    int Vy;
    int pVx;
    int pVy;
    int nCount = 0;


    int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
    {
       hInst = hInstance;
       static TCHAR szAppName[]=TEXT("Snow");  
       HWND hwnd;  
       MSG msg;
       WNDCLASS wndclass ;  
       wndclass.style           =CS_HREDRAW|CS_VREDRAW;    
       wndclass.lpfnWndProc     =WndProc;      
       wndclass.cbClsExtra      =0;      
       wndclass.cbWndExtra      =0;        
       wndclass.hInstance       =hInstance;      
       wndclass.hIcon           =LoadIcon(hInst,MAKEINTRESOURCE(IDI_ICON1));      
       wndclass.hCursor         =LoadCursor(NULL,IDC_IBEAM);        
       wndclass.hbrBackground   =(HBRUSH)GetStockObject(WHITE_BRUSH);  
       wndclass.lpszMenuName    =NULL;        
       wndclass.lpszClassName   =szAppName;    
       if (!RegisterClass(&wndclass))        
       {
           MessageBox(NULL,TEXT("This program requires Windows NT!"),szAppName,MB_ICONERROR) ;        
           return 0 ;        
       }      
       hwnd=CreateWindow(szAppName,
           TEXT("Snow"),  
           WS_OVERLAPPEDWINDOW,
           CW_USEDEFAULT,
           CW_USEDEFAULT,
           CW_USEDEFAULT,
           CW_USEDEFAULT,
           NULL,                
           NULL,            
           hInstance,  
           NULL);
       ShowWindow(hwnd,SW_SHOWMINIMIZED);        
       UpdateWindow(hwnd);
       while (GetMessage(&msg,NULL,0,0))      
       {
           TranslateMessage(&msg);        
           DispatchMessage(&msg);        
       }        
       return msg.wParam;        
    }
    这里就是处理消息的了。
    LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)        
    {                
       switch (message)        
       {
       case WM_CREATE:
           {
               srand((unsigned)time(NULL));

               Vx = rand()%4-2;
               Vy = rand()%2+2;

               SetTimer(hwnd,1,10,NULL);

               hScrnDC = CreateDC("DISPLAY",NULL,NULL,NULL);

               xScrn = GetDeviceCaps(hScrnDC,HORZRES);
               yScrn = GetDeviceCaps(hScrnDC,VERTRES);

               for(int i = 0;i  300;i++)
               {
                   Position[0] = rand()%xScrn;
                   Position[1] = rand()%yScrn;
               }

               hCoverScrnDC = CreateCompatibleDC(hScrnDC);
               hCoverScrnBmp = CreateCompatibleBitmap(hScrnDC,xScrn,yScrn);
               SelectObject(hCoverScrnDC,hCoverScrnBmp);
               BitBlt(hCoverScrnDC,0,0,xScrn,yScrn,hScrnDC,0,0,SRCCOPY);

               hMaskDC = CreateCompatibleDC(hScrnDC);
               hSnowDC = CreateCompatibleDC(hScrnDC);

               hMaskBmp = CreateCompatibleBitmap(hScrnDC,300,108);
               hSnowBmp = CreateCompatibleBitmap(hScrnDC,300,108);

               hMaskBmp = LoadBitmap(hInst,MAKEINTRESOURCE(IDB_BITMAP));
               hSnowBmp = LoadBitmap(hInst,MAKEINTRESOURCE(IDB_BITMAP));

               SelectObject(hMaskDC,hMaskBmp);
               SelectObject(hSnowDC,hSnowBmp);

               return 0;
           }
       case WM_TIMER:



       {
               int i;
               if(nCount  200)
               {
                   nCount = 0;
                   Vx = rand()%4-2;
                   Vy = rand()%2+2;
                   return 0;
               }
               for(i = 0;i  300;i++)
               {
                   BitBlt(hScrnDC,Position[0],Position[1],30,30,hCoverScrnDC,Position[0],Position[1],SRCCOPY);

                   pVx = rand()%2-1+Vx*(i%3);
                   pVy = Vy*(i%3+1);
                   Position[0] += pVx;
                   Position[1] += pVy;
                   BitBlt(hScrnDC,Position[0],Position[1],30,30,hMaskDC,150,0,SRCAND);
                   BitBlt(hScrnDC,Position[0],Position[1],30,30,hSnowDC,0,0,SRCPAINT);
                   if(Position[0]  -30 || Position[0]  xScrn || Position[1]  yScrn)
                   {
                       Position[0] = rand()%xScrn;
                       Position[1] = 0;
                   }
               }
               nCount++;
               return 0;
           }                
       case WM_DESTROY:
           {
               BitBlt(hScrnDC,0,0,xScrn,yScrn,hCoverScrnDC,0,0,SRCCOPY);
               KillTimer(hwnd,1);
               DeleteDC(hScrnDC);
               DeleteDC(hCoverScrnDC);
               DeleteDC(hMaskDC);
               DeleteDC(hScrnDC);
               DeleteObject(hCoverScrnBmp);
               DeleteObject(hMaskBmp);
               DeleteObject(hSnowBmp);
               PostQuitMessage(0);
               return 0;
           }
       }        
       return DefWindowProc(hwnd,message,wParam,lParam);        
    }
    这里我们去的DC有点不同,我们使用CreateDC("DISPLAY",NULL,NULL,NULL)所取得的DC就是桌面的了,实际上这个函数我也只在取得桌面DC的时候用用。

    xScrn = GetDeviceCaps(hScrnDC,HORZRES);
    yScrn = GetDeviceCaps(hScrnDC,VERTRES);
    这俩分别取得了屏幕的长度和宽度。函数的第二个参数可以去其他,以得到其他的东西。大家可以查MSDN知道。

    之后CreateCompatibleDC(hScrnDC)也创建了一个DC,这个DC和hScrnDC的属性相同,不过这个DC是在内存中的,不是真真切切的一个画板,但这个虚拟的画板的材料,大小都和hScrnDC相同。

    CreateCompatibleBitmap(hScrnDC,xScrn,yScrn)创建了一个虚拟的位图。同样位图也具有hScrnDC一样的属性。

    SelectObject(hCoverScrnDC,hCoverScrnBmp)然后我们就把这个空空荡荡的位图花在了画板上。

    BitBlt(hCoverScrnDC,0,0,xScrn,yScrn,hScrnDC,0,0,SRCCOPY)我们拿来hScrnDC这个画板,画板上画着你的桌面,我们把这个桌面copy到hCoverScrnDC这个画板上空空荡荡的位图上去。

    hMaskDC = CreateCompatibleDC(hScrnDC);
    hSnowDC = CreateCompatibleDC(hScrnDC);

    hMaskBmp = CreateCompatibleBitmap(hScrnDC,300,108);
    hSnowBmp = CreateCompatibleBitmap(hScrnDC,300,108);
    我们又创建了两个画板,两个空空荡荡的位图。

    大家注意,这个300,180是位图的大小,大家右键位图,然后点属性摘要就看到了。

    hMaskBmp = LoadBitmap(hInst,MAKEINTRESOURCE(IDB_BITMAP));
    hSnowBmp = LoadBitmap(hInst,MAKEINTRESOURCE(IDB_BITMAP));
    我们把资源里的位图画在这两个位图上面。

    SelectObject(hMaskDC,hMaskBmp);
    SelectObject(hSnowDC,hSnowBmp);

    然后把位图画在画板上。初始化工作就完成了。

    大家请注意BitBlt这个函数,第一个参数是目的hdc,2、3参数标明了要copy的位置的左上角距离画板左上角的的x大小和y大小,而4、5则是长和宽。6是源HDC,7、8同2、3,不过是对源hdc.

    剩下的在WM_TIMER中的操作也就是用了上述的一些函数了。大家要深刻理解这些。不懂可以在下面提问。

    简单的说,WM_TIMER中做的就是拿原本屏幕上被雪花覆盖的那一部分先覆盖掉雪花,再移动雪花位置,再接着画雪花。

    这画雪花很微妙。因为我们除了雪花的位置,其他地方都应该是透明的,所以我们用了一种特殊的处理方法。大家看我提供的雪花位图,是不是很奇怪呢?

    我们把那些周围是白的雪花是黑的图案叫做Mask,我们先对Mask做处理。
    BitBlt(hScrnDC,Position[0],Position[1],30,30,hMaskDC,150,0,SRCAND);大家看到了,我们这里不适用SRCCOPY简单的COPY了,而是用AND运算把MASK中雪花的每一个像素和屏幕的像素做和运算。结果显然白色(255,255,255)的地方还是原色,而黑色(0,0,0)的地方则变黑了。

    再拿真正的雪花处理BitBlt(hScrnDC,Position[0],Position[1],30,30,hSnowDC,0,0,SRCPAINT);
    这SRCPAINT是做或运算。结果显然黑色(0,0,0)的地方是原色,而雪花的位置和黑色做或运算后就变成雪花的颜色了。
    大家看游戏中人物的运动就是用了这种技术的。

    至此为止,大家应该明白了这个小程序,掌握之后就可以写出类似的了,比方说屏幕上出现玫瑰花之类的了。

    楼主 2015-12-05 14:18 回复

共有回帖数 0
  • 回 帖
  • 表情 图片 视频
  • 发表

登录直线网账号

Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号 意见反馈 | 关于直线 | 版权声明 | 会员须知