龙腾世纪起源时长的游戏时长大约多久

当前位置:&&&正文
找好玩手机游戏,上&
九阳神功起源公测时间 游戏什么时候公测
在好游快爆上浏览
安装好游快爆,资讯随身看
扫描右侧二维码下载,您可以
· 掌握好玩新游资讯· 查找热门手游攻略· 强大工具助你成功
什么时候公测,在哪里可以下载?蜗牛游戏3D武侠大作《九阳神功起源》封测即将开启,而封测期间,大部分玩家是无法参与其中的。普通玩家想要体验游戏只有等不删档测试或者公测才行,那么什么时候公测呢?下面小编就来给大家预测一下九阳神功起源的公测时间。
问:九阳神功起源什么时候公测?
答:目前游戏将于<font color="#ff日正式开启IOS版首轮封测,测试时间为10天。至于公测时间,官方还没有相关的爆料,不过一般一个游戏的正式上架会经过(精英内测)&&一测&&二测&&(三测)&&不删档测试&&公测。
目前游戏即将进行测首测,后续还将经历二测、保不准还有三测和四测。反正封测结束后才会进行不删档测试,而只有当游戏正在稳定下来后才会进行公测。
但是很多游戏的不删档测试即是公测,因为既然已经不删档了,那就跟公测其实也没差别了,那时玩家基本都可以玩了。
所以,如果按照一般游戏的上架流程,《九阳神功起源》仅仅前面几轮的测试至少需要在3个月左右的时间,再考虑到&蜗牛&公司一贯的速度,小编预计公测时间很可能要拖到2017年后了,小伙伴们耐心等待吧。
想查看更多游戏内容,请继续关注4399专区,或上百度搜索&<font color="#ff九阳神功起源&即可找到我们的专区,查看相关资料和攻略。
下载&&,掌握最新动态,还有更多新游热游开测爆料
游戏下载4399手游官方微信(微信号:sj4399)要你好玩,要你好看!独家礼包,新鲜爆料!
相关文章推荐更多"九阳神功:起源九阳神功起源公测"相关内容
热门游戏推荐
热门关键字
热门游戏专区
扫描二维码,关注4399手游微信刺客信条起源PC配置公布 GTX660即可畅玩
时间:17-10-06 09:58 来源:3DM 作者:未知
&  继《恶灵附身2》后,今年10月发售的另外一个大作《:起源》,PC配置也于今日曝光。
  负责《刺客信条:起源》PC版优化的团队是育碧基辅工作室,他们&工作非常努力&,确保本作的最低配置和前作《刺客信条:枭雄》保持一致。此外还要拓宽各种支持的机器,带来额外的帧数选择,从30FPS到不限帧数。
  据制作人Jose Araiza,最低配置满足玩家在游戏默认画质下实现720P和30FPS。而电脑配置非常好的玩家可以实现4K/30帧或更高。
  开发团队还专注在PC和主机平台上的动态分辨率渲染,确保游戏能尽可能地达到开发团队的目标帧数。不过官方确认PC版方面,玩家可以关闭动态分辨率渲染。
  &PC版玩家,实际上能选择他们想达到的最低帧数,比如30,45,或者是60FPS。当然也可以选择他们的最大帧数,包括30,45,60,90FPS或完全不限帧数。&
  最低配置:
  系统:Win7 SP1,8.1,10,仅支持64位
  CPU:i5-2400s,2.5GHz或FX GHz
  显卡:GTX 660或R9 270
  内存:6GB
  分辨率:720P
  画质预设:最低
  联网:游玩并不需要时刻联网
  推荐配置:
  系统:Win7 SP1,8.1,10,仅支持64位
  CPU:i7-GHz或FX GHz
  显卡:GTX 760或R9 280x
  内存:8GB
  分辨率:1080P
  画质预设:高
  联网:游玩并不需要时刻联网
  尽管《刺客信条:起源》支持离线游玩,但需要连接一次到服务器上进行激活。通过后便可离线游玩。不过育碧还提到&一些在线功能&需要&日常连接到服务器&。我们知道《刺客信条:起源》是单机游戏,至于有哪些在线特性,育碧计划在晚些时候和正在开发的PC版其他更新公布。
  另外育碧表示《刺客信条:起源》首发时不支持SLI和多显卡交火,但会在发售后通过更新的形式追加上。游戏不支持DX12。
  首发时支持的N卡:
  GTX 600系列:推荐GTX 680或更好
  GTX 700系列:推荐GTX 780或更好
  GTX 900系列:推荐GTX 970或更好
  GTX 10系列:推荐GTX 1050或更好
  首发时支持的A卡:
  Radeon 200系列:最低R7 270或更好
  Radeon 300或Fury X系列:推荐R7 370或更好
  Radeon 400系列:推荐Radeon 460或更好
  PC版支持的手柄:
  Xbox 360
  Xbox One(原版和精英版)
  安装文件45GB,发售首日有更新,实际占用超过45GB
  PC版特性:
  性能测试工具(Performance Benchmark)
  这是《刺客信条》系列游戏首次自带一个性能测试工具,帮助玩家验证修改后的设定。游戏引擎会不断地记录和追踪在PC上的性能表现,这包括FPS,CPU、GPU使用率,玩家对自己硬件的性能可以一目了然。
  动态分辨率渲染
  动态分辨率渲染,通过不断调整分辨率可以帮助玩家随时都尽可能地实现目标最高帧数。大部分情况下,玩家不会发觉。
  游戏分辨率
  如果玩家的电脑够强,能在超高分辨率(比如4K)上流畅运行游戏,但你的显示器并不是4K的,那么你可以开启过采样(Oversampling)或超级采样(Supersampling)以更高分辨率运行。
  当然玩家也可以强制游戏以较低的分辨率运行。
  支持键鼠和手柄改建
  支持多显示器
  PC版《刺客信条:起源》预载时间稍后公布。
  本作将于日发售,登陆PC,PS4和Xbox One。
