c语言开发窗口应用程序

转载于:https://blog.csdn.net/liu_rong_fei/article/details/51271901
视频下载地址:链接: http://pan.baidu.com/s/1qYj9BMs 密码: fn8u

《c语言开发窗口应用程序》前言

本教程适合什么样的人学习

适合已经掌握基本的c语言语法,想进一步提高c语言,不想总是玩控制台和做数学计算题的朋友。

通过本教程能学到什么?

1.学会制作带窗口界面的程序。

2.学会软件开发中常用的各种技术,如网络编程,多线程编程,数据库编程。

3.项目实战,让您知道软件是怎么一步一步制作的。可以学会c语言的模块化编程以及一些编程习惯和思路。

教学模式

从需求出发,通过项目实战教学,期间遇到的知识点再详细讲解,最终项目学会了,知识也学会了,并且比零散的知识更加容易记住,做到真正的学以致用。

第一阶段:windows图形编程

第一节:我的第一个窗口

窗口是什么?

程序的窗口到底是什么?其实窗口,可以理解为电脑屏幕上的一个图片或者是像素点,底层的原理都是操作让显示屏显示特定的图形。只是这一切都不需要我们去编写代码来控制屏幕显示。是由操作系统来完成,然后编写操作系统的人编写出来一些函数,提供给程序员去使用,从而达到创建窗口的目的。

Windows系统下如何编写带窗口的程序?

c语言通过调用windows系统提供给的API来创建窗口。

扫描二维码关注公众号,回复: 2632445 查看本文章

API是什么?

API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

你也可以自己编写API提供特定的功能,然后给其他程序员去使用,这就是你的API。我们这里说的API特指windows系统提供的api,他是由编写windows操作系统的程序员编写的,然后提供给需要的程序员使用,比如创建窗口。

简而言之,windows API其实就是开发windows系统的程序员编写来提供给我们程序员用的函数。

为何其他的语言直接鼠标拖拉一下就能有界面?

很多语言都可以调用这些API,比如c、c++、java、vb、c#等等,调用的都是同样的函数,用法也是一样,只是不同语言调用方式上有点区别而已。

像vb、Delphi、易语言之类的,鼠标拖拉一下就有界面。其实只是对我们程序员隐藏了底层的东西,其本质还是一样调用了windows提供给他的API函数来实现。

大家可能会想,既然拖拉一下都可以做出界面,那我们还有必要学习直接用API创建窗口这么麻烦吗?当然是有必要的,理解原理以后,我们就不会被表面的东西遮住眼睛,能够一眼洞穿他的本质,撕去他的伪装。以后大家还会接触到很多的界面库(如:Duilib),有的是开放源码的,当大家去读这些代码的时候,就必须要具备这些基础的理论知识。

动手编写第一个带窗口的程序?

窗口程序的主函数名称 跟控制台程序的主函数名称不一样。

在控制台程序中,基本框架是:

int main(void)

{

return 0;

}

Win32窗口程序,基本框架是:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevinstance, PSTR szCmdLine, int iCmdShow)

{

return 0;

}

看起来比较复杂,是因为有很多数据类型没有见过。

下面来一一解释一下:

int :代表返回值是整形 这个跟控制台程序一样。

WINAPI :

这个是调用约定,实质上是它是宏 #define WINAPI __stdcall  说明是stdcall的调用约定,想了解更多关于调用约定的,可以直接谷歌搜索 调用约定。Ps:谷歌被墙,可以通过这个地址使用谷歌搜索服务(g.zixue7.cn或go.zixue7.cn)

WinMain :

这个是函数名 就像控制台程序中的 main

HINSTANCE :

这个是一个自定义类型, 是句柄型数据类型,相当于装入了内存的资源的ID,比如我们的程序被加载到内存中,就是一个资源,就有一个编号,WinMain函数的第一个参数就是表示我们当前运行这个程序本身的资源id。

hInstance:

应用程序当前实例的句柄。 这个值其实就是程序加载到内存空间后的首地址。

hPrevlnstance:

应用程序的先前实例的句柄。对于同一个程序打开两次,出现两个窗口第一次打开的窗口就是先前实例的窗口。

PSTR szCmdLine:

在WINNT.H中有如下定义

typedef char CHAR;  

typedef CHAR *LPSTR, *PSTR;

这就说明 PSTR 其实就是个char类型的指针而已。

这个参数的作用就是接受命令行参数。比如在启动程序的时候用如下命令:

zixue7.exe admin password

那么这个szCmdLine 中保存的就是 admin password这个字符串

这个参数的值,可以直接使用,也可以通过 GetCommandLine() 函数获取到。

int iCmdShow :这个是控制窗口的显示状态,比如最大化,最小化,隐藏。

上面这些参数解释,一开始看不懂,没有关系,完全不影响我们下面的学习,可以等到以后知识积累足够了,再回头好好理解。

对主函数了解差不多之后,我们来编写第一个窗口程序,代码如下:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevinstance, PSTR szCmdLine, int iCmdShow)

{

MessageBox(NULL, TEXT("窗口内容"), TEXT("窗口标题"), MB_OK);

return 0;

}

其实这还不算真正的窗口程序,只是一个对话框窗口。

接下来,我们先来配置好本次课程用的开发环境。

开发环境搭建

本套教程采用的开发工具是VS2013,下载地址:http://www.zixue7.com/thread-36148-1-1.html

安装过程 略微有点漫长。。

安装的时候遇到这个问题 要么装一下ie10  要么不管他 点继续。

如何创建项目,具体看视频演示,也可以自己摸索一下。

下面看第一个窗口的代码:

#include <windows.h>

//窗口过程函数

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

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)

