WindowsでVTK9.3をビルドする。
Static Link Libraryとしてビルドするには、BUILD_SHARED_LIBS をOFFにする。
ただし、Windowsでこのチェックを外すと、以下のエラーが起こる可能性がある。
そこで次の設定が必要
上記vtkSMPToolsAPIのエラーを避けるため、VTK_SMP_ENABLE_STDTHREADをOFFに設定。
VTK failed to build due to error LNK2019 and error LNK1120 on Windows with msvc
情報自体は大したことはない。ただ確認するためのVTKのビルドが長すぎる。
CSplitterWndでCMainFrameを画面分割する。構造としては、MFCが作成したシングルドキュメントのウィンドウは、CMainFrameを親とし、その子にツールバー、CChildView、ステータスバーが乗っている。このCChildViewをCSplitterWndで置き換えることで実現する。
ドキュメント/ビューアーキテクチャは無効にしておく。

CChildViewのオブジェクトをCSplitterWndのオブジェクトで置き換えるため、コード上のm_wndViewという変数がかかわるコードをすべて無効化する。m_wndViewの定義をコメントアウトする、あるいは検索すれば簡単に見つかる。
// MainFrm.h : CMainFrame クラスのインターフェイス // #pragma once #include "ChildView.h" class CMainFrame : public CFrameWnd { public: CMainFrame() noexcept; protected: DECLARE_DYNAMIC(CMainFrame) /* 略 */ protected: // コントロール バー用メンバー CToolBar m_wndToolBar; CStatusBar m_wndStatusBar; // 使用しない CChildView m_wndView; /* 略 */ };
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; //// フレームのクライアント領域全体を占めるビューを作成します。 //if (!m_wndView.Create(nullptr, nullptr, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, nullptr)) //{ // TRACE0("ビュー ウィンドウを作成できませんでした。\n"); // return -1; //} /* 略 */ return 0; }
void CMainFrame::OnSetFocus(CWnd* /*pOldWnd*/) { // ビュー ウィンドウにフォーカスを与えます。 //m_wndView.SetFocus(); }
protected: // コントロール バー用メンバー CToolBar m_wndToolBar; CStatusBar m_wndStatusBar; // 使用しない CChildView m_wndView; CSplitterWnd m_wndSplitter;// スプリッターの定義
CMainFrameでOnCreateClientをオーバーライドする。
OnCreateClientにはスプリッタの初期設定を記述する。
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { // TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。 // スプリッタウィンドウの作成 m_wndSplitter.CreateStatic(this, 1, 2); // ペインの作成(例:左側にCLeftView、右側にCRightViewを配置) m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CLeftView), CSize(200, 100), pContext); m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CRightView), CSize(100, 100), pContext); return CFrameWnd::OnCreateClient(lpcs, pContext); }
CLeftView,CRightViewに着色すると結果がわかりやすい
void CLeftView::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); // TODO: 描画コードをここに追加してください。 CRect rect; this->GetClientRect(&rect); pDC->FillSolidRect(&rect,RGB(255,0,0)); }
CFileDialogのフィルタ指定の書式は、コンストラクタ指定の場合は "表示用文字列|*.拡張子|"、m_ofn.lpstrFilter指定の場合、"表示用文字列\0*.拡張子\0"となる。
ただし注意があり、m_ofn.lpstrFilterで指定する場合、CStringを使ってはいけない。たぶん、\0が見つかるとそこで文字が切られてしまうため、最初の一つしか選択肢に出てこない。
以下指定例。プロジェクトはマルチバイト文字セットに設定してある。
CFileDialog fileDialog(FALSE, "*.bmp", "output.bmp"); const char* filter = "BMPファイル\0*.bmp\0" "JPGファイル\0*.jpg\0" "PNGファイル\0*.png\0" "全てのファイル\0*.*\0\0"; fileDialog.m_ofn.lpstrFilter = filter; fileDialog.DoModal();
CFileDialog fileDialog( FALSE, "*.bmp", "output.bmp", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "BMPファイル|*.bmp|" "JPGファイル|*.jpg|" "PNGファイル|*.png|" "全てのファイル|*.*||" ); fileDialog.DoModal();
CStringは\0を含められない(?)ので、m_ofn.lpstrFilterに与える文字列をCStringにしてはいけない。
CFileDialog fileDialog(FALSE, "*.bmp", "output.bmp"); // これは失敗する。 CString filter = "BMPファイル\0*.bmp\0" "JPGファイル\0*.jpg\0" "PNGファイル\0*.png\0" "全てのファイル\0*.*\0\0"; fileDialog.m_ofn.lpstrFilter = (LPCTSTR)filter; fileDialog.DoModal();
CFileDialog fileDialog( FALSE, "*.bmp", "output.bmp", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, // これは成功する (LPCTSTR)CString( "BMPファイル|*.bmp|" "JPGファイル|*.jpg|" "PNGファイル|*.png|" "全てのファイル|*.*||" ) ); fileDialog.DoModal();
指定した拡張子は、上から1スタートで番号が振られている。
fileDialog(FALSE, "bmp", "output.bmp"); const char* filter = "BMPファイル\0*.bmp\0" // index == 1 "JPGファイル\0*.jpg\0" // index == 2 "PNGファイル\0*.png\0" // index == 3 "全てのファイル\0*.*\0\0"; fileDialog.m_ofn.lpstrFilter = (LPCTSTR)filter; if (fileDialog.DoModal() == IDOK) {
// 選択されたファイルのインデックスを取得 int filterIndex = fileDialog.m_ofn.nFilterIndex; switch (filterIndex) { case 1: MessageBoxA("BMP"); break; case 2: MessageBoxA("JPG"); break; case 3: MessageBoxA("PNG"); break; case 4: MessageBoxA("*.*"); break; }
// なお入力されたファイル名
CString filePath = fileDialog.GetPathName();
MessageBoxA(filePath);
}
wxWidgetsのGUIを作るためのデザイナー。いろいろな言語に対応しているが私はC++用コードを出力するために使用してみる。
wxFormBuilderでデザインした後はGenerate CodeでC++のソースコードに変換できるので、.hと.cppを読み込めば簡単に組み込める。
https://github.com/wxFormBuilder/wxFormBuilder/releases
Githubにビルド済みライブラリページへのリンクが張ってあるのでダウンロードすれば起動できる。

1.FormsからFrameなどを追加
2.LayoutからwxBoxSizerなどを追加
3.CommonからwxButtonなどを追加
4.File → Save から.fbp形式で保存
5.File → Generate Code
Generate Codeをすると、fbp と同じ場所に .h/.cpp が作成される。

noname.h / noname.cpp をプロジェクトに追加し、noname.h をincludeする。この中にMyFrame1等の名前でフレーム類が定義してあるので、OnInitの中でそのインスタンスを作成する。
#ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 ///////////////////////////////////// ///////////////////////////////////// /////////////////////////////////////
// wxFormBuilder で出力したファイル。内部で MyFrame1 が定義されている #include "noname.h" ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxAppはここから開始。自分で書く class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame1* frame = new MyFrame1(0,-1,"Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // エントリポイントも自分で定義。WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
Rustでスレッドプールを使う場合、threadpoolクレートが有名らしい。
https://crates.io/crates/threadpool
use threadpool::ThreadPool; fn main() { // スレッドを四枚作成 let pool = threadpool::ThreadPool::new(4); // テスト用データ let myarray = std::sync::Arc::new(std::sync::Mutex::new(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); for i in 0..10{ // myarrayは参照なので、cloneして参照を複製する // クロージャに渡すmoveキーワードによって、myarray2がクロージャにムーブされる let array2 = myarray.clone(); // スレッドプールにジョブを追加 pool.execute(move || {
let mut data = array2.lock().unwrap(); data[i] *= 2;
} ); } // スレッドの終了を待つ pool.join(); // 結果を表示 let data = myarray.lock().unwrap(); println!("{:?}", data); }
channel()を使って各ジョブの結果を送信できる。
use threadpool::ThreadPool; // チャンネルを使うために必要 use std::sync::mpsc::channel; fn main() { // スレッドを四枚作成 let pool = threadpool::ThreadPool::new(4); // スレッドで処理した結果を受け取るためのチャンネルを作成 let (sender,receiver) = channel::<i32>(); // テスト用データ let myarray = std::sync::Arc::new(std::sync::Mutex::new(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); for i in 0..10{ // myarrayは参照なので、cloneして参照を複製する // クロージャに渡すmoveキーワードによって、myarray2がクロージャにムーブされる let array2 = myarray.clone(); let sender2 = sender.clone(); // スレッドプールにジョブを追加 pool.execute(move || { let mut data = array2.lock().unwrap(); data[i] *= 2; sender2.send(data[i]*10).unwrap(); // このジョブの結果をチャンネルに送信 } ); } // スレッドの終了を待つ pool.join(); // 全ての処理が終わったら、チャンネルを閉じる drop(sender); // チャンネルから結果を受け取る for itr in receiver.iter(){ print!("{} ", &itr); } }
Daz to Blender BridgeでBlenderへエクスポート可能だが、ポーズを正確に移行できないので、まずフィギュアだけをエクスポートし、Blender側でdufファイルをインポートする。
Daz Studio 4.21で、[File] → [Send To] → [Daz to Blender]でエクスポートが可能だが、Blender側の準備が必要になる。一応使い方がダイアログ内に書いてある。
ここで、使用するBlenderのバージョンを指定し、 Install Pluginをクリックする。
Blender側では、DazToBlenderアドオンがリストにあるのでPreferenceから有効にする
ポーズをプリセットから選択し、そのポーズをBlenderへエクスポートすることを考える。
ポイントはフィギュアだけをエクスポートした後でポーズを適用すると成功する。
まずフィギュアを読み込み、服を着せ髪を乗せる。
次に、[File] → [Send To] → [Daz to Blender]を開き、Acceptする。
注意点として、フィギュアの頭だけ、髪だけという選択状態だと失敗する可能性があるので、何も選択しないか、フィギュア本体だけを選択しておく必要がある。


