zwx_helper
- 2020 年 5 月 3 日
- 筆記
- C++, C++linux, gui, wxWidgets, c++11, linux
通過重載c++operator,實現一種輕鬆的wxWidgets介面編程風格,如html編寫頁面一樣直觀容易。
舉一例,一個介面頁有四塊區,如果是開發html的話,是從頭到腳一氣書寫
<div id='0'> <div id='1'> <input type="button" onclick="handler()"> </div> <div id='2'> <input type="button" onclick="handler()"> </div> <div id='3'> <input type="button" onclick="handler()"> </div> <div id='4'> <input type="button" onclick="handler()"> </div> </div>
我的目標是,讓c++開發的程式碼書寫也一氣呵成
Frame* frame = new Frame; layout::begin(new layout) (layout::begin(new layout) (new button) [ onclick = [] (event&) {} ] (layout::end) ) (layout::begin(new layout) (new button) [ onclick = [] (event&) {} ] (layout::end) ) (layout::begin(new layout) (new button) [ onclick = [] (event&) {} ] (layout::end) ) (layout::begin(new layout) (new button) [ onclick = [] (event&) {} ] (layout::end) ) (layout::end, [=] (layout& layout) { frame->SetLayout(layout); });
菜單布局編寫直觀自然,處理器函數綁定同時到位
Frame* frame = new Frame; menu::begin(new MenuBar) ("File", menu::begin(new Menu(ID_OPEN, "open"), [=] (event&) { }) (new Menu(ID_NEW, "new"), [=] (event&) { }) (menu::end) ) ("About", menu::begin(new Menu(ID_HELP, "help"), [=] (event&) { }) (menu::end) ) (menu::end, [=] (MenuBar* mb) { frame->SetMenuBar(mb); });
理想總是美好的,現在回到現實中的介面開發,讓人眼痛的c++程式碼,
1 必須在多處程式碼操作,步驟煩多,分散在多處程式碼。
2 布局編寫不符合人的思維-將整體拆分再拆分,而是從枝節末端創建一層一層往上添加掛鉤。
MFC,WTL與wxWidgets的開發步驟類似,這裡只關注wxWidgets,
1 在頭文件中,自定義窗口類聲明事件(窗口消息)分派函數;
2 在源文件中,添加事件(窗口消息)分派函數的實現宏;
3 添加一個事件(窗口消息)處理器函數,
3-1 在頭文件中,聲明為自定義窗口類的成員函數
3-2 在源文件中,實現該函數
3-3 在源文件中,在那個TABLE宏里添加事件id,處理器函數
4 在某個窗口容器的初始化回調中,創建事件id對應的子窗口/控制項,設置相關的id
5 過了一段時間後,維護某個控制項的處理器函數
5-1 在布局程式碼中找到這個控制項,看到它相關的id
5-2 在源程式碼找到分派事件的窗口類,在它的TABLE宏里眾多條目中找出對應項
5-3 最後才定位到目標處理器函數
做一樣事情,卻要在多處程式碼文件中跳轉切換,只要漏了一步又會浪費時間調試一翻,實在煩人煩心。
// MyFrame.h enum { ID_SOME }; class MyFrame : public wxFrame { public: void hanlde_some_event(wxEvent&); private: wxDECLARE_EVENT_TABLE() }
// MyFrame.cpp wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_XXXX(ID_SOME, &MyFrame::handle_some_event) wxEND_EVENT_TABLE() void MyFrame::handle_some_event(wxEvent&) { // todo } MyFrame::MyFrame() { // .... this->Add(new wxSomeWindow(this, ID_SOME, ...); // .... }
下面是用zwx_helper重寫官方layout sample布局的程式碼的對比
先是官方程式碼
layout.h //github.com/wxWidgets/wxWidgets/blob/master/samples/layout/layout.h
layout.cpp //github.com/wxWidgets/wxWidgets/blob/master/samples/layout/layout.cpp
// ---------------------------------------------------------------------------- // MyFrame // ---------------------------------------------------------------------------- wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(LAYOUT_ABOUT, MyFrame::OnAbout) EVT_MENU(LAYOUT_QUIT, MyFrame::OnQuit) EVT_MENU(LAYOUT_TEST_PROPORTIONS, MyFrame::TestProportions) EVT_MENU(LAYOUT_TEST_SIZER, MyFrame::TestFlexSizers) EVT_MENU(LAYOUT_TEST_NB_SIZER, MyFrame::TestNotebookSizers) EVT_MENU(LAYOUT_TEST_GB_SIZER, MyFrame::TestGridBagSizer) EVT_MENU(LAYOUT_TEST_SET_MINIMAL, MyFrame::TestSetMinimal) EVT_MENU(LAYOUT_TEST_NESTED, MyFrame::TestNested) EVT_MENU(LAYOUT_TEST_WRAP, MyFrame::TestWrap) wxEND_EVENT_TABLE() // Define my frame constructor MyFrame::MyFrame() : wxFrame(NULL, wxID_ANY, "wxWidgets Layout Demo") { SetIcon(wxICON(sample)); // Make a menubar wxMenu *file_menu = new wxMenu; file_menu->Append(LAYOUT_TEST_PROPORTIONS, "&Proportions demo...\tF1"); file_menu->Append(LAYOUT_TEST_SIZER, "Test wx&FlexSizer...\tF2"); file_menu->Append(LAYOUT_TEST_NB_SIZER, "Test ¬ebook sizers...\tF3"); file_menu->Append(LAYOUT_TEST_GB_SIZER, "Test &gridbag sizer...\tF4"); file_menu->Append(LAYOUT_TEST_SET_MINIMAL, "Test Set&ItemMinSize...\tF5"); file_menu->Append(LAYOUT_TEST_NESTED, "Test nested sizer in a wxPanel...\tF6"); file_menu->Append(LAYOUT_TEST_WRAP, "Test wrap sizers...\tF7"); file_menu->AppendSeparator(); file_menu->Append(LAYOUT_QUIT, "E&xit", "Quit program"); wxMenu *help_menu = new wxMenu; help_menu->Append(LAYOUT_ABOUT, "&About", "About layout demo..."); wxMenuBar *menu_bar = new wxMenuBar; menu_bar->Append(file_menu, "&File"); menu_bar->Append(help_menu, "&Help"); // Associate the menu bar with the frame SetMenuBar(menu_bar); #if wxUSE_STATUSBAR CreateStatusBar(2); SetStatusText("wxWidgets layout demo"); #endif // wxUSE_STATUSBAR wxPanel* p = new wxPanel(this, wxID_ANY); // we want to get a dialog that is stretchable because it // has a text ctrl in the middle. at the bottom, we have // two buttons which. wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL ); // 1) top: create wxStaticText with minimum size equal to its default size topsizer->Add( new wxStaticText( p, wxID_ANY, "An explanation (wxALIGN_RIGHT)." ), wxSizerFlags().Align(wxALIGN_RIGHT).Border(wxALL & ~wxBOTTOM, 5)); topsizer->Add( new wxStaticText( p, wxID_ANY, "An explanation (wxALIGN_LEFT)." ), wxSizerFlags().Align(wxALIGN_LEFT).Border(wxALL & ~wxBOTTOM, 5)); topsizer->Add( new wxStaticText( p, wxID_ANY, "An explanation (wxALIGN_CENTRE_HORIZONTAL)." ), wxSizerFlags().Align(wxALIGN_CENTRE_HORIZONTAL).Border(wxALL & ~wxBOTTOM, 5)); // 2) top: create wxTextCtrl with minimum size (100x60) topsizer->Add( new wxTextCtrl( p, wxID_ANY, "My text (wxEXPAND).", wxDefaultPosition, wxSize(100,60), wxTE_MULTILINE), wxSizerFlags(1).Expand().Border(wxALL, 5)); // 2.5) Gratuitous test of wxStaticBoxSizers wxBoxSizer *statsizer = new wxStaticBoxSizer( new wxStaticBox(p, wxID_ANY, "A wxStaticBoxSizer"), wxVERTICAL ); statsizer->Add( new wxStaticText(p, wxID_ANY, "And some TEXT inside it"), wxSizerFlags().Border(wxALL, 30)); topsizer->Add( statsizer, wxSizerFlags(1).Expand().Border(wxALL, 10)); // 2.7) And a test of wxGridSizer wxGridSizer *gridsizer = new wxGridSizer(2, 5, 5); gridsizer->Add(new wxStaticText(p, wxID_ANY, "Label"), wxSizerFlags().Align(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL)); gridsizer->Add(new wxTextCtrl(p, wxID_ANY, "Grid sizer demo"), wxSizerFlags(1).Align(wxGROW | wxALIGN_CENTER_VERTICAL)); gridsizer->Add(new wxStaticText(p, wxID_ANY, "Another label"), wxSizerFlags().Align(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL)); gridsizer->Add(new wxTextCtrl(p, wxID_ANY, "More text"), wxSizerFlags(1).Align(wxGROW | wxALIGN_CENTER_VERTICAL)); gridsizer->Add(new wxStaticText(p, wxID_ANY, "Final label"), wxSizerFlags().Align(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL)); gridsizer->Add(new wxTextCtrl(p, wxID_ANY, "And yet more text"), wxSizerFlags().Align(wxGROW | wxALIGN_CENTER_VERTICAL)); topsizer->Add( gridsizer, wxSizerFlags().Proportion(1).Expand().Border(wxALL, 10)); #if wxUSE_STATLINE // 3) middle: create wxStaticLine with minimum size (3x3) topsizer->Add( new wxStaticLine( p, wxID_ANY, wxDefaultPosition, wxSize(3,3), wxHORIZONTAL), wxSizerFlags().Expand()); #endif // wxUSE_STATLINE // 4) bottom: create two centred wxButtons wxBoxSizer *button_box = new wxBoxSizer( wxHORIZONTAL ); button_box->Add( new wxButton( p, wxID_ANY, "Two buttons in a box" ), wxSizerFlags().Border(wxALL, 7)); button_box->Add( new wxButton( p, wxID_ANY, "(wxCENTER)" ), wxSizerFlags().Border(wxALL, 7)); topsizer->Add(button_box, wxSizerFlags().Center()); p->SetSizer( topsizer ); // don't allow frame to get smaller than what the sizers tell it and also set // the initial size as calculated by the sizers topsizer->SetSizeHints( this ); }
最後是我的程式碼
std::shared_ptr<MyFrameDelegate> sptr_delegate; bool MyApp::OnInit() { if (!wxApp::OnInit()) return false; MyFrame* frame = new MyFrame; sptr_delegate = std::shared_ptr<MyFrameDelegate>(new MyFrameDelegate(frame)); MyFrameDelegate* delegate = sptr_delegate.get(); wxPanel* p = new wxPanel((wxWindow*)frame, wxID_ANY); wxMenuBar* mb = menu::begin(new wxMenuBar) ("&File", menu::begin(new wxMenu) (LAYOUT_TEST_PROPORTIONS, "&Proportions demo...\tF1", &MyFrame::TestMenuCommand, frame) (LAYOUT_TEST_SIZER, "Test wx&FlexSizer...\tF2", &MyFrame::TestMenuCommand, frame) (LAYOUT_TEST_NB_SIZER, "Test ¬ebook sizers...\tF3", &MyFrame::TestMenuCommand, frame) (LAYOUT_TEST_GB_SIZER, "Test &gridbag sizer...\tF4", frame, [=](wxCommandEvent& event) { frame->TestMenuCommand(event); }) (LAYOUT_TEST_SET_MINIMAL, "Test Set&ItemMinSize...\tF5") (LAYOUT_TEST_NESTED, "Test nested sizer in a wxPanel...\tF6", frame, [=](wxCommandEvent& event) { sptr_delegate->TestMenuCommand(event); })(LAYOUT_TEST_WRAP, "Test wrap sizers...\tF7") (menu::end)) ("&Help", menu::begin(new wxMenu) (LAYOUT_ABOUT, "&About", "About layout demo...") (menu::end)) ("level-tree", menu::begin(new wxMenu) (wxID_ANY, "level-1.1", menu::begin(new wxMenu) (wxID_ANY, "level-1.1-level-2.1") (wxID_ANY, "level-1.1-level-2.2") (menu::end)) (wxID_ANY, "level-1.2", menu::begin(new wxMenu) (wxID_ANY, "level-1.2-level-2.1") (wxID_ANY, "level-1.2-level-2.2") (menu::end)) (menu::end)) (menu::end, [=](wxMenuBar* mb) { frame->SetMenuBar(mb); }); layout::begin(new wxBoxSizer(wxVERTICAL)) // 1) top: create wxStaticText with minimum size equal to its default size (new wxStaticText( p, wxID_ANY, "An explanation (wxALIGN_RIGHT)." ), wxSizerFlags().Align(wxALIGN_RIGHT).Border(wxALL & ~wxBOTTOM, 5)) (new wxStaticText( p, wxID_ANY, "An explanation (wxALIGN_LEFT)." ), wxSizerFlags().Align(wxALIGN_LEFT).Border(wxALL & ~wxBOTTOM, 5)) (new wxStaticText( p, wxID_ANY, "An explanation (wxALIGN_CENTRE_HORIZONTAL)." ), wxSizerFlags().Align(wxALIGN_CENTRE_HORIZONTAL).Border(wxALL & ~wxBOTTOM, 5)) // 2) top: create wxTextCtrl with minimum size (100x60) (new wxTextCtrl( p, wxID_ANY, "My text (wxEXPAND).", wxDefaultPosition, wxSize(100,60), wxTE_MULTILINE), wxSizerFlags(1).Expand().Border(wxALL, 5)) // 2.5) Gratuitous test of wxStaticBoxSizers (layout::begin(new wxStaticBoxSizer(new wxStaticBox(p, wxID_ANY, "A wxStaticBoxSizer"), wxVERTICAL)) (new wxStaticText(p, wxID_ANY, "And some TEXT inside it"), wxSizerFlags().Border(wxALL, 30)) (layout::end), wxSizerFlags(1).Expand().Border(wxALL, 10)) // 2.7) And a test of wxGridSizer (layout::begin(new wxGridSizer(2, 5, 5)) (new wxStaticText(p, wxID_ANY, "Label"), wxSizerFlags().Align(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL)) (new wxTextCtrl(p, wxID_ANY, "Grid sizer demo"), wxSizerFlags(1).Align(wxGROW | wxALIGN_CENTER_VERTICAL)) (new wxStaticText(p, wxID_ANY, "Another label"), wxSizerFlags().Align(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL)) (new wxTextCtrl(p, wxID_ANY, "More text"), wxSizerFlags(1).Align(wxGROW | wxALIGN_CENTER_VERTICAL)) (new wxStaticText(p, wxID_ANY, "Final label"), wxSizerFlags().Align(wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL)) (new wxTextCtrl(p, wxID_ANY, "And yet more text"), wxSizerFlags().Align(wxGROW | wxALIGN_CENTER_VERTICAL)) (layout::end), wxSizerFlags().Proportion(1).Expand().Border(wxALL, 10)) #if wxUSE_STATLINE // 3) middle: create wxStaticLine with minimum size (3x3) (new wxStaticLine( p, wxID_ANY, wxDefaultPosition, wxSize(3,3), wxHORIZONTAL), wxSizerFlags().Expand()) #endif // wxUSE_STATLINE // 4) bottom: create two centred wxButtons (layout::begin(new wxBoxSizer( wxHORIZONTAL )) (new wxButton( p, wxID_ANY, "Two buttons in a box" ), wxSizerFlags().Border(wxALL, 7))[onclick = [=](wxCommandEvent& e) { delegate->OnClick(e); } ] (new wxButton( p, wxID_ANY, "(wxCENTER)" ), wxSizerFlags().Border(wxALL, 7)) (layout::end), wxSizerFlags().Center()) (layout::end, [=](wxSizer* s) { p->SetSizer(s); s->SetSizeHints(frame); }); frame->Show(true); return true; }
zwx_helper將會在//github.com/bbqz007/zhelper-wxWidgets/發布。