{

//定义窗口类结构体变量

WNDCLASS wc;

//窗口类名称

static TCHAR* szAppName =TEXT("zixue7");

//窗口句柄

HWND hwnd = NULL;

//消息结构

MSG msg;

/**

下面的代码填充窗口类信息,如图标,鼠标样式,背景,过程函数等

*/

wc.style = CS_HREDRAW | CS_VREDRAW; //窗口样式

wc.lpfnWndProc = WndProc; //过程函数

wc.cbClsExtra = 0; //扩展字段

wc.cbWndExtra = 0; //扩展字段

wc.hInstance = hInstance; //当前实例句柄

wc.hIcon = LoadIcon(hInstance,IDI_APPLICATION); //设置程序图标

wc.hCursor = LoadCursor(NULL,IDC_ARROW); //设置鼠标

//用白色画刷填充背景

wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

//菜单

wc.lpszMenuName = NULL;

//类名

wc.lpszClassName = szAppName;

//像操作系统注册窗口类

if (!RegisterClass(&wc))

{

MessageBox(NULL,TEXT("程序只能在windowsNT下运行"),

szAppName, MB_ICONERROR);

return 0;

}

//创建窗口

hwnd = CreateWindow(szAppName, //要注册的窗口类名

TEXT("c语言编写的第一个窗口程序-www.zixue7.com"),//窗口标题

WS_OVERLAPPEDWINDOW, //窗口样式

CW_USEDEFAULT, //窗口距离屏幕左上角都横坐标

CW_USEDEFAULT, //窗口距离屏幕左上角都纵坐标

400, //窗口宽度

300, //窗口高度

NULL, //父窗口句柄

NULL, //菜单句柄

hInstance, //当前实例句柄

NULL); //指向一个值的指针,该值传递给窗口 WM_CREATE消息。一般为NULL

//显示窗口

ShowWindow(hwnd, iCmdShow);

//更新窗口

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_DESTROY:

//发送结束请求,里面的参数为退出码

PostQuitMessage(0);

break;

}

//调用默认的过程函数

return DefWindowProc(hwnd,message, wParam, lParam);

}

运行效果:

我们已经用纯代码生成了第一个窗口,这是一个好的开始,虽然只有短短的几十行代码,但是其中的信息量是非常大的,今后所有的窗口程序,我们都是基于上面的一个基本框架代码。

我们先来看看在windows下创建一个窗口的整个过程:

1.告诉系统,我们要创建一个怎么样的窗口。

如何告诉?通过WNDCLASS这个结构来告诉他,我们设置这个结构的不同字段。

设置好字段之后,调用RegisterClass函数来告诉系统,我们要注册一个这样的窗口类型。

2.创建窗口。

上一步创建了一个窗口类型,其实这个窗口类型可以理解为一个模板,接下来我们就告 操作系统,我们要以xx模板创建一个窗口。怎么告诉他呢?就是调用函数CreateWindow,这个函数参数十分的多,也不需要去死记硬背,书写多了自然就记住 了,写的时候不知道有哪些参数就百度搜下函数名称就好了。

3.显示窗口

创建好窗口之后,默认是没显示的,我们还需要让他显示出来,同样是调用函数

ShowWindow来显示, 调用这个函数来更新窗口UpdateWindow

4.消息循环

创建好窗口之后,不是要等待用户的操作吗?比如鼠标单击,键盘按键等等。

这些东西在windows系统中都是一种消息。

上面的 WndProc 这个函数就是专门处理这些消息,我们可以在这个函数中处理我们感

兴趣的消息。

重点:消息机制(也就是消息循环具体是怎么循环的,消息从哪里来,要到哪里去)

一个消息的产生到结束是怎样一个过程,就以用户点击鼠标为例。

1.用户在应用程序A上面单击了左键

2.操作系统捕捉到了这个单击左键的消息,就把它放到由操作系统管理的消息队列中。

3.我们的程序创建好之后就通过GetMessage函数一直不停的去检测消息队列中是否有自己关心的消息(至于如何判断哪些消息是我们窗口所关心的,这个由操作系统去管,此处不必深究。)

4.获取到消息之后,操作系统(可以看到我们并没有在我们代码中直接调用)就调用窗口过程函数来处理,窗口过程函数就是上面代码中的WndProc。 他怎么知道要调用这个函数来处理呢?是因为在我们注册窗口类的时候通过这段代码告诉他的wc.lpfnWndProc = WndProc;  程序又不是神,不告诉他,他怎么会知道呢?O(∩_∩)O~

5.如此反复,一直不停的处理消息,直到程序结束。

整个过程大致就是

操作系统获取到消息,再告诉相关的程序,相关程序在处理消息。

课后作业:

自己动手把上面的代码多输入几次,熟练他,并且理解整个创建窗口的过程。

代码熟练到可以不用看着原有的代码 都能自己打出来。

输入代码的时候 心中想着窗口创建的步骤,就很容易记住了。

注:目的不是让大家背代码,而是通过敲打代码来熟悉创建窗口的整个流程。

第二节:定时提醒喝水工具

使用场景与软件需求分析:

场景:作为程序员的我们,每天在电脑面前,低头抬头就是几个小时,有时候整天都不喝水,所以通过软件来定时提醒我们喝水是很有必要的。

第一:定时功能。

第二:提醒功能。

窗口创建到销毁的整个过程。

1.调用CreateWindow之后,窗口显示之前,会收到,WM_CREATE消息(只会收到一次)

2.ShowWindow函数设置窗口的显示状态(根据WinMain函数最后一个参数来定)

3.UpdateWindow 会发送一个WM_PAINT,消息处理函数会去绘制窗口的客户区,至此窗口创建显示完毕。

4.当检测到用户单击关闭按钮的时候,系统会给程序发送一个WM_SYSCOMMAND消息。

