LoginSignup
2
2

More than 3 years have passed since last update.

フォークと懺悔 ~A Window Library~

Last updated at Posted at 2019-10-08

こんにちは。
今日は懺悔のお時間です。

懺悔

以前、私はウインドウアプリケーションを書きたい欲求に駆られていました。
そこで見つけたのです。@seekerkrtさんのライブラリ を!
そしてパクって生願成就となったわけですが、それを自分のモノのように考えるにはちょっと違うなぁとシャキッとしない思いにさいなまれたのです。
良く言えばフォーク。悪く言えばパクリです。

と、いうわけで供養のために同じサービスであるQiitaに投げておきます。

構造

C++にちょっと前にスマートポインタが入ったので、それを活用したライブラリです。
ベースのライブラリは関数を呼んでいたと思いますが、それをクラスオブジェクトに差し替えました。
基本的にはそれだけです。

コード

暇なときに書いたサンプルコードです。
起動したら、マウスカーソルをウインドウに入れてください。
右クリックで線が増えます。左クリックで線が減ります。

WindowClass.h
#pragma once
#include <memory>
#define NOMINMAX
#include <windows.h>
#include <windowsx.h>
#include <tchar.h>

class Window {
public:
    class EventHandler;
    typedef std::shared_ptr<EventHandler> SharedEventHandler;
public:
    Window();
    virtual ~Window();
public: 
    static const int MAX_LOADSTRING = 100;
    bool Create(HINSTANCE hInstance, const TCHAR (class_name)[],const TCHAR (app_name)[], int width, int height);
    bool IsCreated();   //ウィンドウが生成(生存)されているか確認
    HWND GetHWND() { return hWnd; }
    bool UpdateWindow();
    bool ShowWindow(int nCmdShow);
    void SetTitle(const TCHAR title[]);
    WPARAM GetMessageLoop(UINT wMsgFilterMin, UINT wMsgFilterMax);  //汎用かつ定番のメッセージループ
    WPARAM PeekMessageLoop(UINT wMsgFilterMin, UINT wMsgFilterMax,/* UINT wRemoveMSG,*/ UINT NanoSleep);    //特定用途メッセージループ


    bool AddStyle(LONG In) {
        LONG lStyle;
        lStyle = GetWindowLong(hWnd, GWL_STYLE);
        lStyle |= In;
        lStyle = SetWindowLong(hWnd, GWL_STYLE, lStyle);
        return true;
    }
    bool SubStyle(LONG In) {
        LONG lStyle;
        lStyle = GetWindowLong(hWnd, GWL_STYLE);
        lStyle &= ~In;
        lStyle = SetWindowLong(hWnd, GWL_STYLE, lStyle);
        return true;
    }

    bool SetClientArea(int W, int H) {
        RECT wrt = { 0, };
        RECT crt = { 0, };
        GetWindowRect(hWnd, &wrt);
        GetClientRect(hWnd, &crt);

        int BW = (wrt.right - wrt.left) - (crt.right-crt.left);
        int BH = (wrt.bottom - wrt.top) - (crt.bottom-crt.top);

        return TRUE == MoveWindow(hWnd, wrt.left, wrt.top, BW + W, BH + H, TRUE);


    }

    class EventHandler {
    public:
        virtual void InnerPeekMessage();//PeekMessageLoopで呼ばれる
        virtual LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); //デフォルトではDefWndProcを呼ぶかWM_DESTORYを処理するぐらい
        virtual ~EventHandler() {}
    };

    bool SetEventHandler(SharedEventHandler In);


public: //カスタマイズするメソッド
    virtual void InnerPeekMessage();//PeekMessageLoopで呼ばれる
    //virtual LRESULT LocalWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); //デフォルトではDefWndProcを呼ぶかWM_DESTORYを処理するぐらい

private:
    static LRESULT CALLBACK BaseWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); //ラップするためのトリックな関数
    void SetPointer(HWND hWnd); //windowオブジェクト(thisポインタ)とウィンドウハンドルを関連付けるためのトリックな関数

protected:
    volatile HWND hWnd = nullptr;
    SharedEventHandler EH = std::make_shared<EventHandler>();
};

WindowClass.cpp
#include <thread>
#include <chrono>
#include "WindowClass.h"

//コンストラクタ
Window::Window() {}
//デストラクタ
Window::~Window() {}

