在学习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.

什么都没做,FPS只有60左右,如果真用在实际开发中,这种延迟是没法忍的。
参考资料:http://kvakvs.github.io/hge/doc/index.html?main_childwin.html