翻页快捷键:←|→
关注 52PK游戏网 微信公众号第一时间获取最火游戏激活码 最有趣游戏资讯
*网友评论仅代表其个人看法,并不表明本站同意其观点及描述。
最新游戏专题
最新热点资讯
单机下载排行起源的游戏时长大约多久啊?全任务,不搞全收集,包括DLC【单机游戏吧】_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0可签7级以上的吧50个
本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:1,309,488贴子:
起源的游戏时长大约多久啊?全任务,不搞全收集,包括DLC
起源的时长大约多久啊?全任务,不搞全收集,包括DLC
东方玄幻题材的单机游戏,超级耐玩!全部都是你没有玩过的新鲜玩法,马上体验.单机游戏无需下载,注册就能玩!
八小时,怎么可能
没有全收集这一说,起源没什么收集品,跟巫师3一样都是地图问号,问号全清的话我现在120个小时还在玩最后一个DLC
50-80小时差不多
我现在玩了50多小时,35级还有四五个省份没走,估计本体通关接近60小时。dlc没玩据说要六七小时
关注逆天邪神吧,并且捧场5000T豆(含历史行为),
「逆天邪神 」游戏,同名小说改编的东方玄幻游戏.全新双号玩法:一个游戏两个号, 两个号同玩,一个号免单.
没意思,删了
贴吧热议榜
使用签名档&&
保存至快速回贴《刺客信条:起源》全新4K实录曝光 游戏10.27发售 _多玩新闻中心_多玩游戏网博客访问: 74065
博文数量: 26
博客积分: 1410
博客等级: 上尉
技术积分: 220
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: C/C++ 13:18:46
第一章 Windows编程基础
本章目的是介绍WINDOWS编程基础。在本章结束时,你应该能够很好的工作了,虽燃可能是简单的WINDOWS程序。你需要有C语言的基础知识,我很少将C++的代码扩充到程序中。当然,由于WINDOWS本身就是面向对象的,一点类的知识是不会对你有什么损害的。如果你不熟悉C++,没有关系,我想你还是能从我这里学到大部分的东西。所有的程序代码都通过了MICROSOFT VISUAL C++6.0的编译,如果你还没有合适的编译器,那就用它好了,还是很棒的。开动吧!
多数的Windows程序都需要Windows.h和Windowsx.h这两个头文件,要确保使用它们。当然,你还需要其它的标准的C的头文件,象stdio.h,conio.h等。除了这些,你还会经常看到在程序的开始有这样一行代码:
#define WIN32_LEANAND_MEAN&
它表示Windows的头文件中将拒绝接受MFC的东西,这将加速你的build时间。如果你从没有打算应用MFC在你的游戏编程中,那就使用它吧。如果你以前从没有看过这种声明类型——在#define后,直接加上一个“单词”,那么它的作用就是有条件编译。看看下面的例子:
#ifdef DEBUG_MODE&&& printf("Debug mode is active!");#endif&
意思是:如果程序的开始包含#define DEBUG_MODE,那么就printf(),否则退出。这个对于你跟踪程序的逻辑错误是很有帮助的。
WinMain()函数
DOS下的C语言从main()开始,Windows下的C语言从WinMain()开始,一个空的WinMain()函数是这样的:
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){&&& return(0);}&
一个函数即使什么也没做,也应该返回一个值。是的,有好多东西我们不熟悉。首先的首先,WINAPI是个什么声明?WINAPI是在windows.h头文件中定义的一个宏,它把函数调用翻译成正确的调用约定。当我们在程序中需要用到汇编语言的时候,我们在来深究它好了,记住,如果要用WinMain(),就必须要有WINAPI。
下一步让我们来看看括号里的四个参数:
◎ HINSTANCE hinstance:HINSTANCE是一个句柄类型的标识符。变量hinstance是一个整数,用于标识程序实例。Windows设置这个参数的值,并把它传递给你的程序代码。很多Windows函数都要用到它。
◎ HINSTANCE hPreInstance:你不用担心这个参数,它已经被废掉了。它只是为古老的Windows版本服务的。你将还会看到类似的情况。
◎ LPSTR lpCmdLine:是一个指向字符串的指针,它仅在程序名是从DOS命令行输入或是从Run对话框中输入时才起作用。因此,很少被程序代码所用。
◎ int nCmdShow:决定了窗口在初始显示时的状态。Windows通常给这个参数分配一个值。通常是SW_打头的一个常量。例如SW_SHOWNORMAL表示默认状态,SW_MAXINIZE或SW_MINIMIZE分别表示最大和最小模式等等。
以上大体上是WinMain()的参数的介绍。下面对变量、参数、常量、类等的命名方法介绍一下。
当你在DOS下编程的时候,你不必担心其它程序的运行,因为DOS是独占模式。但你在Windows平台上编程时,你不得不考虑其它正在运行的程序。鉴于此,Windows通过“消息”来连接操作申请和具体操作。简单的说,就是我们指示程序或程序本身向Windows发出诸如移动窗口、放大窗口、关闭窗口等申请,Windows再根据申请,考察实地情况,拒绝或发出指令,让程序(计算机)作出相应的动作。再例如,鼠标随时向Windows发出消息,汇报光标位置,左键或右键是否按下等,Windows再根据消息作出相应的反应。总之,无论何时,Windows都要随时掌控所有的消息,而且,Windows是一直不断地接收到各种消息。
这种功能是通过一种被命名为CALLBACK函数类型实现的。不用害怕,消息传递来传递去都是由Windows自己完成的,你只要声明一个CALLBACK函数就可以了,就像WINAPI用在WinMain()前一样。如果还没有明白,不要紧,往下看你就明白了。现在,我要离开这个话题一会儿,因为你只有先建立窗口(Windows),传递消息才有可能实现。
二、窗口类
现在谈论一点C++的知识,因为要想建立一个窗口,你就得先建立一个窗口类。窗口类包含所有的有关窗口的信息,如用什么样的鼠标符号,菜单样式等等。开发任何一个窗口程序,都离不开窗口类的建立。为了达到此目的,你必须填写WNDCLASSEX结构。EX的意思是“扩充”的意思,因为有一个老的结构叫作WNDCLASS,这里,我们将使用WNDCLASSEX结构,它的样子如下:
typedef struct _WNDCLASSEX {&&&&&&& UINT cbS&&&&&&& UINT&&&&&&& WNDPROC lpfnWndP&&&&&&& int cbClsE&&&&&&& int cbWndE&&&&&&& HANDLE hI&&&&&&& HICON hI&&&&&&& HCURSOR hC&&&&&&& HBRUSH hbrB&&&&&&& LPCTSTR lpszMenuN&&&&&&& LPCTSTR lpszClassN&&&&&&& HICON hIconSm;} WNDCLASSEX;&
这个结构有不少成员,讨厌的是,你必须为窗口类设置每一个成员。莫发愁,纸老虎一个。让我们来个速成。
※ UINT cbSize:指定了以字节为单位的结构的大小。这个成员是通过sizeof(WNDCLASSEX)实现的。你将会经常看到它,尤其是你使用了DirectX。
※ UINT style:指定了窗口的风格。它经常被以CS_打头的符号常量定义。两种或两种以上的风格可以通过C语言中的“或”(|)运算符加以组合。大多数情况我们只应用四种风格,出于对文章长度的考虑,我们只列出这四种。若你还需要其它的,到MSDN里找一下好了。当然前提是你使用的是Visual C++。
◎ CS_HREDRAW:一旦移动或尺寸调整使客户区的宽度发生变化,就重新绘制窗口。
◎ CS_VREDRAW:一旦移动或尺寸调整使客户区的高度发生变化,就重新绘制窗口。
◎ CS_OWNDC:为该类中的每一个窗口分配一个唯一的设备上下文。
◎ CS_DBLCLKS:当用户双击鼠标时向窗口过程发送双击消息。
※ WNDPROC lpfnWndProc:是指向窗口过程的指针。一般都指向CALLBACK函数。如果你没有用过函数指针,简单理解为函数的地址就是函数的名字,名字后面别带括号。
※ int cbClsExtra:它是为类保留的额外信息 。大多数程序员不用它,你在在写游戏程序时也不太可能用它,所以,设为0好了。
※ int cbWndExtra:同上一个差不多,设为0好了。
※ HANDLE hInstance:是指向窗口过程实例的句柄。同时也是WinMain()函数的参数之一。应该设置为hinstance。
※ HICON hIcon:指向窗口图标的句柄,它通常被LoadIcon()函数设置。在你学会如何在你的程序中使用资源前,你先设置成如下样子:LoadIcon(NULL,IDI_WINLOGO)。当然,还有一些其它的IDI_打头的符号常量,可以自己去帮助文件里寻找。
※ HCURSOR hCursor:指向窗口光标的句柄,它通常被LoadCursor()函数设置,在你学会如何在你的程序中使用资源前,你先用Windows默认的吧,LoadCursor(NULL,IDC_ARROW)。
※ HBRUSH hbrBackground:当你的窗口过程得到消息,要求刷新(或重画)窗口时,至少要用一种纯色或“brush”(画刷)重画窗口区域,画刷是由参数确定的。你可以使用GetStockObject()函数调用几种常备的画刷,如BLACK_BRUSH, WHITE_BRUSH, GRAY_BRUSH等。现在,你就用GetStockObject(BLACK_BRUSH)吧。也许你觉得我说的太简单了,是因为不想把开始弄得太复杂。在以后的篇幅里会详细讲述的。
※ LPCTSTR lpszMenuName:如果你想建立一个有下拉菜单的窗口,你得给这个参数赋一个菜单名称(这涉及到资源),由于你还不知道怎么创建菜单,你就先用NULL设置成一个没有菜单的窗口吧。
※ LPCSTR lpszClassName:很显然,你需要给类起个名字,随你便,如“**”。要用双引号引上。
※ HICON hIconSm:指向小图标的句柄。小图标用来显示在窗口的标题栏里。要用到LoadIcon()函数,现在,先用Windows默认的吧,LoadIcon(NULL,IDI_WINLOGO)。
好了,现在你关于WNDCLASSEX结构知道的已经差不多了,你可以自己设置它了。下面是一个例子:
WNDCLASSEX sampleC // declare structure variable sampleClass.cbSize = sizeof(WNDCLASSEX); // always use this!sampleClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; // standard settingssampleClass.lpfnWndProc = MsgH // we need to write this!sampleClass.cbClsExtra = 0; // extra class info, not usedsampleClass.cbWndExtra = 0; // extra window info, not usedsampleClass.hInstance = // parameter passed to WinMain()sampleClass.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Windows logosampleClass.hCursor = LoadCursor(NULL, IDC_ARROW); // standard cursorsampleClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // a simple black brushsampleClass.lpszMenuName = NULL; // no menusampleClass.lpszClassName = "Sample Class" // class namesampleClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // Windows logo again&
我想,现在你已经有点儿不太崇拜Windows程序员了吧。言归正传,有一点我得提醒你,注意函数GetStockObject()前的(HBRUSH)类型配置,这是因为GetStockObject()可以调用其它的对象,不仅仅是“brush”,所以你需要一个HBRUSH类型配置。在Visual C++旧版本里不用配置,但新的6.0版本需要它,否则会编译出错。
下一件事是注册这个窗口类,只有这样,你才能创建新的窗口。十分简单,你只需要调用一个RegisterClassEX()函数,它只有一个参数,就是你的窗口类的地址(名字),根据我上面给的例子,这里应该这样:
RegisterClassEx(&sampleClass);&
我们的窗口类创建完了,我们可以用它创建一个窗口了。
三、创建窗口
好消息,创建窗口你所要做的只是调用一个CreateWindowEx()函数。坏消息是,这个函数有好多的参数。以下便是函数原形:
HWND CreateWindowEx(&&&&&&& DWORD dwExStyle, // extended window style&&&&&&& LPCTSTR lpClassName, // pointer to registered class name&&&&&&& LPCTSTR lpWindowName, // pointer to window name&&&&&&& DWORD dwStyle, // window style&&&&&&& int x, // horizontal position of window&&&&&&& int y, // vertical position of window&&&&&&& int nWidth, // window width&&&&&&& int nHeight, // window height&&&&&&& HWND hWndParent, // handle to parent or owner window&&&&&&& HMENU hMenu, // handle to menu, or child-window identifier&&&&&&& HINSTANCE hInstance, // handle to application instance&&&&&&& LPVOID lpParam // pointer to window-creation data);&
首先:函数的返回值。也就是函数的类型。是不是所有创建窗口用的函数的类型的讨厌样子都感觉亲切了一点儿?不要紧,你会习惯的,肯定比你想象的速度要快。这里返回的类型是HWND,是一个窗口的句柄(句柄就是窗口的标识符)。你将把CreateWindowEx()的返回值传递给一个窗口的句柄,就像一个参数一样。现在,我们来琢磨一下这些参数,很多根据名字就知道它是干什么的了。
※ DWORD dwExStyle:扩充的窗口风格。你将很少使用扩充的窗口风格,所以多数时间你会把它设置为NULL。如果有兴趣,查一下帮助文件,可以一试由WS_EX_打头的扩充风格。
※ LPCTSTR lpClassName:还记得你的窗口类的名称吗?再用一次。
※ LPCTSTR lpWindowName:将显示在窗口的标题栏里的简短文字。
※ DWORD dwStyle:窗口的风格。它将允许你详细的描绘你所要创建的窗口的风格。有很多风格你可以利用哦,都是以WS_打头的,你可以利用(|)符号组合利用它们。我将在这儿介绍几个常用的。
◎ WS_POPUP 指定一个弹出的窗口。
◎ WS_OVERLAPPED 指定一个具有标题栏和边界的重叠窗口。
◎ WS_OVERLAPPEDWINDOW 指定一个具有所有标准控件的窗口。
◎ WS_VISIBLE 指定一个初始时可见的窗口。
看得出,WS_OVERLAPPEDWINDOW是一个组合体。简单的说,你可以按照如下规律:如果你要创建一个可以最大化、最小化、随意改变大小等等地窗口,就选择WS_OVERLAPPEDWINDOW;如果你只想要一个具有标题栏、可改变大小的窗口,就选择WS_OVERLAPPED;如果你只想要一个光秃秃的窗口,就选择WS_POPUP;如果你只想显示一个黑色的大方框,可能你要用它写一个全屏的游戏,选择WS_VISIBLE是没错的。
※ int x,y:你所要创建的窗口的左上角的坐标。
※ int nWidth,nHeight:猜也猜到了,是窗口的长和高。
※ HWND hWndParent:指向父窗口的句柄。你若想在窗口下再建立一个窗口,那么第一个窗口就叫父窗口。咱先建立一个主窗口,所以设置为NULL,也就意味着Windows桌面是父窗口。
※ HMENU hMenu:这是用在窗口上的菜单句柄。若你学会建立和使用资源,即建立自己的菜单,你可以用LoadMenu()函数调用自己的菜单资源。目前,先设为NULL。
※ HINSTANCE hInstance:是一个名柄,它指向由Windows传递给WinMain()的实例。
※ LPVOID lpParam:对于游戏编程来说,没有什么用的东西,只有简单的窗口程序用到它。设置为NULL好了。
我们现在万事具备,东风也有了。我先给个示例:
HWNDif (!(hwnd = CreateWindowEx(NULL,&&&&&&&&&&&&&&&& // extended style, not needed&&&&&&&&&&&&&&&&&&&&&&&&&&& "Sample Class",&&&&&& // class identifier&&&&&&&&&&&&&&&&&&&&&&&&&&& "Sample Window",&&&&& // window title&&&&&&&&&&&&&&&&&&&&&&&&&&& WS_POPUP | WS_VISIBLE,// parameters&&&&&&&&&&&&&&&&&&&&&&&&&&& 0, 0, 320, 240,&&&&&& // initial position, size&&&&&&&&&&&&&&&&&&&&&&&&&&& NULL,&&&&&&&&&&&&&&&& // handle to parent (the desktop)&&&&&&&&&&&&&&&&&&&&&&&&&&& NULL,&&&&&&&&&&&&&&&& // handle to menu (none)&&&&&&&&&&&&&&&&&&&&&&&&&&& hinstance,&&&&&&&&&&& // application instance handle&&&&&&&&&&&&&&&&&&&&&&&&&&& NULL)))&&&&&&&&&&&&&& // who needs it?&&& return(0);&
你可能会在游戏编程中用上这这段代码,因为它是一个弹出式窗口。注意,我用了if形式,目的是一旦CreateWindowsEX()函数失灵,返回一个NULL,也就意味着如果窗口由于某种原因不能被建立,那么WinMain()就被简单的返回,程序结束。现在我们学会了足够的知识建立一个小有功能的窗口了。还记得我们建立窗口类“sample class”时,一个指向“CALLBACK”类型函数的指针吗?对,是“lpfnWndProc”。要想让你的窗口真正做点事儿,我们还得来处理一下它指向的“窗口过程”函数。
四、显示窗口
CreateWindowEx()从内部创建窗口,但并不显示它。要显示这个窗口,必须调用另外两个函数:ShowWindow()和UpdateWindow()。头一个设置窗口的显示状态,后一个则更新窗口的客户区。对于程序的主窗口,ShowWindow()必须被调用一次,调用代码如下:
ShowWindow(hwnd,nCmdShow);&
第一个参数是由CreateWindowEx()函数返回的窗口句柄;第二个参数就是窗口的显示模式参数,在☆WinMain()函数中提到过,就不重复了。UpdateWindow()函数的调用代码如下:
UpdateWindow(hwnd);&
参数hwnd同ShowWindow()函数的hwnd一样。
五、消息的处理
我已经说过消息在窗口里的作用了,下面让我们来仔细学习一下它。处理消息的函数结构如下:
LRESULT CALLBACK MsgHandler(&&&&&&&&&&&&&&&&&&&&&&&&&&&& HWND hwnd,&&&& // window handle&&&&&&&&&&&&&&&&&&&&&&&&&&& UINT msg,&&&&& // the message identifier&&&&&&&&&&&&&&&&&&&&&&&&&&& WPARAM wparam, // message parameters&&&&&&&&&&&&&&&&&&&&&&&&&&& LPARAM lparam& // more message parameters);&
这个LRESULT类型要求返回一个32位的整数。实际取值依赖于消息,但是这个值很少在应用程序代码中得到应用。以前我们谈到过一点CALLBACK协定,它的参数很简单:
※ HWND hwnd:是接收消息的窗口的句柄,也是由CreateWindowEx()函数返回的句柄。
※ UINT msg:这是一个消息标识符,都是以WM_打头的符号常量,意思是“Windows Message”。很多的,这里只介绍一些常用的:
◎ WM_ACTIVATE:一个新窗口被激活。
◎ WM_CLOSE:一个窗口被关闭。
◎ WM_COMMAND:一个菜单功能被选择。
◎ WM_CREATE:一个窗口被建立。
◎ WM_LBUTTONDBLCLK:鼠标左键被双击。
◎ WM_LBUTTONDOWN:鼠标左键被按下。
◎ WM_MOUSEMOVE:鼠标被移动。
◎ WM_MOVE:一个窗口被移动。
◎ WM_PAINT:窗口的一部分需要重画。
◎ WM_RBUTTONDBLCLK:鼠标的右键被双击。
◎ WM_RBUTTONDOWN:鼠标的右键被按下。
◎ WM_SIZE:窗口的大小被改变。
◎ WM_USER:做你想做的。
※ WPARAM wparam,LPARAM lparam:消息参数。它们提供有关消息的附加信息,这两个值对于每条消息来说都是特定的。
你要把所有要发生的消息都写进程序代码的话,我想你可能已经累疯了。我想我会的。感谢上帝,Windows提供了默认消息处理,如果你没有任何特殊的消息需要处理了,你总是要用DefWindowPorc()函数的,下面给一个最简单的例子,没有任何特定的消息要处理的例子:
LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam){&&& return(DefWindowProc(hwnd, msg, wparam, lparam));}&
简单吧!但通常你都需要处理一些自己的消息,你要写自己的程序代码,然后返回0,来告诉程序你干完了。下面是一个例子,当窗口建立时,你调用了一个初始化的函数Initialize_Game(),然后返回0,最后告诉程序自己处理那些默认的消息吧:
LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam){&&& if (msg == WM_CREATE)&&& {&&&&&&& Initialize_Game();&&&&&&& return(0);&&& }&&& return(DefWindowProc(hwnd, msg, wparam, lparam));}&
你很可能需要一个“switch”结构来手动完成你想要控制的消息,然后把剩下的交给DefWindowProc()去做。大功告成前,我不得不提醒您一件事,就是怎样使你的消息控制得到响应呢?
六、读取消息队列
这里先给你一个switch结构的例子吧:
LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam){&&& switch(msg)&&& {&&& case WM_CREAT:&&&&&&& [初始化游戏]&&&&&&& return 0;&&& case WM_PAINT:&&&&&&& [画一架飞机]&&&&&&& return 0;&&& case ……………………&&&&&&& ……………………&&& }&&& return(DefWindowProc(hwnd, msg, wparam, lparam));}&
在进入程序的主循环前,你需要看看你的消息控制(就是你在switch结构里编的那些),尤其是还没有用到的消息控制是否被机器存了起来,以备一旦用到,马上响应。做到正确的响应,你需要做几件事。首先你需要PeekMessage()函数。下面是它的原形:
BOOL PeekMessage(&&& LPMSG lpMsg,&&&&&&&&& // pointer to structure for message&&& HWND hWnd,&&&&&&&&&&& // handle to window&&& UINT wMsgFilterMin,&& // first message&&& UINT wMsgFilterMax,&& // last message&&& UINT wRemoveMsg&&&&&& // removal flags);&
这是一个布尔类型,也就是一个int型,不过只有两个值,TRUE和FALSE,如果有一条消息在队列中等待,函数返回TRUE,否则,返回FALSE。它的参数也很简单:
※ LPMSG lpMsg:这是一个MSG类型的指针变量。如果有消息在等待,消息信息将被填入该变量。
※ HWND hWnd:你所要检查的消息队列的窗口的句柄。
※ UINT wMsgFilterMin,wMsgFilterMax:索引第一个和最后一个消息,一般你都从第一个消息开始检索,所以把它们都设置为0好了。
※ UINT wRemoveMsg:一般来说,它有两个指,PM_REMOVE或者PM_NOREMOVE。使用前者会在消息被读取后从队列中移除,后者是继续保留。通常,我们选择前者PM_REMOVE。
真正处理消息时,你需要做两件事,很简单,第一件是TranslateMessage(),第二件是DispatchMessage()。它们的原形很相似:
BOOL TranslateMessage(CONST MSG *lpmsg);LONG DispatchMessage(CONST MSG *lpmsg);&
头一个是把消息翻译过来,第二个是从MSG结构中调用相应的信息。你只需要知道这么多。伴随着程序主循环的反复执行,如果有消息出现,你就调用这两个函数,函数MsgHandler()会安排好一切的。下面是个例子:
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){&&& TranslateMessage(&msg);&&& DispatchMessage(&msg);}&
没问题,你现在完全可以写一个窗口程序了。不坏吧?在结束本章前,我还有几点要提醒你。还记得我们在消息时,说要在后面进一步讨论它吗?那么怎样主动向Windows发送消息呢?
七、发送消息
有两种办法可以做到:PostMessage()函数或SendMessage()函数。
它们的原形很相似:
BOOL PostMessage(&&&&&&&&&&& HWND hWnd,&&&& // handle of destination window&&&&&&&&&&& UINT Msg,&&&&& // message to post&&&&&&&&&&& WPARAM wParam, // first message parameter&&&&&&&&&&& LPARAM lParam& // second message parameter);LRESULT SendMessage(&&&&&&&&&&& HWND hWnd,&&&& // handle of destination window&&&&&&&&&&& UINT Msg,&&&&& // message to post&&&&&&&&&&& WPARAM wParam, // first message parameter&&&&&&&&&&& LPARAM lParam& // second message parameter);&
它们的参数相同,并且和前面讲过的函数MsgHandler()的参数功能相同,就不重复了。现在我们只谈谈它们之间的区别。
PostMessage()被经常用来向队列中加入消息,成功,返回TRUE,否则,返回FALSE。它只是简单的把消息加入到队列中,然后返回。多数情况下,调用它将返回TRUE。
SendMessage()则有些不同,它并不是把消息加入到队列里,而是直接翻译消息和调用消息处理,直到消息处理完成后才返回。所以,SendMessage()比PostMessage()有更高的应急性。你想立刻干的事情,就应该调用它。消息是DOS和Windows编程之间重要的区别标志。
八、程序的流程
在DOS中,我们不必担心消息这种东西,不必担心多个程序同时运行,但在Windows里,你必须考虑这些。在Windows平台上编程,有一些不同于DOS下编程的地方。让我们看看下面这段虚拟的代码:
// main game loopdo{&&& // handle messages here&&& // ...&&& // update screen if necessary&&& if (new_screen)&&& {&&&&&&& FadeOut();&&&&&&& LoadNewMap();&&&&&&& FadeIn();&&& }&&& // perform game logic&&& WaitForInput();&&& UpdateCharacters();&&& RenderMap();} while (game_active);&
假设FadeOut()函数这样工作:当函数被调用,在一秒内屏幕图象暗淡下来,当屏幕完全黑了,函数返回。LoadNewMap()调用一个新的图象;FadeIn()使屏幕逐渐亮起来,好显示新图象。当有键子按下,调用WaitForInput()函数,再继续调用下去。这在DOS游戏编程里是合情合理的,但在Windows下不行。为什么呢?让我们看看新画面诞生的过程。画面逐渐变黑,调用图片,逐渐恢复。这大概要2秒钟,用户可以等待,也可能要移动一下窗口,但程序只专心的干调用图片的工作,不会对窗口的移动作出反应。这是很糟糕的,你做了机器不知道的事情,这可能导致系统崩溃,我们必须要让机器对用户的任何操作作出正确的反应。不多说了,总之你要换一换脑筋,如果你从来就没在DOS下编过程序,那正好,你赶上潮流了!
本章我们讲了Windows编程的基础,虽然只是一个空白的窗口,但包含了最基本的东西。接下来的连载我们将学习创建资源和利用资源,你就可以用有自己风格的光标、图标、声音、菜单等等,还要生成一个EXE文件呢!第二章 使用Win32程序资源
欢迎继续收看!通过本章题目可能你已经猜出了本章论题,我将教会你在Windows程序中使用资源。简单的讲,资源即数据,它们通常是和程序的EXE文件相关联的,但是它们又是独一无二的。首先,资源在运行过程中不能被修改。它们实际上都是只读文件,而且程序代码不能够直接访问它们。另外,资源并不在程序的数据区内。在装入时,程序资源通常在某个磁盘文件中,直到程序需要它们时才被装入。使用资源是一件很容易的事情,并且它的妙处无穷。Windows为我们提供了大量的资源类型,但我们这里只学一些最常用,最容易的:图标(icon)、光标(cursor)、位图(bitmap)、菜单(menu)和字符串(string)。此后,我还将教你建立自己风格类型的资源,使你为所欲为。
重复一下,要想看懂本章,你得有点C语言的基础。C++有时用一用,但不影响你学习本章内容。并且我假定你已经读过了上一章内容“Windows编程基础”。还是用Microsoft Visual C++的编译器。
在进行细节之前,我们要先搞懂怎样要编译器知道它所要编译的资源类型。方法是使用称之为资源脚本的特殊文件,它是一个简单的文本文件,可以手工编辑,也可以让Visual C++自动编辑,或者你用其它的自动编辑器编辑。无论如何,资源脚本文件要有一个.rc的扩展名。大多数的脚本文件都从定义资源行开始,最简单的资源行通常要用到资源类型,就像这样:
[identifier]&& [resource type]&& [filename]& 【标识符】&&&&&& 【资源类型】&&&& 【文件名称】&
标识符可以用两种方式表示:一种是能表示资源意思的字符串,另一种是在资源相对应的头文件中用#define定义过的数字常量。如果你选择数字常量,这通常是一个好主意,别忘了把相应的头文件加入到你的资源脚本。资源脚本使用C语言风格的文件格式好像比较容易理解。以下是一个比较简单的资源脚本实例:
#include "resource.h"// iconsICON_MAIN ICON myicon.ico// bitmapsIMG_TILESET1 BITMAP tileset.bmpIMG_TILESET2 BITMAP tileset2.bmp&
例子中的ICON_MAIN和IMG_TILESET是字符串呢,还是数字常量?这无伤大雅,编译器编译的时候会自己判断。如果发现在头文件中有#define的定义,那就认为是字符常量,否则,就是字符串。如果有些迷茫,不要紧。我将解释我们要用到的每一个资源类型。如果您觉得麻烦那让我们用全自动的资源插入系统吧!(在Visual C++中,在“插入”下拉菜单中,选择“资源”)现在你知道了建立资源脚本的基础知识,让我们开始进一步的行程吧!
图标和光标
你每天在使用的大多数的Windows程序,都有自己的图标,简单的说,就是EXE文件同这个图标资源相关联了,独特风格的光标也是如此。你已经知道图标的脚本行样子了,光标的和它很相似,看看吧:
[identifier] CURSOR [filename][identifier] ICON [filename]&
增加了一行脚本行后,也就是意味着你的EXE文件又多了一个关联。也就是说你的EXE文件要根据标识符去相应的位置寻找相应的文件[filename]。你可以使用任何你喜欢用的图标/光标编辑器去编辑相应的文件。我通常利用Visual C++中的编辑器。
把资源脚本做出来后,并没有完事儿,因为你还不知道怎么调用相应的资源,要想知道图标和光标是怎样在你的程序中被调用的,让我们回过头来,看一看上一章中的窗口类(windows class)文件:
WNDCLASSEX sampleC // declare structure variablesampleClass.cbSize = sizeof(WNDCLASSEX);// always use this!sampleClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;// standard settingssampleClass.lpfnWndProc = MsgH // message handler functionsampleClass.cbClsExtra = 0; // extra class info, not usedsampleClass.cbWndExtra = 0; // extra window info, not usedsampleClass.hInstance = // parameter passed to WinMain()sampleClass.hIcon = LoadIcon(NULL, IDI_WINLOGO);// Windows logosampleClass.hCursor = LoadCursor(NULL, IDC_ARROW);// standard cursorsampleClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // a simple black brushsampleClass.lpszMenuName = NULL; // no menusampleClass.lpszClassName = "Sample Class" // class namesampleClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO);// Windows logo again&
还记得它吧?这个hIcon用来表示整个程序;hIconSm用来出现在开始菜单和窗口的标题栏里;hCursor用来表示在你所创建的窗口中的光标的样子。我向你保证,我们要实现自己的风格一点都不复杂。下面是它们的原形:
HICON LoadIcon(&&&&&&& HINSTANCE hInstance,&&// handle to application instance&&&&&&& LPCTSTR lpIconName& &&// icon-name string or icon resource identifier);HCURSOR LoadCursor(&&&&&&& HINSTANCE hInstance, &&// handle to application instance&&&&&&& LPCTSTR lpCursorName &&// name string or cursor resource identifier);&
返回的类型是它们自己相对应的类型。其内部的参数都很直观:
※ HINSTANCE hInstane:但程序执行时,把图标或光标相对应的句柄传递给WinMain()函数。若要使用Windows的标注图标或光标,就把它设置为NULL。
※ LPCTSTR lpIconName,lpCursorName:是你要调用的资源的标识符字符串。如果你在脚本文件中用字符串直接作为标识符,就直接传送它好了;如果你是用数字常量,就要使用一个Windows头文件里的宏MAKEINTRESOURCE()来把它们协调一致。
让我们看一看下面的资源脚本,是关于图标和光标的:
#include "resource.h"ICON_MAIN ICON myicon.icoCURSOR_ARROW CURSOR arrow.cur&
如果标识符ICON_MAIN合CURSOR_ARROW在头文件resource.h中没有被#define定义过,那么我们将直接传递它给资源调用函数,象这样:
sampleClass.hIcon = LoadIcon(hinstance, "ICON_MAIN");&
如果它们在头文件resource.h中这样定义过:
#define ICON_MAIN 1000#define CURSOR_ARROW 2000&
你就必须用宏MAKEINTRESOURCE()把它们转变为LPCTSTR类型。下面给出你几种意义相同的调用方法,都是正确的喔!
sampleClass.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(ICON_MAIN));or...sampleClass.hIcon = LoadIcon(hinstance,MAKEINTRESOURCE(1000));or...int ident = 1000;sampleClass.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(ident));&
关于图标和光标的调用,你学的差不多了。就这个话题,我还想告诉你一件事儿。如果你除了在程序的开始设置光标外,在程序中还要设置光标,有一个简单的Windows函数可以完成它:
HCURSOR SetCursor(HCURSOR hCursor);&
仅仅一个参数,它是一个句柄,是在调用LoadCursor()时得到的,函数返回调用的上一个光标的句柄,如果没有设置过上一个光标,返回值是NULL。
想要往程序里添加图象,通过位图资源可能是最简单的办法了。位图是Windows之本,当然提供了一些函数来处理位图,请记住,如果你使用了太多的位图,你的EXE文件将要非常巨大。在资源脚本中设置位图同图标和光标没什么区别:
[identifier] BITMAP [filename]&
有一个函数LoadBitmap(),同LoadCursor()和LoadIcon()的用法很相似,它将得到一个句柄,由于我还没有讲过图形(graphics),就不具体说函数的功能了,你可以猜一猜它是怎样工作的,一旦你得到了图形句柄,你将怎样使用它呢?更多的留待以后再讲。不要担心,现在只是要你有点儿准备。下面看看我们还应该学点儿什么。
字符串表格
字符串表是我最喜欢的资源类型。正象你所想的:一个充满字符串的庞大表格。字符串表有很多用处。你可以用它存储你的文件名称,游戏中的人物对话,消息框中的文本,菜单中的文本等等。在资源脚本里建立一个字符串表很容易,就像这样:
STRINGTABLE{// entries go here}&
一个字符串表由几部分组成:一个标识字符串的数字;紧跟着一个逗号;然后是加了双引号的字符串本身。字符串表里的字符串被允许使用溢出符号,如
或 。注意,字符串表本身并没有标识符,所以每个程序只能有一个字符串表。一个简单的字符串表可能象下面这个样子:
// program informationSTRINGTABLE{&&& 1, "3D Space Game v1.0"&&& 2, "Written by The Masked Coder"&&& 3, "(C) 2000 WienerDog Software"}&
从程序的字符串表里调用字符串,将使用——你可能猜到了——LoadString()函数。这是它的原形:
int LoadString(&&& HINSTANCE hInstance, // handle to module containing string resource&&& UINT uID,&&&&&&&&&&& // resource identifier&&& LPTSTR lpBuffer,&&&& // pointer to buffer for resource&&& int nBufferMax&&&&&& // size of buffer);&
函数返回的实数是字符的数量,不包括空字符,它将被赋值到程序数据段的缓冲区中去,相当于字符串的长度。如果你调用了一个空字符串或者调用失败,都将返回0。下面来看看具体参数:
※ HINSTANCE hInstance:同以前的一样,你所有操纵项目的句柄。
※ UINT uID:你想要调用的字符串的数码标识符。
※ LPTSTR lpBuffer:指向接收字符串的字符数组的指针。
※ int nBufferMax:缓冲区的字节长度。如果被调用的字符串的长度大于缓冲区的长度,字符串将被按照缓冲区的大小缩减。
例如,调用“WienerDog Software’s copyright”的信息,代码应该如下:
char buffer[80];LoadString(hinstance, 3, buffer, sizeof(buffer));&
尽管在资源脚本中字符串使用数字声明,而不是标识符,但我通常在使用字符串表时,习惯于在头文件中用#define定义一下字符串的声明数字。针对上面的代码,我可能加一行:
#define ST_WIENERDOGCOPYRIGHT 3&
这样一来,用LoadString()函数时,你的程序代码更容易读懂,也使你的思路更加清晰。但也并不是意味着你必须为字符串表里的每一个字符串都定义一个常量标识符。当字符串表相当大时,或者你感觉记不清时,就应该定义常量标识符。我通常在每个常量标识符的前面加上一个前缀ST_。具体的说,ST_FILENAMES作为存储文件名称字符串的索引,ST_DIALOGUE作为人物对话字符串的索引,等等。
这是我们要讲的最后一个Windows资源,当然,不是为了凑数才讲的哦。窗口的菜单条紧接在标题栏的下发显示,这个菜单有时被称为“主菜单”或“顶层菜单”。菜单通常在建立窗口类时被调用。还记得吗?上一章中窗口类建立过程中,有这样一行:
sampleClass.lpszMenuName = NULL;&
如果你正在建立一个窗口程序,并希望有菜单,你就得需要用到菜单资源。它的脚本文件可能要复杂一点儿,但下面是一个最基本的框架:
[identifier] MENU{&&& POPUP [menu name]&&& {&&&&&&& MENUITEM [item name], [identifier]&&& }}&
[identifier]标识符是你知道的:一个字符串或一个数字常量。在MENU的大括号中,可以有一个或者几个POPUP(弹出式)菜单,每一个都有一个下拉菜单,[menu name]中填入菜单名称。在POPUP的大括号中,可以有一个或者多个菜单条,[item name]中填入菜单条名称,后面必须跟着一个数字常量的标识符。(所谓数字常量的标识符,就是用#define定义过的标识符。如:#define MENUID_NEW 101)如果你还想在菜单里建立热键,就要用(&)符号。在你想成为热键的字符前加上&,例如,你想用Alt+F代替用鼠标点击File按钮,你就应该写成 &File ,菜单的名称都要用双引号引上。看看下面的例子就更清楚了:
MAIN_MENU MENU{&&& POPUP "&File"&&& {&&&&&&& MENUITEM "&New", MENUID_NEW&&&&&&& MENUITEM "&Open...", MENUID_OPEN&&&&&&& MENUITEM "&Save", MENUID_SAVE&&&&&&& MENUITEM "Save &As...", MENUID_SAVEAS&&&&&&& MENUITEM "E&xit", MENUID_EXIT&&& }&&& POPUP "&Help"&&& {&&&&&&& MENUITEM "&Contents", MENUID_CONTENTS&&&&&&& MENUITEM "&Index...", MENUID_INDEX&&&&&&& MENUITEM "&About", MENUID_ABOUT&&& }}&
你还可以在POPUP下建立子菜单,你自己琢磨吧,我就不讲了,我们还得往下进行。获得菜单资源的句柄,我们需要用LoadMenu()函数,它的原形如下:
HMENU LoadMenu(&&& HINSTANCE hInstance, // handle to application instance&&& LPCTSTR lpMenuName&& // menu name string or menu-resource identifier);&
现在你应该已经熟悉这些参数了。第一个参数是你的程序实例的句柄,第二个是你的菜单资源的标识符。如果你使用了数字常量作为标识符,别忘了使用MAKEINTRESOURCE()这个宏转换一下哦!现在,你有两个方法为窗口创建菜单。第一个方法是在创建窗口类时直接设置:
sampleClass.lpszMenuName = LoadMenu(hinstance, MAKEINTRESOURCE(MAIN_MENU));&
第二个方法是在设置窗口类时,让lpszMenuName等于NULL,以后再加入菜单。当你要建立两个独立的菜单,而又不想定义不同的窗口类时,这个选择是很有意义的,你需要用SetMenu()函数:
BOOL SetMenu(&&& HWND hWnd,&& // handle to window&&& HMENU hMenu, // handle to menu);&
如果创建菜单的功能实现了,将返回TRUE,否则返回FALSE。它的参数是很容易理解的:
※ HWND hWnd:是你所要创建菜单的那个窗口的句柄。也就是你在调用CreateWindowEx()时产生的那个窗口句柄。
※ HMENU hMenu:识别菜单,使用它的形式是:hMenu=LoadMenu(hInstance,菜单标识符),所以它得到的是LoadMenu()函数返回的菜单句柄。如果给它赋值NULL,指定窗口的菜单将被移除。
资源是个好东西,因为它使我们很容易就生成了菜单。但是当我们点击了菜单上的选项,将会发生什么呢?答案是Windows将会发出一个WM_COMMAND的消息给程序,程序再让Windows作出相应的反应。让我们具体看一看。
控制菜单事件
你可能记得,Windows的消息都是通过CALLBACK函数控制的,通常它是这个样子:WindowProc()或类似的样子。我们在上一章中用到的是这个样子:MsgHandler()。它的原形如下:
LRESULT CALLBACK MsgHandler(&&& HWND hwnd,&&&& // window handle&&& UINT msg,&&&&& // the message identifier&&& WPARAM wparam, // message parameters&&& LPARAM lparam, // more message parameters};&
当一个菜单消息被送到,msg将等于WM_COMMAND,所选择的菜单项目将被包含进wparam。这就是为什么菜单的标识符不能是字符串的原因,它需要适合wparam参数。更特别的是,菜单标识符只占用wparam的低位字。WPARAM,LPARAM,int等都是32位,分高、低位字的变量。Windows提供了宏LOWORD()和HIWORD()分别来提取变量中的低位字和高位字,原形如下:
#define LOWORD(l) ((WORD) (l))#define HIWORD(l) ((WORD) (((DWORD) (l) >> 16) & 0xFFFF))&
LOWORD()宏的实际情况是,由于简单的定义为WORD,就自然的取得了低端的16位。HIWORD()函数把高端的16位向右移,然后同0xFFFF之间调用了逻辑“和”(AND),确保把高于16位的字节变为0。可能你不太熟悉>>和<<操作符号,它们是位移操作符。“<>”就是向右移动。例如,我们有一个16位的变量x,它的值是224,二进制表示为11 0100。下面是一个关于位移的例子:
short int x = 244,y = x << 4;Contents(内容) of x: 11 0100Contents (内容)of y: 00 0000&
总之,使用LOWORD()宏你得到了wparam的低端字,也就是说你得到了被选择菜单的ID(标识符)。所以,在你的MsgHandler()函数中,你应该这样做:
// handle menu selectionsif (msg == WM_COMMAND){&&& switch (LOWORD(wparam))&&& {&&& case MENUID_NEW:&&&&&&& // code to handle File->New goes here&&&&&&&&&& case MENUID_OPEN:&&&&&&& // code to handle File->Open goes here&&&&&&&&&&&&&& // the rest of the option handlers go here&&& }&&& // tell Windows you took care of it&&& return(0);}&
当然,还有一些其它的资源类型,如加速表(快捷键)、HTML页、WAV文件等。但我想以上这些是最有用,最要紧学习的。在结束之前,我还要告诉你Windows编程的一大强力特色——定制自己的资源类型。
标准的程序资源给我们带来了很大方便。但不仅仅是这些标准的类型,你还可以创建自己的资源类型。资源可以是你希望的任何一种数据。使用自己定制的资源需要多付出一点劳动,因为你必须手工定位和读取资源数据。比想象的要容易,因为你已经习惯了定义资源的格式:
[identifier] [resource type name] [filename]&
[resource type name]资源类型名称是让你命名的一个字符串。还是举例说明吧:假设我们要用到plconfig.dat文件作为资源,它包含初始化游戏人物的必需信息。我们将把它定义为CHARCONFIG资源类型,脚本文件应该是这个样子:
DATA_PLAYERINIT CHARCONFIG p1config.dat&
现在,你已经拥有了数据(plconfig.dat),你还必须分三步使一个指针指向资源数据。这包括我们还没有提到过的需要调用的函数让我们一起解决。第一步,我们必须调用FindResource()函数去发现资源。函数原形如下:
HRSRC FindResource(&&& HMODULE hModule, // module handle&&& LPCTSTR lpName,& // pointer to resource name&&& LPCTSTR lpType&& // pointer to resource type);&
返回值是一个资源信息块儿的句柄,如果调用失败,返回NULL。参数意义如下:
※ HMODULE hModule:HMODULE相当于HINSTANCE。不要问我为什么换了另一个名字,你只要把你的程序实例句柄传送给它就好了,你不需要什么类型转换,它们是相同的。
※ LPCTSTR lpName:这个是资源的标识符。如果你使用了数字的常量作为标识符,别忘了使用MAKEINTRESOURCE()宏。
※ LPCTSTR lpType:这个是资源的类型,你需要把你定义的资源类型名称的字符串传递给它。我们的是CHARCONFIG。
调用函数方式如下:
HRSRC hRsrc = FindResource(hinstance, MAKEINTRESOURCE(DATA_PLAYERINIT), "CHARCONFIG");&
这是信息块儿所在资源的句柄。下一步是要得到指向数据的指针。需要把句柄传递给LoadResource()函数,来调用数据。这将产生一个资源本身的句柄。下面是函数的原形:
HGLOBAL LoadResource(&&& HMODULE hModule, // resource-module handle&&& HRSRC hResInfo&& // resource handle);&
返回类型HGLOBAL是一个普通句柄类型,是相对于我们说过的那些HBITMAP或HICON等句柄类型。如果调用函数失败,将返回NULL。参数解释如下:
※ HMODULE hModule:老东西,程序实例的句柄。
※ HRSRC hResInfo:把FindResource()得到的句柄传递给它。
现在,我们有了资源的句柄,就可以得到指向数据(自定义的)的指针了,这需要调用LockResource()函数来完成。原形如下:
LPVOID LockResource(HGLOBAL hResData);&
仅仅把调用LoadResource()函数得到的句柄传递给它就万事大吉了。如果返回值是NULL,说明函数调用失败。否则,我们就得到梦寐以求的指针!现在我们可以自由得处理数据了。注意:返回的类型是LPVOID,(相当于void*),所以若你想把指针指向队列符号,你还要注意转换成类似BYTE*型的哦!现在,我们完成了所有的步骤,这里,我将展示给你一个指针指向特殊资源的实例:
UCHAR* LoadCustomResource(int resID){&&& HRSRC hResI&&& HGLOBAL hR&&& // first find the resource info block&&& if ((hResInfo = FindResource(hinstance, MAKEINTRESOURCE(resID), "CUSTOMRESOURCETYPE")) == NULL)&&&&&&& return(NULL);&&& // now get a handle to the resource&&& if ((hResource = LoadResource(hinstance, hResInfo)) == NULL)&&&&&&& return(NULL);&&& // finally get and return a pointer to the resource&&& return ((UCHAR*)LockResource(hResource));}&
以上就是关于资源的部分。Windows编程比想象的容易吧。学了这么多,好像还是不能做什么,所以,下一章,我将向你介绍一些基本的Windows图形设备接口函数,你就可以利用我们所学过的所有东西作一点作品出来了。第三章 跟踪你的窗口和使用GDI
如果你看过了头两篇连载,这次我们将学习WINDOWS GDI(图形设备接口)和其它一些相关的东西,象响应用户输入和处理Windows产生的一些消息。至于显示图形,我们将接触三个课题:文本显示,绘制象素,显示位图。我们先来研究一下几个Windows消息的细节。重复的话:你需要C语言的基础知识,最好看过上两章。由于本章将使你能做一个具体的图形DEMO,有一个源代码例程附在本章后面。是用Visual C++写的和编译的。在连载一里,我们创建和注册了一个窗口类,其中有一行定义了窗口的风格(功能),是这个样子:
sampleClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;// standard settings&
其中三个属性是很一般的,但这个——CS_OWNDC,需要解释一下。设备上下文是一个结构,是一个表现一组图形对象和属性的结构,还有一些输出设备的设置和属性。使用设备上下文允许你直接操纵图形,不用考虑低级细节。Windows GDI是一个图形翻译系统,是介于应用程序和图形硬件之间的一层。GDI可以输出到任意的兼容设备,不过最常使用的设备是视频监视器、图形硬拷贝设备(如打印机或绘图仪),或者是内存中的图元文本。GDI函数能够绘制直线、曲线、封闭的图形和文本。所有访问GDI的Windows函数都需要一个设备上下文句柄作为参数。这是非常容易做到的。你若想得到一个窗口的设备上下文句柄,你可以用这个函数:
HDC GetDC(&&& HWND hWnd& // handle to a window);&
很简单,所有你做的是,把要操作的窗口的句柄传递给它,然后返回一个设备上下文句柄。如果你传递的是NULL,将返回整个屏幕的设备上下文(DC,以后都用DC表示)句柄。如果函数调用失败,将返回NULL。
处理显示图形的DC类型,称作显示DC,处理打印的,称作打印DC;处理位图数据的,称作内存DC,还有其它一些设备DC。感觉有点复杂吧,不要紧,这是Windows,它的主要功能就是迷惑群众。一旦我们接触一些代码,就不会觉得难了。
当你结束使用DC时,一定要释放它,也就是释放它占用的内存空间。要把这种思想贯穿到以后的编程中去,占用了内存,不用时要释放,切记!释放DC是一个很简单的函数:
int ReleaseDC(&&& HWND hWnd, // handle to window&&& HDC hDC&&& // handle to device context);&
若成功释放,返回值是1,否则是0。参数有注释,我还是说一下:
※ HWND hWnd:你所要控制的那个窗口的句柄。如果你开始传递的是NULL,现在还要传递NULL。
※ HDC hDC:DC的句柄。
在用DC和GDI进行图形显示前,我们先看看创建窗口实例时要遇到的几条重要的消息。我将要提到的四条消息是:WM_MOVE、WM_SIZE、WM_ACTIVATE、WM_PAINT。
追踪窗口状态
头两个是很简单的。当窗口被用户移动时将发送WM_MOVE消息,窗口新位置的坐标储存在lparam中。(还记得吗,消息在lparam和wparam中被进一步描述,它们是消息控制函数的参数)lparam的低端字中存储窗口客户区左上角的坐标x,高端字中存储坐标y。当窗口的大小被改变时,将发送WM_SIZE消息。同WM_MOVE消息差不多,lparam的低端字中存储客户区的宽度,高端字存储高度。同WM_MOVE不同的是,wparam参数也控制了一些重要的东西。它可以是下列中任意一个值:
※ SIZE_MAXHIDE:其它的窗口被最大化了。
※ SIZE_MAXIMIZED:本窗口被最大化了。
※ SIZE_MAXSHOW:其它的窗口被还原了。
※ SIZE_MINIMIZED:本窗口被最小化了。
※ SIZE_RESTORED:窗口被改变了尺寸,但既没最大化,也没有最小化。
当我编写窗口实例时,我通常喜欢把窗口的当前位置和大小保留在几个全局变量里。假设我们命名这些全局变量为xPos,yPos,xSize和ySize,你最好这样控制WM_SIZE和WM_MOVE这两个消息:
if (msg == WM_SIZE){&&& xSize = LOWORD(lparam);&&& ySize = HIWORD(lparam);}if (msg == WM_MOVE){&&& xPos = LOWORD(lparam);&&& yPos = HIWORD(lparam);}&
现在轮到WM_ACTIVATE消息了。它告诉你一个新窗口被激活。这是很有用的,因为如果出现优先的申请,你就不可能处理程序里的所有逻辑。有时,例如写一个全屏的DIRECTX程序,忽略WM_ACTIVATE消息将导致你的程序出现致命的错误,可能它做了一些你不希望它做的事情。在任何情况下,守候WM_ACTIVATE消息从而采取行动,是一个好主意。
窗口被激活和被解除激活都会发出WM_ACTIVATE消息,我们可以通过检测wparam的低端字来得知是被激活还是被取消。这将有三种可能的值:
※ WA_CLICKACTIVE:窗口被鼠标激活。
※ WA_ACTIVE:窗口被其它东西激活。(键盘、函数调用、等等)
※ WA_INACTIVE:窗口被解除激活。
为了处理这个消息,我保留了另一个全局变量bFocus,当接收到WM_ACTIVATE消息,它的值将改变。示例如下:
if (msg == WM_ACTIVATE){&&& if (LOWORD(wparam) == WA_INACTIVE)&&&&&&& focus = FALSE;&&& else&&&&&&& focus = TRUE;&&&& // tell Windows we handled it&&& return(0);}&
有两个相关联的消息WM_KILLFOCUS和WM_SETFOCUS,在窗口接收到输入焦点的时候,Windows消息WM_SETFOCUS被发送给它,在失去焦点的时候则发送WM_KILLFOCUS消息。应用程序可以截取这些消息以得知输入焦点的任何改变情况。什么是输入焦点呢?存有输入焦点的应用程序(窗口)就是被激活的那个窗口。你就认为被激活的窗口就是输入焦点就行了。因为可能出现没有窗口具有输入焦点,所以我建议用WM_ACTIVATE消息跟踪你的窗口状态。往下进行。
WM_PAINT 消息
WN_PAINT消息通知程序,全部或部分客户窗口需要重新绘制。当用户在最小化、重叠或调整客户窗口区域的时候,就会产生这条消息。重新绘制,你需要做两件事,首先是要用到WM_PAINT消息专用的一对函数,第一个是BeginPaint()函数,这是它的原形:
HDC BeginPaint(&&& HWND hwnd,&&&&&&&&&&&& // handle to window&&& LPPAINTSTRUCT lpPaint& // pointer to structure for paint information);&
在我告诉你返回值是什么之前,让我们先看看参数:
※ HWND hwnd:需要重绘的窗口的句柄。你应该已经对于这种参数比较熟悉了。
※ LPPAINTSTRUCT lpPaint:这是很重要的一个。是指向PAINTSTRUCT结构的指针,该结构包含所有的要被重绘区域的信息。
继续之前,我应该给你看看PAINTSTRUCT结构:
typedef struct tagPAINTSTRUCT { // ps&&& HDC&&& BOOL fE&&& RECT rcP&&& BOOL fR&&& BOOL fIncU&&& BYTE rgbReserved[32];} PAINTSTRUCT;&
结构内的成员如下:
※ HDC hdc
※ BOOL fErase:指明应用程序是否应该抹去背景。如果是FALSE,说明系统已经删除了背景。还记得在Windows类中我们曾经用黑色画刷定义了一个背景吗?这就意味着系统将用这个画刷抹去无效的区域。
※ RECT rcPaint:这是最重要的一个成员。它将告诉你需要被重绘的无效区域的矩形。我将稍后告诉你RECT结构。
※ BOOL fRestore,BOOL fIncUpdate,BYTE rgbReserved[32]:好消息,这些是保留成员,为老Windows服务的,所有你我都不必管它们。:)
现在我已经给你看了这么多,这就是BeginPaint()函数的全部。它做了三件事儿。首先,它使窗口再次有效,直到下一次被改变,WM_PAINT消息发出前,这个窗口都是有效的。第二,如果在窗口类(Windows class)里定义了背景画刷,就像我们做过的那样,就用这个画刷重绘无效的区域。(所谓无效,就是被改变的)第三,返回了被重绘区域的DC句柄。重绘的区域,是由RECT结构定义的:
typedef struct _RECT {&&& LONG&&& LONG&&& LONG&&& LONG} RECT;&
我们已经指出这个结构描绘了一个矩形,但是有一件事情需要说说。RECT包含左上角,但不包含右下角。什么意思呢?让我们先定义一个RECT对象:
RECT myRect = {0, 0, 5, 5};&
这个RECT包含象素(0,0),但是没有达到(5,5),所以矩形的右下角实际是(4,4)。看起来没有什么意义,但是你得习惯它。现在,还记得我所说的关于使用DC的事儿吗?一旦你用完了,你就必须释放它。用EndPaint()函数释放。回应WM_PAINT消息,每次调用完BeginPaint()函数,必须匹配一个EndPaint()函数释放DC。这是函数的原形:
BOOL EndPaint(&&& HWND hWnd,&&&&&&&&&&&&&&&& // handle to window&&& CONST PAINTSTRUCT *lpPaint // pointer to structure for paint data);&
函数通过返回TRUE或FALSE分别表明成功还是失败。有两个简单的参数:
※ HWND hWnd:就是窗口的句柄。
※ CONST PAINSTRUCT *lpPaint:指向PAINTSTRUCT类型的结构变量地址。同BeginPaint()的第二个参数是一回事。不要被CONST迷惑了,它只是保证和确认函数没有改变结构的内容。你还可以通过调用ValidateRect()函数代替BeginPaint()函数使得窗口再次有效。但你得手工操作一切。可能我们真的什么时候就要用到它。所以给你它的原形:
BOOL ValidateRect(&&& HWND hWnd,&&&&&&&& // handle of window&&& CONST RECT *lpRect // address of validation rectangle coordinates);&
通过返回TRUE或FALSE来确定函数调用成功还是失败。参数很简单:
※ HWND hWnd
※ CONST RECT *lpRect:是指向RECT结构是否有效的指针。如果你传递NULL,则整个客户区域都是有效的。
现在把以上讲到的做个样子给你看吧,假设我们已经定义了一个全局的变量hMainWindow作为我们的窗口句柄。
if (msg == WM_PAINT){&&& PAINTSTRUCT // declare a PAINTSTRUCT for use with this message&&& HDC // display device context for graphics calls&&& hdc = BeginPaint(hMainWindow, &ps); // validate the window&&& // your painting goes here!&&& EndPaint(hMainWindow, &ps); // release the DC&&& // tell Windows we took care of it&&& return(0);}&
这段代码很简单。
有三个消息看起来差不多,都是处理关闭的事情的。它们是WM_DESTROY,WM_CLOSE,和WM_QUIT。它们的确很相似,但你需要知道它们之间的不同!一个窗口或者应用程序应该被关闭时发出WM_CLOSE消息,当接收到WM_CLOSE消息时,如果你愿意,向用户提出是否真的退出。你知道让用户作确认或有错误出现或有什么应该注意的事情发生的时候,往往弹出一个消息框。消息框的制作是很容易的,由于它用途广泛,我们还是介绍一下:
int MessageBox(&&& HWND hWnd,&&&&&&&& // handle of owner window&&& LPCTSTR lpText,&&& // address of text in message box&&& LPCTSTR lpCaption, // address of title of message box&&& UINT uType&&&&&&&& // style of message box);&
这些参数,尤其是最后一个,需要一些解释:
※ HWND hWnd:过一会儿我将向你介绍一个不含有它的函数,我保证。
※ LPCTSTR lpText:这是将要显示在消息框里的文本。你可以用\n等调整一下格式。
※ LPCCTSTR lpCaption:这是显示在消息框标题栏里的文本。
※ UINT uType:这个参数可以联合使用几个不同的标记。这些标记可以根据你的目的选择,有好多MB_打头的标记供你选择,联合使用时要用“|”分隔开。下面列出了一些常用的:
◎ MB_ABORTRETRYIGNORE:建立有“Abort”、“Retry”、“Ignore”按钮的消息框。
◎ MB_OK:建立有“OK”按钮的消息框。
◎ MB_OKCANCEL:建立有“OK”和“Cancel”按钮的消息框。
◎ MB_RETRYCANCEL:建立有“Retry”、和“Cancel”按钮的消息框。
◎ MB_YESNO:建立有“Yes”和“NO”按钮的消息框。
◎ MB_YESNOCANCEL:建立有“Yes”、“No”和“Cancel”按钮的消息框。
◎ MB_ICONEXCLAMATION:加个惊叹号图标。
◎ MB_ICONINFORMATION:加个消息图标。(好像是个问号)
◎ MB_ICONQUESTION:加个问号图标
◎ MB_ICONSTOP:加个停止图标。
默认按钮标志
◎ MB_DEFBUTTON1:设置第一个按钮为默认按钮。(默认按钮即消息框弹出后,直接敲回车就被按下的那个按钮)
◎ MB_DEFBUTTON2:第二个为默认按钮。
◎ MB_DEFBUTTON3:第三个为默认按钮。
◎ MB_DEFBUTTON4:第四个为默认按钮。
其它的标志
◎ MB_HELP:添加一个帮助按钮。通常按下该按钮或者敲F1键都将产生WM_HELP消息。
◎ MB_RIGHT:文本右对齐。
◎ MB_TOPMOST:设置消息框总在窗口的最上面。
如果消息框建立失败,返回值为0,否则是下列任一个值:
◎ IDABORT:“Abort”按钮被选择。
◎ IDCANCEL:“Cancel”按钮被选择。
◎ IDIGNORE:“Ignore”按钮被选择。
◎ IDNO:“No”按钮被选择。
◎ IDOK:“OK”按钮被选择。
◎ IDRETRY:“Retry”按钮被选择。
◎ IDYES:“Yes”按钮被选择。
总之,当收到WM_CLOSE消息,你可以做两件事儿。一件是你接受默认的处理返回一个值,你若这样做了,应用程序或窗口按照计划关闭;再者,你返回0,应用程序或窗口将保持原样。以下是代码的基本部分:
if (msg == WM_CLOSE){&&& if (MessageBox(hMainWindow, "Are you sure want to quit?",&"Notice", MB_YESNO | MB_ICONEXCLAMATION) == IDNO)&&&&&&& return(0);&&& // otherwise, let the default handler take care of it}&
WM_DESTROY消息有点儿不同。它是窗口正在关闭时发出的。当得到WM_DESTROY消息的时候,窗口已经从视觉上被删除。一个主窗口被关闭,并不意味着应用程序结束了,它将在没有窗口的条件下继续运行。然而,当一个用户关闭了主窗口,也就意味着他要结束应用程序,所以如果你希望应用程序结束,在收到WM_DESTROY消息的时候,你必须发出一个WM_QUIT消息。你可以使用PostMessage()函数,但由于这是一个特殊的情况,就为它准备了一个特殊的函数:
VOID PostQuitMessage(int nExitCode);&
参数nExitCode是你的应用程序返回给Windows的一个退出代码(通常是0)。记住,WinMain()返回的是一个int(实数),不是void(空的)。nExitCode参数的值被赋值给wparam。WM_QUIT消息表示要关闭应用程序,所以得到这个消息后,你应跳出循环,把wparam返回给Windows。下面是一个简单的WinMain()函数实例:
int WinMain(HINSTANCE hinstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow){&&& // initialization stuff goes here&&& // main loop - infinite!&&& while (TRUE)&&& {&&&&&&& // check the message queue&&&&&&& if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))&&&&&&& {&&&&&&&&&&& if (msg.message == WM_QUIT) // exit main loop on WM_QUIT&&&&&&&&&&&&&&&&&&&&&& TranslateMessage(&msg);&&&&&&&&&&& DispatchMessage(&msg);&&&&&&& }&&&&&&& // main program logic goes here&&& }&&& // perform any shutdown functions here - releasing objects and such&&& return(msg.wparam); // return exit code to Windows}&
只要你取得了显示设备上下文的句柄,就可以用GDI绘制象素了。记住,要调用GetDC()取得句柄。绘制单个的象素,不出意外的话,你就用SetPixel()函数:
COLORREF SetPixel(&&& HDC hdc,&&&&&&&&&& // handle to device context&&& int X,&&&&&&&&&&&& // x-coordinate of pixel&&& int Y,&&&&&&&&&&&& // y-coordinate of pixel&&& COLORREF crColor&& // pixel color);&
返回类型COLORREF是我们没有讲过的。它不是一个结构,但是在表格0x00bbggrr里是一个32位的值。Bb是蓝色成分的8位值,gg是绿色,rr是红色。高字节00没有用,总是00。让我们看看函数里面的参数:
※ HDC hdc:你将要GetDC()得到的DC句柄。(DC就是设备上下文)你只能调用GetDC()一次,然后其它的函数也可以用这个DC句柄了。你每次绘制单个象素不用再取得新的DC句柄了。
※ int X,Y:是象素的x和y坐标。是在客户区的坐标,(0,0)即是窗口客户区左上角的坐标,不是屏幕左上角的坐标。
※ COLORREF crColor:象素的颜色。设置象素的颜色,用RGB()宏最简单。RGB()括号中的三个值分别是红色、绿色和蓝色,各个颜色的值可从0-255间取,不同的值组合成不同的颜色。
如果函数调用成功,将返回一个颜色,就是你要绘制的象素的颜色。由于视频硬件的限制,返回的颜色可能与调用函数时要求的颜色并不一致。如果调用失败,返回-1。如果你要设置窗口客户区的左上角为红色,函数将是这样:
SetPixel(hdc, 0, 0, RGB(255,0, 0));&
是在假设你已经取得了DC句柄hdc后,调用这个函数。这个函数的更快的版本是SetPixelV():
BOOL SetPixelV(&&& HDC hdc,&&&&&&&&&& // handle to device context&&& int X,&&&&&&&&&&&& // x-coordinate of pixel&&& int Y,&&&&&&&&&&&& // y-coordinate of pixel&&& COLORREF crColor&& // new pixel color);&
参数相同,但返回类型不同。SetPixelV()返回的是布尔类型,成功,TRUE;失败,FALSE。如果你没有必要使用SetPixel()提供的额外信息,那我们当然是选择使用SetPixelV()了。(我们总是希望快一些)
还有一件事情,我们需要得到绘制好的象素的值(颜色)。没问题,使用GetPixel()函数就解决了:
COLORREF GetPixel(&&& HDC hdc,& // handle to device context&&& int XPos, // x-coordinate of pixel&&& int nYPos // y-coordinate of pixel);&
很明显,返回值是象素所在位置的颜色。如果坐标出了DC句柄控制的区域,返回值是CLR_INVALID。参数同SetPixel()差不多:DC句柄,象素的x,y坐标。绘制象素就说到这吧,下面看看GDI的文本输出。
GDI的文本输出函数
有两个关于绘制文本的函数,其中简单一点儿的是TextOut()函数,原形是:
BOOL TextOut(&&& HDC hdc,&&&&&&&&& // handle to device context&&& int nXStart,&&&&& // x-coordinate of starting position&&& int nYStart,&&&&& // y-coordinate of starting position&&& LPCTSTR lpString, // pointer to string&&& int cbString&&&&& // number of characters in string);&
我们已经看过很多返回类型是布尔类型的函数了,意思都一样:成功TRUE,失败FALSE。函数的参数如下:
※ HDC hdc:DC句柄。它既可以使GetDC的返回值,也可以是在处理WM_PAINT消息时BeginPaint的返回值。
※ int nXStart,nYStart:定义客户区内字符串开始的位置。字符串中第一个字符的左上角位于坐标(xXStart,nYStart)。在默认DC中,客户区的左上角为(0,0)。
※ LPCTSTR lpString:要显示的字符串。由于下一个参数给出字符串的长度,所有字符串不需要空的中止符。
※ int cbString:串中字符的个数。(字符串的长度)
TextOut()函数使用当前的文本颜色,背景颜色和背景类型。你若想自己改变这些,就得用如下的函数:
COLORREF SetTextColor(&&& HDC hdc,&&&&&&&& // handle to device context&&& COLORREF crColor // text color);COLORREF SetBkColor(&&& HDC hdc,&&&&&&&& // handle of device context&&& COLORREF crColor // background color value);&
SetTextColor()函数设置当前文本颜色,SetBkColor()函数设置当前背景颜色。参数的解释是显而易见的。第一个是DC句柄。第二个是颜色,你当然记得可以用RGB()宏来设置颜色。例如设置文本为蓝色:SetTextColor(hdc,RGB(0,0,255))。设置背景为白色:SetBkColor(hdc,RGB(255,255,255))。最后,设置背景类型用SetBkMode()函数:
int SetBkMode(&&& HDC hdc,&&&& // handle of device context&&& int iBkMode& // flag specifying background mode);&
第一个参数就不讲了。第二个参数iBkMode可以分别取两个值:TRANSPARENT或OPAQUE。如果取TRANSPARENT,文本不会对背景造成影响。如选择OPAQUE,则相反。函数的返回值是背景的模式。
关于TextOut()函数还有一点要说,你可以控制文本的排列方式,用SetTextAlign()实现:
UINT SetTextAlign(&&& HDC hdc,&&& // handle to device context&&& UINT fMode& // text-alignment flag);&
参数如下:
※ HDC hdc:DC句柄。没有什么特别的。
※ UINT fMode:一个标志或一组标志(用“|”分开)将决定TextOut()输出文本的对齐模式。你不可以随便组合,要合理组合。这些标志如下:
◎ TA_BASELINE:参考点将在文本的基线。
◎ TA_BOTTOM:参考点将在矩形范围的底边。
◎ TA_TOP:参考点将在矩形范围的顶部。
◎ TA_CENTER:参考点将在矩形范围的中心。
◎ TA_LEFT:参考点将在矩形范围的左边。
◎ TA_RIGHT:参考点将在矩形范围的右边。
◎ TA_NOUPDATECP:当前的位置没有被文本输出函数调用,参考点被每次调用传递。
◎ TA_UPDATECP:当前的位置被每次文本函数输出调用,当前的位置作为参考点。
默认的设置是TA_LEFT|TA_TOP|TA_NOUPDATECP。如果你设置成TA_UPDATECP,后来调用的TextOut()函数将忽略nXStart和nYStart这两个参数,把文本放置在……。OK,告一段落,我们来看看TextOut()函数的兄弟DrawText():
int DrawText(&&& HDC hDC,&&&&&&&&& // handle to device context&&& LPCTSTR lpString, // pointer to string to draw&&& int nCount,&&&&&& // string length, in characters&&& LPRECT lpRect,&&& // pointer to struct with formatting dimensions&&& UINT uFormat&&&&& // text-drawing flags);&
DrawText()函数能格式化文本,多种排列方式。返回值是文本象素的高度。返回0,说明调用失败。让我们看看它的参数:
※ HDC hDC:一样的东东。我们的好朋友DC句柄。
※ LPCTSTR lpString:要显示的字符串。用双引号引起来。
※ int nCount:字符串中字符的数量。(字符串长度)
※ LPRECT lpRect:是RECT类型结构的地址,该结构包含了将要显示字符串的区域的逻辑坐标。
※ UINT uFormat:文本格式选项,你可以用“|”符号组合。下面列出最常用到的标志:
◎ DT_BOTTOM:指定底部对齐文本。必须与DT_SINGLELINE组合使用。
◎ DT_CALCRECT:返回矩形的宽度和高度。在多文本行的情况下,DrawText()将使用lpRect所指向的矩形的宽度,并扩展矩形的底部直到包含文本的最后一行。在单文本行的情况中,DrawText()将改变矩形的右边界,使它包含最后一个字符。不管在那种情况下,DrawText()都返回格式化后的文本高度,但是不重新绘制文本。
◎ DT_CENTER:文本水平居中。
◎ DT_EXPANDATABS:扩充Tab键跳跃的字符数,默认情况下,每按一次Tab键跳跃8个字符。
◎ DT_LEFT:指定文本左对齐。
◎ DT_NOCLIP:绘制屏幕时无需剪切。当使用DT_NOCLIP后,程序性能提高。
◎ DT_RIGHT:指定文本右对齐。
◎ DT_SINGLELINE:指定单行文本,忽略回车和换行。
◎ DT_TABSTOP:设置Tab键停止。在uFormat的低端字的高阶字节(15-8)中存放Tab键每按一次跳跃的字符数。默认是8。
◎ DT_TOP:顶部对齐文本(仅用于单行文本)。
◎ DT_VCENTER:指定垂直居中(仅对单行文本)。
还有一些其它的标志,但你看到的已经足够了。有了这些,你就可以轻松驾驭文本了,但记住,是以牺牲函数速度为代价的。你可以选择比较常用的TextOut()函数。
用GDI显示位图
位图是很容易操纵的,因为Windows本身就是位图。现在让我们看看到底有多容易吧!用GDI显示位图需要四个基本的步骤:
1、得到你要操作的窗口的DC句柄。
2、获得位图的句柄。
3、为位图创建设备上下文。
4、传送位图。
你已经知道第一步怎么做了,以前我也间接提到过第二步的做法,但没有具体说。我说过通过函数LoadBitmap()可以得到位图资源的句柄,但它有些过时了,有一个更灵活的函数LoadImage()取代了它,现在让我们看看怎么使用这个新函数。原形如下:
HANDLE LoadImage(&&& HINSTANCE hinst,& // handle of the instance containing the image&&& LPCTSTR lpszName, // name or identifier of image&&& UINT uType,&&&&&& // type of image&&& int cxDesired,&&& // desired width&&& int cyDesired,&&& // desired height&&& UINT fuLoad&&&&&& // load flags);&
如果函数调用失败,返回NULL。成功,你得到位图的句柄,意味着你就可以从资源或外部文件调用位图了。注意,这个函数还可以取得光标、图标的句柄,所以返回类型只是简单的HANDLE。在Visual C++6.0中,你需要用HBITMAP类型定义位图的句柄变量,否则编译器会生气的。例如:
HBITMAP hBitmap;hBitmap =LoadImage(……);&
下面是LoadImage()函数的参数说明:
※ HINSTANCE hinst:如果你从资源调用位图,这应该是你的应用程序的实例。如果你要从外部文件调用位图,就把它设置为NULL。
※ LPCTSTR lpszName:这个要么是资源标识符,记住用MAKEINTRESOURCE()宏转变数字常量;要么就是你要调用的图象的完整文件名称。
※ UINT uType:根据你的调用对象来决定。应该是IMAGE_BITMAP、IMAGE_CURSOR和IMAGE_ICON中的一种。
※ int cxDesired,cyDesired:这是你希望的图象的尺寸。如果你都设置为0,将是图象的真实尺寸。
※ UINT fuLoad:这是可以组合用的标志,当然是用“|”来连接。以下是一些常用的标志:
◎ LR_CREATEDIBSECTION:如果uType是IMAGE_BITMAP,将导致函数返回一个DIB(DIB,设备无关的位图)。基本的意思就是返回一个不依赖于显示设备的位图。
◎ LR_DEFAULTSIZE:对于光标和图标,如果cxDesired和cyDesired都设置为0,这个标志将启用Windows的默认尺寸,而不是图形的实际尺寸。
◎ LR_LOADFROMFILE:如果你要从外部文件中调入图象,你就必须用这个标志。条件允许的话,你应当尽量使用LR_CREATEDIBSECTION和LR_LOADFROMFILE这两个标志。现在,你已经得到了图象(过去我们总说位图,好像不太准确,毕竟有时我们不从资源里调用)的句柄,下一步你必须建立设备上下文把图象放进去。位图的应该独有的特点是:它只能被选入到内存设备上下文中。内存设备上下文被定义为一个具有显示表面的设备上下文。它只存在于内存中,并且与特定的设备上下文相关。使用内存设备上下文,你必须首先创建它。CreateCompatibleDC()函数正是用于这个目的。它的一般形式如下:
HDC CreateCompatibleDC(HDC hdc);&
函数唯一的参数是与内存设备上下文相兼容的设备上下文句柄。如果内存设备上下文与视频屏幕兼容,则这个参数可以为NULL。我们就用NULL。如果函数失败,返回NULL。现在,我们把位图(或图象)放入内存失败上下文,我们用这个函数:
HGDIOBJ SelectObject(&&& HDC hdc,&&&&&&&& // handle to device context&&& HGDIOBJ hgdiobj& // handle to object);&
函数的返回类型HGDIOBJ是一个比HBITMAP更通用的类型。不用担心,HGDIOBJ和HBITMAP的一致性没有任何问题。以下是函数的参数说明:
※ HDC hdc:是设备上下文的句柄。要调用图象,必须是内存设备上下文的句柄。
※ HGDIOBJ hgdiobj:要调用对象的的句柄。可调用的有位图、画刷、字体、画笔等。这里是位图(图象)的句柄。
返回值是要调入设备上下文中的对象的句柄。这里是位图的句柄。如果失败,返回NULL。
现在你已经把位图装入设备上下文,你还需要进行最后的一步:把内存DC里的内容拷贝到显示设备上下文中。然而,我们首先要得到位图的一些信息,如尺寸,这是显示图象时必须的。所以,我们还需要另一个函数GetObject(),它可以用于获得对象的信息,当然,这里我们是要获得位图的信息。函数的一般形式如下:
int GetObject(&&& HGDIOBJ hgdiobj,& // handle to graphics object of interest&&& int cbBuffer,&&&& // size of buffer for object information&&& LPVOID lpvObject& // pointer to buffer for object information);&
返回值是一个字节数。如果失败,返回0。当GetObject()调用的目标是位图时,返回的信息是与位图的宽度、高度和颜色格式有关的结构成员。参数说明如下:
※ HGDIOBJ hgdiobj:要得到信息的对象的句柄。这里我们传送位图的句柄。
※ int cbBuffer:存放调用返回的信息的缓冲区的大小。对于位图,我们将得到BITMAP类型结构,所以这里设置成sizeof(BITMAP)。
※ LPVOID lpvObject:指向存放由调用返回的信息的缓冲区的指针。
你需要定义一个BITMAP结构类型的变量,调用GetObject()函数放入缓冲区的信息。由于BITMAP结构对我们来说是一个新的结构,所以就介绍一下:
typedef struct tagBITMAP { // bm&&& LONG bmT&&& LONG bmW&&& LONG bmH&&& LONG bmWidthB&&& WORD bmP&&& WORD bmBitsP&&& LPVOID bmB} BITMAP;&
很多成员,但我们实际上只对其中的两个有兴趣。但我们还是都介绍一下:
※ LONG bmType:指定位图类型,必须为0。
※ LONG bmWidth,bmHeight:分别是位图的宽度和高度,以象素为单位。必须都大于0。
※ LONG bmWidthBytes:指定每一行扫描线中的字节数。因为Windows假定位图是字对齐的,所以这个值必须能够被2整除。
※ LONG bmPlanes:指定颜色面的数目。
※ LONG bmBitsPixel:指定表述象素颜色所需的位数。(好像没什么用)
※ LPVOID bmBits:如果你想存取实际的位图数据,这个指针指向位图位值得位置。
一旦位图被选入内存设备上下文,且代码已经得到了位图宽度和高度的必要信息后,我们就可以将内存中存储的位图通过位块传输到达屏幕,然后在任意位置对它进行显示。有两个函数需要说明,先说第一个:
BOOL BitBlt(&&& HDC hdcDest, &// handle to destination device context&&& int nXDest,& &// x-coordinate of destination rectangle's upper-left corner&&& int nYDest,& &// y-coordinate of destination rectangle's upper-left corner&&& int nWidth,& &// width of destination rectangle&&& int nHeight, &// height of destination rectangle&&& HDC hdcSrc,& &// handle to source device context&&& int nXSrc,&& &// x-coordinate of source rectangle's upper-left corner&&& int nYSrc,&& &// y-coordinate of source rectangle's upper-left corner&&& DWORD dwRop& &// raster operation code);&
BitBlt()函数是执行位图显示操作最简单且最直接的方法。根据函数调用的成功或失败,返回值是TRUE或FALSE。布尔函数都是这样。有很多参数,但很好理解:
※ HDC hdcDest:目标设备上下文句柄。根据我们的情况,应该是显示设备上下文句柄。
※ int nXDest,nYDest:目标矩形左上角的x、y坐标,也就是被显示的位图左上角的屏幕位置。
※ int nWidth,nHeight:位图的宽度和高度。
※ HDC hdcSrc:原来的设备上下文句柄。根据我们的情况,应该是内存设备上下文句柄。
※ int nXSrc,nYSrc:源位图的x、y坐标。由于进行位块传输的矩形必须与在参数nWidth和参数nHeight中定义的尺寸相同,所以通常都设为0。但不一定总是这样,例如你只想显示位图的一部分,就不能都设置为0。
※ DWORD dwRop:有很多光栅代码你可以选择,但只有一个我们感兴趣,SRCCOPY。它直接把源DC内的内容拷贝到目标DC中。
以上就是第一个函数。下面说说第二个函数StretchBlt(),简单的说,位图实际的拉伸或压缩就是通过它来实现的。函数的一般形式如下:
BOOL StretchBlt(&&& HDC hdcDest,&&&&& &// handle to destination device context&&& int nXOriginDest, &// x-coordinate of upper-left corner of dest. rectangle&&& int nYOriginDest, &// y-coordinate of upper-left corner of dest. rectangle&&& int nWidthDest,&& &// width of destination rectangle&&& int nHeightDest,& &// height of destination rectangle&&& HDC hdcSrc,&&&&& &// handle to source device context&&& int nXOriginSrc, &// x-coordinate of upper-left corner of source rectangle&&& int nYOriginSrc,& &// y-coordinate of upper-left corner of source rectangle&&& int nWidthSrc,&&& &// width of source rectangle&&& int nHeightSrc,&& &// height of source rectangle&&& DWORD dwRop&&&&&& &// raster operation code);&
它比BitBlt()复杂一些,所以它比BitBlt()慢。它们的参数差不多,并且有了注释,这里就不再重复了。光栅代码也是选择SRCCOPY。现在,只剩下最后一件事情——清除。建立一个设备上下文【CreateCompatibleDC(HDC hdc)】不同于得到设备上下文【(GetDC)】,不能用ReleaseDC(),要用:
BOOL DeleteDC(HDC hdc);&
参数是建立的DC的句柄。返回值是一个布尔类型,下面我们把以上步骤合并。事先假设你已经定义了一个全局的应用程序实例的句柄hinstance。
int ShowBitmapResource(HDC hDestDC, int xDest, int yDest, int nResID){&&& HDC hSrcDC;&&&&& // source DC - memory device context&&& HBITMAP // handle to the bitmap resource&&& }

我要回帖

更多关于 刺客信条起源游戏时间 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信