Skip to content

Instantly share code, notes, and snippets.

@sayurin
Last active December 4, 2017 23:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sayurin/c86c790d63ae326f74583c28f316fc83 to your computer and use it in GitHub Desktop.
Save sayurin/c86c790d63ae326f74583c28f316fc83 to your computer and use it in GitHub Desktop.
#pragma once
#include <array>
#include <type_traits>
#include <Windows.h>
#include <windowsx.h>
template<int... controls>
struct Controls;
template<class RightAnchoredControls, class BottomAnchoredControls, class StretchingControls>
class Resizable;
template<int... anchorRight, int... anchorBottom, int... anchorStretch>
class Resizable<Controls<anchorRight...>, Controls<anchorBottom...>, Controls<anchorStretch...>> {
static const UINT flags = SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_DEFERERASE | SWP_ASYNCWINDOWPOS;
SIZE minimum;
SIZE current;
public:
Resizable() : current{} {}
Resizable(SIZE current) : current{ current } {}
SIZE getCurrent() const { return current; }
void onSize(HWND dialog, LONG cx, LONG cy) {
LONG dx = cx - current.cx, dy = cy - current.cy;
if (dx != 0)
for (auto id : std::array<int, sizeof...(anchorRight)>{ anchorRight... }) {
auto control = GetDlgItem(dialog, id);
RECT r;
GetWindowRect(control, &r);
POINT p{ r.left, r.top };
ScreenToClient(dialog, &p);
SetWindowPos(control, 0, p.x + dx, p.y, 0, 0, SWP_NOSIZE | flags);
}
if (dy != 0)
for (auto id : std::array<int, sizeof...(anchorBottom)>{ anchorBottom... }) {
auto control = GetDlgItem(dialog, id);
RECT r;
GetWindowRect(control, &r);
POINT p{ r.left, r.top };
ScreenToClient(dialog, &p);
SetWindowPos(control, 0, p.x, p.y + dy, 0, 0, SWP_NOSIZE | flags);
}
if (dx != 0 || dy != 0)
for (auto id : std::array<int, sizeof...(anchorStretch)>{ anchorStretch... }) {
auto control = GetDlgItem(dialog, id);
RECT r;
GetWindowRect(control, &r);
SetWindowPos(control, 0, 0, 0, r.right - r.left + dx, r.bottom - r.top + dy, SWP_NOMOVE | flags);
}
current = { cx, cy };
InvalidateRect(dialog, nullptr, FALSE);
}
void onSizing(HWND dialog, RECT* targetSize, int edge) {
if (targetSize->right - targetSize->left < minimum.cx) {
if (edge == WMSZ_LEFT || edge == WMSZ_TOPLEFT || edge == WMSZ_BOTTOMLEFT)
targetSize->left = targetSize->right - minimum.cx;
else
targetSize->right = targetSize->left + minimum.cx;
}
if (targetSize->bottom - targetSize->top < minimum.cy) {
if (edge == WMSZ_TOP || edge == WMSZ_TOPLEFT || edge == WMSZ_TOPRIGHT)
targetSize->top = targetSize->bottom - minimum.cy;
else
targetSize->bottom = targetSize->top + minimum.cy;
}
onSize(dialog, targetSize->right - targetSize->left, targetSize->bottom - targetSize->top);
}
void initialize(HWND dialog) {
RECT r;
GetWindowRect(dialog, &r);
minimum = { r.right - r.left, r.bottom - r.top };
if (current.cx == 0)
current = minimum;
else {
auto copied = current;
current = minimum;
SetWindowPos(dialog, 0, 0, 0, copied.cx, copied.cy, SWP_NOMOVE | flags);
}
}
};
namespace detail {
template<class Data>
class Dialog {
template<class T> static constexpr auto hasOnInit() -> decltype(std::declval<T>().OnInit(HWND{}), true) { return true; }
template<class T> static constexpr auto hasOnInit(...) { return false; }
template<class T> static constexpr auto hasOnCommand1() -> decltype(std::declval<T>().OnCommand(HWND{}, WORD{}), true) { return true; }
template<class T> static constexpr auto hasOnCommand1(...) { return false; }
template<class T> static constexpr auto hasOnCommand2() -> decltype(std::declval<T>().OnCommand(HWND{}, WORD{}, WORD{}), true) { return true; }
template<class T> static constexpr auto hasOnCommand2(...) { return false; }
template<class T> static constexpr auto hasOnNotify() -> decltype(std::declval<T>().OnNotify(HWND{}, std::declval<NMHDR*>()), true) { return true; }
template<class T> static constexpr auto hasOnNotify(...) { return false; }
template<class T> static constexpr auto hasOnMessage() -> decltype(std::declval<T>().OnMessage(HWND{}, UINT{}, WPARAM{}, LPARAM{}), true) { return true; }
template<class T> static constexpr auto hasOnMessage(...) { return false; }
template<class T> static constexpr auto hasResizable() -> decltype(T::resizable, true) { return true; }
template<class T> static constexpr auto hasResizable(...) { return false; }
public:
static INT_PTR CALLBACK proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_INITDIALOG) {
auto ptr = reinterpret_cast<Data*>(lParam);
SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, lParam);
auto result = TRUE;
if constexpr (hasOnInit<Data>())
result = ptr->OnInit(hwndDlg);
if constexpr (hasResizable<Data>())
ptr->resizable.initialize(hwndDlg);
return result;
}
auto get = [hwndDlg] { return reinterpret_cast<Data*>(GetWindowLongPtrW(hwndDlg, GWLP_USERDATA)); };
if constexpr (hasOnCommand1<Data>()) {
if (uMsg == WM_COMMAND)
return get()->OnCommand(hwndDlg, GET_WM_COMMAND_ID(wParam, lParam));
} else if constexpr (hasOnCommand2<Data>()) {
if (uMsg == WM_COMMAND)
return get()->OnCommand(hwndDlg, GET_WM_COMMAND_CMD(wParam, lParam), GET_WM_COMMAND_ID(wParam, lParam));
}
if constexpr (hasResizable<Data>()) {
if (uMsg == WM_SIZING) {
get()->resizable.onSizing(hwndDlg, reinterpret_cast<RECT*>(lParam), static_cast<int>(wParam));
return TRUE;
}
if (uMsg == WM_SIZE) {
RECT r;
GetWindowRect(hwndDlg, &r);
get()->resizable.onSize(hwndDlg, r.right - r.left, r.bottom - r.top);
return 0;
}
}
if constexpr (hasOnNotify<Data>()) {
if (uMsg == WM_NOTIFY)
return get()->OnNotify(hwndDlg, reinterpret_cast<NMHDR*>(lParam));
}
if constexpr (hasOnMessage<Data>()) {
return get()->OnMessage(hwndDlg, uMsg, wParam, lParam);
}
return FALSE;
}
};
}
// DialogBoxを表示します。
// 次の要件を満たした型を受け入れます。
// struct Data {
// // 必須。ダイアログプロシージャからEndDialog()を呼び出す際に使われる値の型を宣言します。そのままDialog()関数の戻り値になります。
// using result_t = ...;
// // 任意。宣言されている場合はダイアログのサイズを可変にします。第1テンプレートパラメーターは右端に固定されるID、第2は下端に固定されるID、第3は伸縮するIDをそれぞれ指定します。
// // resizable.getCurrent()で現在のダイアログサイズを取得できます。
// Resizable<Controls<ID1, ID2, ...>, Controls<ID3, ID4, ...>, Controls<ID5, ID6, ...>> resizable;
// // 任意。WM_INITDIALOGメッセージを処理するコールバック。
// INT_PTR OnInit(HWND);
// // 任意。WM_COMMANDメッセージを処理するコールバック。第2引数は押されたコマンドのIDです。
// INT_PTR OnCommand(HWND, WORD);
// };
template<class Data, class T = std::remove_reference_t<Data>>
static inline auto Dialog(HINSTANCE instance, int resourceId, HWND parent, Data&& data) {
return (T::result_t)DialogBoxParamW(instance, MAKEINTRESOURCEW(resourceId), parent, detail::Dialog<T>::proc, (LPARAM)&data);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment