游戏开发论坛

 找回密码
 立即注册
搜索
楼主: loserwang

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

[复制链接]

20

主题

136

帖子

172

积分

注册会员

Rank: 2

积分
172
 楼主| 发表于 2008-1-27 19:13:00 | 显示全部楼层

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

整理GDI浪费了不少时间,平常又不会直接用到的东西。
键盘消息结构:
struct KEYFLAGS
{
  unsigned short repeat_count;
  unsigned char scan_code;
  unsigned char extend_key : 1;
  unsigned char no_used : 2;
  unsigned char reserved : 2;
  unsigned char context_code : 1;
  unsigned char previous : 1; // 0 先前释放、1 已按下 或者重复的按键码
  unsigned char transition : 1; // 0 正被按下、1 正被释放
};

分解消息:
//LRESULT (ClsTy::*)(unsigned int, KEYFLAGS)
template<typename ClsTy> class invoke_ui_kf
  :public invoke_root
{
public:
  _InheritClass(invoke_ui_kf);
  typedef unsigned int arg_type_1;
  typedef KEYFLAGS arg_type_2;

  typedef LRESULT(ClsTy::*method_type)(arg_type_1, arg_type_2);

  _Type(class_type* This_, method_type Met_)
    :_Base(This_), Method(Met_)
  {}

  virtual LRESULT operator()(WPARAM wparam, LPARAM lparam)
  {
    arg_type_1 arg1 = arg_type_1(wparam);
    arg_type_2 arg2 = *(arg_type_2*)(&lparam);
    return (reinterpret_cast<ClsTy*>(_This)->*Method)(arg1, arg2);
  }
private:
  method_type Method;
};

符号重载:
//=======================================================================
//WM_CHAR
//LRESULT (ClsTy::*)(unsigned int char_code, KEYFLAGS pKeyflags)
// char_code: 字符码
// pKeyflags: 0-16、重复按键,17-23、扫描码,24、101/102增强键位,29、环境量,30、先前状态,31、转换状态
//=======================================================================
template<typename ClsTy> void invoke(wm_char_tag, typename invoke_ui_kf<ClsTy>::method_type met, ClsTy* this_val)
{
  this_val->message_map[wm_char_tag::wm_value]
  = static_cast<invoke_root*>(new invoke_ui_kf<ClsTy>(this_val, met));
}
    };
  };
};

编写使用键盘消息的过程:
LRESULT on_char(unsigned int vkey, KEYFLAGS keyflags)
{
  los::dc clientdc(hwnd);

  stringstream ss;
  ss << t("字符码") << int(vkey) << t(":   ") << tchar(vkey) << std::endl;
  ss << t("重复计数") << int(keyflags.repeat_count) << std::endl;
  ss << t("扫描码") << int(keyflags.scan_code) << std::endl;
  ss << t("101/102增强键位") << int(keyflags.extend_key) << std::endl;
  ss << t("环境量") << int(keyflags.context_code) << std::endl;
  ss << t("先前状态") << int(keyflags.previous) << std::endl;
  ss << t("状态") << int(keyflags.transition) << std::endl;

  los::rect rc;
  rc.from_client(hwnd);
  clientdc.drawtext(rc, ss.str(), DT_CENTER | DT_VCENTER | DT_WORDBREAK);
  return 0;
}

17

主题

166

帖子

174

积分

注册会员

Rank: 2

积分
174
发表于 2008-1-28 13:09:00 | 显示全部楼层

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

我对写gui程序没有爱,更不用说gui库了。。

20

主题

136

帖子

172

积分

注册会员

Rank: 2

积分
172
 楼主| 发表于 2008-1-29 01:40:00 | 显示全部楼层

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


[LoserPower]包装GDI对象

1、派生关系的类型传递:
#define _BaseClass(cls)    public:         typedef cls _Type

#define _InheritClass(cls)  public:         typedef _Type _Base;         typedef cls _Type

这样就可以使用_Type表示当前类型,_Base引用基类型。
class gdi_object
{
public:
  _BaseClass(gdi_object);
...
};

class pen
  :public gdi_object
{
public:
  _InheritClass(pen);
...
};

2、枚举Stock类型
enum _StockMask
{
  penWhite = WHITE_PEN,
  penBlack = BLACK_PEN,
  penNull = NULL_PEN,

  brushWhite = WHITE_BRUSH,
  brushLtGray = LTGRAY_BRUSH,
  brushGray = GRAY_BRUSH,
  brushDkGray = DKGRAY_BRUSH,
  brushBlack = BLACK_BRUSH,
  brushNull = NULL_BRUSH,
  brushHollow = HOLLOW_BRUSH
};
因为枚举值依赖<wingdi.h>,使用到宏常量的创建API并不会接受_StockMask枚举类型数据时引发数值越界。
*使用枚举的好处:1)不会占用对象内存空间,2)可以作为对象数据简化编程,3)直接使用枚举类型来创建一个和枚举子项内存布局兼容的数据,控制编译时行为。4)避免了创建冗长分散的静态常量。