Blender側で、Daz To Blenderを有効にしていれば、画面右側にタブが増えているので、それを表示し、[Import New Genesis Figure]をクリックする
Daz側に戻り、ポーズのプリセット一覧を表示する。使用したいポーズを見つけたら右クリックし、[Browse to File Location...]を選択、エクスプローラで表示する。
該当ポーズがあるパスとファイル名がわかったら、Blender側に戻る。
[Import Pose]をクリックし、ファイル読み込みダイアログを開く。先ほど調べたパスへ移動し、ファイル名を探して選択、[Import Pose]をクリックする。

レンダリングしてみる。概ねフィットしているが服が飛び出している部分がある。
println!のように、関数のように呼び出すマクロをfunction-likeマクロというらしい。
マクロを定義するとき、
例えばこんな感じ。
macro_rules! マクロ名 { ($引数:expr) => { println!("引数を使う: {}", $引数); }; }
使用例。
macro_rules! print_arg { ($arg:expr) => { println!("Argument: {}", $arg); }; }
fn main(){ print_arg!(3.5); // 出力: Argument: 3.5 }
macro_rules! print_arg {
($e1:expr, $e2:expr) =>{
println!("e1: {} e2 {}", $e1,$e2);
};
} fn main(){ print_arg!(3.5 , "hello"); }
macro_rules! print_arg { ( $($arg:expr) , * ) => { $( println!("Argument: {}", $arg); ) * }; } fn main(){ print_arg!(3.5 , "hello" , 6); // Argument: 3.5 // Argument: hello // Argument: 6 }
trait MyPrint{ fn print(&self); }
impl MyPrint for i32{ fn print(&self){ println!("i32: {}", *self); } } impl MyPrint for f32{ fn print(&self){ println!("f32: {}", *self); } }
macro_rules! print_arg { ($e:expr) =>{ MyPrint::print(&$e); }; }
fn main(){ print_arg!(3.5); // f32: 3.5 print_arg!(100); // i32: 100 }
Index Buffer使用時には、GL_ELEMENT_ARRAY_BUFFERをBindした後glDrawElementsを使用。
glEnableClientStateで特別な指定をする必要はない。
// バッファ名 GLuint buffer[3]; const int VTX = 0; const int COL = 1; const int IDX = 2;
void init_data() { float v = 0.7; // 頂点データ std::vector< std::array<float,3> > vertex = { { 0.0f, 0.0f ,0.0f}, // 中心 (0) { 1.0f, 0.0f ,0.0f}, // 右中 (1) { 0.5f, 0.87f,0.0f}, // 右上 (2) {-0.5f, 0.87f,0.0f}, // 左上 (3) {-1.0f, 0.0f ,0.0f}, // 左中 (4) {-0.5f, -0.87f,0.0f}, // 左下 (5) { 0.5f, -0.87f,0.0f} // 右下 (6) }; // 色データ std::vector< std::array<float, 3> > color = { {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 1.0f}, {0.5f, 0.5f, 0.5f} }; // インデックスデータ std::vector< std::array<GLuint, 3> > index = { { 0, 1, 2 }, { 0, 2, 3 }, { 0, 3, 4 }, { 0, 4, 5 }, { 0, 5, 6 }, { 0, 6, 1 } }; // バッファ作成 glGenBuffers(3, &buffer[0]); /////////////////////////////////////////// // 頂点バッファの作成 glBindBuffer(GL_ARRAY_BUFFER, buffer[VTX]); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * vertex.size(), vertex[0].data(), GL_STATIC_DRAW); /////////////////////////////////////////// // 色バッファの作成 glBindBuffer(GL_ARRAY_BUFFER, buffer[COL]); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 4 * color.size(), color[0].data(), GL_STATIC_DRAW); /////////////////////////////////////////// // インデックスバッファの作成 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[IDX]); glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * 3 * index.size(), index[0].data(), GL_STATIC_DRAW ); }
void render_data() { glEnableClientState(GL_COLOR_ARRAY); // 色配列を有効化 glEnableClientState(GL_VERTEX_ARRAY); // 頂点配列を有効化 // 頂点配列を有効化 glBindBuffer(GL_ARRAY_BUFFER, buffer[VTX]); glVertexPointer(3, GL_FLOAT, 0, 0); // 色配列を有効化 glBindBuffer(GL_ARRAY_BUFFER, buffer[COL]); glColorPointer(4, GL_FLOAT, 0, 0); // インデックス配列を有効化 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[IDX]); // 描画 glDrawElements(GL_TRIANGLES, 6*3/*6頂点。各頂点3要素*/, GL_UNSIGNED_INT, 0); // 頂点配列を無効化 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); }
void finalizer() { glDeleteBuffers(3, buffer); }
Vertex Buffer Objectが導入されたのはOpenGL 1.5からで、この時はシェーダの指定が必要なかったとのこと。別にシェーダを用意するのもテストでは面倒なのでありがたい。
glEnableClientStateで使用するバッファの種類を指定。glBindBufferした後glVertexPointerする。
glVertexPointerの最後の「0」はCPUメモリ上のバッファのポインタを入れるが、0を指定すると使用する頂点バッファがバインドしたバッファになる。
#include <Windows.h> #include <GL/glew.h> #include <gl/GL.h> #include <gl/GLU.h> #include <gl/freeglut.h> #include <vector> #include <array> #pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glew32.lib") //ウィンドウの幅と高さ int width, height; double rotate_angle = 0; // バッファ名 GLuint buffer;
void init_data() { glEnableClientState(GL_VERTEX_ARRAY); float v = 0.7; std::vector< std::array<float,3> > data; data.push_back({ 0.f,0.f,0.f }); data.push_back({ -v,-v,0 }); data.push_back({ v,-v,0 }); glGenBuffers(1, &buffer); glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * data.size(), data[0].data(), GL_STATIC_DRAW); glVertexPointer(3, GL_FLOAT, 0, 0); }
void render_data() { glColor3f(1, 0, 0); // バッファのデータと関連づけ glBindBuffer(GL_ARRAY_BUFFER, buffer); glVertexPointer(3, GL_FLOAT, 0, 0); // 描画 glDrawArrays(GL_TRIANGLE_STRIP, 0, 3); }
//描画関数 void disp(void) { glClearColor(0.2, 0.2, 0.2, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0, 0, width, height); //カメラの設定 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60, width / (double)height, 0.1, 3); glMatrixMode(GL_MODELVIEW);//モデルビューの設定 glLoadIdentity(); glTranslated(0, 0, -2); glRotated(rotate_angle, 0, 1, 0); //////////////////////////////////////////// render_data(); //////////////////////////////////////////// glFlush(); }
void finalizer() { glDeleteBuffers(1, &buffer); }
//ウィンドウサイズの変化時に呼び出される void reshape(int w, int h) { width = w; height = h; disp(); } void timer(int value) { rotate_angle += 5; disp(); glutTimerFunc(10, timer, 0); } //エントリポイント int main(int argc, char** argv) { glutInit(&argc, argv); glutInitWindowPosition(100, 50); glutInitWindowSize(500, 500); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA); glutCreateWindow("sample"); glewInit(); init_data(); glutDisplayFunc(disp); glutReshapeFunc(reshape); glutTimerFunc(1000, timer, 0);//タイマー glutMainLoop();
finalizer();
return 0; }
glBindBuffer → glVertexPointer で、バインドしたバッファを頂点座標に使用することを教える。
glBindBuffer → glColorPointer で、バインドしたバッファを頂点色に使用することを教える。
#include <Windows.h> #include <GL/glew.h> #include <gl/GL.h> #include <gl/GLU.h> #include <gl/freeglut.h> #include <vector> #include <array> #pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glew32.lib") //ウィンドウの幅と高さ int width, height; double rotate_angle = 0; // バッファ名 GLuint buffer[2]; const int VTX = 0; const int COL = 1;
void init_data() { float v = 0.7; // 頂点データ std::vector< std::array<float,3> > vertex; vertex.push_back({ 0,0,0 }); vertex.push_back({ -v,-v,0 }); vertex.push_back({ v,-v,0 }); // 色データ std::vector< std::array<float, 4> > color; color.push_back({ 1,0,0,1 }); color.push_back({ 0,1,0,1 }); color.push_back({ 0,0,1,1 }); // バッファ作成 glGenBuffers(2, &buffer[0]); /////////////////////////////////////////// glBindBuffer(GL_ARRAY_BUFFER, buffer[VTX]); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * vertex.size(), vertex[0].data(), GL_STATIC_DRAW); /////////////////////////////////////////// glBindBuffer(GL_ARRAY_BUFFER, buffer[COL]); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 4 * color.size(), color[0].data(), GL_STATIC_DRAW); /////////////////////////////////////////// }
void render_data() { glEnableClientState(GL_COLOR_ARRAY); // 色配列を有効化 glEnableClientState(GL_VERTEX_ARRAY); // 頂点配列を有効化 // 頂点配列を有効化 glBindBuffer(GL_ARRAY_BUFFER, buffer[VTX]); glVertexPointer(3, GL_FLOAT, 0, 0); // 色配列を有効化 glBindBuffer(GL_ARRAY_BUFFER, buffer[COL]); glColorPointer(4, GL_FLOAT, 0, 0); // 有効化の設定ここまで glBindBuffer(GL_ARRAY_BUFFER, 0); // 描画 glDrawArrays(GL_TRIANGLES, 0, 3); }
//描画関数 void disp(void) { glClearColor(0.2, 0.2, 0.2, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0, 0, width, height); //カメラの設定 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60, width / (double)height, 0.1, 3); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslated(0, 0, -2); glRotated(rotate_angle, 0, 1, 0); //////////////////////////////////////////// render_data(); //////////////////////////////////////////// glFlush(); }
void finalizer() { glDeleteBuffers(2, buffer); }
実行するとgluPerspectiveやglTranslated,glRotatedでの指定で三角形が回転する。
算譜記録帳
http://mklearning.blogspot.com/2014/08/opengl.html
床井研究
前回矩形を分割するプログラムを書いたのだが、親要素に対する割合でしか指定するように作っていなかったことに気づいた。なのでピクセル単位での指定もできるようにした。
#include <windows.h> #include <iostream> #include <string> #include <vector> #include <pugixml.hpp> #include <functional> // 領域の計算方法 enum class SizeType { RATIO, // 親要素に対する割合で指定 PIXEL, // ピクセル数で指定 AUTO, // 余っているサイズから自動計算 }; // 子要素の配置方法 enum class Arrangement { Horizonal, Vertical, }; // 各情報を元に計算される実際のサイズ。sx,sy, ex,eyは矩形の最小、最大を表すピクセル座標 struct Rect { int sx, sy; int ex, ey; }; struct BOX { SizeType sizeType; // この領域のサイズの指定方法 Arrangement type; // 子要素の配置方法 Rect rect; // この領域の矩形 std::string name; // この領域の名前 // ratioとpixelはどちらか一方が指定される double ratio; // この領域が親要素のどれだけの割合を占めるか int pixel; // この領域が親要素のどれだけのピクセルを占めるか 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() ); node.pixel = xmlNode.attribute("pixel").as_int(-1); const auto& attr_ratio = xmlNode.attribute("ratio"); const auto& attr_pixel = xmlNode.attribute("pixel"); // "ratio" および "pixel" が存在しない場合はAUTO if (attr_ratio.empty() && attr_pixel.empty() ) { node.sizeType = SizeType::AUTO; } // "ratio" が存在し、かつ内容が"auto"であればAUTO else if (!attr_ratio.empty() && std::string(attr_ratio.as_string()) == "auto") { node.sizeType = SizeType::AUTO; } // "pixel" が存在し、かつ内容が"auto"であればAUTO else if (!attr_ratio.empty() && std::string(attr_pixel.as_string()) == "auto") { node.sizeType = SizeType::AUTO; } else if (!attr_ratio.empty()) { node.sizeType = SizeType::RATIO; } else if (!attr_pixel.empty()) { node.sizeType = SizeType::PIXEL; } else { node.sizeType = SizeType::AUTO; } // 配置方法を取得。デフォルトを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; }
// UpdateLayout関数 void UpdateLayout(BOX& node, const Rect& parent) { node.rect = parent; int totalWidth = parent.ex - parent.sx; int totalHeight = parent.ey - parent.sy; int total; if (node.type == Arrangement::Horizonal) { // 水平方向に並べる場合 total = totalWidth; } else { total = totalHeight; } ///////////////////////////////// // 親要素を占める割合の計算 // ratioを省略可能としている // ratioが省略されたときは、その領域内の残りのratioを省略されたbox数で割る int undefinedRatioCount = 0; // ratioが定義されていないboxの個数 size_t last_item_index; // まず、指示のある領域のサイズを計算する // タイプがpixelの場合は、そのままpixelを使う // タイプがratioの場合は、親要素の割合を使い、ピクセルに変換する // タイプが指定されていない場合は、その数をカウントし、childを記憶しておく // totalから確定したサイズを引いた残りをピクセルで計算 int remainingPixel = total; std::vector<int> sizes(node.children.size()); for (size_t child_id = 0; child_id < node.children.size(); child_id++) { switch (node.children[child_id].sizeType) { case SizeType::PIXEL: // pixelが指定されている場合はそのまま使う sizes[child_id] = node.children[child_id].pixel; remainingPixel -= node.children[child_id].pixel; // 残りのピクセル数を計算 break; case SizeType::RATIO: sizes[child_id] = node.children[child_id].ratio * total; remainingPixel -= sizes[child_id]; // 残りのピクセル数を計算 break; case SizeType::AUTO: sizes[child_id] = -1; // 未定義の場合は-1にしておく undefinedRatioCount++; last_item_index = child_id; break; } } // ratioが定義されていないノードがある場合は、使用可能な割合を等分する if (undefinedRatioCount > 0) { // 未定義のノードに割り当てる割合を計算 // 残っている使えるピクセル数 / サイズ未定義のノード数 const double equaldivision = (double)remainingPixel / (double)undefinedRatioCount; int sizesum = 0; // 全てのノードに割り当てた場合のピクセル数を計算 for (size_t child_id = 0; child_id < node.children.size(); child_id++) { if (sizes[child_id] == -1) { sizesum += equaldivision; } else { sizesum += sizes[child_id]; } } // 最後のノードで誤差を吸収 // 分割したピクセル数の合計が親要素のピクセル数より大きければマイナスになる int lastitem_error = total - sizesum; // 実際にサイズを与える for (size_t child_id = 0; child_id < node.children.size(); child_id++) { if (sizes[child_id] == -1) { sizes[child_id] = equaldivision; } } sizes[last_item_index] += lastitem_error; } // 計算した長さを子要素の矩形に与える int offset=0; for (size_t child_id = 0; child_id < node.children.size(); child_id++) { auto& child = node.children[child_id]; if (child_id == 0) { offset=0; } else { offset += sizes[child_id-1]; } if (node.type == Arrangement::Horizonal) { child.rect.sx = parent.sx + offset; child.rect.ex = child.rect.sx + sizes[child_id]; child.rect.sy = parent.sy; child.rect.ey = parent.ey; } else { child.rect.sy = parent.sy + offset; child.rect.ey = child.rect.sy + sizes[child_id]; child.rect.sx = parent.sx; child.rect.ex = parent.ex; } } // 子要素の子要素の矩形を計算する 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; if (node.type == Arrangement::Horizonal) { 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); // ペンを削除 } else { // 塗りつぶした矩形 HBRUSH hBrush = CreateSolidBrush(RGB(r, g, b)); // ブラシを作成 HGDIOBJ oldBrush = SelectObject(hdc, hBrush); // ブラシ Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); SelectObject(hdc, oldBrush); // 元のブラシに戻す DeleteObject(hBrush); // ブラシを削除 } 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_CREATE: GetClientRect(hwnd, &rect); UpdateLayout(rootArea, Rect{ 0, 0, rect.right, rect.bottom }); InvalidateRect(hwnd, nullptr, TRUE); return 0; case WM_SIZING: GetClientRect(hwnd, &rect); UpdateLayout(rootArea, Rect{ 0, 0, rect.right, rect.bottom }); InvalidateRect(hwnd,nullptr,TRUE); 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"(C:\test\layout3.xml)"); UpdateLayout(rootArea, rootRect); hwnd = CreateWindow( TEXT("SZL-WINDOW"), TEXT("test window"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 800, 500, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 0; while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); return msg.wParam; }
<?xml version="1.0" encoding="utf8" ?> <box name="main" ratio="1.0" arrangement="horizonal"> <box name="menu" pixel="300" arrangement="vertical"> <box name="menu-top" pixel="50" arrangement="horizonal"> <box name="area-a" pixel="50" arrangement="vertical"></box> <box name="area-b" pixel="50" arrangement="vertical"></box> <box name="area-c" ratio="auto" arrangement="vertical"></box> </box> <box name="menu-middle" ratio="0.2" arrangement="horizonal"></box> <box name="menu-bottom" arrangement="horizonal"></box> </box> <box name="containts" ratio="auto" arrangement="vertical" > </box> </box>