5.WndProc消息处理函数将这个消息传给DefWindowProc,DefWindowProc会发出一个WM_CLOSE消息。

6.消息处理函数收到一个WM_CLOSE消息(一般可以在这里提示用户是否真的要关闭窗口)

7.如果说用户不去拦截处理WM_CLOSE消息,将他叫给DefWindowProc处理,DefWindowProc函数会发送WM_DESTROY消息,一般对WM_DESTROY消息的处理是,调用PostQuitMessage函数。

8.PostQuitMessage函数将会发送一条WM_QUIT消息。

9.消息循环中的GetMessage函数获取到WM_QUIT消息之后,返回值为0,退出消息循环。

10.最后主函数返回 msg.wParam,这个msg.wParam的值 其实就是调用PostQuitMessage函数时传入的值,被填充到msg.wParam参数中,作为WinMain函数的返回值。至此,整个程序结束。

GDI简介

GDI (图形设备接口(GraphicsDeviceInterface)),他是windows提供的绘图接口,让我们无需关心底层设备以及设备驱动就可以在设备上输出内容,方便了开发工作。

设备内容(DC)和设备内容句柄(HDC):

DC是GDI内部保存的数据结构,跟显示器或打印机这类设备相关,DC中的有些属性值定义了GDI绘图函数的具体细节。

HDC是设备内容句柄,句柄实际上就是一个数值,Windows以它在内部使用对象。从windows获取到句柄以后,就可以在GDI函数中使用这个句柄来绘制内容了。

可以通过如下方法获得HDC:

1.通过BeginPaint和EndPaint函数(会让无效区域变成有效区域):

处理WM_PAINT时,通过调用BeginPaint函数,他的返回值就是HDC,通常使用如下代码:

PAINTSTRUCT ps ;

HDC hdc ;

case WM_PAINT:

  hdc = BeginPaint (hwnd, &ps) ;

   //在这里使用GDI函数

  EndPaint (hwnd, &ps) ;

  return 0 ;

2.通过GetDC 和ReleaseDC函数(不会让无效区域变成有效区域):

hdc = GetDC (hwnd) ;

//使用GDI函数

ReleaseDC (hwnd, hdc) ;

注意:调用GetDC 和ReleaseDC函数不会使无效区域变成有效区域,所以如果处理WM_PAINT消息时使用这种方式来绘制内容时,可以调用ValidateRect (hwnd, NULL) 函数来使无效矩形变为有效。

GetDC(NULL); 如果参数为NULL会获取到整个屏幕的设备句柄,意思就是,可以在整个屏幕上绘制文字或图像,比如以前看到过的一个全屏下雪的程序,就可以通过这个实现

O(∩_∩)O~。

与GetDC相似的函数是GetWindowDC。GetDC传回用于写入窗口显示区域的设备内容句柄,而GetWindowDC传回写入整个窗口的设备内容句柄。例如,您的程序可以使用从GetWindowDC传回的设备内容句柄在窗口的标题列上写入文字。然而,程序同样也应该处理WM_NCPAINT(「非显示区域绘制」)消息。

窗口的绘制与更新

在以前的控制台程序中,我们用printf输出一段文字之后,他就会一直显示在窗口上,不必担心他会莫名其妙的消失。

重点:在windows中,他不一定能保证我们显示的东西一直存在,比如:我们的窗口被其他窗口遮住的时候,windows不会自动保存被遮住部分的内容,等到窗口没有遮住的时候,他没有办法重新恢复那一部分的内容,但是他会发一个WM_PAINT消息给我们的程序,让我们自己去更新那一部分内容。

引用《windows程序设计第五版》中的一段文字:

在发生下面几种事件之一时,窗口消息处理程序会接收到一个WM_PAINT消息:

l ·在使用者移动窗口或显示窗口时,窗口中先前被隐藏的区域重新可见。

l ·使用者改变窗口的大小(如果窗口类别样式有着CS_HREDRAW和CS_VREDRAW位旗标的设定)。

l ·程序使用ScrollWindow或ScrollDC函数滚动显示区域的一部分。

l ·程序使用InvalidateRect或InvalidateRgn函数刻意产生WM_PAINT消息。

 

在某些情况下,显示区域的一部分被临时覆盖,Windows试图保存一个显示区域,并在以后恢复它,但这不一定能成功。在以下情况下,Windows可能发送WM_PAINT消息:

l ·Windows擦除覆盖了部分窗口的对话框或消息框。

l ·菜单下拉出来,然后被释放。

l ·显示工具提示消息。

 

在某些情况下,Windows总是保存它所覆盖的显示区域,然后恢复它。这些情况是:

l ·鼠标光标穿越显示区域。

l ·图标拖过显示区域。

有效矩形和无效矩形:

一般来说,当程序接收到WM_PAINT消息就准备更新整个窗口,但经常只会更新一个很小都区域。比如:当窗口被一个对话框覆盖一部分,当对话框移开的时候,需要重绘的只是先前被对话框覆盖部分而已。只有在显示区域中存在无效矩形时,窗口才会接收到WM_PAINT消息。

在windows内部为每个窗口保存了一个【绘图信息结构】,其中就记录了无效矩形的坐标和其他信息。当消息队列中已经存在一个WM_PAINT消息,在这个消息被处理之前,又有一个区域变成了无效区域,此时系统会自动计算两个无效区域,将两个区域合并成为一个新的无效矩形,最终消息队列中只有一个WM_PAINT消息,其中的无效区域就是合并后的无效矩形所包含的区域。

注意:由于以上原因,我们必须在接收到WM_PAINT消息的时候,重新绘制我们的内容,并把窗口中的无效矩形变成有效矩形。

 