使用stock索引创建画刷对象,并填充到LOGBRUSH结构中。
brush::_Type(_StockMask stock_)
  :_Base(stock_)
{
  GetObject(handle, sizeof(LOGBRUSH), &_LogBrush);
}

这个LOGBRUSH结构帮助GDI对象复制的时候创建一份一致的副本。

3、gdi_object作为gdi对象的基类,作为标准界面的用处大于基类的通常用途。因为派生出的gdi对象通常和指定逻辑结构匹配,例如逻辑画笔LOGPEN
typedef struct tagLOGPEN {
  UINT     lopnStyle;
  POINT    lopnWidth;
  COLORREF lopnColor;
} LOGPEN, *PLOGPEN;
而这需要明确的具体类,因此说gdi_object没有完整性特性,只提供基本的生存期控制,复制的对象无法获得新的句柄,相当于原对象句柄的引用。

gdi_object的标准界面:
_Type(_StockMask stock_); //从stock索引中选入对象句柄
virtual void release(); //通常是调用DeleteObject删除句柄
virtual void from_handle(HGDIOBJ); //包装句柄,以提供操作
virtual void from_stock(_StockMask); //运行时选入对象句柄
HGDIOBJ get() const; //获得当前句柄

4、派生类将执行对逻辑对象结构的管理和复制
pen::_Type(_StockMask stock_)
  :_Base(stock_)
{
  GetObject(handle, sizeof(LOGPEN), &_LogPen);
}
pen对象从stock依据索引选入画笔后,同时取出LOGPEN结构信息。
之后便可以调用复制语义
pen::_Type(const _Type& copy_)
{
  if (copy_.handle)
    _Create(copy_._LogPen);
}

作为具体类,from_handle等亦进行了扩展
void pen::from_handle(HPEN handle_)
{
  _Release();
  handle = HGDIOBJ(handle_);
  GetObject(handle, sizeof(LOGPEN), &_LogPen);
}

整体上,类的实现可以认为是特定于GDI对象的智能指针。

5、选进的stock对象句柄亦会被执行销毁,当然,系统对象被销毁时什么也不会发生。

6、选入到设备表,外部对象的句柄会被选入设备表,设备表的生命期应该覆盖外部对象的生命期,而这是很容易保证的,并且也应当保证。较好的做法是使用operator=创建一个副本,但意味着较高的开销(画笔的创建和销毁可能是每个实时周期发生的)。
const pen& select_pen(const pen& p_)
{
  restore_pen();
  _OldPen.from_handle(SelectPen(hdc, p_.get()));
  return _OldPen;
}

const pen& select_stock_pen(int id_)
{
  restore_pen();
  _OldPen.from_handle(SelectPen(hdc, GetStockPen(id_)));
  return _OldPen;
}

画笔的还原:
void restore_pen()
{
  if (_OldPen.get())
    SelectPen(hdc, _OldPen.get());
}

对象还原发生在设备表销毁期间
void dc::_Release()
{
  restore_brush();
  restore_pen();
  restore_font();
}
这会恢复默认设置。

7、为了创建持久的GDI对象,比如从属于窗口的生存期,这需要创建指定窗口的成员数据,并在创建窗口期间调用dc::from_client(hwnd),然后将有效的GDI对象选入该设备表对象。试图让局部设备表对象持有超过其生命期的设备表对象句柄存在逻辑谬论。
virtual void dc::from_client(HWND hwnd_)
{
  _Release();
  ReleaseDC(hwnd, hdc);
  hdc = GetDC(hwnd_);
}
首先_Release()恢复旧设备表,ReleaseDC()释放旧设备表资源。
WM_PAINT的paint_dc不需要窗口类生命期和from_client()方法,故使用virtual创建唯一的项目,并在paint_dc内指定到私有域:
private:
  void from_client(HWND)
  {}
  void from_window(HWND)
  {}

8、恢复设备表对象在使用上述设备表类中并不是必须的,但是不排除外部程序会直接执行针对句柄的操作,并由某种形式维护资源。

20

主题

136

帖子

172

积分

注册会员

Rank: 2

积分
172
 楼主| 发表于 2008-1-30 01:41:00 | 显示全部楼层

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

[LoserPower]分解和响应鼠标消息



