directx开发:使用C++和Directx开发GUI( 3)



欢迎回到\"使用C和DX开发GUI\"第 3部分.(这里是第部分和第 2部分).接着我们主题(描述我如何为我未来游戏构建GUI),本文将探讨建造GUI所需些通用Control控件.我们将详细描述几种区别Control控件形式,包括按钮,列表框,文本框等等.

节并不像其他章节那样有很多代码--这主要是我们员对于GUI外观是很挑剔.我们喜欢把我们按钮,文本框和GUI做看起来独无 2,并且符合我们自己审美标准.这样结果是,每个人Control控件代码都很区别,而且不会想要我特殊绘制代码.此外,写绘制GUI元素代码是很有趣,事实上,以我来看,这是在写GUI过程中最有趣部分了.现在继续.

个很重要问题是,在我们开始的前-把你gui_window析构做成虚.在第 2部分里我没有提到这点,我们没有从gui_window中派生出任何子类,但是现在我提出这点-把你gui_window和所有它派生类析构做成虚是很明智做法,这将确保没有内存泄漏--由于派生析构没有被.小心C陷阱.

说完这点的后,让我们首先判断我们需要什么样GUIControl控件.

我们需要GUIControl控件

我不想花太多时间来为我GUI开发Control控件;我只会专注于最简单Control控件集.所以,我先列出我认为是最小Control控件集Control控件:

1.静态文本,图标和组合框(最重要).这些空间将对话框中其他Control控件标志或分组.静态Control控件很重要;我们可能不需要帧Control控件,但它非常简单,并且在某些情况下能够使对话框易于导航,所以我会包括它.图标Control控件也很简单,但是应该能够表现动画-为我们对话框和菜单提供很酷背景动画(神偷:黑暗计划).

2.按钮和选择框(最重要).特殊形式按钮不是必需,然而大多数游戏不能没有基本按钮和选择框.

3.列表框(重要).我发现列表框,特别是多列列表Control控件,在创建游戏GUI时是不可或缺.他们应用无所不在.你需要个智能,重量级列表Control控件,和windows列表Control控件样好或者更为出色.对我而言,列表Control控件是最难开发Control控件了.

4.滑动条和滚动条(重要).最常见于音量控制.坏消息是我们可能需要水平和垂直滚动条,好消息是他们很相似所以开发很简单.

5.文本框(最重要).你必须能够键入你mega-3133t,super-kewl玩家标志,对吧?

6.进度条-对显示生命值是必需,\"我快要装载好了!\"等等情况也是如此.

这里缺少是纺锤状按钮(spin button),单选框(我们可以用个单选列表框代替),下拉组合框(同样我们可以用列表框代替)以及树状Control控件.通过设计巧妙列表Control控件来缩进特定物体,我们能够实现树状列表德功能.

由于我游戏并没有足够GUI来保证表状Control控件,所以在此没有包含它,虽然你可能会需要.

即使有这些遗漏,上述\"最小\"列表可能看上去还是很繁杂,但是我们能够简化点儿.

把它打破:组合简单空间来实现复杂Control控件

