游戏开发论坛

 找回密码
 立即注册
搜索
123
返回列表 发新帖
楼主: loserwang

创建基于c++标准程序库的Windows + Directx通用库,寻找同好者

[复制链接]

4

主题

110

帖子

167

积分

注册会员

Rank: 2

积分
167
发表于 2008-2-12 14:03:00 | 显示全部楼层

Re:创建基于c++标准程序库的Windows + Directx通用库,寻找同

楼主有没有看过 MFC 2008 BETA版, 超强,超强的

20

主题

136

帖子

172

积分

注册会员

Rank: 2

积分
172
 楼主| 发表于 2008-2-17 03:19:00 | 显示全部楼层

[LoserPower]子窗口控制高级主题





1、控件封装:
编辑框、列表框
封装通知码、控制消息、样式枚举

2、消息持有者:MessageHolder
class MessageHolder
{
public:
  MessageHolder(Message& msg_)
    :_Msg(msg_), _oMsg(msg_)
  {}

  ~MessageHolder()
  {
    _Msg = _oMsg;
  }

private:
  Message& _Msg;
  Message _oMsg;
};
利用该持有者以保持当前逻辑代码的消息,以避免消息路由重入造成的逻辑错误:
int MessageBox(const tstring& txt_, uint mb_style_ = MB_OK | MB_ICONASTERISK)
{
  MessageHolder mh(msg);
  int result = ::MessageBox(GetHwnd(), txt_.c_str(), wndname.c_str(), mb_style_);
  return result;
}
消息框作为模态窗口将接管消息循环,循环期间将导致当前消息丢失,使用消息持有者管理类似情况。
其中一个引用类型保存指针,一个值类型保存数据,析构的时候进行还原。

3、Range类:
template<typename RgTy> class Range
{
public:
  typedef RgTy value_type;

  Range()
    :_Begin(RgTy()), _End(RgTy())
  {}

  Range(RgTy begin_, RgTy end_)
    :_Begin(begin_), _End(end_)
  {}

  RgTy Begin() const
  {
    return _Begin;
  }

  RgTy End() const
  {
    return _End;
  }
private:
  RgTy _Begin;
  RgTy _End;
};
利用该类型储存区间。

4、对子类化窗口的识别:
子类化窗口不对其调用部分消息映射,由默认子窗口过程自行管理。为此,需要识别子类化窗口:
if (_ClsExist)
{
  _ClsBrushIndex = Color::_SysColor(GetClassLong(hwnd, GCL_HBRBACKGROUND) - 1);
  //避免旧回调地址和新值重复导致循环调用,置入条件判断
  WNDPROC oProc = WNDPROC(::SetWindowLong(hwnd, GWL_WNDPROC, LONG(GlobalWndProc)));
  if (oProc != GlobalWndProc)
    msg.def_proc = oProc, _ClsSub = true;
}
用户创建窗口回调过程总是为GlobalWndProc,只有系统定义窗口类才包含其他回调。利用该特性在窗口创建时加于识别(_ClsSub = true)

5、整理文件布局。
1)为不同名称空间提供用户编程者界面文件,如los::controls。
该界面文件使用名称空间名:Controls.h,引入下属类型文件:
#include "Window.h"
#include "Timer.h"
#include "Button.h"
#include "Static.h"
#include "Edit.h"
#include "ListBox.h"

引入下属类型声明:
namespace los
{
  namespace controls
  {
    //Window.h
    class Window;

    //Timer.h
    class Timer;
...
  };
}
下属类型文件将不依赖于该文件,而是自满足的,包含各自必须引入条件。
亲系派生树将位于同一个文件。如Button.h包含
class Button;
class CheckBox;
class RadioButton;
class AutoRadioButton;
class GroupBox;
等,利于有效维护。亲系的亲远关系没有唯一定义,一般派生不超过两层,并应具有某种相似性,如以上类型只是样式的不同,均属于Button控件。
2)分割消息委托模型:
//消息类
#include "MessageClasses.h"
//辅助型别,如Message、MessageHolder、CommandTypes等
#include "MessageTypes.h"
//委托适配器
#include "MessageAdaptors.h"
//委托符号
#include "MessageInvokes.h"
//从消息类包含消息索引的实例化标识
#include "MessageIdentifiers.h"
3)用户编程者界面应该先关心各名称空间登记的类型声明,再向下检索。利用该布局形势有利于文件级别的逻辑关联性维护。仅依靠名称空间将使名称空间广度膨胀或者深度下陷。同时亦形成指导性规则。用户编程者界面将尽可能分离各个类型的文件分布,并利用统一的名称空间主文件登记各个子文件的类型,并分发给下一级抽象层(如实现了一个网络通讯的快速开发组件库)。