1、鼠标消息指示类和携带的掩码
struct wm_mouse_tag
  :public wm_null_tag
{
  enum _KeyMask
  {
    mkLButton  =  MK_LBUTTON,
    mkRButton  =  MK_RBUTTON,
    mkShift    =  MK_SHIFT,
    mkControl  =  MK_CONTROL,
    mkMButton  =   MK_MBUTTON,

#if (_WIN32_WINNT >= 0x0500)
    mkXButton1  =  MK_XBUTTON1,
    mkXButton2  =  MK_XBUTTON2,
#endif //_WIN32_WINNT >= 0x0500
  };

  struct flags
  {
    bool lbutton;
    bool rbutton;
    bool shift;
    bool control;
    bool mbutton;

#if (_WIN32_WINNT >= 0x0500)
    bool xbutton1;
    bool xbutton2;
#endif //_WIN32_WINNT >= 0x0500
  };
}wm_mouse;

子消息类型将继承统一的界面:WM_LBUTTONDOWN、WM_MOUSEMOVE等

2、分解消息掩码的委托器
//LRESULT (ClsTy::*)(point, wm_mouse_tag::flags)
template<typename ClsTy> class invoke_pt_mf
  :public invoke_root
{
public:
  _InheritClass(invoke_pt_mf);
  typedef point arg_type_1;
  typedef wm_mouse_tag::flags arg_type_2;

  typedef LRESULT(ClsTy::*method_type)(arg_type_1, arg_type_2);

  _Type(class_type* This_, method_type Met_)
    :_Base(This_), Method(Met_)
  {}

  virtual LRESULT operator()(WPARAM wparam, LPARAM lparam)
  {
    arg_type_1 arg1;
    arg1.x = int(LOWORD(lparam));
    arg1.y = int(HIWORD(lparam));
    arg_type_2 arg2;
    arg2.lbutton = int(wparam) & wm_mouse_tag::mkLButton;
    arg2.rbutton = int(wparam) & wm_mouse_tag::mkRButton;
    arg2.shift = int(wparam) & wm_mouse_tag::mkShift;
    arg2.control = int(wparam) & wm_mouse_tag::mkControl;
    arg2.mbutton = int(wparam) & wm_mouse_tag::mkMButton;

#if (_WIN32_WINNT >= 0x0500)
    arg2.xbutton1 = wparam & wm_mouse_tag::mkXButton1;
    arg2.xbutton2 = wparam & wm_mouse_tag::mkXButton2;
#endif //_WIN32_WINNT >= 0x0500

    return (reinterpret_cast<ClsTy*>(_This)->*Method)(arg1, arg2);
  }
private:
  method_type Method;
};

上述依据编译目标windows版本而提供了延伸的信息。

3、一个实现的委托符号
//=======================================================================
//WM_MOUSEMOVE
//LRESULT (ClsTy::*)(point mouse_point, ClsTy::flags Mouseflags)
// mouse_point: 鼠标指针位置
// Mouseflags: 被下压按键:LBUTTON、RBUTTON、SHIFT、CONTROL、MBUTTON、[WINNT(0x0500) XBUTTON1、XBUTTON2]
//=======================================================================
template<typename ClsTy> void invoke(wm_mousemove_tag, typename invoke_pt_mf<ClsTy>::method_type met, ClsTy* this_val)
{
  this_val->message_map[wm_mousemove_tag::wm_value]
  = static_cast<invoke_root*>(new invoke_pt_mf<ClsTy>(this_val, met));
}

4、使用
LRESULT on_mouse(los::point pt, wm_mouse_tag::flags flags)
{
  cur_pt = pt;

  event.str(t(""));
  event << std::endl;
  event << t("WM_MOUSE") << t("[") << pt.x << t(", ") << pt.y << t("]") << std::endl;
  los::point wp = pt.client_to_screen(hwnd);
  event << t("WM_MOUSE") << t("[") << wp.x << t(", ") << wp.y << t("]") << std::endl;
  event << t("MK_LBUTTON") << flags.lbutton << std::endl;
  event << t("MK_RBUTTON") << flags.rbutton << std::endl;
  event << t("MK_SHIFT") << flags.shift << std::endl;
  event << t("MK_CONTROL") << flags.control << std::endl;
  event << t("MK_MBUTTON") << flags.mbutton << std::endl;
  invalidate(0, true);
  return 0;
}

5、尚未包装:
1)TrackMouseEvent来定时发送WM_MOUSELEAVE、WM_MOUSEHOVER等消息。
2)ShowCursor(BOOL)通过计数来显/隐鼠标指针。
3)GetCursorPos(&pt),SetCursorPos(x, y)操作鼠标指针。
4)SetCapture(hwnd)跟踪越界鼠标按钮状态。
5)GetKeyState(vkey)以确认和鼠标关联的虚拟键状态。
6)其他未涉及主题。