bool Window::Create(HINSTANCE hInstance,const TCHAR class_name[],const TCHAR app_name[], int width, int height) {
    WNDCLASSEX wcex;
    ZeroMemory(&wcex, sizeof(wcex));

    wcex.cbSize = sizeof(WNDCLASSEX);   //この構造体自体のサイズなので。
    wcex.cbClsExtra = 0;    //拡張用?とりあえず使わないので0
    wcex.cbWndExtra = 0;    //拡張用?とりあえず使わないので0
    wcex.lpfnWndProc = Window::BaseWndProc;//このウィンドウクラスのプロシージャを指定。このソースコードではこうする
    wcex.hInstance = hInstance;
    wcex.lpszClassName = class_name;
    wcex.lpszMenuName = nullptr;    //メニューの名前。使わないならとりあえずNULLでOK
    wcex.style = CS_VREDRAW | CS_HREDRAW;
    wcex.hCursor = ::LoadCursor(hInstance, IDC_ARROW);      //カーソルアイコン
    wcex.hIcon = ::LoadIcon(hInstance, IDI_APPLICATION);    //プログラムアイコン
    wcex.hIconSm = ::LoadIcon(hInstance, IDI_APPLICATION);  //プログラムアイコン(小)::タイトルバーに使われるやつ?
    wcex.hbrBackground = GetStockBrush(GRAY_BRUSH);      //クライアント領域の塗りつぶし色(ブラシ)
//↑とかは特に、メソッドの引数を受け取ってカスタマイズできるようにするのが望ましいかな                                                 
            //ATOMへWNDCLASSEX登録
    if (!::RegisterClassEx(&wcex)) {
        return false;
    }
    //ウィンドウ生成
    HWND ret = ::CreateWindow(
        class_name,
        app_name,
        WS_OVERLAPPEDWINDOW,    //ウィンドウスタイル。とりあえずデフォルトな感じで
        CW_USEDEFAULT, CW_USEDEFAULT,   //初期位置。適当にやってくれる。
        width, height,      //ウィンドウサイズ
        nullptr,    //親ウィンドウのハンドル。特にないんで今回はNULL
        nullptr,    //メニューハンドル。特にないので今回はNULL
        hInstance,
        this    //トリックの肝。CreateParameterに設定
    );
    //ウィンドウ生成に失敗?
    if (ret == nullptr) {
        return false;
    }
    //成功したのでtrueを返して抜ける
    return true;
}

bool Window::IsCreated()
{
    if (this->hWnd == nullptr) {
        return false;
    }
    else {
        return true;
    }
}

bool Window::UpdateWindow() {
    if (this->hWnd == nullptr) {
        return false;
    }
    ::UpdateWindow(this->hWnd);
    return true;
}

bool Window::ShowWindow(int nCmdShow) {
    if (this->hWnd == nullptr) {
        return false;
    }
    ::ShowWindow(this->hWnd, nCmdShow);
    return true;
}

void Window::SetTitle(const TCHAR title[MAX_LOADSTRING]) {
    ::SetWindowText(hWnd, title);
}