6、Bug修复:消息循环的优先处理:
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}
else if (message_pump.size())
{
  message_pump.front().Send();
  message_pump.pop();
}
else if ((!RealTime()) && GetLoopWindow() == GetMainWindow())
  PostQuitMessage(0);
每次只处理一个状态,按“消息、应用程序消息泵、实时”优先次序处理。假若实时过程总是被执行,则在消息频发的时候会导致UI更新的不连续性,影响用户视觉。

7、32位色彩结构(未包含16色565、555格式等的导出)
1)支持系统颜色获取,通过内部枚举可以隐式构造一个结构。
2)支持转换到565、555格式,未实现
3)直接访问分量
4)利用构造和类型转换符重载实现转出、转入COLORREF。
5)利用联合和位域在4字节内保存完整索引数值。
6)辅助画刷、画笔类等,直接利用系统色枚举构造画刷、画笔等。
7)对于系统色别名,则添加辅助枚举,使用系统色的函数应实现针对枚举类型和辅助枚举的两个版本。数据保存者则通过强制转换。
struct Color
{
  enum _SysColor
  {
    clrScrollBar = COLOR_SCROLLBAR,
...
  };

  enum _SysColor_
  {
    clrDeskTop = COLOR_DESKTOP,
...
  };

  union
  {
    COLORREF ref;
    struct
    {
      COLORREF red : 8;
      COLORREF green : 8;
      COLORREF blue : 8;
      COLORREF alpha : 8;
    };
  };
...
  Color(COLORREF ref_)
    :ref(ref_)
  {}

  Color(_SysColor sysclr_)
    :ref(GetSysColor(sysclr_))
  {}

  Color(_SysColor_ sysclr_)
    :ref(GetSysColor(sysclr_))
  {}

  operator COLORREF()
  {
    return ref;
  }
};
注意:GetSysColor(nIndex)假若系统色索引越界,返回的0值仍是有效的颜色值。利用GetSysColorBrush(nIndex)在创建画刷时通过返回NULL以便察觉不合法的画刷。避免错误的应用。不推荐由GetSysColor的返回值直接创建背景。
如:设置窗口类背景画刷索引:
SetClassBrush(Color::_SysColor(100));
使用了错误的索引值,则:
Brush SetClassBrush(Color::_SysColor sysclr_ = Color::clrBtnFace)
{
  Brush oBr;
  oBr.Create(_ClsBrushIndex);
  if (GetSysColorBrush(int(sysclr_)) == 0)
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    return oBr;
  _ClsBrushIndex = sysclr_;
  SetClassLong(hwnd, GCL_HBRBACKGROUND, long(_ClsBrushIndex) + 1);
  _ClsBrush.Create(_ClsBrushIndex);
  return oBr;
}
通过判断避免了空画刷的选入。因为子控件可能也将依赖该画刷进行绘图,最后什么也看不到。

8、Bug修复:窗口类画刷的生存期。
1)窗口类可能会多次创建,并且前一个窗口可能被析构,因此窗口类画刷不能位于窗口对象内部,应位于应用程序管理器。但是这将需要建立新的映射表,以进行资源索引。或者仅仅动态申请画刷而不处理资源释放,这意味着内存泄漏,在一个持续运行的系统会带来麻烦。最后,选择了使用系统色索引。避免了窗口类画刷的管理。窗口对象所需的画刷实例,则在每个窗口对象创建时动态通过该系统色索引生成。这份副本将辅助子窗口控件的绘制。
Window::_CtlBrushIndex是系统色索引,建立窗口类的时候对其增1。
wce.hbrBackground = HBRUSH(int(_ClsBrushIndex) + 1);
取出以便建立副本的时候对其减1。
_ClsBrushIndex = Color::_SysColor(GetClassLong(hwnd, GCL_HBRBACKGROUND) - 1);
_ClsBrush.Create(_ClsBrushIndex);
2)窗口类固定只持有系统色索引所指示的画刷句柄,但是用户编程者界面可能需要特定的画刷,如一个具有水平横线的画刷:
SetBrush(Brush(Brush::hsHorizontal, 0x000000));
Window类映射了WM_ERASEBKGND消息:
LRESULT OnEraseBkGnd(DC& dc)
{
  if (!brush.Get())
    return msg.DefProc();
  Rect rc;
  rc.FromClient(hwnd);
  dc.FillRect(rc, brush);
  return 1;
}
返回非0预示着背景清除完成。
至此,实现了几种背景着色模式:
//类画刷索引
this->SetClassBrush(Color::clrBtnFace);
//着色画刷
this->SetBrush(Brush(Brush::hsHorizontal, Color::clrWindowText));

