【Windows API】ウィンドウのクラス化

2018年10月5日

Windows APIでウィンドウを扱う必要があったので、その辺りのコードをクラス化した。

コード

#pragma once

#include <Windows.h>
#include <string>

class Window
{
public:
    Window()    = default;
    ~Window()   = default;

    bool Initialize( 
        const wchar_t*  pName, 
        int             width, 
        int             height, 
        const Window*   pParent = nullptr
    );
    void Show();
    bool Update();
    void Finalize();
    HWND GetWindowHandle() const;

    LRESULT CALLBACK RespondMessage( 
        HWND    windowHandle, 
        UINT    message, 
        WPARAM  wParam, 
        LPARAM  lParam
    );
private:
    bool AddClass();
    void RemoveClass();
    bool Create( int width, int height, HWND parentWindowHandle );
    void Destroy();
    bool SetClientSizeAndMoveToCenter( int width, int height );

    HINSTANCE       instanceHandle  = nullptr;
    HWND            windowHandle    = nullptr;
    std::wstring    name            = L"";
};
#include "Window.h"

LRESULT CALLBACK OnReceivedMessage( HWND, UINT, WPARAM, LPARAM );

bool Window::Initialize( 
    const wchar_t*  pName, 
    int             width, 
    int             height, 
    const Window*   pParent
) {
    this->instanceHandle    = GetModuleHandle( nullptr );
    this->name              = pName;

    if ( !this->AddClass() ) return false;

    auto parentWindowHandle = pParent != nullptr ? pParent->windowHandle : nullptr;

    if ( !this->Create( width, height, parentWindowHandle ) ) return false;

    SetWindowLongPtrW( this->windowHandle, GWLP_USERDATA, reinterpret_cast<LONG>( this ));

    this->SetClientSizeAndMoveToCenter( width, height );

    return true;
}

void Window::Show()
{
    ::ShowWindow( this->windowHandle, SW_SHOW );
    ::UpdateWindow( this->windowHandle );
}

bool Window::Update() 
{
    MSG messageInfo;

    if ( PeekMessage( &messageInfo, nullptr, 0, 0, PM_REMOVE ) == 0 ) return true;
    if ( messageInfo.message == WM_QUIT ) return false;

    ::TranslateMessage( &messageInfo );
    DispatchMessage( &messageInfo );

    return true;
}

void Window::Finalize() 
{
    this->Destroy();
    this->RemoveClass();
}

bool Window::AddClass()
{
    WNDCLASSEXW windowClass;

    windowClass.cbSize          = sizeof( WNDCLASSEXW );
    windowClass.style           = CS_HREDRAW | CS_VREDRAW;
    windowClass.cbClsExtra      = 0;
    windowClass.cbWndExtra      = 0;
    windowClass.hInstance       = this->instanceHandle;
    windowClass.hCursor         = LoadCursor( nullptr, IDC_ARROW );
    windowClass.hbrBackground   = static_cast<HBRUSH>(::GetStockObject( WHITE_BRUSH ));
    windowClass.lpszMenuName    = nullptr;
    windowClass.lpszClassName   = this->name.c_str();
    windowClass.hIcon           = LoadIcon( this->instanceHandle, IDI_APPLICATION );
    windowClass.hIconSm         = windowClass.hIcon;
    windowClass.lpfnWndProc     = OnReceivedMessage;

    return ::RegisterClassExW( &windowClass ) != 0;
}

void Window::RemoveClass()
{
    if ( this->instanceHandle == nullptr ) return;

    UnregisterClass( this->name.c_str(), this->instanceHandle );

    this->instanceHandle    = nullptr;
    this->name              = L"";
}

bool Window::Create( int width, int height, HWND parentWindowHandle )
{
    auto pName = this->name.c_str();

    this->windowHandle = CreateWindowW(
        pName,
        pName,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        width,
        height,
        parentWindowHandle,
        nullptr,
        this->instanceHandle,
        nullptr
    );

    return this->windowHandle != nullptr;
}

void Window::Destroy() 
{
    if ( this->windowHandle == nullptr ) return;

    ::DestroyWindow( this->windowHandle );

    this->windowHandle = nullptr;
}

bool Window::SetClientSizeAndMoveToCenter( int width, int height )
{
    RECT windowSize, clientSize;
    
    ::GetWindowRect( this->windowHandle, &windowSize );
    ::GetClientRect( this->windowHandle, &clientSize );
    
    auto windowWidth        = windowSize.right  - windowSize.left;
    auto windowHeight       = windowSize.bottom - windowSize.top;
    
    auto clientWidth        = clientSize.right  - clientSize.left;
    auto clientHeight       = clientSize.bottom - clientSize.top;

    auto nonClientWidth     = windowWidth - clientWidth;
    auto nonClientHeight    = windowHeight - clientHeight;
    
    auto newWindowWidth     = nonClientWidth + width;
    auto newWindowHeight    = nonClientHeight + height;

    auto positionX          = ::GetSystemMetrics( SM_CXSCREEN ) / 2 - newWindowWidth / 2;
    auto positionY          = ::GetSystemMetrics( SM_CYSCREEN ) / 2 - newWindowHeight / 2;

    return ::SetWindowPos( 
        this->windowHandle, 
        nullptr, 
        positionX, 
        positionY, 
        newWindowWidth,
        newWindowHeight,
        SWP_NOZORDER 
    ) != 0;
}

HWND Window::GetWindowHandle() const 
{
    return this->windowHandle;
}

LRESULT CALLBACK Window::RespondMessage( HWND windowHandle, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch ( message )
    {
    case WM_DESTROY:
        ::PostQuitMessage( 0 );

        this->windowHandle = nullptr;
        this->RemoveClass();
        break;
    default:
        return DefWindowProc( windowHandle, message, wParam, lParam );
        break;
    }

    return 0;
}

LRESULT CALLBACK OnReceivedMessage( HWND windowHandle, UINT message, WPARAM wParam, LPARAM lParam )
{
    auto pWindow = reinterpret_cast<Window*>( GetWindowLongPtr( windowHandle, GWLP_USERDATA ) );

    if ( pWindow == nullptr )
    {
        return DefWindowProc( windowHandle, message, wParam, lParam );
    }

    return pWindow->RespondMessage( windowHandle, message, wParam, lParam );
}

使い方

こんな感じで使える。一応ループ内にグラフィックAPIの処理を入れる想定で作ってある。

int APIENTRY wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow ) 
{
    auto window = Window();

    window.Initialize( L"テスト", 800, 600 );
    window.Show();

    while ( window.Update() )
    {
		// グラフィックAPIの処理とか
    }

    window.Finalize();

    return 0;
}