コメントに質問があったので使用例を置いておきたい。
HashSetは等価であることを比較するためにEqualsメソッドを持っていなければならない。
全てのクラスはObjectの子クラスなので、Equalsをオーバーライドしてもいいが、IEquatableを継承するとObject型ではなく指定した型のデータをとるEqualsをオーバーライドできるようになる。
#include "pch.h"
      // 自作クラス // IEquatableを継承しているのでEqualsメソッドをオーバーライドすることで // インスタンスの等価判定が可能 public ref class DataType : System::IEquatable<DataType^> { private: int a, b; public: DataType() : a(0), b(0) {} DataType(int _a, int _b) : a(_a), b(_b) {} int geta() { return a; } int getb() { return b; } void set(int _a, int _b) { a = _a; b = _b; } /* // IEquatableを使用しない場合はObject^型で受け取って評価できる // ObjectのEqualsメソッドをオーバーライド virtual bool Equals(Object^ obj) override { if (obj == nullptr) return false; DataType^ other = dynamic_cast<DataType^>(obj); if (other == nullptr) return false; return a == other->a && b == other->b; } */ // IEquatableのEqualsメソッドを実装 virtual bool Equals(DataType^ other) { if (other == nullptr) return false; return (a == other->a) && (b == other->b); } // ObjectのGetHashCodeメソッドをオーバーライド virtual int GetHashCode() override { return a.GetHashCode() ^ b.GetHashCode(); } };