在呼叫BeginPaint函数之后,整个显示区域将变成有效区域。

程序也可以通过呼叫ValidateRect函数使显示区域内的任意矩形区域变为有效。如果这呼叫具有令整个无效区域变为有效的效果,则目前队列中的任何WM_PAINT消息都将被删除。

一般地,处理WM_PAINT消息的形式如下:

case WM_PAINT:

  hdc = BeginPaint (hwnd, &ps) ;

         //使用GDI函数

  EndPaint (hwnd, &ps) ;

  return 0 ;

如果不处理WM_PAINT必须把它传给DefWindowProc函数处理,他默认使用如下代码来处理WM_PAINT消息:

case WM_PAINT:

    BeginPaint(hwnd, &ps);

    EndPaint(hwnd, &ps);

   return 0 ;

 

下面的写法是错误的!!!!

case WM_PAINT:

return 0 ;  

//错误写法,这样做的后果是,因为无效区域没有变成有效区域,所以会不停的收到WM_PAINT消息,消息队列中一直存在WM_PAINT消息!!!

实战编写定时提醒喝水工具

1.在WM_CREATE消息中设置定时器。

2.在WM_TIMER 消息中处理提示信息,比如闪动窗口,弹出窗口,播放声音等等。

3.在WM_DESTROY 消息中销毁定时器

定时器的使用

SetTimer / KillTimer 函数

控制台下使用消息循环以及定时器

#include <windows.h>

#include <stdio.h>

void CALLBACK TimerProc(HWND hWnd,UINT nMsg,UINT nTimerid,DWORD dwTime)

{

  printf("%s","abc");

}

int main(void)

{

int timerId = 0;

//创建一个定时器,返回定时器标识

    timerId = SetTimer(NULL, 0, 1000, TimerProc);

//如果创建失败,退出程序

if(0 == timerId)

{

printf("定时器创建失败");

return 1;

}

//消息结构体

    MSG   msg;

//消息循环

    while(GetMessage(&msg,NULL,0,0))   

    {

        //翻译消息

TranslateMessage(&msg);

//分发消息

DispatchMessage(&msg);

    }   

//销毁定时器

KillTimer(NULL, timerId);

return 0;

}

课后作业:

添加窗口变成顶层窗口。

添加自启动。

第三节:制作一个简单的画板

本节知识点

1.简单的绘图函数

2.获取鼠标信息

3.画笔的创建,选择,删除

涉及的函数:

SetPixel (hdc, x, y, crColor) ;

crColor = GetPixel (hdc, x, y)

MoveToEx (hdc, xBeg, yBeg, NULL) ;

        

LineTo (hdc, xEnd, yEnd) ;

        

 

Polyline和PolylineTo画一系列相连的直线。

CreatePen  创建画笔

SelectObject 选择画笔

DeleteObject 删除画笔

消息:

鼠标左键按下,松开

鼠标右键按下,松开

画板代码:

#include <windows.h>

//窗口过程函数

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

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)

{

//定义窗口类结构体变量

WNDCLASS wc;

//窗口类名称

static TCHAR* szAppName =TEXT("zixue7");

//窗口句柄

HWND hwnd = NULL;

//消息结构

MSG msg;

/**

下面的代码填充窗口类信息,如图标,鼠标样式,背景,过程函数等

*/

wc.style = CS_HREDRAW | CS_VREDRAW; //窗口样式

wc.lpfnWndProc = WndProc; //过程函数

wc.cbClsExtra = 0; //扩展字段

wc.cbWndExtra = 0; //扩展字段

wc.hInstance = hInstance; //当前实例句柄

wc.hIcon = LoadIcon(hInstance,IDI_APPLICATION); //设置程序图标

wc.hCursor = LoadCursor(NULL,IDC_ARROW); //设置鼠标

//用白色画刷填充背景

wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

//菜单

wc.lpszMenuName = NULL;

//类名

wc.lpszClassName = szAppName;

//像操作系统注册窗口类

if (!RegisterClass(&wc))

{

MessageBox(NULL,TEXT("程序只能在windowsNT下运行"),

szAppName, MB_ICONERROR);

return 0;

}

//创建窗口

hwnd = CreateWindow(szAppName, //要注册的窗口类名

TEXT("c语言制作简单的画板-www.zixue7.com"),//窗口标题

WS_OVERLAPPEDWINDOW, //窗口样式

CW_USEDEFAULT, //窗口距离屏幕左上角都横坐标

CW_USEDEFAULT, //窗口距离屏幕左上角都纵坐标

800, //窗口宽度

600, //窗口高度

NULL, //父窗口句柄

NULL, //菜单句柄

hInstance, //当前实例句柄

NULL); //指向一个值的指针,该值传递给窗口 WM_CREATE消息。一般为NULL

//显示窗口

ShowWindow(hwnd, iCmdShow);

//更新窗口

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)

