WinAPI だけでアプリを作成する

Visual Studio を使うと VC++ 版の Windows GUI アプリのひな型が作成されるが、それでもアプリを構築するのは大変である。今は大半の人は C# など .net を使うと思うが、.net のない環境で GUI アプリを作成する羽目になったので、備忘録代わりにやり方を残しておこうと思う。

WinMain, RegisterClass, WndProc までは自動生成してくれるが、問題はここからである。

まず、WndProc に WM_CREATE 処理ハンドラを追加する。

LRESULT CALLBACK WndProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam) {
  switch (mes) {
  case WM_CREATE:
    onCreate(hWnd, wParam, lParam);
    break;
  }
  ...
}

onCreate() でメインウィンドウに配置する子ウィンドウを作成する。

std::vector<HWND> hChild;

void onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam) {
  HWND hChild[j] = CreateWindow(lpClass, lpTitle, dType, x, y, w, h, hWnd, (HMENU)j, ((LPCREATESTRUCT)lParam)->hInstance, nullptr);
}

WndProc の WM_COMMAND 処理ハンドルに子ウィンドウ処理を追加する。

LRESULT CALLBACK WndProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam) {
  switch(mes) {
  case WM_COMMAND:
    {
      WORD j = LOWORD(wParam);
      switch (j) {
      case IDM_xxx:
        onCommandXX(hWnd, hChild[j], HIWORD(wParam));
      }
    }
    break;
  }
}

子ウィンドウ処理では、例えばテキストボックスの値が変わった場合、

void onCommandXX(HWND hParent, HWND hChild, WORKD iCode) {
  if (iCode == EN_CHANGE) {
    GetWindowText(hChild, szEditWindow, ARRAYSIZE(szEditWindow));
  }
}

とすることで、変更後の値を取得することができる。

ファイルオープン・書き込みダイアログの API は SHLWAPI に移動されたようだ。まずオープンから。

#pragma comment (lib, "shlwapi.lib")
#include <shlwapi.h>

void onBaseFileOpen(HWND hWnd, WORD iCode, const COMDLG_FILTERSPEC* pf, int fsize, LPCTSTR lpIniFile, LPCTSTR lpDefault, void (*cb)(PWSTR str)) {
    if (iCode == BN_CLICKED) {
        HRESULT hr = 0;
        IFileDialog* pfd;

        hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
        DWORD dwFlags;
        hr = pfd->GetOptions(&dwFlags);
        if (SUCCEEDED(hr)) {
            pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
            hr = pfd->SetFileTypes(fsize, pf);
            if (SUCCEEDED(hr)) {
                pfd->SetFileTypeIndex(1);
                pfd->SetDefaultExtension(lpDefault);
                if (lpIniFile) {
                    pfd->SetFileName(GetFileName(lpIniFile).c_str());
                }
                hr = pfd->Show(hWnd);
                if (SUCCEEDED(hr)) {
                    IShellItem* psiResult;
                    hr = pfd->GetResult(&psiResult);
                    if (SUCCEEDED(hr)) {
                        PWSTR pszFilePath = nullptr;
                        hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
                        cb(pszFilePath);
                        CoTaskMemFree(pszFilePath);
                        psiResult->Release();
                    }
                }
            }
        }
        pfd->Release();
    }
}

void onFileOpenCallback(PWSTR str) {
    std::unordered_map<int, WndCreate>::iterator i = wndCreate.find(2);
    if (i != wndCreate.end()) {
        SetWindowText(i->second.hWnd, str);
        _tcscpy_s(szSourceFile, ARRAYSIZE(szSourceFile), str);
    }
}

void onFileOpen(HWND hWnd, HWND hChild, WORD iCode) {
    static const COMDLG_FILTERSPEC c_filter[] = {
        { L"テキスト (*.txt)", L"*.txt"}
    };
    static LPCTSTR lpDefault = L"txt";
    void (*cb)(PWSTR str) = onFileOpenCallback;
       
    onBaseFileOpen(hWnd, iCode, c_filter, ARRAYSIZE(c_filter), szSourceFile, lpDefault, cb);
}

ファイル保存は以下の通り。

void onLogFileSaveCallback(PWSTR str) {
    std::unordered_map<int, WndCreate>::iterator i = wndCreate.find(9);
    if (i != wndCreate.end()) {
        SetWindowText(i->second.hWnd, str);
        _tcscpy_s(szLogFile, ARRAYSIZE(szLogFile), str);
    }
}

void onBaseFileSave(HWND hWnd, WORD iCode, const COMDLG_FILTERSPEC* pf, int fsize, LPCTSTR lpIniFile, LPCTSTR lpDefault, void (*cb)(PWSTR str)) {
    if (iCode == BN_CLICKED) {
        HRESULT hr = 0;
        IFileDialog* pfd;

        hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
        DWORD dwFlags;
        hr = pfd->GetOptions(&dwFlags);
        if (SUCCEEDED(hr)) {
            pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
            hr = pfd->SetFileTypes(fsize, pf);
            if (SUCCEEDED(hr)) {
                pfd->SetFileTypeIndex(1);
                pfd->SetDefaultExtension(lpDefault);
                if (lpIniFile) {
                    pfd->SetFileName(GetFileName(lpIniFile).c_str());
                }
                hr = pfd->Show(hWnd);
                if (SUCCEEDED(hr)) {
                    IShellItem* psiResult;
                    hr = pfd->GetResult(&psiResult);
                    if (SUCCEEDED(hr)) {
                        PWSTR pszFilePath = nullptr;
                        hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
                        cb(pszFilePath);
                        CoTaskMemFree(pszFilePath);
                        psiResult->Release();
                    }
                }
            }
        }
        pfd->Release();
    }
}

void onLogFileSave(HWND hWnd, HWND hChild, WORD iCode) {
    static const COMDLG_FILTERSPEC c_filter[] = {
        { L"CSVファイル (*.csv)", L"*.csv"}
    };
    static LPCTSTR lpDefault = L"csv";
    void (*cb)(PWSTR str) = onLogFileSaveCallback;

    onBaseFileSave(hWnd, iCode, c_filter, ARRAYSIZE(c_filter), szLogFile, lpDefault, cb);
}

onCommandXX の代わりに onFileOpen, OnLogFileSave を呼び出すようにすればよい。

子ウィンドウの作成及び処理は1つのクラスにまとめた方が都合が良い。

struct WndRect {
    int x, y, w, h;
    WndRect(int x, int y, int w, int h) : x(x), y(y), w(w), h(h) {}
};

struct WndCreate {
    void (*createEx)(HWND hWnd, WndCreate &wCreate);
    void (*onCommand)(HWND hWnd, HWND hChild, WORD iCode);
    WndRect rect;
    DWORD dType;
    LPCTSTR lpClassName;
    LPCTSTR lpTitle;
    HWND hWnd;
    std::unordered_map<int, std::wstring> vInit;
    WndCreate() : hWnd(0), createEx(nullptr), onCommand(nullptr), rect(0, 0, 0, 0), dType(0LL), lpClassName(nullptr), lpTitle(nullptr), vInit() {}
};

extern std::vector<WndCreate> vCreate;

今となっては全然思い出せないが、昔はこんな苦労してウィンドウを書いていたのだった。

投稿者について
みのしす

小さいときは科学者になろうとしたのに、その時にたまたま身に着けたプログラミングで未だに飯を食っているしがないおじさんです。(年齢的にはもうすぐおじいさん)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です