天天看点

【HGE】使用子窗口功能

在学习HGE的过程中,发现所有例子程序都没有自己创建过窗口,因为HGE内部都帮我们创建好了。那么如果我们有某种需求想自己创建窗口,HGE只负责在给定的窗口中绘图是否可行呢?也是可以的,HGE提供了子窗口功能,但它并不是在你的窗口上直接画图,HGE依然会创建窗口,但会作为一个子窗口而存在。

我们看看System_Initiate的实现

bool HGE_CALL HGE_Impl::System_Initiate() {
    //...

    // Register window class

    winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    winclass.lpfnWndProc = WindowProc;
    winclass.cbClsExtra = 0;
    winclass.cbWndExtra = 0;
    winclass.hInstance = h_instance_;
    winclass.hCursor = LoadCursor(nullptr, IDC_ARROW);
    winclass.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
    winclass.lpszMenuName = nullptr;
    winclass.lpszClassName = WINDOW_CLASS_NAME;
    if (icon_) {
        winclass.hIcon = LoadIcon(h_instance_, icon_);
    }
    else {
        winclass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
    }

    if (!RegisterClass(&winclass)) {
        post_error("Can't register window class");
        return false;
    }

    // Create window
    // Thanks to Bryan for window rectangle fix in later Windows systems 7,8
    // http://i-am-bryan.com/webs/tutorials/hge/fix-hge-window-sizing-bug/

    // Take approximately center position
    const auto scr_width = GetSystemMetrics(SM_CXSCREEN);
    const auto scr_height = GetSystemMetrics(SM_CYSCREEN);
    rect_windowed_.left = (scr_width - screen_width_) / 2;
    rect_windowed_.top = (scr_height - screen_height_) / 2;
    rect_windowed_.right = rect_windowed_.left + screen_width_;
    rect_windowed_.bottom = rect_windowed_.top + screen_height_;

    style_windowed_ = WS_BORDER
            | WS_POPUP
            | WS_CAPTION
            | WS_SYSMENU
            | WS_MINIMIZEBOX
            | WS_VISIBLE;

    //Fullscreen
    rect_fullscreen_.left = 0;
    rect_fullscreen_.top = 0;
    rect_fullscreen_.right = screen_width_;
    rect_fullscreen_.bottom = screen_height_;
    style_fullscreen_ = WS_POPUP | WS_VISIBLE; //WS_POPUP

    if (hwnd_parent_) {
        rect_windowed_.left = 0;
        rect_windowed_.top = 0;
        rect_windowed_.right = screen_width_;
        rect_windowed_.bottom = screen_height_;
        style_windowed_ = WS_CHILD | WS_VISIBLE;
        windowed_ = true;
    }

    // Fix for styled windows in Windows versions newer than XP
    AdjustWindowRect(&rect_windowed_, style_windowed_, false);

    if (windowed_)
        hwnd_ = CreateWindowEx(0, WINDOW_CLASS_NAME, win_title_, style_windowed_,
                               rect_windowed_.left, rect_windowed_.top,
                               rect_windowed_.right - rect_windowed_.left,
                               rect_windowed_.bottom - rect_windowed_.top,
                               hwnd_parent_, nullptr, h_instance_, nullptr);
    else
        hwnd_ = CreateWindowEx(WS_EX_TOPMOST, WINDOW_CLASS_NAME,
                               win_title_, style_fullscreen_,
                               0, 0, 0, 0,
                               nullptr, nullptr, h_instance_, nullptr);
    if (!hwnd_) {
        post_error("Can't create window");
        return false;
    }

    ShowWindow(hwnd_, SW_SHOW);

    //...
}
           

System_Initiate中包含了创建窗口的代码,创建窗口时会指定一个父窗口hwnd_parent_,它是通过System_SetState(HGE_HWNDPARENT, Parent)来指定的。

我们尝试自己创建窗口

#include "pch.h"
#include <hge.h>

#pragma comment(lib, "hge")

HGE* hge = nullptr;

const wchar_t* WINDOW_CLASS_NAME = L"HGE_WINDOW";

LRESULT CALLBACK WindowProc(const HWND hwnd, const UINT msg, WPARAM wparam, LPARAM lparam) 
{
	return DefWindowProc(hwnd, msg, wparam, lparam);
}

HWND CreateForm()
{
	const auto instance = GetModuleHandle(nullptr);
	WNDCLASS winclass;
	winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	winclass.lpfnWndProc = WindowProc;
	winclass.cbClsExtra = 0;
	winclass.cbWndExtra = 0;
	winclass.hInstance = instance;
	winclass.hCursor = LoadCursor(nullptr, IDC_ARROW);
	winclass.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
	winclass.lpszMenuName = nullptr;
	winclass.lpszClassName = WINDOW_CLASS_NAME;
	winclass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
	RegisterClass(&winclass);

	const auto style_windowed = WS_BORDER
		| WS_POPUP
		| WS_CAPTION
		| WS_SYSMENU
		| WS_MINIMIZEBOX
		| WS_VISIBLE;

	const auto hwnd = CreateWindowEx(0, WINDOW_CLASS_NAME, L"Test", style_windowed, 100, 100, 800, 600, nullptr, nullptr, instance, nullptr);
	return hwnd;
}