{

//记录鼠标移动时的坐标

long xPos, yPos;

static int i = 0;//保存坐标数组下标

static POINT pts[5000];//保存鼠标移动产生的坐标信息

static int leftDown = 0;//记录鼠标左键是否按下

TCHAR buf[512];

static HPEN hPenRed, hPenGreen, hPenBlue, hPenCur;//创建画笔句柄

HDC hdc; //设备内容句柄

switch (message)

{

case WM_CREATE:

//创建画笔

hPenRed = CreatePen(PS_DASH, 1, RGB(255, 0, 0));

hPenGreen = CreatePen(PS_DOT, 1, RGB(0, 255, 0));

hPenBlue = CreatePen(PS_SOLID, 5, RGB(0, 0, 255));

//让蓝色画笔成为当前画笔

hPenCur = hPenBlue;

return 0;

case WM_KEYDOWN:

sprintf(buf, "key-code:%d\n", wParam);

OutputDebugString(buf);

//判断哪个按键被按下

switch (wParam)

{

case 'R':

hPenCur = hPenRed;

break;

case 'G':

hPenCur = hPenGreen;

break;

case 'B':

hPenCur = hPenBlue;

break;

default:

break;

}

return 0;

//鼠标移动

case WM_MOUSEMOVE:

if (0 == leftDown) return 0;

xPos = LOWORD(lParam);//获取lParm中的低字,保存的是x坐标

yPos = HIWORD(lParam);//获取lParm中的高字,保存的是y坐标

//获取设备内容句柄

hdc = GetDC(hwnd);

//保存鼠标当前的作坐标到数组

pts[i].x = xPos;

pts[i].y = yPos;

++i;  //数组下标向后移动一个

InvalidateRect(hwnd, NULL, FALSE); //手动让整个窗口变为无效区域,从而产生WM_PAINT消息

ReleaseDC(hwnd, hdc);

return 0;

//鼠标左键单击

case WM_LBUTTONDOWN:

leftDown = 1;  //设置为左键按下

i = 0; //设置下标从0开始,重新画一条线

return 0;

case WM_LBUTTONUP:

leftDown = 0; //设置为鼠标左键放开

return 0;

case WM_PAINT:

hdc = GetDC(hwnd);

SelectObject(hdc, hPenCur); //选择hPenCur为当前的画笔

Polyline(hdc, pts, i); //根据pts数组绘制 连续的线

ReleaseDC(hwnd, hdc);

ValidateRect(hwnd, NULL); //因为没有使用 BeginPaint,客户区域的无效区域不会自动变为有效区域,所以手动掉用这个函数来让那个整个客户区域变成有效区域

return 0;

case WM_DESTROY:

DeleteObject(hPenRed);

DeleteObject(hPenGreen);

DeleteObject(hPenBlue);

//发送结束请求,里面的参数为退出码

PostQuitMessage(0);

return 0;

}

//调用默认的过程函数

return DefWindowProc(hwnd,message, wParam, lParam);

}

作业:

新建一个空项目,所有代码都自己输入,完成本次画板的功能。

要求:1.添加单击就清空画板的功能。

附加:制作一个画面可以刷新窗口而不消失的画板。

第四节:制作简易计算器

知识点:

1.创建常见的控件(子窗口创建)

2.响应子控件消息

3.父窗口向子控件发送消息

项目代码:

#define _CRT_SECURE_NO_WARNINGS

#include <windows.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#define                BTN0                100

#define                BTN1                101

#define                BTN2                102

#define                BTN3                103

#define                BTN4                104

#define                BTN5                105

#define                BTN6                106

#define                BTN7                107

#define                BTN8                108

#define                BTN9                109

#define                BTN_CLS                110

#define                BTN_ADD                111

#define                BTN_SUB                112

#define                BTN_MUL                113

#define                BTN_DIV                114

#define                BTN_EQU                115

#define                TEXT_BOX        120

char labels[][10] = { "1", "2", "3", "+", "4", "5", "6", "-", "7", "8", "9",

"*", "CLS", "0", "=", "/" };

int bid[] = {

BTN1, BTN2, BTN3, BTN_ADD,

BTN4, BTN5, BTN6, BTN_SUB,

BTN7, BTN8, BTN9, BTN_MUL,

BTN_CLS, BTN0, BTN_EQU, BTN_DIV };

char first[100], second[100], cz = 0;

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

void create_button(HWND hwnd,int x, int y,int id);

void create_textbox(HWND hwnd);

void onCommand(HWND hwnd,WPARAM wParam);

HINSTANCE ghInstance;

INT WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR str,INT iCmdShow) {

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(NULL,IDI_APPLICATION);

wndClass.hCursor = LoadCursor(NULL,IDC_ARROW);

wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

wndClass.lpszMenuName = NULL;

wndClass.lpszClassName = TEXT("calc");

RegisterClass(&wndClass);

ghInstance = hInstance;

hWnd = CreateWindow(

TEXT("calc"),   // window class name

TEXT("计算器-noxue.com"),// window caption

WS_OVERLAPPEDWINDOW,// window style

CW_USEDEFAULT,// initial x position

CW_USEDEFAULT,// initial y position

175,// initial x size

230,// initial y size

NULL,// parent window handle

NULL,// window menu handle

hInstance,// program instance handle

NULL);// creation parameters

ShowWindow(hWnd, iCmdShow);

UpdateWindow(hWnd);

while (GetMessage(&msg,NULL, 0, 0)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}  // WinMain

LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,

LPARAM lParam) {

HDC hdc;

PAINTSTRUCT ps;

switch (message) {

case WM_CREATE:

for (int i = 0; i < 16; i++) {

create_button(hWnd, 10 + (i % 4 * 35), 50 + (i / 4 * 35), i);

}

create_textbox(hWnd);

return 0;

case WM_COMMAND:

onCommand(hWnd, wParam);

return 0;

case WM_PAINT:

hdc = BeginPaint(hWnd, &ps);

EndPaint(hWnd, &ps);

return 0;

case WM_DESTROY:

PostQuitMessage(0);

return 0;

default:

return DefWindowProc(hWnd,message, wParam, lParam);

}

} // WndProc

void create_button(HWND hwnd,int x,int y,int id) {

CreateWindow("Button", labels[id],WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, x, y, 30, 30, hwnd, (HMENU)bid[id], ghInstance,NULL);

}

void create_textbox(HWND hwnd) {

CreateWindow("Edit",NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_DISABLED, 10, 10, 135, 30,hwnd, (HMENU)TEXT_BOX, ghInstance,NULL);

}