//控件色画刷,选入空画刷以暴露着色画刷
lb.SetPaintBrush(Brush::brushNull);
//着色画刷,可具有图形样式
lb.SetBrush(Brush(Brush::hsDiagCross, 0x000000));
//文字前景色
lb.SetTextColor(Color::clrWindowText);
//文字背景色
lb.SetBkColor();
//文字背景模式,设为透明
lb.SetBkMode(DC::modeTransparent);

20

主题

136

帖子

172

积分

注册会员

Rank: 2

积分
172
 楼主| 发表于 2008-2-23 03:36:00 | 显示全部楼层

[LoserPower]运行时、资源、句柄、对象层、属性



1、运行时:
为类交叉引用和其他全局服务创建运行时库。除未知符号外,一般编译依然依据库文件尽可能在线编译。

2、资源:
1)光标
class Cursor
  :public Handle<HCURSOR>

Type(const tstring& res, HINSTANCE hinst = 0)
  :Base(LoadIcon(hinst, res.c_str()), true)//Shared resource
{}
构造时向基类传递,创建共享句柄(析构时不释放资源)
2)图标
class Icon
  :public Handle<HICON>

3、句柄:
template<typename HdrTy> class Handle
  : public Object
1)包含一个状态开关用于向释放回路表明资源当前已经释放:
void Released()
{
  _HoldHandle = false;
}
2)手动启用资源的共享模式,如窗口类的资源指定和替换动作需要维护该共享模式,以便多个窗口共享一套资源:
bool SharedHandle(bool shared_ = true)
{
  bool oState = _FromHandle;
  _FromHandle = shared_;
  return oState;
}
3)复合体用于代码自解释和访问便利性:
union
{
  HdrTy handle; //基础句柄
  struct
  {
    HdrTy _HoldHandle; //bool值,是否持有句柄
  };
  //句柄别名
  struct
  {
    HWND hwnd; //窗口对象的句柄别名
  };
  struct
  {
    HDC hdc; //设备表对象的句柄别名
  };
};
应用,进行类画刷替换,资源替换前应取消共享,否则旧有资源将因得不到释放而泄漏:
Brush SetClassBrush(const Brush& br_= Color::clrBtnFace)
{
  Brush oBr = _ClsBrush;
  //取消共享
  _ClsBrush.SharedHandle(false);
  //替换资源
  _ClsBrush = br_;
  //设置共享
  _ClsBrush.SharedHandle();
  if (hwnd)
    oBr = HBRUSH(SetClassLong(hwnd, GCL_HBRBACKGROUND, long(_ClsBrush.Get())));
  return oBr;
}

4、对象层Object Layer。
1)并不是严格的运行时类型识别。它所携带的信息仅仅是一个层次标识:
class Object
{
  BaseClass(Object);
  ObjectLayer(_Object);

  enum _LayerMask
  {
    _Object = 1000,
    _Handle = 1001,
    _DC = 1002,
    _PaintDC = 1003,
    _GDIObject = 1004,
    _Brush = 1005,
    _Pen = 1006,
    _Window = 1007,
    _Icon = 1008,
    _Cursor = 1009,
  };
};
ObjectLayer(ly)展开:
virtual int LayerId()
{
  return ly;
}
2)用例:DC和PaintDC的句柄释放动作:
一般派生类持有的基类资源可由基类管理释放或者宣告结束动作,如GDI包装对象之间的关系,或者窗口控件的关系,固定由一个层次完成相应句柄的回收。但是DC和PaintDC分别用ReleaseDC和EndPaint完成句柄归还。为此需要启用对象层标识:
bool DC::_Release()
{
  if (!BaseRelease())
    return false;
  if (LayerId() == _DC)
  {
    ReleaseDC(hwnd, hdc);
    Released();
  }
  RestoreBrush();
  RestorePen();
  RestoreFont();
  return true;
}

bool PaintDC::_Release()
{
  if (!BaseRelease())
    return false;
  if (LayerId() == _PaintDC)
    EndPaint(hwnd, &ps);
  Released();
  return true;
}
如上,句柄归还动作需要访问对象层标识,以完成资源释放,并标记为已释放的,这将导致句柄值被清除为0。
当然,完成对象层识别仅需要以下说明段:
ObjectLayer(_DC);

ObjectLayer(_PaintDC);
主要标识被登记在Object::_LayerMask,用户自定义派生树需要自己维护其对象层标识。当然通常情况下并不推荐使用。

