wxAuiNotebook是wxWidgets库中用于实现同一窗口显示多页的页面tab,这是一种很流行的页面布局方式。在wxWidgets中,可以将从wxWindow继承的页面添加到notebook中,页面上可以再放多个控件。另外,因为控件本身也从wxWindow继承,所以也可以将单个的一个控件添加到notebook中。
notebook的分页可以支持页面拖放,即拖动页面的位置。但要支持拖动需要在创建notebook时指定style。有两个支持拖动的style:
wxAUI_NB_TAB_MOVE:支持将页面在同一个notebook从一个位置拖动到另一个位置。
wxAUI_NB_TAB_EXTERNAL_MOVE:支持将页面从一个notebook拖动到另一个notebook。
下面的代码片断创建了两个notebook,每个notebook上添加两个text控件,即每个notebook有两个分页。
//wxAuiNotebook的style。 long noteStyle = wxAUI_NB_TOP | wxAUI_NB_TAB_SPLIT | wxAUI_NB_TAB_MOVE | wxAUI_NB_SCROLL_BUTTONS | wxAUI_NB_CLOSE_ON_ACTIVE_TAB | wxAUI_NB_WINDOWLIST_BUTTON | wxAUI_NB_TAB_EXTERNAL_MOVE; //创建两个notebook wxAuiNotebook *pNoteBook1 = new wxAuiNotebook(this, idNotebook1, wxDefaultPosition, wxSize(200,200), noteStyle); wxAuiNotebook *pNoteBook2 = new wxAuiNotebook(this, idNotebook2, wxDefaultPosition, wxSize(100,100), noteStyle); //创建四个控件 wxTextCtrl *pText1 = new wxTextCtrl(this, wxID_ANY); wxTextCtrl *pText2 = new wxTextCtrl(this, wxID_ANY); wxTextCtrl *pText3 = new wxTextCtrl(this, wxID_ANY); wxTextCtrl *pText4 = new wxTextCtrl(this, wxID_ANY); //将四个控件分别添加到两个notebook上 pNoteBook1->AddPage(pText1, "text1"); pNoteBook2->AddPage(pText2, "text2"); pNoteBook1->AddPage(pText3, "text3"); pNoteBook2->AddPage(pText4, "text4"); //将两个notebook分别停靠在左边和下面 m_Mgr.AddPane(pNoteBook1, wxLEFT, "Pane Caption"); m_Mgr.AddPane(pNoteBook2, wxBOTTOM, "Pane Caption"); //更新窗口布局管理器 m_Mgr.Update();
创建完后,试了一下拖动功能。在同一个notebook拖动页面是没问题了。但是当将页面拖动到另一个notebook上时却发现不行。原本以为是个bug,后来看了下wxWidgets源代码关于wxAUI_NB_TAB_EXTERNAL_MOVE样式的控制,才发现另有玄机。
看看wxWidgets处理拖动的代码:
void wxAuiNotebook::OnTabEndDrag(wxAuiNotebookEvent& evt) { m_mgr.HideHint(); wxAuiTabCtrl* src_tabs = (wxAuiTabCtrl*)evt.GetEventObject(); wxCHECK_RET( src_tabs, _T("no source object?") ); src_tabs->SetCursor(wxCursor(wxCURSOR_ARROW)); // get the mouse position, which will be used to determine the drop point wxPoint mouse_screen_pt = ::wxGetMousePosition(); wxPoint mouse_client_pt = ScreenToClient(mouse_screen_pt); // check for an external move if (m_flags & wxAUI_NB_TAB_EXTERNAL_MOVE) { wxWindow* tab_ctrl = ::wxFindWindowAtPoint(mouse_screen_pt); while (tab_ctrl) { if (tab_ctrl->IsKindOf(CLASSINFO(wxAuiTabCtrl))) break; tab_ctrl = tab_ctrl->GetParent(); } if (tab_ctrl) { wxAuiNotebook* nb = (wxAuiNotebook*)tab_ctrl->GetParent(); if (nb != this) { // find out from the destination control // if it's ok to drop this tab here wxAuiNotebookEvent e(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, m_windowId); e.SetSelection(evt.GetSelection()); e.SetOldSelection(evt.GetSelection()); e.SetEventObject(this); e.SetDragSource(this); e.Veto(); // dropping must be explicitly approved by control owner nb->GetEventHandler()->ProcessEvent(e); if (!e.IsAllowed()) { // no answer or negative answer m_mgr.HideHint(); return; } // drop was allowed int src_idx = evt.GetSelection(); wxWindow* src_page = src_tabs->GetWindowFromIdx(src_idx); // Check that it's not an impossible parent relationship wxWindow* p = nb; while (p && !p->IsTopLevel()) { if (p == src_page) { return; } p = p->GetParent(); } // get main index of the page int main_idx = m_tabs.GetIdxFromWindow(src_page); wxCHECK_RET( main_idx != wxNOT_FOUND, _T("no source page?") ); // make a copy of the page info wxAuiNotebookPage page_info = m_tabs.GetPage(main_idx); // remove the page from the source notebook RemovePage(main_idx); // reparent the page src_page->Reparent(nb); // found out the insert idx wxAuiTabCtrl* dest_tabs = (wxAuiTabCtrl*)tab_ctrl; wxPoint pt = dest_tabs->ScreenToClient(mouse_screen_pt); wxWindow* target = NULL; int insert_idx = -1; dest_tabs->TabHitTest(pt.x, pt.y, &target); if (target) { insert_idx = dest_tabs->GetIdxFromWindow(target); } // add the page to the new notebook if (insert_idx == -1) insert_idx = dest_tabs->GetPageCount(); dest_tabs->InsertPage(page_info.window, page_info, insert_idx); nb->m_tabs.AddPage(page_info.window, page_info); nb->DoSizing(); dest_tabs->DoShowHide(); dest_tabs->Refresh(); // set the selection in the destination tab control nb->SetSelectionToPage(page_info); // notify owner that the tab has been dragged wxAuiNotebookEvent e2(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, m_windowId); e2.SetSelection(evt.GetSelection()); e2.SetOldSelection(evt.GetSelection()); e2.SetEventObject(this); GetEventHandler()->ProcessEvent(e2); return; } } } //。。。 }
原来wxWidgets对于拖动到另一个notebook的情况并不直接放进去,而是先发一个wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND事件给目标notebook,问一下是否允许放进去,
if (!e.IsAllowed()) //wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND是否被allowed
{
// no answer or negative answer
m_mgr.HideHint();
return; //不allowed,直接return,当然就没法拖放了。
}
看了这个之后就明白了,那我们只要处理目标notebook的wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND事件就可以了。下面就添加notebook的事件处理:
BEGIN_EVENT_TABLE(wxNote1Frame, wxFrame) EVT_AUINOTEBOOK_ALLOW_DND(idNotebook1, wxNote1Frame::AllowDND) EVT_AUINOTEBOOK_ALLOW_DND(idNotebook2, wxNote1Frame::AllowDND) END_EVENT_TABLE() void wxNote1Frame::AllowDND(wxAuiNotebookEvent& event) { //EVT_AUINOTEBOOK_ALLOW_DND处理,允许拖放 event.Allow(); }
编译完,再跑一下,果然可以拖动到外部了。
wxWidgets确实是个挺好用的界面库,类体系和MFC差不多,但却封装了很多MFC没有的控件,省去自己再扩展MFC的麻烦。只是文档还是不够全面。像这个wxAUI_NB_TAB_EXTERNAL_MOVE样式支持外部拖放需要自己再处理事件的都没写清楚。看来要推广开来还需要努力啊。