C++教程:DirectX11Frame

  前面几讲的内容过于难了一些,当然看不懂没关系,因为在整个框架里面他们只是作为底层应用来使用,而最终暴露的接口都相当的易于使用,那么,对于拿来主义这就足够了,因为接下来的内容都注定会是一马平川。

  回顾我们前面说过的内容:一套反射机制(对于这种非主流的GUI库来说没有反射靠硬编码来实现界面配置的话那么结果会是有些吃力不讨好),一套事件系统(新的事件机制没有推送,以前我们使用的事件响应是依赖于boost的signal,不过boost的signal后来被我废弃了,所以现在的事件响应是一套全新的方案,当然也不算是全新的了,因为大致结构是模仿C#的事件),一套完备的属性系统(微信里面推送的是当初设计这个属性的一个版本,它的缺点我们已经说过,至于第二个版本没有在微信里进行推送,第二个版本不但支持多槽函数进行绑定还支持同步和异步两种连接方式,当然最重要的还实现了对象有效性的追踪),一个基础对象MObject(该对象继承至属性和反射,所以凡是MObject的子类都可以实现动态创建对象以及除了使用新的事件机制向外部发送事件外还能够使用属性的操作方式和一些需要接收事件的槽函数进行同步)。

  好吧,简单的总结了一些我们前面说过的一些东西后下面我们继续向前,如同前面我们说过,DirectX本身并没有窗口的概念,他需要依附在特定的窗口上面才能够进行可视化的绘制,而标识这个窗口的东西就是HWND。虽然当初这个Frame使用win32来实现的,但后来被应用在Qt里面(公司使用的是Qt)进行项目开发,所以该Frame的设计就是支持跨平台的(不是指操作系统,而是指在Windows下面的开发平台),只要能够获取到HWND就能够使用这套Frame。

  为了达到上面的效果,我们有必要来设计一个基础窗口类,它提供了一些必须的接口以及一些共有的实现,这个类就是MAppWindow.

  //===============================

  #pragma once

  #include "MDx11Comm.h"

  #include "MObject.h"

  class MAppWindow : public MObject

  {

  public:

  //

  // 鼠标消息函数

  // 鼠标按键,鼠标动作,x,y,是否为全局坐标

  //

  //

  typedef std::function MouseFunType;

  typedef std::function ADD_INIT; // 附加初始化函数

  typedef std::function RECALL_INIT; // 初始化函数

  typedef std::function RECALL_DRAW; // 绘图函数

  typedef std::function RECALL_CLEAR; // 清理资源函数

  typedef std::function RECALL_RESHAPE; // 更改尺寸处理函数

  typedef std::function RECALL_WNDPROC; // 底层消息回调处理函数

  typedef std::function RECALL_IDLE; // 空闲处理函数

  typedef std::function RECALL_UPDATA; // 动画更新函数

  typedef std::function InputFunType; // 输入回掉函数

  typedef std::function KeyEventFunType; // 键盘状态回调函数

  typedef std::function KeyInputCharFunType;// 处理输入字符函数

  public:

  MAppWindow();

  virtual ~MAppWindow();

  //

  // 获取窗口句柄

  //

  virtual HWND WindHwnd(){ return nullptr; }

  //

  // 移除窗口边框

  //

  virtual void RemoveBorder(){}

  //

  // 注册函数

  //

  void RegisterFunInitD3D(RECALL_INIT initGLfun);

  void RegisterDrawScreen(RECALL_DRAW drawFun); // 注册显示函数,很重要,没他,就显示不出3D画面

  void RegisterReShape(RECALL_RESHAPE reshapeFun); // 移动窗口会被调用

  void RegisterWndProc(RECALL_WNDPROC WndProc); // 注册事件回调函数,该函数将会相应窗口消息

  void RegisterAddProcFun(RECALL_WNDPROC WndProc);

  void RegisterOnIdleFun(RECALL_IDLE idlefun); // 注册系统闲时处理函数

  void RegisterClearFun(RECALL_CLEAR clearFun); // 注册清除资源函数

  void RegisterUpdataFun(RECALL_UPDATA updatafun) // 注册更新界面函数

  void RegisterMouseEnventFun(MouseFunType fun); // 注册鼠标事件函数

  protected:

  MouseFunType mMouseEventFun{ nullptr };

  RECALL_INIT mInitD3d;

  RECALL_DRAW mDrawScreen;

  RECALL_RESHAPE mReshape;

  RECALL_CLEAR mClearFun;

  RECALL_IDLE mIdleFun;

  RECALL_UPDATA mUpdateFun;

  };

  //==========================================

  注册系列函数我们已经实现,唯一需要用户自行实现的就只有WindHwnd和RemoveBorder这两个函数,对于使用Win32或者MFC的同学来说获取这个HWND实在是太简单了,对于使用Qt的同学来说这两个函数的实现依然很简单,对了,为什么我们要实现RemoveBorder呢?看看我们文章开头的图片,整个窗口全都是使用DirectX绘制出来的,包括标题栏和边框,这样我们就可以实现我们想要的任意风格了,否则就算你把Client区域做得多华丽但是一看窗口边框和标题栏就会不自觉的认为这不是一个风格的。

  由于我们现在是在win32下,所以这里我们可以使用MAppWindow来作为我们真正的窗口基类。真正的win32窗口类MWindow:

  //==================================

  #pragma once

  #include "MDx11String.h"

  #include

  #include "MNoCopy.h"

  #include

  #include "MAppWindow.h"

  using namespace MDx11;

  class MWindow;

  class MEventFun;

  //

  // 消息回调函数

  //

  HRESULT __stdcall WndProc(HWND, UINT, WPARAM, LPARAM);

  class MWindow : public MAppWindow, public MNoCopy

  {

  DECLARE_CLASS(MWindow)

  public:

  explicit MWindow(MWindow* parent = nullptr);

  virtual ~MWindow();

  public:

  unsigned Width() const{ return mWidth; }

  unsigned Height() const{ return mHeight; }

  void SetTitle(const MDx11String& Title);

  //

  // 重写继承而来的两个虚函数

  //

  void RemoveBorder();

  virtual HWND WindHwnd(){ return mHwnd; }

  //

  // 开启消息循环

  //

  int Run() const;

  //

  // 显示窗口

  //

  void Show();

  virtual void SetExStyle(DWORD dwExStyle);

  virtual void SetStyle(DWORD dwStyle);

  virtual void SetIsFullScreen(bool fullscreen);

  virtual void Update(){ InvalidateRect(*this, nullptr, false); }

  virtual operator HWND() const{ return mHwnd; }

  virtual LRESULT __stdcall MemWndProc(HWND, UINT, WPARAM, LPARAM);

  bool IsFullScreen(){ return bIsFullscreen; }

  bool IsActive(){ return bIsActive; }

  void CalculateFrameStats();

  //

  // 事件

  //

  public:

  friend LRESULT __stdcall WndProc(HWND, UINT, WPARAM, LPARAM); // 窗口回调函数

  //

  // 定义几个事件属性

  //

  static MDx11String MouseClickedEvent;

  static MDx11String MouseMoveEvent;

  static MDx11String MouseEnterEvent;

  static MDx11String MouseLeverEvent;

  static MDx11String ContentChangedEvent;

  static MDx11String SelectedChangedEvent;

  void RegisterEventFun(const MDx11String& EventDesc, MEventFun* SlotFun);

  protected:

  virtual bool InitWindow();

  bool RegisterWndClass(); // 注册窗口类

  bool GenWindow(); // 创建窗口

  virtual int Msgloop() const; // 消息循环

  virtual void MouseEnterWindow();

  virtual void MouseLeavesWindow();

  //

  // 窗口消息经回调函数转而进相关的成员函数,方便操作成员数据

  //

  LRESULT __stdcall BaseWndProc(HWND, UINT, WPARAM, LPARAM);

  RECALL_WNDPROC mAddFun; // 消息回调附加函数

  private:

  MDx11String mTitle;

  HWND mHwnd;

  unsigned mWidth;

  unsigned mHeight;

  DWORD mDwExStyle; // 窗口风格

  DWORD mDwStyle; // 窗口风格

  bool* bIsKeys; //监控键状态

  bool bIsInited;

  volatile bool bIsDone;

  bool bIsFullscreen;

  bool bIsActive;

  bool bIsAppPaused;

  bool bIsMinimized;

  bool bIsMaximized;

  bool bIsResizing;

  WNDCLASS mWndclass;

  MDx11String mWndClassName;

  //

  // 一个计时器,凡是以I开头的东西都是从com组件中导出来的

  //

  ITimer* pTimer{ nullptr };

  //

  // 记录鼠标是否进入窗口

  //

  bool bIsEnter{ false };

  //

  // 保存消息响应函数

  //

  std::unordered_map mEventFunMap;

  };

  //=====================================

  这个类没啥多说的,唯一的技巧性操作是将回调函数过程转发指成员函数身上,要解决这个问题很简单,正cpp文件中创建一个全局窗口对象,在类的构造函数中将this直接赋给该全局指针,然后在回掉函数中根据HWND来检查出对应的窗口,从而将事件转发指相应的成员函数身上(其实如果只是简单的实现一个窗口大不必要这么麻烦,之所以这么干是考虑到以后该窗口类的扩展性)。

  现在只需要下面的代码窗口就出来了:

  //=====================================

  MWindow w; //创建一个对象

  w.SetTitle("Hello World"); // 设置窗口标题

  w.Show(); // 显示窗口

  w.Run(); // 开启消息循环

  //=======================================

  ok,今天的就到这里吧,具体实现因为比较简单就比贴出来了,只需要知道这些类有这些接口以及相关成员就足够了,接下来我们开始真正的进入DirectX,下一讲我们来说说该类的渲染核心——DirectX11的初始化。