5、属性:
注意:该实现版本并不是最有效的实现,仅满足于含有
BaseClass(cls);
InheritClass(cls);
指示Base和Type分别为基类和派生类型别的类定义上作为内置的属性。
1)制作属性模板:
template<typename Ty, typename Owner> class PropertyTmpl
{
public:

  PropertyTmpl& operator()(Owner* this_, const_reference val = value_type());

  reference operator&()
  {
    return (*this);
  }

  operator reference()
  {
    return Get();
  }

  virtual reference Get() = 0;

  virtual value_type operator=(const_reference val) = 0;

  //operators
  bool operator==(const_reference val_);

  bool operator!=(const_reference val_);

  bool operator!();

protected:
  Owner* owner;
  value_type value;
};
该模板向实现类提供了Get和Set接口。因为operator=是不可继承的,总是会被派生类覆盖,因为派生类在不显式定义operator=的时候总是从复制构造中获得执行动作,而实现新版本operator=将一样使得旧版本被覆盖,而这失去了属性的语法纯粹性,派生类不应在机制上依赖宏或者用户实现。
可以将派生类名传回给基类,基类将其定义别名:
typedef ThisTy Set。
然后在派生类按以下格式:
Set(const type& val);
这看似可行,但是有以下问题:第一,不能有返回值;第二,不能使用在具有
宿主资源访问的属性上,而这时绝有可能的情况,因为传入类型将会利用该函数构造出一个有效对象,并利用默认赋值将新对象数据覆盖到属性对象上,而这时候便丢失了宿主指针,发生内存访问错误:
PropertyA = val_a;
除非这样调用:
PropertyA = TypePropertyA(val_a, this);
但是没有人会使用这样的“属性”语法。
2)使用宏修整外观:
#define Property(name, type)
    struct _##name     :public PropertyTmpl<type, Type>
因为某种原因,宏展开的未命名类型并没有得到编译器的临时串关联,总是生成同样的名称:
__unnamed
而非:
__unnamed_f8d2bd48_1等。
为了该不幸的理由,属性头要指出属性名称以便制作唯一的属性类型名。
#define Set(val)
    operator=(val)
同样不幸的,构造式和operator=均为Set的实现带来了麻烦,为此,采用了最简单的办法,将Set展开成operator=。一个好消息是:尽管Get()函数名经常被使用,但是Set()是相当少见的,实在不放心的话为其添加前缀下划线。
3)好了,具备属性了:
property:
  Property(ClassName, tstring)
  {
    tstring& Get()
    {
      return value;
    }

    tstring Set(const tstring& val)
    {
      tstring oName = value;
      if (!owner->_HoldHandle || owner->_FromHandle)
        value = val;
      return oName;
    }
  }ClassName;
说明如下:一、Get()总是返回数据类型引用。二、不应为Set()指定默认值,这同样是operator=所不支持的,Set()返回值类型。三、value是属性值的界面,owner是宿主指针的界面。四、完成一个属性类型应该同时为其指定属性实例,属性类型名和实例命名不需要配对,为了简单,尽量完成配对。
4)又是麻烦的情况:属性对象构造在声明时不能指定构造数据,在宿主构造式不能访问宿主this指针,仅应在构造体上操作:
Text(this, text_);
ClassName(this, clsname_);
每个构造式均应有针对性地完成该模拟构造过程,它使用了operator(),其中初始化值是可省略的:
Text(this);
ClassName(this);
当然可以不指定宿主,仅仅因为它“麻烦”,要在宿主完成构造的时候才允许访问,但是属性将因此而不能更新宿主状态,仅仅是孤立的数据管理单元。而在界面编程中,这种属性简直一无是处。到处都是关联的且实时变化的属性和宿主状态。
5)属性值的方法调用。
属性实质上是一个值的包装类,并不是值本身,这意味着它要表现出值的特性,要么制作感兴趣的代理接口,如operator==,operator!=,operator!等操作符。要么利用显式方法来传出值引用类型,这就不像外部访问中,仅仅让编译器完成隐式转换那么轻松了,使用ref()和operator&来获取值引用:
reference operator&()
{
  return (*this);
}
因为优先级的关系,所以使用operator&需要加括号:
wce.lpszClassName = (&ClassName).c_str();
就像一般内存对象的解地址:
(&Abc)->mtd();
6)所有这一切为了界面:
Text = los::app->LoadString(IDS_MAINWND);
MessageBox(Text);
替代了:
SetText(los::app->LoadString(IDS_MAINWND));
MessageBox(GetText());
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-12-20 04:34

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表