0

主题

228

帖子

285

积分

中级会员

Rank: 3Rank: 3

积分
285
发表于 2008-1-30 10:30:00 | 显示全部楼层

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

LZ努力,贵在坚持

4

主题

61

帖子

61

积分

注册会员

Rank: 2

积分
61
发表于 2008-1-30 17:52:00 | 显示全部楼层

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

想法是美好的
道路是坎坷的
前途是乌黑的

35

主题

1735

帖子

1739

积分

金牌会员

Rank: 6Rank: 6

积分
1739
QQ
发表于 2008-1-30 21:30:00 | 显示全部楼层

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

果然执着......

20

主题

136

帖子

172

积分

注册会员

Rank: 2

积分
172
 楼主| 发表于 2008-2-1 00:13:00 | 显示全部楼层

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

[LoserPower]鼠标高级主题

写这段的时候,估计要休息一两天,为一个功能竟写了这么多辅助代码。整理完通用控件、高级控件、对话框资源后就可以设计独立的积木式窗口布局。

1)鼠标捕获
void _PreMessage()
{
  if (msg.message == WM_LBUTTONDOWN || msg.message == WM_MBUTTONDOWN || msg.message == WM_RBUTTONDOWN)
    SetCapture(hwnd);
  if (msg.message == WM_LBUTTONUP || msg.message == WM_MBUTTONUP || msg.message == WM_RBUTTONUP)
    ReleaseCapture();
}
其中msg是当前消息副本,因为WM_MOUSEMOVE并不实际被跟踪,所以不进行捕捉

2)消息标示器
//消息标示类
struct wm
{
  _BaseClass(wm);
  const unsigned int wm_value;
  _Type(unsigned int wm_value_)
    :wm_value(wm_value_)
  {}
};

struct wm_ui_ii
  :public wm
{
  _InheritClass(wm_ui_ii);
  _Type(unsigned int wm_value_)
    :_Base(wm_value_)
  {}
};
为指定格式消息委托提供识别功能:
template<typename ClsTy> void invoke(wm_ui_ii wm_, typename invoke_ui_ii<ClsTy>::method_type met, ClsTy* this_val)

因为从类方法提取偏移指针的语法比较丑陋,提供了一个简单宏:
#define hold(met) &_Type::##met, this

现行版本:
invoke(wm(WM_MOUSELEAVE), hold(on_mouseleave));
其中WM_MOUSELEAVE是没有显式消息类,依靠wm携带其数据。

3)消息指示类、委托器的命名格式:
{wm|invoke}_{({AB|E}_{CD|F})|void}
如wm_AB_F、wm_void、wm_E_CD
A:LOWORD(wParam)|void
B:HIWORD(wParam)|void
C:LOWORD(lParam)|void
D:HIWORD(lParam)|void
E:wParam|void
F:lParam|void
可选符号
ui:unsigned int
i:int
v:void
p:void*
kf:wm_key_tag::flags
mf:wm_mouse_tag::flags


例:
invoke_vui_p
LOWORD(wParam) => void
HIWORD(wParam) => unsigned int
lParam => void*

4)更新的鼠标键码状态值掩码:
struct flags
{
  union
  {
    struct
    {
      unsigned int lbutton : 1;
      unsigned int rbutton : 1;
      unsigned int shift : 1;
      unsigned int control : 1;
      unsigned int mbutton : 1;

#if (_WIN32_WINNT >= 0x0500)
      unsigned int xbutton1 : 1;
      unsigned int xbutton2 : 1;
#endif //_WIN32_WINNT >= 0x0500
    };
    unsigned int bits;
  };

  operator uint()
  {
    return bits;
  }
};
}wm_mouse;

5)鼠标悬浮指针跟踪
invoke(wm_mousemove, hold(on_mousemove));
LRESULT on_mousemove(wm_mouse_tag::flags flags, los::point pt)
{
  if (!_Tracking)
  {
    TRACKMOUSEEVENT tme;
    tme.cbSize = sizeof(TRACKMOUSEEVENT);
    tme.dwFlags = TME_HOVER | TME_LEAVE /*TME_NONCLIENT*/;
    tme.hwndTrack = hwnd;
    tme.dwHoverTime = 1;
    _TrackMouseEvent(&tme);
    _Tracking = true;
  }
  return 0;
}

具有两个跟踪状态机:
bool _Tracking; //跟踪客户区
bool _NcTracking; //跟踪非客户区