void onCommand(HWND hwnd,WPARAM wParam) {

WORD w = LOWORD(wParam);

HWND h = NULL;

char buf[50] = "", text[100] = "";

//如果是0-9的数字键被按下,执行下面的操作

if (w >= BTN0 && w <= BTN9) {

//获取被单击的子控件的句柄

h = GetDlgItem(hwnd, w);

//获取被单击的子控件的内容

GetWindowText(h, buf, 50);

//获取编辑框的句柄

h = GetDlgItem(hwnd, TEXT_BOX);

//获取编辑框的内容

GetWindowText(h, text, 500);

//把内容拼接起来

strcat(text, buf);

//设置编辑框内容

SetWindowText(h, text);

}

else if (w >=BTN_ADD && w <= BTN_DIV) {

h = GetDlgItem(hwnd, TEXT_BOX);

memset(first, 0, 100);

GetWindowText(h, first, 500);

SetWindowText(h, "");

//保存哪个操作按钮(+-*/)被单击

h = GetDlgItem(hwnd, w);

GetWindowText(h, buf, 50);

cz = buf[0];

}

else if (w ==BTN_EQU) {

h = GetDlgItem(hwnd, TEXT_BOX);

GetWindowText(h, text, 100);

long num1 = atol(first);

long num2 = atol(text);

long result = 0;

switch (cz) {

case '+':

result = num1 + num2;

break;

case '-':

result = num1 - num2;

break;

case '*':

result = num1 * num2;

break;

case '/':

if (0 == num2) break; //防止被0除

result = num1 / num2;

break;

default:

break;

}

if ('/' == cz && 0 == num2)

{

sprintf(text, "不能被0除");

}

else 

{

sprintf(text, "%d%c%d=%d", num1, cz, num2, result);

}

SetWindowText(h, text);

}

else if (w ==BTN_CLS) {

h = GetDlgItem(hwnd, TEXT_BOX);

SetWindowText(h, "");

}

}

第五节:绘制透明背景的位图

如何显示位图

首先添加资源:

选择Bitmap位图,然后选择bmp图片作为资源:

打开资源管理器:

接下来修改资源id号(默认不是下图这个名字):

把id号修改成IDB_FLOWER(这个随你开心,建议IDB_作为前缀,这表示是一个Bitmap的编号)

通过上面的步骤,引入了位图资源,接下来在代码中把他画出来。

/**

消息处理函数

*/

LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)

{

static HBITMAP hBitmap;

BITMAP bitmap;

HDC hdc, hdcMem;

PAINTSTRUCT ps;

static int fcx, fcy;

static HINSTANCE hInstance;

switch (message)

{

case WM_CREATE:

//获取实例句柄,下面LoadBitmap函数要做为参数

hInstance = ((LPCREATESTRUCT)lParam)->hInstance;

//加载bitmap资源

hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_FLOWER));

//获取Bitmap信息

GetObject(hBitmap, sizeof(BITMAP), &bitmap);

fcx = bitmap.bmWidth;

fcy = bitmap.bmHeight;

return 0;

case WM_PAINT:

hdc = BeginPaint(hwnd, &ps);

//创建内存设备句柄

hdcMem = CreateCompatibleDC(hdc);

//把位图句柄选到内存设备句柄中

SelectObject(hdcMem, hBitmap);

//把内存设备中的位图拷贝到设备内容句柄中,也就是显示图片

BitBlt(hdc, 0, 0, fcx, fcy, hdcMem, 0, 0, SRCCOPY);

DeleteDC(hdcMem);

EndPaint(hwnd, &ps);

return 0;

case WM_DESTROY:

//发送结束请求,里面的参数为退出码

PostQuitMessage(0);

return 0;

}

//调用默认的过程函数

return DefWindowProc(hwnd,message, wParam, lParam);

}

 

如何绘制透明背景的位图

首先用ps将要显示的bmp图片背景变成纯色,比如蓝色,如下图:

我们要将这个玫瑰花显示在一个背景为蓝天白云的图片上。

要实现透明背景的位图,用以下步骤即可:

首先将玫瑰花图片变成中间那样,

然后将背景图片变成左边那样,

最后通过位或操作(SRCPAINT)合并在一起,像右边那样就透明了(没有把图片扣好,所以玫瑰花周围还有蓝色的杂色)。

实现代码:

/**

消息处理函数

*/

LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)

{

static HBITMAP hBitmap,hBitmapSky,hMaskBmp;

BITMAP bitmap;

HDC hdc, hdcMem,hMaskDC;

PAINTSTRUCT ps;

static int scx, scy,bgcx,bgcy;

switch (message)

{

case WM_CREATE:

hBitmap = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance,MAKEINTRESOURCE(IDB_FLOWER));

hBitmapSky = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance,MAKEINTRESOURCE(IDB_SKY));

GetObject(hBitmap, sizeof(BITMAP), &bitmap);

scx = bitmap.bmWidth;

scy = bitmap.bmHeight;

GetObject(hBitmapSky, sizeof(BITMAP), &bitmap);

bgcx = bitmap.bmWidth;

bgcy = bitmap.bmHeight;

return 0;

case WM_PAINT:

hdc = BeginPaint(hwnd, &ps);

hdcMem = CreateCompatibleDC(hdc);

hMaskDC = CreateCompatibleDC(hdc);

//显示蓝天白云背景图片

SelectObject(hdcMem, hBitmapSky);

BitBlt(hdc, 0, 0, bgcx, bgcy, hdcMem, 0, 0, SRCCOPY);

//制作掩码图片

hMaskBmp = CreateBitmap(scx, scy, 1, 1, NULL);

SelectObject(hMaskDC, hMaskBmp);

SelectObject(hdcMem, hBitmap);

//在hdcMem中的蓝色为背景色