// 比較用クラス // IComparerを継承してCompareメソッドをオーバーライドする。 // このクラスをArray::Sortの第二引数に渡すことで、ソートの基準を変更できる。 public ref class Ccmp : System::Collections::Generic::IComparer<DataType^> { public: virtual int Compare(DataType^ x, DataType^ y) { if (x->geta() < y->geta()) return -1; if (x->geta() > y->geta()) return 1; if (x->getb() < y->getb()) return -1; if (x->getb() > y->getb()) return 1; } };
void sort_test() { array<DataType^>^ ary = gcnew array<DataType^>(3); ary[0] = gcnew DataType(1, 2); ary[1] = gcnew DataType(6, 8); ary[2] = gcnew DataType(2, 4); //ソート実行。 Ccmp^ cmp = gcnew Ccmp();//比較用クラスのインスタンスを作成し、 System::Array::Sort(ary, cmp);//Array::Sortの第二引数にそのハンドルを渡す for each (DataType^ data in ary) { System::Console::WriteLine("a = {0}, b = {1}", data->geta(), data->getb()); } }
void hashset_test() { using namespace System::Collections::Generic; // HashSetを使えるようにする // DataTypeのHashSetを作成 HashSet<DataType^>^ hashSet = gcnew HashSet<DataType^>(); // DataTypeのインスタンスを作成してHashSetに追加 DataType^ data1 = gcnew DataType(-1, 3); // 重複するデータ DataType^ data2 = gcnew DataType(4, 2); DataType^ data3 = gcnew DataType(-1, 3); // 重複するデータ hashSet->Add(data1); hashSet->Add(data2); hashSet->Add(data3); //hashSet内の全データ表示 for each (DataType ^ data in hashSet) { System::Console::WriteLine("a = {0}, b = {1}", data->geta(), data->getb()); } }
int main(array<System::String^>^ args) { sort_test(); hashset_test(); return 0; }
VTKをMFCのウィンドウに張り付ける。VTKのビルド時にMFCサポートを入れておく必要がある。
VTKのビルド時、VTK_MODULE_ENABLE_VTK_GUISupportMFCを設定しておく必要がある。
VTK_MODULE_ENABLE_VTK_GUISupportMFCをYESに設定。
この設定をすると、vtkMFCWindow.hなどが使用できるようになる。
MFCを使用するプロジェクトの設定を「マルチバイト文字セットを使用する」に変更する。
経験上、プロジェクト設定がUnicodeだとデバッグモードでウィンドウを作成できない(例外発生)。
この設定をしないと特にデバッグモードでvtkMFCWindow内でCWnd::Createのあたりで落ちる。
vtkMFCWindow::vtkMFCWindow(CWnd* pcWnd)
{
  this->pvtkWin32OpenGLRW = nullptr;
  // create self as a child of passed in parent
  DWORD style = WS_VISIBLE | WS_CLIPSIBLINGS;
  if (pcWnd)
    style |= WS_CHILD;
  BOOL bCreated =
    CWnd::Create(nullptr, _T("VTK-MFC Window"), style, CRect(0, 0, 1, 1), pcWnd, (UINT)IDC_STATIC);
  SUCCEEDED(bCreated);
  // create a default vtk window
  vtkWin32OpenGLRenderWindow* win = vtkWin32OpenGLRenderWindow::New();
  this->SetRenderWindow(win);
  win->Delete();
}
#pragma once /////////////////////////////// // VTKに必要 #include <vtkMFCWindow.h> // vtkMFCWindowを使うために必要 #include <vtkWin32OpenGLRenderWindow.h> // vtkMFCWindowを使うために必要 #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkConeSource.h> #include <vtkPolyDataMapper.h> #include <vtkActor.h> #include <vtkCamera.h> /* * 以下をはじめとする、wgl系のリンクエラーが出たら、opengl32.libをリンクする 1>vtkRenderingOpenGL2-9.3.lib(vtkWin32OpenGLRenderWindow.obj) : error LNK2001: 外部シンボル __imp_wglCreateContext は未解決です */ #pragma comment(lib,"opengl32.lib") /* 以下のエラーが出たら、Psapi.libをリンクする vtksys-9.3.lib(SystemInformation.obj) : error LNK2001: 外部シンボル GetProcessMemoryInfo は未解決です */ #pragma comment(lib, "Psapi.lib") /* 以下のエラーが出たら、Dbghelp.libをリンクする vtksys-9.3.lib(SystemInformation.obj) : error LNK2001: 外部シンボル __imp_SymGetLineFromAddr64 は未解決です vtksys-9.3.lib(SystemInformation.obj) : error LNK2001: 外部シンボル __imp_SymInitialize は未解決です vtksys-9.3.lib(SystemInformation.obj) : error LNK2001: 外部シンボル __imp_SymFromAddr は未解決です */ #pragma comment(lib, "Dbghelp.lib") class CChildView : public CWnd { vtkMFCWindow* m_vtkMFCWindow; /* 略 */ protected: afx_msg void OnPaint(); DECLARE_MESSAGE_MAP() public: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnDestroy(); };
#include "pch.h" #include "framework.h" #include "MFCApplication4-with-vtk.h" #include "ChildView.h" // trackball 対応 #include <vtkInteractorStyleImage.h> #include <vtkWin32RenderWindowInteractor.h> //win32api対応 // vtkWin32RenderWindowInteractor.h 内でincludeされている //#include <vtkRenderWindowInteractor.h> #include <cstdio> #ifdef _DEBUG #define new DEBUG_NEW #endif // CChildView CChildView::CChildView() { m_vtkMFCWindow = nullptr; } CChildView::~CChildView() { } BEGIN_MESSAGE_MAP(CChildView, CWnd) ON_WM_PAINT() ON_WM_CREATE() ON_WM_SIZE() ON_WM_DESTROY() END_MESSAGE_MAP() // CChildView メッセージ ハンドラー BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) { if (!CWnd::PreCreateWindow(cs)) return FALSE; cs.dwExStyle |= WS_EX_CLIENTEDGE; cs.style &= ~WS_BORDER; cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, ::LoadCursor(nullptr, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), nullptr); return TRUE; } void CChildView::OnPaint() { CPaintDC dc(this); // 描画のデバイス コンテキスト
// 初回起動時のウィンドウサイズ指定 static bool firsttime = true; if (m_vtkMFCWindow && firsttime) { firsttime = false; // ウィンドウの現在のサイズを取得 CRect rect; GetClientRect(&rect); // VTKのウィンドウサイズを変更 m_vtkMFCWindow->GetRenderWindow()->SetSize(rect.right, rect.bottom); }
if (m_vtkMFCWindow) { m_vtkMFCWindow->DrawDC(&dc);// 描画 // こちらでもいい。 //m_vtkMFCWindow->GetRenderWindow()->Render(); }
} int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CWnd::OnCreate(lpCreateStruct) == -1) return -1;
// VTKのMFC用ウィンドウ作成 m_vtkMFCWindow = new vtkMFCWindow(this); m_vtkMFCWindow->SetParent(this);
// レンダラーの作成 vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New(); m_vtkMFCWindow->GetRenderWindow()->AddRenderer(renderer); // オブジェクトを作成・登録 vtkSmartPointer<vtkConeSource> coneSource; coneSource = vtkSmartPointer<vtkConeSource>::New(); vtkSmartPointer<vtkPolyDataMapper> mapper; mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputConnection(coneSource->GetOutputPort()); vtkSmartPointer<vtkActor> actor; actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); renderer->AddActor(actor); vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); m_vtkMFCWindow->GetRenderWindow()->SetInteractor(interactor); auto iren = vtkSmartPointer<vtkWin32RenderWindowInteractor>::New(); vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New(); iren->SetInteractorStyle(style); iren->SetRenderWindow(m_vtkMFCWindow->GetRenderWindow());
return 0; } void CChildView::OnSize(UINT nType, int cx, int cy) { CWnd::OnSize(nType, cx, cy);
// ウィンドウサイズの変更に合わせて // VTKの表示範囲を変更 RECT rect; m_vtkMFCWindow->MoveWindow(0, 0, cx,cy);
} void CChildView::OnDestroy() { CWnd::OnDestroy(); // これをしないとアプリケーション終了しようとしても // なぜか終了しないことがある PostQuitMessage(0); }
ドラッグ&ドロップの実装は、SetDropTargetにwxFileDropTarget を継承したクラスを指定する。
すると、ドラッグ時にMyFileDropTarget::OnDropFilesが呼び出されるので、そこから(必要であれば)MyFrame::OnDropFilesを呼び出す。
この時ただ関数呼び出しするのではなくてwxDropFilesEvent 型のイベントオブジェクトを作ってwxPostEventする。
この時、wxDropFilesEvent オブジェクトに渡したwxString* pathはwxWidgetsが管理する(らしい)のでdelete[]は書かない。
// プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #ifdef _DEBUG #pragma comment(lib,"wxbase32ud.lib") #pragma comment(lib,"wxbase32ud_net.lib") #pragma comment(lib,"wxbase32ud_xml.lib") #pragma comment(lib,"wxmsw32ud_adv.lib") #pragma comment(lib,"wxmsw32ud_aui.lib") #pragma comment(lib,"wxmsw32ud_core.lib") #pragma comment(lib,"wxmsw32ud_gl.lib") #pragma comment(lib,"wxmsw32ud_html.lib") #pragma comment(lib,"wxmsw32ud_media.lib") #pragma comment(lib,"wxmsw32ud_propgrid.lib") #pragma comment(lib,"wxmsw32ud_qa.lib") #pragma comment(lib,"wxmsw32ud_ribbon.lib") #pragma comment(lib,"wxmsw32ud_richtext.lib") #pragma comment(lib,"wxmsw32ud_stc.lib") #pragma comment(lib,"wxmsw32ud_webview.lib") #pragma comment(lib,"wxmsw32ud_xrc.lib") #else #pragma comment(lib,"wxbase32u.lib") #pragma comment(lib,"wxbase32u_net.lib") #pragma comment(lib,"wxbase32u_xml.lib") #pragma comment(lib,"wxmsw32u_adv.lib") #pragma comment(lib,"wxmsw32u_aui.lib") #pragma comment(lib,"wxmsw32u_core.lib") #pragma comment(lib,"wxmsw32u_gl.lib") #pragma comment(lib,"wxmsw32u_html.lib") #pragma comment(lib,"wxmsw32u_media.lib") #pragma comment(lib,"wxmsw32u_propgrid.lib") #pragma comment(lib,"wxmsw32u_qa.lib") #pragma comment(lib,"wxmsw32u_ribbon.lib") #pragma comment(lib,"wxmsw32u_richtext.lib") #pragma comment(lib,"wxmsw32u_stc.lib") #pragma comment(lib,"wxmsw32u_webview.lib") #pragma comment(lib,"wxmsw32u_xrc.lib") #endif #include <string> // ドラッグドロップに必要 #include <wx/dnd.h> ///////////////////////////////////// ///////////////////////////////////// /////////////////////////////////////
// ドラッグ&ドロップに対応するクラス class MyFileDropTarget : public wxFileDropTarget { wxWindow* m_owner; public: MyFileDropTarget(wxWindow* owner) : m_owner(owner) {}
virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override { size_t nFiles = filenames.GetCount(); // wxString* の配列を作成。このメモリはwxWidgetsが管理する(らしい)のでdelete[]しないko wxString* paths = new wxString[nFiles]; for (size_t i = 0; i < nFiles; i++) { paths[i] = filenames[i]; } wxDropFilesEvent event(wxEVT_DROP_FILES, nFiles, paths); wxPostEvent(m_owner, event);// MyFrame::OnDropFilesを呼び出す return true; }
};
      
// ウィンドウ作成 class MyFrame : public wxFrame { public: void PostCreate() { this->Layout(); // レイアウトの更新 } MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); // ドラッグ&ドロップを有効にする SetDropTarget(new MyFileDropTarget(this)); // ドラッグドロップされた時に呼ばれるイベントを設定 Bind(wxEVT_DROP_FILES, &MyFrame::OnDropFiles, this); }
// wxEVT_DROP_FILES に対応するイベントハンドラ void OnDropFiles(wxDropFilesEvent& event) { wxString* filePaths = (wxString*)event.GetFiles();// ファイル名一覧を取得 wxString str; for (size_t i = 0; i < event.GetNumberOfFiles(); i++) { str += filePaths[i]; str += "\n"; } wxMessageBox(str); // 管理はwxWidgetsが行っているので解放しない //delete[] filePaths; }
private: }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
(多分)PCが強制終了せいで、Visual Stdio 2022が起動しなくなった。スタートウィンドウまではいくが数秒で落ちる。
Developer Command Prompt For VS 2022を起動し、以下のコマンドでVisual Studioのログを出力する。
ログを見ると、
と、シャットダウンの理由らしきものが書かれていたので、これで調べる。
%USERPROFILE%\AppData\Local\Microsoft\VSCommon\OnlineLicensing\ にある、VisualStudio\ディレクトリを名前変更する。
↓
これで起動するとクラッシュしなくなった。
https://learn.microsoft.com/en-us/answers/questions/1116991/visual-studio-2022-crashing-on-startup
wxImage::LoadFileを使用したら、Failed to load image from fileというエラーダイアログが出た。エラーは自分で処理したいので、このダイアログが出ないようにしたい。
void PostCreate() { wxImage myimage; // エラーの時、望まないメッセージボックスが出る bool ret = myimage.LoadFile(R"(D:\test.jpeg)", wxBITMAP_TYPE_PNG); this->Layout(); // レイアウトの更新 }
wxLogNull のインスタンスを作成しておくと、そのインスタンスが破棄されるまではログの出力を行わない。
void PostCreate() { { // ログを表示しない // logNo がスコープを抜けるまでエラー時のログがメッセージボックスで表示されない wxLogNull logNo; wxImage myimage; // エラーの時、望まないメッセージボックスが出る bool ret = myimage.LoadFile(R"(D:\test.jpeg)", wxBITMAP_TYPE_PNG); // エラーハンドリングを自分で行う if (ret == false) { wxMessageBox("画像の読み込みに失敗しました"); return; } } this->Layout(); // レイアウトの更新 }
.xrcファイルはxml形式なので、独自のタグを入れられる。ただしそれを解析する機能がデフォルトではないので、ハンドラを用意する必要がある。
.xrcファイルにはコントロールwx_my_Buttonという識別子でボタンを配置する。このボタンはC++では標準のボタン(wxButton)として生成する。wxButtonのハンドラを書き換えられないので、wx_my_Buttonのハンドラを作成し、中でwxButtonを作成する。
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- トップレベルはコントロールである必要がある。パネルを作成。 --> <object class="wxPanel" name="myContainerPanel"> <bg>#00FF00</bg> <!-- 名前 myButton を追加 --> <object class="wx_my_Button" name="myButton"> <myoption attr="5">ユーザー定義オプション</myoption> <label>ボタン</label> </object> </object> </resource>
// wxXmlNode を使うために必要 #include <wx/xml/xml.h>
SetClientDataで値のインスタンス(ここではmyoption_dataの変数)へのポインタをvoid*で指定する。
struct myoption_data { int attr; wxString str; };
GetParamNodeの使用にはwx/xml/xml.hが必要。
// .xrcから独自タグを読み込むには、自作ウィジェットをwxXmlResourceが読み込むための // ハンドラを作成して登録する必要がある class MyButtonXmlHandler : public wxXmlResourceHandler { public: MyButtonXmlHandler() : wxXmlResourceHandler() { AddWindowStyles(); } virtual wxObject* DoCreateResource() override { XRC_MAKE_INSTANCE(mybutton, wxButton); // 普通のボタンを作成 // プロパティの設定 mybutton->Create( m_parentAsWindow, GetID(), GetText("label"), GetPosition(), GetSize(), GetStyle(), wxDefaultValidator, GetName() );
// タグ名 wxString tagname = "myoption"; // データを格納する構造体のインスタンスを作成 // あとで解放しなければいけない myoption_data *pdata = new myoption_data; // 値を取得 // GetTextでも取得できるが、GetTextは\nや$を勝手にエスケープしたり置換したりするので // 内容をそのまま取得できるGetParamValueを使う wxString myopt = GetParamValue(tagname); if (!myopt.IsEmpty()) { pdata->str = myopt; } // 属性を取得 wxXmlNode* node = GetParamNode(tagname); if (node) { wxString attr = node->GetAttribute("attr"/*属性名*/, "0"/*初期値*/); pdata->attr = wxAtoi(attr); } mybutton->SetClientData(pdata);// データをコントロールに指定
SetupWindow(mybutton); return mybutton; } // このハンドラが、xrc内に定義された"MyControl"というウィジェットを作成できることを示すために必要 virtual bool CanHandle(wxXmlNode* node) override { // 生成するのはただのボタンだが、wxButtonのハンドラを置換はできないので // 別のクラス名を識別子にする。 return IsOfClass(node, "wx_my_Button"); } // wxCreateObject() ,GetClassInfo() ,ms_classInfo,wxCreateObject()が定義される wxDECLARE_DYNAMIC_CLASS(MyButtonXmlHandler); }; // 自作ウィジェットのハンドラを登録 IMPLEMENT_DYNAMIC_CLASS(MyButtonXmlHandler, wxXmlResourceHandler)
生成されるのは普通のwxButtonなので、生成時にAddHandlerしておくこと以外には特別なことはしない。ただしハンドラ内でnewしたmyoption_dataのポインタは自分で削除しなければいけないので、wxEVT_DESTROYに後始末用関数をバインドする。
// ウィンドウ作成 class MyFrame : public wxFrame { public: void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 // カスタムコントロールのハンドラを登録 wxXmlResource::Get()->AddHandler(new MyButtonXmlHandler); wxXmlResource::Get()->Load(R"(C:\test\myctrl.xrc)"); // ファイル読み込み wxXmlResource::Get()->LoadPanel(this, "myContainerPanel"); // GUIの生成 this->Layout(); // レイアウトの更新 wxButton* btn = (wxButton*)FindWindowByName("myButton"); btn->Bind(wxEVT_BUTTON, &MyFrame::OnClick, this); // ボタンのクリックイベント btn->Bind(wxEVT_DESTROY, &MyFrame::OnBtnDestroy, this); // ユーザー定義のデータを破棄するための処理を追加 } MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); }
// ボタンのクリックイベント void OnClick(wxCommandEvent& event) { wxButton* btn = (wxButton*)event.GetEventObject(); myoption_data* data = (myoption_data*)btn->GetClientData(); wxString text = wxString::Format("%s %d", data->str, data->attr); wxMessageBox(text); }
// ユーザー定義のデータを持つボタンの破棄イベント void OnBtnDestroy(wxWindowDestroyEvent& event) { wxButton* btn = (wxButton*)event.GetEventObject(); myoption_data* data = (myoption_data*)btn->GetClientData(); delete data; }
};
wxWidgetsでは、wxWindowなどを継承してカスタムコントロールを作れる。それを.xrcファイルに定義した場合、.xrcを読み込む wxXmlResource がカスタムコントロールを知らないため、専用のハンドラを定義しなければいけない。
以下のようなカスタムコントロール CMyControl を定義する
// ボタンとテキストを持つコントロールをパネルで作成 class CMyControl : public wxWindow { public: // 今回はMyControlXmlHandler側でXRC_MAKE_INSTANCEでインスタンス生成をする // MyControlXmlHandlerはデフォルトコンストラクタを呼び出すので定義する CMyControl() :wxWindow() { } // デフォルトコンストラクタでは最小限の初期化しか行わないので // 初期化用のCreate関数を定義する必要が出てくる bool Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name) { if (!wxWindow::Create(parent, id, pos, size, style, name)) { return false; } my_create(); return true; } // parentを指定するコンストラクタ CMyControl(wxWindow* parent) :wxWindow(parent, wxID_ANY) { my_create(); } // ウィジェットの初期化関数 void my_create() { // ボタンの作成 wxButton* myButton = new wxButton(this, wxID_ANY, "ボタン", wxPoint(10, 10), wxSize(150, 50)); // テキストの作成 wxTextCtrl* myText = new wxTextCtrl(this, wxID_ANY, "テキスト", wxPoint(10, 70), wxSize(150, 50)); this->SetBackgroundColour(wxColour(255, 0, 0)); } };
以下のように.xrcファイルを定義する
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- トップレベルはコントロールである必要がある。パネルを作成。 --> <object class="wxPanel" name="myContainerPanel"> <bg>#00FF00</bg> <!-- レイアウト用のサイザーを作成 --> <object class="wxBoxSizer" name="myBoxSizer"> <orient>wxVERTICAL</orient> <!-- レイアウト方法の指定 --> <!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 周囲に15pxの余白を入れる --> <flag>wxALL | wxEXPAND</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わる --> <option>1</option> <!-- カスタムコントロールを追加 --> <object class="MyControl" name="nameMyControl" /> </object> </object> </object> </resource>
// .xrcから読み込むには、自作ウィジェットをwxXmlResourceが読み込むための // ハンドラを作成して登録する必要がある class MyControlXmlHandler : public wxXmlResourceHandler { public: MyControlXmlHandler() : wxXmlResourceHandler() { XRC_ADD_STYLE(wxTAB_TRAVERSAL); XRC_ADD_STYLE(wxNO_BORDER); XRC_ADD_STYLE(wxDOUBLE_BORDER); AddWindowStyles(); }
// これが必要 // wxXmlResource::LoadObject() が呼び出されたときに呼び出される関数 virtual wxObject* DoCreateResource() override { // 概ねやっていることは以下と同じ: // MyControl* control = new MyControl; // 注意として、このマクロはMyControlのデフォルトコンストラクタを呼び出すので // 引数なしのコンストラクタが必要 // 別にXRC_MAKE_INSTANCEを使わなくても、普通にnewしてもいい。 XRC_MAKE_INSTANCE(control, CMyControl); // デフォルトコンストラクタでは最小限の初期化しかされないので // Create()を呼び出して、実際の初期化を行う // 従ってMyControl::Create()を定義しておく control->Create( m_parentAsWindow, // 親ウィンドウ GetID(), GetPosition(), GetSize(), GetStyle(), GetName() ); SetupWindow(control); return control; }
// このハンドラが、xrc内に定義された"MyControl"というウィジェットを作成できることを示すために必要 virtual bool CanHandle(wxXmlNode* node) override { return IsOfClass(node, "MyControl"); }
// wxCreateObject() ,GetClassInfo() ,ms_classInfo,wxCreateObject()が定義される wxDECLARE_DYNAMIC_CLASS(MyControlXmlHandler);
}; // 自作ウィジェットのハンドラを登録 IMPLEMENT_DYNAMIC_CLASS(MyControlXmlHandler, wxXmlResourceHandler)
読み込むときは、AddHandlerで、作成したハンドラのインスタンスをwxXmlResourceに渡す。
// ウィンドウ作成 class MyFrame : public wxFrame { public: void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 // カスタムコントロールのハンドラを登録 wxXmlResource::Get()->AddHandler(new MyControlXmlHandler); wxXmlResource::Get()->Load(R"(C:\test\myctrl.xrc)"); // ファイル読み込み wxXmlResource::Get()->LoadPanel(this, "myContainerPanel"); // GUIの生成 this->Layout(); // レイアウトの更新 } MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); } };
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- トップレベルはコントロールである必要がある。パネルを作成。 --> <object class="wxPanel" name="myMainPanel"> <!-- レイアウト用のサイザーを作成 --> <object class="wxBoxSizer" name="myBoxSizer"> <orient>wxVERTICAL</orient> <!-- レイアウト方法の指定 --> <!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 左側と上側に15pxの余白を入れる --> <flag>wxLEFT|wxTOP</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わらない --> <option>0</option> <!-- 名前 myButton を追加 --> <object class="wxButton" name="myButton"> <label>ボタン</label> <size>100,30</size> </object> </object> <!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 上下左右の余白は15px --> <flag>wxALL|wxEXPAND</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わる --> <option>1</option> <object class="wxTextCtrl" name="myText"> <value>テキスト</value> </object> </object> </object> </object> </resource>
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #pragma comment(lib,"wxbase32u.lib") #pragma comment(lib,"wxbase32u_net.lib") #pragma comment(lib,"wxbase32u_xml.lib") #pragma comment(lib,"wxmsw32u_adv.lib") #pragma comment(lib,"wxmsw32u_aui.lib") #pragma comment(lib,"wxmsw32u_core.lib") #pragma comment(lib,"wxmsw32u_gl.lib") #pragma comment(lib,"wxmsw32u_html.lib") #pragma comment(lib,"wxmsw32u_media.lib") #pragma comment(lib,"wxmsw32u_propgrid.lib") #pragma comment(lib,"wxmsw32u_qa.lib") #pragma comment(lib,"wxmsw32u_ribbon.lib") #pragma comment(lib,"wxmsw32u_richtext.lib") #pragma comment(lib,"wxmsw32u_stc.lib") #pragma comment(lib,"wxmsw32u_webview.lib") #pragma comment(lib,"wxmsw32u_xrc.lib") ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// #include <wx/xrc/xmlres.h> #include <string> // ウィンドウ作成 class MyFrame : public wxFrame { public:
void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 wxXmlResource::Get()->Load(R"(C:\test\layout.xrc)"); // ファイル読み込み wxXmlResource::Get()->LoadPanel(this, "myMainPanel"); // GUIの生成 this->Layout(); // レイアウトの更新 // ボタンの取得 wxButton* myButton = XRCCTRL(*this, "myButton", wxButton); // ボタンのクリックイベントに対するイベントハンドラを接続 myButton->Bind(wxEVT_BUTTON, &MyFrame::OnMyButtonClick, this); }
MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); }
// ボタンがクリックされたときのイベントハンドラ void OnMyButtonClick(wxCommandEvent& event) { // テキストボックスの取得 wxTextCtrl* textctrl = XRCCTRL(*this, "myText", wxTextCtrl); // テキストボックスの内容を取得 wxString str = textctrl->GetValue(); // ボタンがクリックされたときの処理 wxMessageBox(str); }
private: }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
XRCCTRLの実体はFindWindowをdynamic_castしたもので、実際はもっと複雑だがざっくりいうと以下のようなことをやっている。
wxButton* myButton = dynamic_cast<wxButton*>(this->FindWindow("myButton"));
wxWidgetsで画面デザインを定義する.xrcファイルを読み込んでGUIを作成する。
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- トップレベルはコントロールである必要がある。パネルを作成。 --> <object class="wxPanel" name="myMainPanel">
    
        <!-- レイアウト用のサイザーを作成 -->
        <object class="wxBoxSizer" name="myBoxSizer">
        
            <orient>wxVERTICAL</orient> <!-- レイアウト方法の指定 -->
            
      <!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 左側と上側に15pxの余白を入れる --> <flag>wxLEFT|wxTOP</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わらない --> <option>0</option>