6)消息对象层次路由:
对于跟踪鼠标悬浮指针等处理过程,基类应当获得调用方能保证跟踪逻辑被正确执行。也就是对于同一个消息,派生层次对象和基类层次对象均能有效执行,这在MFC中通过帮助向导自动插入含有等性的参数表的基类过程调用,没有自维护能力。
解决方法:
1、不同对象层次维护不同的映射表,效率最差,逻辑最混乱,最难维护
2、不同对象层次添加动态类型信息,并扩充委托器能力,包含多个受委托消息处理器,但是一般用户不希望使用所有层次的消息处理器,而且效率也不允许,为了屏蔽特定消息处理器又要执行多余的维护动作。
3、映射动态生成表,为此提供了动态委托符以便立即执行处理。
LRESULT _Proc()
{
  //对应当前类型的消息映射动态生成表,供派生类调用
  //派生类亦可实现该表,并往下传递
  //不实现该表的派生类之派生类直接使用当前表
  if (msg.message == WM_COMMAND)
    return invoker(wm_command, hold(on_command))(msg.wparam, msg.lparam);
  if (msg.message == WM_MOUSEMOVE)
    return invoker(wm_mousemove, hold(on_mousemove))(msg.wparam, msg.lparam);
//...//
  if (msg.message == WM_CLOSE)
    return invoker(wm_close, hold(on_close))(msg.wparam, msg.lparam);
  return 0;
}

在派生类中使用:
LRESULT on_close()
{
  modalwindow w;
  if (do_modal(w) == IDOK)
    return _BaseProc();
  return 0;
}
//========================================================
//基类方法调用
//========================================================
#define _BaseCreate    _Base::_Create
#define _BaseInvokes    _Base::_Invokes
#define _BaseProc    _Base::_Proc
#define _BaseRelease    _Base::_Release

所有的宏旨在消除古怪的语法表示,并且这些语法是约束界面的组成部分。

7)由消息向基类传递引申的问题:模式窗口/对话框/消息框
对象层次依靠一个内部数据
message_package msg;
作为消息副本。WM_NCMOUSEMOVE和WM_NCMOUSELEAVE等就是利用这个副本完成自定义过程后同时调用默认过程,以便更新非客户区用户者界面的悬停效果:
LRESULT on_ncmouseleave()
{
  _NcTracking = false;
  return msg.def_proc();
}
但是弹出模态窗口后,消息循环被该窗口管理。主窗口的逻辑代码被系统挂起,但是线程并没有挂起,而是依然在处理消息,如此,逻辑上挂起的过程实际上已经被覆盖了逻辑正确的消息包副本。为此,需要为当前逻辑代码创建一个临时副本(副本位于栈上)
int message_box(const tstring& txt_, unsigned int mb_style_ = MB_OK | MB_ICONASTERISK)
{
  message_package oMsg = msg;
  int result = MessageBox(hwnd, txt_.c_str(), wndname.c_str(), mb_style_);
  msg = oMsg;
  return result;
}。
为了有效管理模态窗口,类似于MessageBox,主窗口利用自己的过程进行了调用,以免用户手动创建该副本:
int do_modal(window& wnd)
{
  message_package oMsg = msg;
  int result = wnd._Modal(this);
  msg = oMsg;
  return result;
}
*派生类可以向基类引用传参。
使用_Modal命名告诉界面用户,不应该直接使用该方法。
LRESULT on_close()
{
  modalwindow w;
  if (do_modal(w) == IDOK)
    return _BaseProc();
  return 0;
}
可以确定的是,包含模态窗口的过程不应该出现在对消息发送时间、顺序敏感的消息处理上,因此,逻辑结构上总是认为被恢复的消息副本是有效的。如WM_CLOSE就是这一类,而像一般的键盘鼠标消息等则始终表现为怪异的行为和逻辑,除非被关注的是其携带的命令的处理。

20

主题

136

帖子

172

积分

注册会员

Rank: 2

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

[LoserPower]子窗口控制(2008.2.3 - 2.9)



1、计时器类Timer
class Timer
{
public:
  BaseClass(Timer);

//1)初始化时指定id以便绑定到指定消息处理函数
  Type(uint id_)
    :_Wnd(0), _Id(id_), _Elapse(0), _TimerFunc(0)
    , counter(0)
  {
    if (_Id == 0)
      throw std::underflow_error("The timer event identity can not equal to zero.");
  }
//2)设定计时器
  void Set(Window* wnd_, uint elapse_, TIMERPROC timerfunc_ = 0)
  {
    _Elapse = elapse_;
    _Wnd = wnd_;
    _TimerFunc = timerfunc_;
  }
//3)启动计时器
  uint Start()
  {
    counter = 0;
    _Id = SetTimer(_Wnd->GetHwnd(), _Id, _Elapse, _TimerFunc);
    return _Id;
  }
//4)关闭计时器
  void End()
  {
    counter = 0;
    KillTimer(_Wnd->GetHwnd(), _Id);
  }
};
5)不绑定到窗口,则创建应用程序计时器,分配唯一id,并需要指定回调函数。对于指定回调函数的情况,总是优先传递到回调函数而不是用消息。