SetBkColor(hdcMem, RGB(0, 0, 0XFF));

//SetTextColor(hdcMem, RGB(0, 0, 0xff));

BitBlt(hMaskDC, 0, 0, scx, scy, hdcMem,0,0,SRCCOPY);

//在蓝天白云背景图上,显示黑色的玫瑰花图片

BitBlt(hdc, 0, 300, scx, scy, hMaskDC, 0, 0, SRCAND);

//黑色背景的玫瑰花图片

SetBkColor(hdcMem, RGB(0, 0, 0));

SetTextColor(hdcMem, RGB(255, 255, 255));

BitBlt(hdcMem, 0, 0, scx, scy, hMaskDC, 0, 0, SRCAND);

//将黑色背景的玫瑰花图片跟,跟蓝天白云背景带有黑色玫瑰花的图片做抑或操作

BitBlt(hdc, 0, 300, scx, scy, hdcMem, 0, 0, SRCPAINT);

DeleteDC(hdcMem);

DeleteObject(hMaskBmp);

EndPaint(hwnd, &ps);

return 0;

case WM_DESTROY:

//发送结束请求,里面的参数为退出码

PostQuitMessage(0);

return 0;

}

//调用默认的过程函数

return DefWindowProc(hwnd,message, wParam, lParam);

}

封装绘制透明背景位图的函数:

void DrawTransparentBmp(HDC hdc,HBITMAP hBitmap,int x,int y,COLORREF transColor)

{

HDC hMaskDC, hMemDC;

HBITMAP hMaskBmp;

BITMAP bitmap;

COLORREF oldColor;

int bx, by;

GetObject(hBitmap,sizeof(bitmap), &bitmap);

bx = bitmap.bmWidth;

by = bitmap.bmHeight;

hMaskBmp = CreateBitmap(bx, by, 1, 1, NULL);

hMaskDC = CreateCompatibleDC(hdc);

SelectObject(hMaskDC, hMaskBmp);

hMemDC = CreateCompatibleDC(hdc);

SelectObject(hMemDC, hBitmap);

//设置透明色

SetBkColor(hMemDC, transColor);

//创建一个图片为黑色,背景为白色的掩码图片

BitBlt(hMaskDC, 0, 0, bx, by, hMemDC, 0, 0, SRCCOPY);

//把要透明显示的图片的背景变成黑色

SetBkColor(hMemDC, RGB(0, 0, 0)); //告诉hMemDc,背景颜色设置为黑色。

SetTextColor(hMemDC, RGB(255, 255, 255));//告诉hMemDc,背景颜色设置为白色。

BitBlt(hMemDC, 0, 0, bx, by, hMaskDC, 0, 0, SRCAND);

//在背景上面扣一个要透明显示的图片的黑洞

SetBkColor(hdc, RGB(255, 255, 255));

SetTextColor(hdc, RGB(0, 0, 0)); //设置前景为黑色,再跟背景做与操作,这样就会留下一个黑色的洞

BitBlt(hdc, x, y, bx, by, hMaskDC, 0, 0, SRCAND);

//再把上面生成的两个图片(1黑色背景的要透明显示的图片,2.扣了个洞的背景图片)做与操作。

BitBlt(hdc, x, y, bx, by, hMemDC, 0, 0, SRCPAINT);

DeleteObject(hBitmap);

DeleteDC(hMemDC);

DeleteDC(hMaskDC);

}

参考内容:

SRCAND

Combines the colors of the source and destination rectangles by using the Boolean AND operator.

通过“按位与(AND)”操作混合源和目标区域。

SRCCOPY

Copies the source rectangle directly to the destination rectangle.

直接将源拷贝到目标区域

SRCPAINT

Combines the colors of the source and destination rectangles by using the Boolean OR operator.

通过“按位或(OR)”操作混合源和目标区域

SetBkColor和SetTextColor在Bitblt中的作用(难点):

分两种情况:

第一种情况:彩色图片转黑白图片。

SetBkColor告诉彩色图片dc,彩色图片中的什么颜色是背景色(windows默认背景色是白色,前景色是黑色)。

第二种情况:黑白图片转彩色图片。

SetBkColor 告诉彩色图片dc黑白图片的背景色是什么颜色。

SetTextColor 告诉彩色图片dc 黑色图片的前景色是什么颜色。

最后执行BitBlt的时候,

如果是SRCCOPY就会把上面设置的颜色复制到彩色图片dc上;

如果是SRCAND 就会用彩色图片跟上面设置的背景色和前景色做位与操作。

比如:

SetBkColor(hMemDC, RGB(0, 0, 0)); //告诉hMemDc,背景颜色设置为黑色。

SetTextColor(hMemDC, RGB(255, 255, 255));//告诉hMemDc,背景颜色设置为白色。

BitBlt(hMemDC, 0, 0, bx, by, hMaskDC, 0, 0, SRCAND);

假如上面的hMemDC里面是彩色图片DC,hMaskDC是黑白图片DC。

BitBlt最后一个参数是SRCAND,意思就是,彩色图片跟一张背景为黑色,前景为白色 的黑白图片做位与操作(其实就是对应的颜色值做位运算,白色就是1,黑色就是0),

做好运算之后,就会显示彩色图片的前景部分(因为前景色是白色(1)),背景颜色是 黑色(0),所以,彩色图片的背景部分,就变成了黑色。

第六节:全屏玫瑰花实现(上节课的一点纠错)

知识点:

问题:上节课透明位图,有点小问题,刷新后,位图不再透明。

全屏玫瑰花实现思路。

完善后的透明位图绘制函数:

void DrawTransparentBmp(HDC hdc,HBITMAP hBitmap,int x,int y,COLORREF transColor)