<!-- 名前 myButton を追加 --> <object class="wxButton" name="myButton"> <label>ボタン</label> <size>100,30</size> </object>
            </object>
      <!-- コントロールは sizeritem に入れる --> <object class="sizeritem"> <!-- 上下左右の余白は15px --> <flag>wxALL|wxEXPAND</flag> <border>15</border> <!-- このコントロールはサイザーのサイズ変更で大きさが変わる --> <option>1</option>
<object class="wxTextCtrl" name="mytext1"> <label>ボタン</label> <size>100,30</size> <!-- サイザーが有効なのでサイズは無視される --> </object>
            </object>
              </object>
      </object> </resource>
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 #pragma comment(lib,"wxbase32u.lib") #pragma comment(lib,"wxbase32u_net.lib") #pragma comment(lib,"wxbase32u_xml.lib") #pragma comment(lib,"wxmsw32u_adv.lib") #pragma comment(lib,"wxmsw32u_aui.lib") #pragma comment(lib,"wxmsw32u_core.lib") #pragma comment(lib,"wxmsw32u_gl.lib") #pragma comment(lib,"wxmsw32u_html.lib") #pragma comment(lib,"wxmsw32u_media.lib") #pragma comment(lib,"wxmsw32u_propgrid.lib") #pragma comment(lib,"wxmsw32u_qa.lib") #pragma comment(lib,"wxmsw32u_ribbon.lib") #pragma comment(lib,"wxmsw32u_richtext.lib") #pragma comment(lib,"wxmsw32u_stc.lib") #pragma comment(lib,"wxmsw32u_webview.lib") #pragma comment(lib,"wxmsw32u_xrc.lib") ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// #include <wx/xrc/xmlres.h> // ウィンドウ作成 class MyFrame : public wxFrame { public:
void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 wxXmlResource::Get()->Load(R"(C:\test\layout.xrc)"); // ファイル読み込み wxXmlResource::Get()->LoadPanel(this, "myMainPanel"); // GUIの作成 this->Layout(); // レイアウトの更新 }
MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); } private: }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
パネルにはLoadPanelがあるが、すべてのコントロールにLoad***が用意されているわけではない。LoadObject関数を使って、部品名とその部品のクラスを指定して読み込む。
<?xml version="1.0" ?> <resource version="2.3.0.1"> <!-- 名前 myButton を追加 --> <object class="wxButton" name="myButton"> <label>ボタン</label> <size>300,30</size> </object> </resource>
void PostCreate() { wxXmlResource::Get()->InitAllHandlers();// 初期化 wxXmlResource::Get()->Load(R"(C:\test\button.xrc)"); // ファイル読み込み wxWindow* btn = (wxWindow*)wxXmlResource::Get()->LoadObject(this, "myButton"/*識別子*/, "wxButton"/*クラス*/); // boxsizerの追加 wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); this->SetSizer(sizer); sizer->Add(btn, 0, wxALL, 10); this->Layout(); // レイアウトの更新 }
原則として、メモリ上のテキストを読み込む方法はない。しかし、wxWidgetsにはメモリ上に仮想的なファイルを作成する機能があるので、これを使って仮想的な.xrcファイルを作れば実現できる。
仮想ファイルを作るために、fs_mem.h が必要。
#include <wx/fs_mem.h>
void PostCreate() { wxXmlResource::Get()->InitAllHandlers(); wxString layout_xrc =
R"(<?xml version="1.0" ?> <resource version="2.3.0.1"> <object class="wxPanel" name="myPanel"> <object class="wxBoxSizer"> <orient>wxVERTICAL</orient> <object class="sizeritem"> <object class="wxTextCtrl" name="text"/> <option>0</option> <size>100,30</size> <flag>wxALL</flag> <border>10</border> </object> </object> </object> </resource> )"
; wxFileSystem::AddHandler(new wxMemoryFSHandler); wxMemoryFSHandler::AddFile("myxrc", layout_xrc.GetData().AsWChar(), layout_xrc.size()*2); wxXmlResource::Get()->Load("memory:myxrc"); wxWindow* mysizer = wxXmlResource::Get()->LoadPanel(this, "myPanel"); this->Layout(); // レイアウトの更新 }
諸事情により簡易的でよいので.exeファイルの解析をしたくなった。.exeファイルをバイナリエディタで見てみて、全く同じプロジェクトを二回以上ビルドした結果が完全一致しない。理由の一つがタイムスタンプなので、これをいじってみる。
VC++でコンソールアプリケーションを作成する。この時、余計なものが埋め込まれるとそれだけビルド時の変化が大きくなる可能性があるので、わかる範囲で不要なオプションを取り除く。
・.pdbファイルを出力しない
プロジェクトのプロパティ → リンカー → デバッグ → デバッグ情報の生成 → いいえ
・マニフェストを埋め込まない
 プロジェクトのプロパティ → リンカー → マニフェスト ファイル → マニフェストの生成 → いいえ
・ASLRをしない(Address Space Layout Randomization) 
ASLRはexe内のデータの位置をランダムにするクラッキング対策のオプションで、多分悪影響を与えるので切っておく。/DYNAMICBASE:NOを設定。
 プロジェクトのプロパティ → リンカー → 詳細設定 → ランダム化されたベースアドレス → いいえ
まずビルドし、上書きされないように.exeファイルの名前を変える。それからもう一度ビルドする。
右クリック→プロパティ→詳細 で更新日時を確認。
実際にはここの表示を変えるわけではないのだが、バイナリ中の値を解析してこの日付とおおむね同じものが出てくれば正解だとわかるので参考にする。
WinMergeで二つの.exeをバイナリ比較する。手元のバージョンでは、CompareボタンのプルダウンにBinary比較がある。
一部違う場所が出てくるので、その右4バイトに着目する。時間を置かずにビルドすると、数秒しか違わないはずなので、違いはせいぜい下1バイトになる可能性が高い。intelのCPUはリトルエンディアンなので、バイトの並び順が逆になっている。従って変化している場所を含めて右に4バイトがタイムスタンプとなる。

上記で得られた値 0x65abe167 が実際にビルド時刻であることを確認するため、以下のプログラムを書いて確認する。
#include <ctime> #include <string> #include <iostream> #pragma warning(disable:4996) // タイムスタンプから時刻表示を作成 std::string TimestampToDate(time_t timestamp) { struct tm* tm = localtime(×tamp);// 整数値 timestamp を tm 構造体に変換 char str[50]; strftime(str, sizeof(str), "%Y/%m/%d %H:%M:%S", tm);// tmを文字列化 return std::string(str); } int main() { // タイムスタンプ(16進数指定) // バイナリに埋め込まれている値はリトルエンディアンなので // バイナリエディタ上の表示とバイト単位で逆順にして入力する time_t timestamp = 0x65abe167; std::cout << TimestampToDate(timestamp) << std::endl; return 0; }
結果
確かにファイルのプロパティ上の表示と一致する内容を得られたので、この数値がタイムスタンプであることは理解できた。今度はこれを書き換えてみる。
まず、適当な日付をタイムスタンプに変換するため、以下のプログラムを書いて走らせる。
#include <ctime> #include <string> #include <iostream> #pragma warning(disable:4996) // 時刻からタイムスタンプを作成 time_t DateToTimestamp(const std::string date) { struct tm tm = {}; int year, month, day; int hour, minute, second; sscanf(date.c_str(), "%d/%d/%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second ); tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = minute; tm.tm_sec = second; return mktime(&tm); } int main() { std::string date_str = "2022/01/01 5:25:30"; // 適当な日付 unsigned int timestamp = DateToTimestamp(date_str); printf("%x \n", timestamp); }
結果
こうして得られたタイムスタンプを、バイナリエディタで書き込む。書き込みにはStirlingを使っている。入力時、リトルエンディアンなのでバイト単位で逆順に指定する。
タイムスタンプはビルド時の色々なステップで入れられるらしいので、同じ値を見つけたらすべて書き換える。

先ほどはファイルのプロパティから確認したが、この日付はファイルのプロパティには実は表示されないので、PEヘッダを読むツールで確認する。探したところPEviewが適している。
以下からPEview version 0.9.9 ( .zip 31KB )をダウンロードする。