2、处理按钮通知
struct id_type
{
  union
  {
    struct
    {
      uint id : 16;
      uint notify : 16;
    };
    uint value;
  };

  id_type(uint value_)
    :value(value_)
  {}

  id_type(uint notify_, uint id_)
    :notify(notify_), id(id_)
  {}

  operator uint()
  {
    return value;
  }
};

该标识用于消息映射时转换到uint并登记到映射表。
Button类中的通知码枚举:
enum _NotifyMask
{
  bnClicked = BN_CLICKED,
  bnPaint = BN_PAINT,
  bnPushed = BN_PUSHED,
  bnUnPushed = BN_UNPUSHED,
  bnDisable = BN_DISABLE,
  bnDblClk = BN_DBLCLK,
  bnSetFocus = BN_SETFOCUS,
  bnKillFocus = BN_KILLFOCUS,
};
请求一个预定义属性(动态包装的数据):
wm_command_tag::id_type id(uint notify_ = bnClicked) const
{
  wm_command_tag::id_type id_(notify_, _Id);
  return id_;
}

利用一系列属性进行通知码映射:
invoke(btn.clicked(), hold(OnBtnClk));

通知码解释器:
LRESULT OnCommand(uint id, uint code, void* handle_)
{
  wm_command_tag::id_type idty(code, id);
  map_type::iterator itr = command_map.find(map_type::key_type(idty));
  if (itr != command_map.end())
    return (*itr->second)(WPARAM(idty), LPARAM(handle_));
  return -1;
}
其根据WM_COMMAND消息重新包装参数,分发到命令路由。

3、虚析构函数
一旦一个基类登记了一个虚析构函数,则将在派生类形成完整的析构对象层次。其行为就如同自动对象的析构过程,自底向上。派生类是否显式使用析构函数,是否声明为虚析构函数,均不会对对象层次产生差异性影响。不过对于一个非虚析构派生的类型,声明为虚析构函数将总是能在该对象层次得到完整析构,使用该类型的指针,行为就如同它是最底基类那样。

4、单选按钮的互斥规则:
1)创建子窗口时,登记到父窗口类的表格中,手动处理单选按钮的互斥逻辑。
2)使用EnumChildWindow(HWND, WNDENUMPROC,LPARAM)完成动态子窗口表创建。
3)使用BS_AUTORADIOBUTTON由系统管理互斥,并加入到BS_GROUP中形成逻辑组。
4)使用CheckRadionButton(int first_, int last_, int check_)方法:
bool CheckRadioButton(int first_, int last_, int check_)
{
  return ::CheckRadioButton(hwnd, first_, last_, check_);
}
int GetCheckedRadioButton(int first_, int last_)
{
  for (int i = first_; i <= last_; ++i)
  {
    HWND hwndchild = GetDlgItem(hwnd, i);
    if (hwndchild)
      if (::SendMessage(hwndchild, BM_GETCHECK, 0, 0) == BST_CHECKED)
        return i;
  }
  return -1;
}
以完成简单灵活的任务。

5、从句柄绑定到Window类
void FromHandle(HWND hwnd_)
{
  if (!hwnd_)
    return;
  Release();
  hwnd = hwnd_;
  hinst = HINSTANCE(GetWindowLong(hwnd, GWL_HINSTANCE));
  hmenu = HMENU(GetWindowLong(hwnd, GWL_ID));
  los::graphics:C dc(hwnd);
  hfont = dc.SelectStockFont(DEFAULT_GUI_FONT);

  tchar buf[256];
  GetClassName(hwnd, buf, 256 - 1);
  clsname = buf;

  WNDCLASSEX wce;
  if (GetClassInfoEx(hinst, clsname.c_str(), &wce))
  {
    hbrush = wce.hbrBackground;
    _ClassStyle = wce.style;
    _ClsExist = true;
  }

  parent = app->GetWindow(::GetParent(hwnd));
  Rect wndrc = this->GetWindowSize();
  _DefaultX = wndrc.left;
  _DefaultY = wndrc.top;
  _DefaultW = wndrc.Width();
  _DefaultH = wndrc.Height();
  _ExStyle = uint(GetWindowLong(hwnd, GWL_EXSTYLE));
  _Style = uint(GetWindowLong(hwnd, GWL_STYLE));
  _FromHandle = true;
}

为句柄包装而创建的简单对象:
Type()
  : hwnd(0)
  , _IdFeedback(0), _IsModal(false), _Tracking(false), _NcTracking(false), _ClsExist(false), _FromHandle(false)
{
}

