一、 VC++ 的优势
Visual C++ 是一个功能强大的可视化应用程序开发工具,用于 Windows 环境下 32 位的应用程序的开发,是计算机世界公认的最优秀的应用开发工具之一。在 Visual C++ 环境下,利用 Microsoft 的基本类库 MFC ,可以使用完全的面向对象的方法来进行 Windows 95/98/NT 应用程序的开发,使得 Windows 程序员从大量的复杂劳动中解救出来,体会到真正的程序语言的强大功能和良好的灵活性。
Visual C++ 的优势有以下几点:
( 一) 面向对象的程序设计方法的使用
Visual C++ 编程是采用了面向对象的程序设计方法,面向对象的程序设计吸取了结构化程序设计的精华,他利用了人们根据对事物分类和抽象的倾向,引入了类和对象的概念,具有封装性、继承和多态的特点。
( 二) 强大的MFC 优势
MFC 以 Visual C++ 形式封装了大部分的 Windows API 来表示框架、窗口、对话框、设备上下文、公共 GDI 对象和其他标准的 Windows 部件。这些类提供了一个面向 Windows 结构的简单的 C++ 成员函数的接口。
应用 MFC 编程有下列优点:
1 MFC 提供了一个标准化的结构,提高了程序开发的效率。
2 类库中的各种对象所提供的强大功能可以完成程序中的绝大部分所需功能,使应用程序在功能和性能 2 个方面满足要求。
3 MFC 完全支持 Windows 所有的函数、控件、消息、 GDI 基本图形函数、菜单及对话框。
4 使用 MFC 易学易用,开发出的应用程序具有标准的、熟悉的 Windows 界面,还支持所有标准 Windows 特性。 MFC 类库的可靠性很高,不会影响所开发程序的可靠性和正确性。
总之,利用 MFC 还可以方便地调用 Windows 中与多媒体有关的 API 函数,可以方便快捷地开发多媒体应用程序,节省大量重复时间,缩短开发周期。 [4]
( 三) 丰富的技术资源
Visual C++ 中集成了大量的最新技术,如 ActiveX , COM 等技术。
二、利用图形设备接口(GDI+ )的编程特点
( 一) 什么是GDI+[4]
GDI+ 是 Windows XP 中的一个子系统,它主要负责在显示屏幕和打印设备输出有关信息,它是一组通过 C++ 类实现的应用程序编程接口。作为图形设备接口的 GDI+ 使得应用程序开发人员在输出屏幕和打印机信息的时候只需调用 GDI+ 库输出的类的一些方法即可完成图形操作 , 真正的绘图工作由这些方法交给特定的设备驱动程序来完成 ,GDI+ 使得图形硬件和应用程序相互隔离,从而使开发人员编写设备无关的应用程序变得非常容易。 [4]
( 二)GDI+ 新增功能[1]
1 渐变的画刷
GDI+ 允许用户创建一个沿路径或直线渐变的画刷 , 来填充外形 , 路径和区域 , 渐变画刷同样也可以画直线、曲线、路径,当你用一个线形画刷填充一个外形时,颜色就能够沿外形逐渐变化。
2 基数样条函数
GDI +支持基数样条函数。基数样条是一组单个曲线按照一定的顺序连接而成的一条较大曲线,样条由一系列点指定,并通过每一个指定的点。由于基数样条平滑地穿过组中的每一个点(不出现尖角),因而它比用直线连接创建的路径更精确。
3 持久路径对象
在 GDI+ 中,绘图工作由 Graphics 对象来完成,你可以创建几个与 Graphics 分开的路径对象,绘图操作时路径对象不被破环,这样你就可以多次使用同一个路径对象画路径了。
4 变形和矩阵对象
GDI+ 提供了矩阵对象,一个非常强大的工具,使得编写图形的旋转、平移、缩放代码变得非常容易。
5 可伸缩区域
GDI+ 用世界坐标存储区域 (Regions), 允许对区域进行任何图形变换(譬如如图所示的缩放),图形变换以变换矩阵存储。
6 多种图像格式支持 .
GDI +除了支持 BMP 等 GDI 支持的图形格式外,还支持 JPEG ( Joint Photographic Experts Group )、 GIF ( Graphics Interchange Format )、 PNG ( Exchangeable Image File )、 TIFF ( Tag Image File Format )等图像格式
GDI+ 还将支持其它技术,譬如重新着色、颜色校正、元数据、图形容器 .[2][3]
( 三) 编程模式的改变 Device Contexts (设备描述表), Handles (句柄), 和 Graphics Objects (图形对象)
设备描述表是 Windows 使用的一个数据结构,用于存储具体设备能力和与如何在设备上重绘一些项目的有关属性信息。而且视频设备的设备描述表还与特定的窗口有关。利用 GDI+ 函数,你可以简单地创建一个图形对象( Graphics ),然后以你熟悉的面向对象的编程方式调用它的方法即可。 Graphics 对象是 GDI+ 的核心,设备描述表( DC )和图形对象( Graphics )在不同的环境下扮演着同样的角色,发挥着类似的作用,但是两者也存在着本质的不同。前者使用基于句柄的编程方法而后者使用面向对象的编程方法。
图形对象和设备对象一样,在 GDI+ 中,你只需把这些绘图对象作为一个参数传递给图形对象 Graphics 方法调用即可,每一个图形对象所使用的绘图工具至于它调用方法使用的参数有关,它可以通过参数使用多种 Pen 和 Brush 绘图,而不是与特定的笔和画刷联系在一起。
GDI 是 Graphics Device Interface 的缩写,含义是图形设备接口,它的主要任务是负责系统与绘图程序之间的信息交换,处理所有 Windows 程序的图形输出。在 Windows 操作系统下,绝大多数具备图形界面的应用程序都离不开 GDI ,我们利用 GDI 所提供的众多函数就可以方便的在屏幕、打印机及其它输出设备上输出图形,文本等操作。
( 四)GDI 是如何实现输出的?
要想在屏幕或者其它输出设备上输出图形或者文字,那么我们就必须先获得一个称为设备描述表 ( DC:Device Context) 的对象的句柄,以它为参数,调用各种 GDI 函数实现各种文字或图形的输出。
设备描述表是 GDI 内部保存数据的一种数据结构,此结构中的属性内容与特定的输出设备(显示器,打印机等)相关,属性定义了 GDI 函数的工作细节,在稍后我们将看到如何使用 TextOut 函数输出文字,在这里属性确定了文字的颜色, x 坐标和 y 坐标映射到窗口显示区域的方式等。
设备描述表句柄一旦获得,那么系统将使用默认的属性值填充设备描述表结构。如果有必要,我们可以使用一些 GDI 函数获取和改变设备描述表中的属性值。 [2][3]
三、PNG 按钮的实现— 在VC++6.0 中用GDI+ 调用png 图片实现半透明渐变的特效窗口
(一)、概述
在 VC++6.0 中调用 *.png 图片实现半透明渐变窗口,该程序实现了指针式和数字式两种时钟显示方式。窗口实现了半透明渐变窗口、窗口拖动无移动矩形框、隐藏了任务栏窗体按钮等。 效果图如图一:
( 二) 准备工作
1 图片资源准备工作。首先在 Photoshop 中编辑好时钟的背景、时针、分针以及数字时钟显示方式的所有图片,如下图:将这些图片保存成为带透明通道的 .png 格式( GDI+ 调用显示时能够透明调背景)。 图1-1 程序执行后与WindowXP 桌面背景效果图
2 下面开始做好在 VC6.0 下展开此项工作的基本准备工作。
① 下载 gdiplus forVC6.0 的 SDK ,(总共两兆多)。
② 、在 C 盘建立文件夹 “GDI+” 将开发包拷贝在里面,亦即建立如下路径,以便例子代码顺利编译(当然你可以放到任意你喜欢的地方,只要在你的 Project 中正确包含路径即可!)。
C:GDI+ Includes
C:GDI+ Lib
C:GDI+ gdiplus.dll
③ 在 stdAfx.h 中添加对 GDI+ 环境的设置
#define UNICODE
#ifndef ULONG_PTR
#define ULONG_PTR unsigned long*
#endif
#include “C:/gdi+Includes+gdiplus.h” // 请修改为你的头文件路径
using namespace Gdiplus;
#pragma comment(lib, “C: gdi+//Lib//GdiPlus.lib”) // 请修改为你的 .lib 文件路径
④ 在 GDIPClock.cpp 中编辑 app 的 InitInstance() 中添加如下代码进行 GDI+ 的初始化工作
GdiplusStartupInput ;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
……
// 在对话框程序结束后关闭 gdiplus 环境
GdiplusShutdown(gdiplusToken);
( 三) 程序的实现全过程
1 、建立一个基于对话框的 Project ,这里的名称为 GDIPClock 。
2 、在 GDIPClockDlg.h 中定义所有类成员变量,包括所有图片的指针和图片的长宽尺寸信息。
Image *m_pImageClock;
Image *m_pImageClock1;
Image *m_pImageHHour;
Image *m_pImageHMinu;
Image *m_pImageHSec;
Image *m_pImageNum;
int m_BakWidth , m_BakHeight ;
int m_HourWidth, m_HourHeight;
int m_MinuWidth , m_MinuHeight;
int m_SecWidth , m_SecHeight ;
HINSTANCE hFuncInst ;
Typedef BOOL (WINAPI *MYFUNC)(HWND,HDC,POINT*,SIZE*,HDC,POINT*,COLORREF,BLENDFUNCTION*,DWORD);
MYFUNC UpdateLayeredWindow;
在这一步中需要特别说明的是,在创建透明窗口式需要调用一个 Windows API 函数
UpdateLayeredWindow (),该函数在 .net 以上的版本的 SDK 中有申明,但是在 VC6.0 下要调用要么下载 200 多兆的高版本 SDK ,要么从动态链接库 “User32.dll” 中调用,这里选择从 “User32.dll” 中调用。以上定义中后三项就是为此作准备的。
3 、在对话框的 OnCreate ()中添加如下代码:对 2 的函数和成员变量进行初始化!(其中 ImageFromIDResource ()函数为从资源中载入 Png 图像的一个方法!)
int CGDIPClockDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) ==-1)
return -1;
hFuncInst = LoadLibrary("User32.DLL");
BOOL bRet=FALSE;
if(hFuncInst)
UpdateLayeredWindow=(MYFUNC)GetProcAddress(hFuncInst, "UpdateLayeredWindow");
else
{
AfxMessageBox("User32.dll ERROR!");
exit(0);
}
// 初始化 gdiplus 的环境
// Initialize GDI+.
//m_Blend 是结构体 BLENDFUNCTION 的对象,用于指定两个 DC( 画图设备 ) 的融合方式。
m_Blend.BlendOp=0; //theonlyBlendOpdefinedinWindows2000 仅仅融合在 WINDOUWS2000 平台下定义
m_Blend.BlendFlags=0; //nothingelseisspecial...
m_Blend.AlphaFormat=1; //... 透明度
m_Blend.SourceConstantAlpha=255;//AC_SRC_ALPHA
ImageFromIDResource(IDR_PNGNUM,"PNG",m_pImageNum);// 从资源类加载 ID 号
ImageFromIDResource(IDR_PNGBAK1,"PNG",m_pImageClock1);
ImageFromIDResource(IDR_PNGBAK,"PNG",m_pImageClock);
ImageFromIDResource(IDR_PNGHOUR,"PNG",m_pImageHHour);
ImageFromIDResource(IDR_PNGMIN,"PNG",m_pImageHMinu);
ImageFromIDResource(IDR_PNGSEC,"PNG",m_pImageHSec);
m_BakWidth =m_pImageClock->GetWidth();
m_BakHeight =m_pImageClock->GetHeight();
m_HourWidth =m_pImageHHour->GetWidth();
m_HourHeight=m_pImageHHour->GetHeight();
m_MinuWidth =m_pImageHMinu->GetWidth();
m_MinuHeight=m_pImageHMinu->GetHeight();
m_SecWidth =m_pImageHSec->GetWidth();
m_SecHeight =m_pImageHSec->GetHeight();
::SetWindowPos(m_hWnd, HWND_TOPMOST,0,0,m_BakWidth,m_BakHeight,SWP_NOSIZE|SWP_NOMOVE);
return 0;
/ 启动后立刻更新窗口样式为透明窗体
UpdateClockDisplay();
SetTimer(1,500,NULL);
// 去除任务栏窗口对应按钮
ModifyStyleEx (WS_EX_APPWINDOW,WS_EX_TOOLWINDOW );
4 、 在 OnInitDialog ()种添加如下代码对调用透明窗体初始化和设置时钟进行刷新,代码意义有注解:
void CGDIPClockDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
UpdateClockDisplay();
CDialog::OnTimer(nIDEvent);
}
5 、 透明窗体创建于刷新,均调用以下函数完成,函数的参数表示整个窗体的透明度在该函数中包括了 GDI+ 中对 Image.DrawImage() 函数的集中重载方式的使用,还有在 GDI+ 中图像变换矩阵的使用初步研究。
BOOL CGDIPClockDlg::UpdateClockDisplay(int Transparent)// 该函数被定时器每一秒触发次
{
// 创建画图设备环境
HDC hdcTemp=GetDC()->m_hDC;
m_hdcMemory=CreateCompatibleDC(hdcTemp);
HBITMAP hBitMap=CreateCompatibleBitmap(hdcTemp,m_BakWidth,m_BakHeight);
SelectObject(m_hdcMemory,hBitMap);
// 确定透明度
if(Transparent<0||Transparent>100) Transparent=100;
m_Blend.SourceConstantAlpha=int(Transparent*2.55);//1~255
HDC hdcScreen=::GetDC (m_hWnd);
RECT rct;
// 得到窗体的区域
GetWindowRect(&rct);
POINT ptWinPos={rct.left,rct.top};
Graphics graph(m_hdcMemory);
// 确定画的背景区域
Point points[] = {
Point(0, 0),
Point(m_BakWidth, 0),
Point(0, m_BakHeight)
};
static bool bFly=false;
// 在此背景区域画蝴蝶扇翅膀的图 , 根据定时器变换图片, FALSE 变 TRUE ,来回得变
graph.DrawImage(m_pImageClock1, points, 3);
bFly=!bFly;
// 坐标原点在表盘中央
int OxyX=140;//m_BakWidth/2+8;
int OxyY=90;//m_BakHeight/2+10;
// 得到系统时间
SYSTEMTIME SystemTime; // address of system time structure
GetLocalTime(&SystemTime);
//Matrix matrixH(a,b,c,d,tx,ty);
//Matrix 的计算其实很简单,就只有两行的公式:
//X’ = a*X + c*Y + tx; Y’ = b*X + d*Y + ty;
//a = 控制 X 的宽度 ;b = 控制 Y 的倾斜 ;c = 控制 X 的倾斜 ;d = 控制 Y 的高度
//tx = 控制 X 坐标位置 ;ty = 控制 Y 坐标位置
Matrix matrixH(1,0,0,1,OxyX,OxyY); // 定义一个单位矩阵,坐标原点在表盘中央 // 参数定义为宽度比例为 1 ,无任何的倾斜,高度比例 1 , xy 坐标为 100
//Math.rotate((50/180)*Math.PI) , 这里是要求旋转 50 度
// 公式为: X' = cos(50)*X + sin(50)*Y + tx; Y' = -sin(50)*X + cos(50)*Y + ty;
matrixH.Rotate(SystemTime.wHour*30+SystemTime.wMinute/2.0-180); // 时针旋转的角度度
Point pointsH[] = { Point(0, 0),Point(m_HourWidth, 0),Point(0, m_HourHeight)};
// 该矩形是 / 被旋转的对象(注意:三个点可以确定一个矩形)
//Matrix.translate(tx1, ty2); // 这里的 tx1 和 ty1 是所要递增或递减原有 tx 和 ty 的数值,相当 // 平移旋转中心坐标
matrixH.Translate(-m_HourWidth/2,-m_HourHeight/6);
matrixH.TransformPoints( pointsH, 3); // 用该矩阵转换 points, 即将矩形转换完毕并 保存在这三个点中
graph.DrawImage (m_pImageHHour,pointsH, 3);// 显示该图片在 pointsH 矩形区域中
Matrix matrixM(1,0,0,1,OxyX,OxyY); // 定义一个单位矩阵,坐标原点在表盘中央
matrixM.Rotate(SystemTime.wMinute*6-180); // 分针旋转的角度度
Point pointsM[] = { Point(0, 0),Point(m_MinuWidth, 0),Point(0, m_MinuHeight)};
matrixM.Translate(-m_MinuWidth/2,-m_MinuHeight/6);
matrixM.TransformPoints( pointsM, 3); // 用该矩阵转换 pointsM
graph.DrawImage (m_pImageHMinu,pointsM, 3);
Matrix matrix(1,0,0,1,OxyX,OxyY); // 定义一个单位矩阵,坐标原点在表盘中央
matrix.Rotate(SystemTime.wSecond*6-180); // 秒针旋转的角度度
Point pointsS[] = { Point(0, 0),Point( m_SecWidth,0),Point(0,m_SecHeight )};
matrix.Translate(-m_SecWidth/2,-m_SecHeight/7);
matrix.TransformPoints( pointsS, 3); // 用该矩阵转换 pointsS
graph.DrawImage (m_pImageHSec,pointsS, 3);
// 下面是时间数字显示,他的工作原理是截取原始图片上的图放在相应位置,比如 1 ,:图片 // 都是截取过来的
// 函数: Status DrawImage( Image* image, INT x, INT y, INT srcx, INT srcy,
// INT srcwidth, INT srcheight, Unit srcUnit);
// 参数说明 (x,y) 用来指定图像 image 显示的位置,这个位置和 image 图像的左上角点相对应。
// srcx 、 srcy 、 srcwidth 和 srcheight 用来指定要显示的源图像的位置和大小, srcUnit 用来指 // 定所使用的单位,默认时使用 PageUnitPixel ,即用像素作为度量单位
//H
graph.DrawImage(m_pImageNum,0, 0, 14*(SystemTime.wHour/10), 0,14,23,UnitPixel); // 该函数从 m_pImageClock 中剪切指定 rect 中的像素 draw 到指定位置
graph.DrawImage(m_pImageNum,20,0,14*(SystemTime.wHour%10),0,14,23,UnitPixel); // 该函数从 m_pImageClock 中剪切指定 rect 中的像素 draw 到指定位置
graph.DrawImage(m_pImageNum,20*2,0, 140, 0,14,23,UnitPixel);
// 该函数从 m_pImageClock 中剪切指定 rect 中的像素 draw 到指定位置 M
graph.DrawImage(m_pImageNum,20*3,0,14*(SystemTime.wMinute/10),0,14,23,UnitPixel); // 该函数从 m_pImageClock 中剪切指定 rect 中的像素 draw 到指定位置
graph.DrawImage(m_pImageNum,20*4,0,14*(SystemTime.wMinute%10), 0,14,23,UnitPixel); // 该函数从 m_pImageClock 中剪切指定 rect 中的像素 draw 到指定位置
graph.DrawImage(m_pImageNum,20*5,0,140,0,14,23,UnitPixel);
// 该函数从 m_pImageClock 中剪切指定 rect 中的像素 draw 到指定位置 S
graph.DrawImage(m_pImageNum,20*6,0,14*(SystemTime.wSecond/10),
0,14,23,UnitPixel); // 该函数从 m_pImageClock 中剪切指定 rect 中的像素 draw 到指定位 / 置
graph.DrawImage(m_pImageNum,20*7,0,14*(SystemTime.wSecond%10),
0,14,23,UnitPixel); // 该函数从 m_pImageClock 中剪切指定 rect 中的像素 draw 到指定位 / 置
// 绘画结束
// 设置窗体的透明特性
SIZE sizeWindow={m_BakWidth,m_BakHeight};
POINT ptSrc={0,0};
DWORD dwExStyle=GetWindowLong(m_hWnd,GWL_EXSTYLE);//GWL_EXSTYLE;// 获得扩展窗口 风格。
if((dwExStyle&0x80000)!=0x80000)//dwExStyle&0x80000 显示 alpha 透明通道,如 // 为 0x80000 窗体为透明,否则设置为透明
SetWindowLong(m_hWnd,GWL_EXSTYLE,dwExStyle^0x80000);
BOOL bRet=FALSE;
//The UpdateLayeredWindow function updates the position, size, shape, content, and //translucency of a layered window.
// 刷新窗口函数
bRet= UpdateLayeredWindow( m_hWnd,hdcScreen,&ptWinPos,
&sizeWindow,m_hdcMemory,&ptSrc,0,&m_Blend,2);
// 释放环境
graph.ReleaseHDC(m_hdcMemory);
::ReleaseDC(m_hWnd,hdcScreen);
hdcScreen=NULL;
::ReleaseDC(m_hWnd,hdcTemp);
hdcTemp=NULL;
DeleteObject(hBitMap);
DeleteDC(m_hdcMemory);
m_hdcMemory=NULL;
return bRet;
}
void CGDIPClockDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
UpdateClockDisplay();
CDialog::OnTimer(nIDEvent);
}
void CGDIPClockDlg::OnRButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CDialog::OnRButtonUp(nFlags, point);
}
void CGDIPClockDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// 禁止显示移动矩形窗体框
::SystemParametersInfo(SPI_SETDRAGFULLWINDOWS,TRUE,NULL,0);
// 非标题栏移动整个窗口
SendMessage(WM_SYSCOMMAND,0xF012,0);
PostMessage(WM_NCLBUTTONDOWN,HTCAPTION,MAKELPARAM(point.x,point.y));
CDialog::OnLButtonDown(nFlags, point);
}
5 结语
以上,介绍了在 Windows 下采用 VC++ 进行 PNG 按钮的实现,并结合实例进行了实际运用说明。在传统的 DOS 环境中,要在打印机上打印一幅图时一件非常复杂的事情,用户必须根据打印机类型和指令规则向打印机输入数据。而 Windows 则提供 图形设备接口 GDI ,它是 Graphics Device Interface 的缩写。使得用户直接利用系统的 GDI 函数就能方便实现输入和输出,而不必关心与系统相连的外部设备类型。我们利用 GDI 所提供的众多函数就可以方便的在屏幕、打印机及其它输出设备上输出图形,文本等操作,能够方便的绘制漂亮的图形界面。