如果我们意识到复杂Control控件仅仅是简单Control控件巧妙组合,列表就会更易于控制.例如,个滚动条基本上只是两个按钮和个滑动条.个选择框是个静态文本和两个按钮(个\"打开\"按钮,个\"关闭\"按钮).个平面按钮能够使用 3个图标Control控件来实现(仅仅显示/隐藏适当图标来使按钮显得被按下),这样你能够重用你绘制代码.如果你确没有时间,你甚至可以把个进度条当作滑动条来用,虽然我更倾向于是用个独立Control控件.

然而,这样做是有缺陷,名义上你GUIControl控件会比他们实际需要占用更多系统资源.仔细考虑它-每个Control控件是个窗体.让我们说你使用了重使用方法则创建了个实际上是 3个静态Control控件按钮Control控件.那么每个按钮就是 3个窗体.现在你使用两个按钮Control控件创建个滚动条,那就是每个滚动条6个窗体.使用水平和垂直滚动条创建个列表Control控件,那么每个列表就是12个窗体.它增加得很快.

所以这就是另个经典有关\"我能多快开发\"和\"我会使用多少资源\"矛盾例子.如果你需要个高性能,没有浪费GUI,从基础来开发每个Control控件.如果你想要快速开发,那就不要介意性能损失,你或许会选择开发Control控件以使实际上绘制到屏幕上是静态Control控件,所有其他Control控件都是由静态Control控件组合而成.

我开发GUI时候,我尽力在两个极端的间取得良好平衡.

现在,让我们开始关注每个Control控件实际开发,从每个人最喜欢静态标志开始吧.

我们需要关注 3种静态Control控件:静态文本Control控件,静态图标Control控件和框架Control控件.这 3种Control控件都很简单,他们不接收消息-他们所作只是在某个位置绘制本身而已.

静态文本Control控件是你将开发最简单Control控件了-仅仅在窗口左上角绘制窗口标题,就行了.如果你想增加代码来以某种方式调整你文本-比如,在绘制框中居中你文本,你可能会使用经典居中算法.-用窗体宽度减去要绘制文本宽度,然后除以2,告诉你从距离窗体左边多少像素开始绘制.

静态图标Control控件稍微难点儿.实际上,\"静态图标Control控件\"这个术语有些歧义,假定我们想要我们图标Control控件可以表现动画话.即使如此,开发这些图标Control控件也不难,假设你已经有了丰富精灵库来处理所有开发动画细节:检测两帧的间时间差,使用这个差值来判断你精灵将要走多少帧,等等.

图标Control控件只有当你在每帧并不绘制整个GUI系统时候才变得麻烦.这种情况下,你多少要处理些图标Control控件裁剪工作,这样即使每帧都绘制,也不会覆盖属于在其上窗口像素(但是没有改变,所以没有绘制).我没有开发这个-我GUI每帧都重画-但是如果你面临这个问题,你可能会想试试为每个图标设立裁剪列表,用它来绘制图标,当有任何个窗体移动、关闭或者打开时重新计算它.这或许是个可行思路方法-我只是如此构想-但是这起码是个好切入点.

框架Control控件也很简单.我开发我框架Control控件时只是围绕m_position绘制边框,然后在大约绘制坐标(5,5)点附近(大约从框架Control控件左上角向右向下5个像素)绘制窗口标题,你可以依照自己想象自己决定.

你在开发静态Control控件中可能碰到麻烦事是稍微改变findwindow功能以使它跳过所有静态Control控件窗口.这样,如果个静态文本Control控件是在个按钮的上,用户可以透过静态Control控件来按这个按钮.当开发\"简易移动\"窗口(即你可以通过按住窗口任何部位来移动窗口,而不仅仅是标题栏,就象winamp)时候,这很有用.

现在让我们来看看如何开发按钮.



按钮Control控件

按钮只比静态Control控件难点儿.你按钮Control控件需要不断跟踪是否它被按下或松开.它通过两个虚来实现,wm_mousedown和wm_mouseup,你calcall需要在适当时候它们.

基本上,在wm_mousedown里,你要设定个布尔变量,我把它叫做\"depressed flag\"(按下标志)为真,而在wm_mouseup里,把它设为假.然后再你绘制代码里如果按下标志为真,绘制按钮按下状态,否则,绘制松开状态.

然后,增加个附加状态-即\"只有当按下标志为真和鼠标指针在绘制区域的中时绘制按钮按下状态,否则把按下标志设为假.\"如果你把鼠标移出按钮这将使你按钮弹起,并且对于精确判断个按钮何时被按下非常重要.

对于普通GUI,当个按钮被点击,将为他父窗体引发个事件,窗体会做按钮所代表任何事-例如,点击关闭按钮将关闭窗口,点击存储按钮将存储文件,等等.我GUI在且仅在wm_mouseup中判断按钮是否被点击,按下标志是否为真.按下标志在mouseup中还为真情况是用户在鼠标在按钮的内按下和松开鼠标键.这允许用户在最后放弃选择-通过保持鼠标键按下并把鼠标指针拖到按钮的外松开,就象其他GUI样.

这就是按钮了.现在来看看文本框吧.

插入符和文本Control控件

我选择是非常简单文本Control控件.它仅仅捕捉击键,而且还不卷屏-但是你可能会要更加复杂,也就是个可以精确处理跳到开始(home)、跳到末尾(end)、插入和删除,或者可能还要通过windows剪贴板支持剪切、拷贝、粘贴.

但是在我们做文本框的前,我们需要个插入符.如果你对这个术语不熟悉,这里解释下.插入符是光标种说法-是,就是那个小小闪动竖线.插入符告诉用户他们击键将会在哪里出现文字.

从我GUI考虑,我很简单处理这些事-我指定活动窗口是具有插入符和句号(这里rick不是很明白)窗口.大多数GUI都是这样,好像也是最好解决办法.而且我GUI象windows那样把文本框标题(caption)当作文本框里文字来处理.

那么你如何开发插入符呢?好,我想我们知道插入符总是在活动窗口里被绘制,并且插入符只有在活动窗口是文本框时候出现,很容易联想到插入符绘制代码是文本框部分并且在文本框绘制里完成.这就使它很易于开发-只要用个整形变量来代表窗口标题索引,你文本框就有要绘制插入符所有信息了.

这就基本上表示,如果是个文本框话,你要做所有绘制工作就是围绕绘制区域画边线,在边线的内绘制窗口标题,然后如果是活动窗口,在正确位置画出插入符.在我GUI里,文本框中最大长度是由文本框窗口大小来决定,也就是说我不用处理在文本框的内滚动文字.然而你或许会想要用户可以在很小文本框里输入很长字串并可以滚动查看文本框中内容.

现在来看看有关文本框最难东西-键盘处理.旦会有击键发生,很容易建立个wm_keypressed并且它,同样很容易为wm_keypressed开发文本框处理器,然后要么把放到窗口标题末尾,要么处理特殊击键(backspace键,等等-这是你字串类要关注东西),然后移动插入符.

地方在于在第位置得到击键.windows提供了至少 3种完全区别思路方法来查询键盘-WM_KEYDOWN事件,GetKeyboardState和GetAsyncKeyState,当然还有DirectInput.我使用了DirectInput思路方法,这是我在开发鼠标光标时候就已经作了大量和DirectInput相关工作,另外通过DirectInput来获取键盘状态对我也是最简洁和优雅思路方法.

要使用DirectInput键盘,你要做件事是建立键盘设备.这和我们在第章中建立DirectInput鼠标设备思路方法令人难以相信相似.基本上,唯差别在于不是告诉DirectInput把我们新设备当作鼠标来处理,而是当作键盘.如果你已经了解DirectInput处理鼠标思路方法,那么再把同样事情为键盘再做遍.

旦获取了键盘设备我们就可以查询它.

要实际判断个键是否被按下需要多点工作.基本上,要判断哪个键被按下,你需要对所有101个键状态两个快照-个来自上帧另个当前帧.当前帧中被按下而上帧没有按下键是被\"点击\",你要为他们发送wm_keypressed消息.

来看看进度条?

进度条

进度条如同静态Control控件样易于开发,他们只接收很少几个消息.

基本上,你需要为进度条做两件事-你要告诉它最大/最小范围和步长.例如,我要创建个载入进度条,由于我要载入100个区别游戏资源.我会创建个范围为0到100进度条.我会把进度条为0,然后,当我载入个资源时候我会用单位长度来让进度条前进个步长.当进度条前进时,它都会重画自身,图形上用个和绘制区成比例长条来表示出它有多长.

进度条很象滚动条;实际上,可以用滚动条思路方法来开发进度条.我把进度条和滚动条分开开发是我想要他们有非常区别外观和细微差别行为-你需要可能会区别.

滑动条和滚动条

绘制滑动条或者滚动条和绘制进度条很相似,这表现在你需要用滑动条绘制矩形百分比,它提供了绘制滑快位置信息,来表现它当前位置.你要为垂直和水平Control控件作些细微修改-我先做了个基类,gui_slider,其中包含了所有公用代码和所有成员变量,然后开发两个区别派生类,gui_slider_horz和gui_slider_vert,它们处理绘制和点击逻辑区别.

就象处理鼠标点击样,我为滑动条选择了简便思路方法.如果鼠标点击在滚动条绘制区内发生,直接自动地滚动到那个位置.在我滑动条里,你不能同时在轴上点击和移动位置-直接跳到你点击地方.我这么做主要是这样会很简单,而且我不喜欢windows默认思路方法.

有关滚动条/滑动条逻辑,你知道和进度条基本设定是-最小、最大、当前位置.然而不象进度条,用户可以通过在Control控件上点击改变当前位置.

现在看看滚动条.我GUI里滚动条就是有两边各有个按钮滑动条.这两个按钮(上/下或左/右箭头)会移动滑快单位距离.这种思路方法消除了大量按钮类和滚动条的间代码复制,我强烈推荐你看看做相似事.

看完了滚动条,看看最复杂Control控件吧.

列表框Control控件

移出精力看这个吧,列表框Control控件是你要花最多时间地方.



// represents a column in our listbox
gui_listbox_column
{
public:
gui_listbox_column { }
virtual ~gui_listbox_column { }

virtual void draw(uti_rectangle &where);

void name(const char *name) { m_name = name; }
uti_ getname(void) { (m_name); }

getwidth(void) { (m_width); }
void width( w) { m_width = w; }

private:
uti_ m_name;
m_width;
};

// an item in our listbox
gui_listbox_item
{
public:
gui_listbox_item { m_isselected = 0; m_indent = 0; }
virtual ~gui_listbox_item { }

virtual draw( colnum, uti_rectangle &where);

void clearallcolumns(void); // boring
void indent( i) { m_indent = i; }
getindent(void) { (m_indent); }

void text( colnum, const char *text); // boring
uti_ gettext( colnum = 0); // boring

void itemdata(unsigned long itemdata) { m_itemdata = itemdata; }
unsigned long getitemdata(void) { (m_itemdata); }

void selected( s = 1) { m_isselected = s; }
getselected(void) { (m_isselected); }

private:
m_isselected;
m_indent; // # of pixels to indent this item
unsigned long m_itemdata;
uti_poer.gif' /> m_coltext;
};

// the listbox itself
gui_fancylistbox : public gui_window
{
public:
gui_fancylistbox { m_multiselect = 0; }
virtual ~gui_fancylistbox { clear; }

getselected( iter = 0);

virtual wm_command(gui_window *win, cmd, param);
virtual wm_pa(coord x, coord y);
virtual wm_lbuttondown(coord x, coord y);

gui_scrollbar_horz &gethscroll(void) { (m_hscroll); }
gui_scrollbar_vert &getvscroll(void) { (m_vscroll); }

virtual wm_sizechanged(void); // the window\'s size has changed somehow

gui_listbox_item *getitemat( index); // boring
gui_listbox_item *additem(const char *text); // boring
delitem( index); // boring
delallitems(void); // boring
gui_listbox_column *getcolumn( index); // boring
addcolumn(const char *name, width); // boring
gui_listbox_column *getcolumnat( index); // boring
delcolumn( index); // boring
delallcolumns(void); // boring

clear(void); // delete columns & items

getnumitems(void);
getnumcols(void);

void deselectall(void);
void selectitem( item);
void selecttoggleitem( item);

void deselitem( item);

private:
m_numdispobjsy;
m_vertgutterwidth; // # of pixels between items vertically

gui_scrollbar_horz m_hscroll;
gui_scrollbar_vert m_vscroll;

bool m_multiselect; // is this multi-selectable?
uti_poer.gif' /> m_items; // .gif' /> of gui_listbox_items
uti_poer.gif' /> m_columns; // .gif' /> of gui_listbox_columns
};

列表框是到现在为止你做最难Control控件吧?但这仅仅是它是最通用.个能够处理多列、缩进、多重选择列表框Control控件将在实战中证明他对你游戏是不可或缺.停下来并想想在大多数游戏里用到列表框地方,你就会很快发现这点.

我把我列表框Control控件分成两部分:个多列\"报表风格\"列表Control控件和个图标列表Control控件,它创建个类似于当你在windows\"我电脑\"里选择大图标察看方式显示.

图表列表Control控件比较容易建立.它使用了列静态图标(在次代码重用),所有具有相同大小.我使用图标宽度除列表框宽,这让我知道有几列可用.(如果证明我列表框比大图表小,我假设我只有列,并让绘制系统剪裁图标以使他们不会超出我绘制区域).旦我有了列数,我通过图标总数除以它计算出我所需要行数.这样我就知道我该怎样设定要包括滚动条.

注意当Control控件改变大小时必须重新计算这些值.为此我设定了个wm_sizechanged消息,calcall将会在窗口绘制区域被改变时候它.

报表风格列表Control控件要复杂些.我先写了两个辅助类,gui_listbox_column和gui_listbox_item,它们包含了所有有关列表中给定物件和列信息.

gui_listbox_column是两者中较简单.主要列表框类有个成员变量身份gui_listbox_column动态,这代表了目前列表框中列.gui_listbox_column包含了在列表框中所需要所有信息,包括列名字,列对齐,显示或隐藏,大小等等.

主要列表框类也有个gui_listbox_item动态数.gui_listbox_item类包含了和我们报表风格列表框中特定行(或物件)相关所有信息.目前这个类最重要数据成员是代表每列数据字串.我也让每个物件通过m_itemdata成员存储个附加32位数据.这个技术类似于windows允许你通过位你列表物件SetItemData和GetItemData来存储32位数据.这个细节很重要,它允许列表框用户为每个物件存储个指针-通常个和该物件有关特定类,以使它以后可用.

如何绘制列和物件呢?我倾向于在要绘制列表框中在每个单独物件/列上有个绝对Control控件.到最后,我决定让列表Control控件通过不断两个虚,gui_listbox_item::draw和gui_listbox_column::draw来绘制他物件和列.每个使用个代表列或者物件在屏幕上位置矩形.默认对这些draw开发仅仅分划出和矩形中特定列和子物件相关文本;然而,我先在可以简单为需要独特外观物件或列派生和重载draw.这种技术目前工作很好,但是我还不足以宣称这是最好思路方法.

然而,绘制物件比行需要更多工作.物件需要用高光绘制,这决定于他们是否被选择.这并不很难,但定不能忘记.



然后就是滚动条问题了.我列表框包含两个成员,m_horzscrollbar和m_vertscrollbar,他们都是GUI滚动条.当列表框大小被改变时(wm_sizechanged),他会看看数据宽度和高度并决定是否显示滚动条.

整理总结

真是绕了大圈子,但是幸运是你对为GUI创建Control控件有了个大致想法.这里我想强调是\"风格就是乐趣\".在做你GUI时不要害怕创新-做做你曾经梦想过东西,和使你游戏最酷东西.如果你游戏很依赖你GUI效能这点尤其重要,比如你在作即时战略游戏.

还要记住当创建Control控件时候,你需要为你游戏其他部分考虑平衡问题-细节表现力和开发时间是成正比.尽量给你玩家最易于上手GUI,但同时不要花时间做50种区别Control控件.你要在功能、好事、复杂性、坏事中做出平衡.

Control控件就到这吧.下章也是最后章中我们会看看资源编辑器,序列化窗口和创建对话框.祝愉快!

参看使用C和Directx开发GUI()
参看使用C和Directx开发GUI( 2)


Tags:  directx使用 directx如何使用 directx怎么使用 directx开发

延伸阅读

最新评论

发表评论