bool FrameFunc()
{
	return hge->Input_GetKeyState(HGEK_ESCAPE);	
}

bool RenderFunc()
{
	hge->Gfx_BeginScene();
	hge->Gfx_Clear(0);
	hge->Gfx_EndScene();
	return false;
}

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	const auto hwnd = CreateForm();
	
	hge = hgeCreate(HGE_VERSION);
	hge->System_SetState(HGE_SHOWSPLASH, false);
	hge->System_SetState(HGE_FRAMEFUNC, FrameFunc);
	hge->System_SetState(HGE_RENDERFUNC, RenderFunc);
	hge->System_SetState(HGE_USESOUND, false);
	hge->System_SetState(HGE_HWNDPARENT, hwnd);

	if (hge->System_Initiate()) {
		while (true)
		{
			MSG msg;
			if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
			{
				if (msg.message == WM_QUIT) break;
				DispatchMessage(&msg);
				continue;
			}

			hge->System_Start();
		}
	}

	hge->System_Shutdown();
	hge->Release();
	return 0;
}
           

仅仅是把HGE创建窗口的代码拷贝出来而已,然后设置了HGE_HWNDPARENT,这也是最关键的地方,只有指定了HGE_HWNDPARENT后,System_Start才不会执行自己内部的消息循环,函数会立即返回。也正因此,消息循环需要我们自己来实现,而我们的消息循环中要不断调用System_Start才行,这样才会触发之前设置的HGE_FRAMEFUNC和HGE_RENDERFUNC回调函数。

这里还要注意FRAMEFUNC回调函数,使用"子窗口"方式工作后,这里的返回值不再决定程序是否退出了,而是决定本次循环是否继续调用渲染回调函数,简单点说就是返回值已经没有多大意义。

---------- 语言分割线 ----------

如果是Delphi版本应该怎么写呢?能用VCL的窗口吗?用是可以用,但有问题。

VCL中主循环由TApplication管理

function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
  Unicode: Boolean;
  Handled: Boolean;
  MsgExists: Boolean;
begin
  Result := False;
  if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then
  begin
    Unicode := (Msg.hwnd <> 0) and IsWindowUnicode(Msg.hwnd);
    if Unicode then
      MsgExists := PeekMessageW(Msg, 0, 0, 0, PM_REMOVE)
    else
      MsgExists := PeekMessage(Msg, 0, 0, 0, PM_REMOVE);
    if not MsgExists then Exit;
    Result := True;
    if Msg.Message <> WM_QUIT then
    begin
      Handled := False;
      if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
      if not IsPreProcessMessage(Msg) and not IsHintMsg(Msg) and
        not Handled and not IsMDIMsg(Msg) and
        not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
      begin
        TranslateMessage(Msg);
        if Unicode then
          DispatchMessageW(Msg)
        else
          DispatchMessage(Msg);
      end;
    end
    else
      FTerminate := True;
  end;
end;
           

只有在有消息到达时才会进入我们的OnMessage回调函数里。没有消息时就无法处理了,此时我们的FrameFunc、RenderFunc函数就无法得到执行,所以如果用子窗口功能的话,就不要用VCL来创建窗口了。

或者还有一个方法,加个定时器来主动去渲染,不使用HGE的回调事件,不过这样的方式效率极低

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, HGE, ExtCtrls;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  HGE: IHGE = nil;
  Fnt: TSysFont;

implementation

{$R *.dfm}

function FrameFunc: Boolean;
begin
  if HGE.Input_GetKeyState(HGEK_ESCAPE) then
    OutputDebugString('Pressed ESC.');
  Result := False;
end;

function RenderFunc: Boolean;
begin
  HGE.Gfx_BeginScene;
  HGE.Gfx_Clear(0);
  Fnt.Print(100,100,IntToStr(HGE.Timer_GetFPS));
  HGE.Gfx_EndScene;
  Result := False;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  HGE.System_Shutdown;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  HGE := HGECreate(HGE_VERSION);
  HGE.System_SetState(HGE_FRAMEFUNC, FrameFunc);
  HGE.System_SetState(HGE_RENDERFUNC, RenderFunc);
  HGE.System_SetState(HGE_USESOUND, False);
  HGE.System_SetState(HGE_HWNDPARENT, Handle);

  HGE.System_Initiate;
  Fnt := TSysFont.Create;
  Fnt.CreateFont('arial', 15, []);

  Timer1.Enabled := True;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  HGE.System_Start;
end;

end.
           
【HGE】使用子窗口功能

什么都没做,FPS只有60左右,如果真用在实际开发中,这种延迟是没法忍的。

参考资料:http://kvakvs.github.io/hge/doc/index.html?main_childwin.html

HGE