简单对象不能创建新窗口:
void _Create(Type* parent_ = 0)
{
  if (clsname == t(""))
    throw los::errortypes::invalid_object();
...
}

句柄包装对象不能释放原窗口资源:
bool _Release()
{
  if (!hwnd || _FromHandle)
    return false;
...
}

6、窗口析构、窗口资源释放、窗口响应消息队列的解构
1)窗口析构和窗口资源释放均访问_Release(),该方法是对应于每个对象层次的特定实现。
bool _Release()
{
  if (BaseRelease())
  {
    ...释放当前对象层次的资源
    return true;
  }
  return false;
}
所不同的是,
1、窗口析构是由虚析构函数访问规则维护其调用形式,由对象生存期决定动作时间。并且每个对象层次的析构函数副本均可以执行。总是从对象层次最外层开始调用析构,所以将外层析构认定为接口的暴露。自上而下解析,底层对象的析构将可能什么也不做。一次析构之后hwnd被清除,不满足任何析构式。
2、手动释放资源是由Release()提供界面的。
virtual bool Release()
{
  return _Release();
}
在需要手动释放资源而不应析构对象的情况下,这个接口就是必须的。如FromHandle(),该成员函数企图从现有窗口句柄选入窗口信息,包装成窗口对象以简化程序应用。特别是只能传递句柄的线程间通讯。
3、Window基类控制窗口UI的分解,所以_Release()应是和对象析构的方向完全相反,先分解基类,逐级上朔,唯一的要求是,完成析构任务后要返回true,这个真值的Window控制由(!hwnd || _FromHandle)辨别,无效窗口和包装对象都不应进行资源释放。
4、在消息队列中的事件响应:
派生类:
LRESULT OnClose()
{
  if (MessageBox(t("收到关闭窗口请求,是否关闭"), MB_ICONQUESTION | MB_YESNO) == IDYES)
    return BaseProc();
  return 0;
}
基类:
LRESULT OnClose()
{
  Release();
  return 0;
}
通过消息映射覆盖和动态消息表来维护对象层次逻辑,这一般适用于非实时的用户界面互动,并可能执行一些逻辑性的清理动作,如更新一个数据库或者其它窗口的用户界面等。
5、除非有资源释放和事件响应需求,否则并不是每个对象层次均要显式提供以上界面和实现。如BaseRelease()和BaseProc()将访问最近的基类过程,对于不打算进一步派生的具体类,可以直接使用接口覆盖版本而不提供特定实现过程。如:
bool Release()
{
  if (BaseRelease())
  {
    ...
    return true;
  }
  return false;
}
这样的类型即使被派生,也会丢失当前的处理过程。仅在用户界面简化编程时被推荐使用。
类似的:
void Create(Type* parent_)
{
  BaseCreate(parent_);
}
void Invokes()
{
  BaseInvokes();
  invoke(wm_paint, hold(OnPaint));
}
没有唯一的方式在所有地方都优于其它方式,对应场合使用具体形式。

20

主题

136

帖子

172

积分

注册会员

Rank: 2

积分
172
 楼主| 发表于 2008-2-12 02:47:00 | 显示全部楼层

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



消息类归并、静态控件、消息反射

1、归并消息类
什么是归并消息类:
WM_MOUSELEAVE
WM_NCMOUSELEAVE
WM_CLOSE
WM_PAINT
WM_DESTROY
等均使用invoke_void作为委托器。以上消息只需要一个wm_void作为公共识别类型便可得到有效的重载版本。
//LRESULT (ClsTy::*)(void)
struct wm_void
  :public wm
{
  InheritClass(wm_void);
  Type(uint wm_value_)
    :Base(wm_value_)
  {}
};
wm_void wm_mouseleave(WM_MOUSELEAVE);
wm_void wm_ncmouseleave(WM_NCMOUSELEAVE);
wm_void wm_close(WM_CLOSE);
等等。
它们均没有分解的参数。其差异仅在对象登记入口的消息索引
template<typename ClsTy> void invoke(const wm_void& wm_, typename invoke_void<ClsTy>::method_type met, ClsTy* this_val)
{
  this_val->message_map[wm_.wm_value]
  = static_cast<invoke_root*>(new invoke_void<ClsTy>(met, this_val));
}

