UIを簡易的に定義したいので、自作xmlとそれに基づいて領域を計算するコードを書く。
まずXMLを定義。
nameは識別子だが一意である必要はない。arrangementは子要素の配置方法。ratioは親要素に対するその要素の割合。
<?xml version="1.0" encoding="utf8" ?> <box name="main" ratio="1.0" arrangement="horizonal"> <box name="red" ratio="0.3" arrangement="vertical"> <box name="area1"></box> <box name="area2"></box> <box name="area3" ratio="0.5"></box> </box> <box name="blue" ratio="0.6" arrangement="vertical" > <box name="area1" ratio="0.2"></box> <box name="area2" ratio="0.8"></box> </box> <box name="green" ratio="0.1" arrangement="vertical" > <box name="area1" ratio="0.3"></box> <box name="area2" ratio="0.3"></box> <box name="area3" ratio="0.3"></box> <box name="area4" ratio="0.1" arrangement="horizonal"> <box name="target1" ratio="0.3"></box> <box name="target2" ></box> </box> </box> </box>
#include <windows.h> #include <iostream> #include <string> #include <vector> #include <pugixml.hpp> #include <functional> // 子要素の配置方法 enum class Arrangement { Horizonal, Vertical, }; // 各情報を元に計算される実際のサイズ。sx,sy, ex,eyは矩形の最小、最大を表すピクセル座標 struct Rect { int sx, sy; int ex, ey; }; struct BOX { Arrangement type; // 子要素の配置方法 Rect rect; // この領域の矩形 std::string name; // この領域の名前 double ratio; // この領域が親要素のどれだけの割合を占めるか std::vector<BOX> children; // 子要素 };
// xmlファイルの内容を解析してノード作成 void parseNode(const pugi::xml_node& xmlNode, BOX& node) { // BOXの名前を取得 node.name = xmlNode.attribute("name").as_string(""); // BOXの親要素に対する割合を取得。指定がない場合はNaNにしておいて、自動計算する node.ratio = xmlNode.attribute("ratio").as_double( std::numeric_limits<double>::quiet_NaN() ); // 配置方法を取得。デフォルトをverticalとしておく。 std::string arrangement = xmlNode.attribute("arrangement").as_string("vertical"); node.type = (arrangement == "horizonal") ? Arrangement::Horizonal : Arrangement::Vertical; // 子要素を再帰的に解析 for (pugi::xml_node child = xmlNode.first_child(); child; child = child.next_sibling()) { BOX childNode; parseNode(child, childNode); node.children.push_back(childNode); } }
// xmlファイルからレイアウト情報を読み込む BOX LoadLayout(const char* xmlfilename) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(xmlfilename); // ファイルが読み込めなかったら例外を投げる if (!result) { throw std::runtime_error("xmlファイルが読み込めませんでした"); } BOX rootNode; pugi::xml_node root = doc.child("box"); parseNode(root, rootNode); return rootNode; }
// 読み込んだxmlファイルから領域を計算 void UpdateLayout(BOX& node, const Rect& parent) { node.rect = parent; int totalWidth = parent.ex - parent.sx; int totalHeight = parent.ey - parent.sy; ///////////////////////////////// // 親要素を占める割合の計算 // ratioを省略可能としている // ratioが省略されたときは、その領域内の残りのratioを省略されたbox数で割る double remainingRatio = 1.0; // 1.0 = 100% parent全体の割合を1.0とする int undefinedRatioCount = 0; // ratioが定義されていないboxの個数 for (auto& child : node.children) { if (std::isnan(child.ratio) == true) { // ratioが定義されていないノードの個数を知りたいのでここで確認する undefinedRatioCount++; } else { // ratioが定義されているノードの割合を引き、 // ratio未定義のboxが使用可能な割合を計算する remainingRatio -= child.ratio; } } // ratioが定義されていないノードがある場合は、使用可能な割合を等分する if (undefinedRatioCount > 0) { // ratioが定義されていないノードは、使用可能な量を等分する double defaultRatio = remainingRatio / undefinedRatioCount; // ratio未定義のノードにratioを与える for (auto& child : node.children) { if (std::isnan(child.ratio) == true) { child.ratio = defaultRatio; } } } // 子要素の矩形を計算する double accumRatio = 0.0; for (auto& child : node.children) { if (node.type == Arrangement::Horizonal) { child.rect.sx = parent.sx + totalWidth * accumRatio; child.rect.ex = child.rect.sx + totalWidth * child.ratio; child.rect.sy = parent.sy; child.rect.ey = parent.ey; } else { child.rect.sy = parent.sy + totalHeight * accumRatio; child.rect.ey = child.rect.sy + totalHeight * child.ratio; child.rect.sx = parent.sx; child.rect.ex = parent.ex; } accumRatio += child.ratio; } // 子要素の子要素の矩形を計算する for (auto& child : node.children) { UpdateLayout(child, child.rect); } }
// 領域を描画 void drawRects(HDC hdc, const BOX& node) { RECT rect = { node.rect.sx, node.rect.sy, node.rect.ex, node.rect.ey }; // ランダムな色を作成 int r = rand() % 256; int g = rand() % 256; int b = rand() % 256; HPEN hPen = CreatePen(PS_SOLID, 1, RGB(r, g, b)); // 1ピクセル幅のペンを作成 HGDIOBJ oldPen = SelectObject(hdc, hPen); // ペンを選択 // 矩形を描画 Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); SelectObject(hdc, oldPen); // 元のペンに戻す DeleteObject(hPen); // ペンを削除 for (const BOX& child : node.children) { drawRects(hdc, child); } } BOX rootArea; LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (msg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); drawRects(hdc, rootArea);// テスト用 矩形描画 EndPaint(hwnd, &ps); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { HWND hwnd; WNDCLASS winc; MSG msg; winc.style = CS_HREDRAW | CS_VREDRAW; winc.lpfnWndProc = WndProc; winc.cbClsExtra = winc.cbWndExtra = 0; winc.hInstance = hInstance; winc.hIcon = LoadIcon(NULL, IDI_APPLICATION); winc.hCursor = LoadCursor(NULL, IDC_ARROW); winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); winc.lpszMenuName = NULL; winc.lpszClassName = TEXT("SZL-WINDOW"); if (!RegisterClass(&winc)) return 0; Rect rootRect{ 0, 0, 800, 300 }; rootArea = LoadLayout(R"(layout2.xml)");// レイアウト読み込み UpdateLayout(rootArea, rootRect);// 矩形を計算 hwnd = CreateWindow( TEXT("SZL-WINDOW"), TEXT("test window"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 0; while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); return msg.wParam; }