WPARAM Window::GetMessageLoop(UINT wMsgFilterMin, UINT wMsgFilterMax) {
    MSG msg;
    while (::GetMessage(&msg, nullptr, wMsgFilterMin, wMsgFilterMax) > 0) {  //「-1」が返ってくるかもしれないのでこうする
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
    this->hWnd = nullptr; //ココに記述しておいてみる
    return msg.wParam;
}
WPARAM Window::PeekMessageLoop(UINT wMsgFilterMin, UINT wMsgFilterMax/*, UINT wRemoveMSG*/,UINT NanoSleep) {
    MSG msg;
    do {
        //既定の処理はプロシージャなどに任せて……
        if (::PeekMessage(&msg, nullptr, wMsgFilterMin, wMsgFilterMax, PM_REMOVE)) {//if 2nd param not null to cant get WM_QUIT.
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
        //そうでない処理は自前でする
        else {
            //自前の処理であるInnerPeekMessage()を呼び出し
            this->InnerPeekMessage();
        }
        std::this_thread::sleep_for(std::chrono::nanoseconds(NanoSleep));
    } while (msg.message != WM_QUIT);//イベントドリブン処理ループを抜けるまで実行
    this->hWnd = nullptr; //ココに記述しておいてみる
    return msg.wParam;
}

void Window::InnerPeekMessage() {
    //TODO:InnerPeekMessage()
    EH->InnerPeekMessage();
}
/** /
LRESULT Window::LocalWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
    switch (msg) {
    case(WM_DESTROY):
        ::PostQuitMessage(0);
        break;
    default:
        return ::DefWindowProc(hWnd, msg, wp, lp);
    }
    return 0;
}
/**/
LRESULT CALLBACK Window::BaseWndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
    //Window::SetPointerでセットしたthisポインタを取得
    Window* window = (Window*)(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
    //取得に失敗してる場合
    if (window == nullptr) {
        //おそらくWM_CREATEの最中なので
        if (msg == WM_CREATE) {
            //CreateWindowのパラメータから取得する
            window = (Window*)(((LPCREATESTRUCT)lp)->lpCreateParams);
        }
        //↑の処理で取得できてるはずなんだけど、一応チェックしてから
        if (window != nullptr) {
            //windowオブジェクトとウィンドウハンドルを関連付ける
            window->SetPointer(hWnd);
        }
    }
    //無事取得できててる
    if (window != nullptr) {
        //そのオブジェクトによるウィンドウプロシージャ実装を使うというか振り分ける
        return window->EH->WndProc(hWnd, msg, wp, lp);
    }
    //よくわからんけど、それでも取得できたりしてない場合(以下のようにするよりも例外作って投げるのがよさそうだがw
    else {
        return ::DefWindowProc(hWnd, msg, wp, lp);
    }
}
void Window::SetPointer(HWND hWnd) {
    ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
    this->hWnd = hWnd;
}

bool Window::SetEventHandler(SharedEventHandler In) {
    this->EH = In;
    return true;
}
void Window::EventHandler::InnerPeekMessage() {
    //TODO:InnerPeekMessage()

}
LRESULT Window::EventHandler::WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
    switch (msg) {
    case(WM_DESTROY):
        ::PostQuitMessage(0);
        break;
    default:
        return ::DefWindowProc(hWnd, msg, wp, lp);
    }
    return 0;
}
WindowProc.h
#pragma once
#include <cstddef>
#include <vector>
#include <thread>
#include <algorithm>
#include <tuple>
#include <random>

#include <Windows.h>
#include "WindowClass.h"

//sample code.

class RollingThunderProc : public Window::EventHandler {

    typedef std::tuple<double, double> Data;//Angle,DeltaAngle

    LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {

        switch (msg) {

        case WM_CREATE: {
            HWnd = hWnd;
            Data Dt{ ui(mt),ui(mt) };
            D.push_back(Dt);

            RECT rt;
            GetClientRect(hWnd, &rt);   

            PX=(rt.right - rt.left) / 2;
            PY=(rt.bottom - rt.top) / 2;
        }
            break;
        case WM_LBUTTONDOWN: {
            RECT rt = { 0, };
            Data Dt{ ui(mt),ui(mt)};
            D.push_back(Dt);
        }
            break;
        case WM_RBUTTONDOWN:
            if (D.size())D.pop_back();
            break;
        case WM_MOUSEMOVE:
            PX = GET_X_LPARAM(lp);
            PY = GET_Y_LPARAM(lp);
            break;

        case WM_PAINT:
        {           
            RECT rt;
            GetClientRect(hWnd, &rt);

            PAINTSTRUCT PS;         

            double X = (rt.right - rt.left) / 2;
            double Y = (rt.bottom - rt.top) / 2;
            double L = (std::min)(X, Y)/3;

            X = (std::max<double>)(0, ((std::min)(X*2, PX)));
            Y = (std::max<double>)(0, ((std::min)(Y*2, PY)));

            HDC hDc = BeginPaint(hWnd, &PS);            

            float F = 1;
            double LP = 0.8;
            std::uniform_int_distribution<> CI(0, 255);
            MoveToEx(hDc, X,Y, nullptr);
            TCHAR S[] = _T("Move Mouse on Window. Push LButton to Add. RButton to Sub.");
            TextOut(hDc, 0, 0, S, std::size(S)-1);
            for (std::size_t i = 0; i < D.size(); i++) {    
                HPEN Pen = CreatePen(PS_SOLID, 3, RGB(CI(mt),CI(mt),CI(mt)));
                HPEN OldPen = SelectPen(hDc, Pen);

                std::get<0>(D[i])+= std::get<1>(D[i]);
                Angle = F*std::fmod(std::get<0>(D[i]), 360.0);
                std::tie(X, Y) = Rot(Angle, L, X, Y);
                L *= LP;
                F *= -1;

                LineTo(hDc, X, Y);
                SelectPen(hDc, OldPen);
                DeletePen(Pen);
            }
            EndPaint(hWnd, &PS);            
        }
            break;

        case WM_DESTROY:
        {
            PostQuitMessage(0);
        }
        break;
        default:
            return DefWindowProc(hWnd, msg, wp, lp);
            break;
        }

        return 0;
    }
public:
    void InnerPeekMessage() {
        InvalidateRect(HWnd, nullptr, TRUE);
        std::this_thread::sleep_for(std::chrono::nanoseconds((1000*1000*1000)/60));
    }
protected:
    std::tuple<double, double> Rot(double Angle, double L, double X, double Y) {
        double Rad = 3.141592 / 180.0;
        double x = std::sin(Rad * Angle) * L+X;
        double y = std::cos(Rad * Angle) * L+Y;

        return{ x,y };
    }
protected:

    HWND HWnd = nullptr;

    std::random_device rd;
    std::mt19937 mt{rd()};
    std::uniform_real_distribution<double> ui{ 0.0, 7.0 };

    std::vector<Data> D;

    double Angle = 0;
    double PX = 0;
    double PY = 0;
};
Main.cpp
#include <windows.h>

#include "WindowClass.h"
#include "WndProc.h"

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    Window W;
    Window::SharedEventHandler WE = std::make_shared<RollingThunderProc>();
    W.SetEventHandler(WE);

    const TCHAR Name[] = _T("RollingThander!!");
    const TCHAR Title[] = _T("RollingThander!!");

    W.Create(hInstance, Name, Title, 800, 600);

    W.ShowWindow(nCmdShow);
    W.UpdateWindow();

    return W.PeekMessageLoop(0,0,0);

}

スクリーンショット!

無題_.png

2
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2