为何归并消息:
1)为存在同类型的消息仅需要定义一个对象
2)智能提示:
//=======================================================================
//WM_TIMER 计数器
//LRESULT (ClsTy::*)(uint event, void* timerfunc)
//event: 计数器标识
//timerfunc: VOID CALLBACK(* )(HWND, UINT, UINT_PTR, DWORD) //窗口句柄、消息、计数器事件、系统计时
//=======================================================================
wm_ui_p wm_timer(WM_TIMER);
从wm_timer的智能提示可以直接了解对应注释,而重载的注释在Visual Assiant显示不正确。并且wm_ui_p亦可作为有效提示
3)避免冗余消息类派生和对应消息类的委托任务。
4)合并鼠标、键盘的系列消息,简化逻辑。
wm_ui_kf wm_keydown(WM_KEYDOWN);
wm_ui_kf wm_keyup(WM_KEYUP);
并使用辅助消息类wm_key_tag获得wm_key_tag::flags键盘扩展信息。
简化了代码和逻辑。类似的为wm_mouse_tag::flags、wm_command_tag::id_type、wm_timer_tag::id_type等。后两个辅助类型用于命令和计时器映射识别。

2、静态控件和消息反射
按钮中的单选、多选等样式均包含有一个静态控件,在更新绘图的时候发送WM_CTLCOLORSTATIC以便同步父窗口颜色配置。因为一般使用父窗口的背景画刷作为静态控件的表面画刷。故为了避免在用户编程界面处理该逻辑。使用了消息反射机制(Message reflect)。
1)包装设备表,但是传入引用:
在委托器中定义如下:
typedef DC arg_type_1;
typedef void* arg_type_2;

typedef HBRUSH(ClsTy::*method_type)(arg_type_1&, arg_type_2);
如此,arg_type_1需要获得一个包装设备表对象
arg_type_1 arg1;
arg1.FromHandle(HDC(wparam));
从句柄获得的对象不会试图删除该句柄。包装后的DC实例需要一个具有引用类型参数的消息响应函数:
HBRUSH OnCtlColorStatic(DC& dc, void* handle_);
直接值传递将需要更大的开销。这在系统持续膨胀,并仍需要维持一致编程界面的情况下,是需要考虑前期优化的。类似wm_mouse_tag::flags直接传递值,是因为本质上它是int长度的。
2)定义一组消息反射用户自定义索引:
#define WM_USER_REF_CTLCOLOR WM_USER + 1
#define WM_USER_REF_COMMAND WM_USER + 2
...
3)父窗口反射有关消息
invoke(wm_ctlcolorstatic, hold(OnCtlColorStatic));
HBRUSH OnCtlColorStatic(DC& dc, void* handle_)
{
  LRESULT rtn = ::SendMessage(HWND(handle_), ref_ctlcolor.wm_value, WPARAM(dc.Get()), LPARAM(hwnd));
  if (!rtn)
    return HBRUSH(msg.DefProc());
  return HBRUSH(rtn);
}
其中ref_ctlcolor是反射消息类:
wm_dc_p ref_ctlcolor(WM_USER_REF_CTLCOLOR);
使用SendMessage直接将消息流向子窗口,并传递子窗口设备表对象句柄和父窗口句柄。子窗口需要传回一个用于控件的背景色画刷,或者传回空值。父窗口消息处理过程将针对空画刷调用系统默认处理过程。非空画刷从子窗口返回。父窗口将得到可用的子窗口背景画刷传回给Windows,完成一次更新。
4)子窗口处理反射消息
invoke(ref_ctlcolor, hold(OnRefCtlColor));

HBRUSH OnRefCtlColor(DC& dc, void* parent_)
{
  dc.SetTextColor(_TextColor);
  dc.SetBkColor(_BkColor);
  dc.SetBkMode(_BkMode);
  if (_CtlBrush.Get())
    return _CtlBrush.Get();
  else
  {
    Window w;
    w.FromHandle(HWND(parent_));
    return w.GetBrush().Get();
  }
}
_TextColor、_BkColor、_BkMode、_CtlBrush是允许进行设置的。默认情况下,_TextColor、_BkColor、_BkMode将在窗口创建时,从句柄包装时,通过窗口设备表获取。
DC dc(hwnd);
_TextColor = dc.GetTextColor();
_BkColor = dc.GetBkColor();
_BkMode = dc.GetBkMode();
而_CtlBrush是一个Brush实例,默认初始化时包含句柄为空,这将导致以上处理过程去获取父窗口的背景画刷。w.GetBrush()务必传回引用,否则得到资源的只是一份拷贝,而引用的对象因为属于父窗口生存期,故其句柄总是有效的。
5)用户者界面:
stc2.SetSize(136, 136, 136, 80);
stc2.Create(this);
stc2.SetBkColor(0x000000);
stc2.SetTextColor(0xFFFF00);
stc2.SetPaintBrush(Brush::brushGray);
唯一需要注意的是,对子窗口的外观控制应该在其创建之后,因为创建过程中将设置默认的设备表包含值。但是无论如何,这对于构建一个界面设计器来说完全不是问题。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2025-12-20 14:20

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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