{

HDC hMaskDC = NULL, hMemDC = NULL, hBmpDC = NULL;

HBITMAP hMaskBmp = NULL, hBitmap1 = NULL;

BITMAP bitmap;

int bx, by;

COLORREF fColor, bColor;

static auto i = 0;

GetObject(hBitmap,sizeof(bitmap), &bitmap);

bx = bitmap.bmWidth;

by = bitmap.bmHeight;

hMaskBmp = CreateBitmap(bx, by, 1, 1, NULL);

hMaskDC = CreateCompatibleDC(hdc);

SelectObject(hMaskDC, hMaskBmp);

hBmpDC = CreateCompatibleDC(hdc);

SelectObject(hBmpDC, hBitmap);

//重新创建一张位图,不修改原来的位图,如果修改了,第二次绘制结果就不对

hMemDC = CreateCompatibleDC(hdc);

hBitmap1 = CreateCompatibleBitmap(hBmpDC, bx, by);

SelectObject(hMemDC, hBitmap1);

BitBlt(hMemDC, 0, 0, bx, by, hBmpDC, 0, 0, SRCCOPY);

//设置透明色

bColor = SetBkColor(hMemDC, transColor);

//创建一个图片为黑色,背景为白色的掩码图片

BitBlt(hMaskDC, 0, 0, bx, by, hMemDC, 0, 0, SRCCOPY);

//BitBlt(hdc, x, y, bx, by, hMemDC, 0, 0, SRCCOPY);

//把要透明显示的图片的背景变成黑色

bColor = SetBkColor(hMemDC, RGB(0, 0, 0));//告诉hMemDc,背景颜色设置为黑色。

fColor = SetTextColor(hMemDC, RGB(255, 255, 255));//告诉hMemDc,背景颜色设置为白色。

BitBlt(hMemDC, 0, 0, bx, by, hMaskDC, 0, 0, SRCAND);

//在背景上面扣一个要透明显示的图片的黑洞

bColor = SetBkColor(hdc, RGB(255, 255, 255));

fColor = SetTextColor(hdc, RGB(0, 0, 0)); //设置前景为黑色,再跟背景做与操作,这样就会留下一个黑色的洞

BitBlt(hdc, x, y, bx, by, hMaskDC, 0, 0, SRCAND);

//再把上面生成的两个图片(1黑色背景的要透明显示的图片,2.扣了个洞的背景图片)做与操作。

BitBlt(hdc, x, y, bx, by, hMemDC, 0, 0, SRCPAINT);

DeleteObject(hMaskBmp);

DeleteObject(hBitmap1);

DeleteDC(hMemDC);

DeleteDC(hMaskDC);

DeleteDC(hBmpDC);

}

生成爱心路径代码:

#include <iostream>

#include <windows.h>

using namespace std;

int main()

{

POINT pt;

POINT pts[10000];

long n=0;

HDC hdc;

HPEN hPen;

hdc = GetDC(NULL);

MoveToEx(hdc, 0, 0, NULL);  

hPen = CreatePen(PS_SOLID,5,RGB(255,0,0));

SelectObject(hdc, hPen);

while(true)

{

GetCursorPos(&pt);

cout << "{" << pt.x << "," << pt.y << "},";

pts[n].x = pt.x;

pts[n].y = pt.y;

//Polyline(hdc,pts,n);

if(n>3)

PolyBezierTo(hdc, pts+n-3, 3);  

SetPixel(hdc, pt.x,pt.y,RGB(255,0,0));

Sleep(100);

}

DeleteObject(hPen);

ReleaseDC(NULL,hdc);

return 0;

}

全屏玫瑰花思路:

1.创建窗口时 使用WS_EX_TRANSPARENT | WS_EX_LAYERED这两个样式

hwnd = CreateWindowEx(WS_EX_TRANSPARENT |WS_EX_LAYERED, szAppName, //要注册的窗口类名

TEXT("子窗口创建-www.noxue.com"),//窗口标题

WS_POPUP, //窗口样式

CW_USEDEFAULT, //窗口距离屏幕左上角都横坐标

CW_USEDEFAULT, //窗口距离屏幕左上角都纵坐标

nScreenWidth, //窗口宽度

nScreenHeight, //窗口高度

NULL, //父窗口句柄

NULL, //菜单句柄

hInstance, //当前实例句柄

NULL); //指向一个值的指针,该值传递给窗口 WM_CREATE消息。一般为NULL

2.在WM_CREATE消息中,设置什么颜色透明

SetLayeredWindowAttributes(hwnd, RGB(255, 255, 255), 0, LWA_COLORKEY); //白色为透明色

//让窗口置顶

SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE |SWP_NOMOVE);

3.调用透明位图绘制函数,在指定的坐标绘制玫瑰花

for (int i = 0; i <sizeof(pts) / sizeof(pts[0]); ++i)

{

DrawTransparentBmp(hdc, hBitmap, pts[i].x, pts[i].y, RGB(0, 0, 0XFF));

}

第二阶段:网络编程和多线程

第三阶段:数据库编程

附:实战项目列表

定时提醒喝水软件

简易画板

简易计算器

QQ喊话工具

个性相册

全屏玫瑰花软件

天气预报工具

U盘小偷

zip压缩工具

个性剪贴板(支持多个内容复制)

电脑锁屏工具

qq群发工具

屏幕截图工具(自定义水印)

IP代理地址采集工具

HTTP代理工具

简易邮箱客户端

手机短信轰炸机

ftp账号爆破工具

webshell爆破工具

多用户聊天工具

QQ空间秒赞工具

记账本

远程协助工具

上面的项目都会贯穿整个教程,按照合适的顺序来讲解。

猜你喜欢

转载自blog.csdn.net/QQ1910084514/article/details/81486354