wxWidgets源码分析(6) - 窗口关闭过程

目录

  • 窗口关闭过程

    • 调用流程
    • 关闭文档
    • 删除视图
    • 删除文档对象
    • 关闭Frame
    • App清理
    • 多文档窗口的关闭
    • 窗口的正式删除
    • 窗口关闭过程总结
    • 如何手工删除view

窗口关闭过程

调用流程

用户点击窗口的关闭按钮后,Win32系统会向当前的Frame对象发送WM_CLOSE消息,此时会进入到Frame的wxFrame::MSWWindowProc函数进行处理:

WXLRESULT wxFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
{
    switch ( message )
    {
        case WM_CLOSE:
            // if we can‘t close, tell the system that we processed the
            // message - otherwise it would close us
            processed = !Close();
            break;
...

Close方法是由wxWindowBase类提供的,调用过程如下:

  1. 创建一个wxEVT_CLOSE_WINDOW消息,传递当前的windowID
  2. 调用当前对象的消息处理函数进行处理。
bool wxWindowBase::Close(bool force)
{
    wxCloseEvent event(wxEVT_CLOSE_WINDOW, m_windowId);
    event.SetEventObject(this);
    event.SetCanVeto(!force);

    // return false if window wasn‘t closed because the application vetoed the
    // close event
    return HandleWindowEvent(event) && !event.GetVeto();
}

继续跟踪wxEVT_CLOSE_WINDOW消息的处理,前面我们有介绍单文档的Frame窗口的继承关系如下,我们可以根据此继承关系找消息处理函数:

wxDocParentFrame -> wxDocParentFrameBase (wxDocParentFrameAny < wxFrame > ) --
    wxFrame & wxDocParentFrameAnyBase

我们可以找到wxDocParentFrameAny模板类中的Create方法中注册了wxEVT_CLOSE_WINDOW消息的处理函数是wxDocParentFrameAny<>::OnCloseWindow

// wxDocParentFrameAny < >
bool Create(wxDocManager *manager, ...)
{
    m_docManager = manager;
    if ( !BaseFrame::Create(frame, id, title, pos, size, style, name) )
        return false;

    this->Connect(wxID_EXIT, wxEVT_MENU,
                  wxCommandEventHandler(wxDocParentFrameAny::OnExit));
    this->Connect(wxEVT_CLOSE_WINDOW,
                  wxCloseEventHandler(wxDocParentFrameAny::OnCloseWindow));
    return true;
}

wxDocParentFrameAny<>::OnCloseWindow会调用m_docManager的Clear方法来实现关闭,只要是MVC类,则必然存在文档管理类。

// wxDocParentFrameAny < >
void OnCloseWindow(wxCloseEvent& event)
{
    if ( m_docManager && !m_docManager->Clear(!event.CanVeto()) )
    {
        // The user decided not to close finally, abort.
        event.Veto();
    }
    else
    {
        // Just skip the event, base class handler will destroy the window.
        event.Skip();
    }
}

接着我们看下文档管理类的wxDocManager::Clear方法实现:

  1. 调用CloseDocuments关闭所有文档,这步很关键,关闭文档的关键路径;
  2. 由于wxDocManager管理多个文档模板,此时需要将所有的文档模板对象全部删除。
bool wxDocManager::Clear(bool force)
{
    if (!CloseDocuments(force))
        return false;

    m_currentView = NULL;

    wxList::compatibility_iterator node = m_templates.GetFirst();
    while (node)
    {
        wxDocTemplate *templ = (wxDocTemplate*) node->GetData();
        wxList::compatibility_iterator next = node->GetNext();
        delete templ;
        node = next;
    }
    return true;
}

跟踪关键路径wxDocManager::CloseDocuments,这里的操作步骤就是找到此文档管理器管理的所有文档,然后调用CloseDocument方法关闭文档:

bool wxDocManager::CloseDocuments(bool force)
{
    wxList::compatibility_iterator node = m_docs.GetFirst();
    while (node)
    {
        wxDocument *doc = (wxDocument *)node->GetData();
        wxList::compatibility_iterator next = node->GetNext();

        if (!CloseDocument(doc, force))
            return false;
        node = next;
    }
    return true;
}

执行关闭文档操作,几个步骤:

  1. 调用doc类的Close方法执行关闭;
  2. 调用doc类的DeleteAllViews方法关闭此文档关联的View;
  3. delete文档对象
bool wxDocManager::CloseDocument(wxDocument* doc, bool force)
{
    if ( !doc->Close() && !force )
        return false;

    doc->DeleteAllViews();

    if (m_docs.Member(doc))
        delete doc;
    return true;
}

关闭文档

wxDocManager调用文档类的wxDocument::Close方法执行关闭,这个方法的默认实现是关闭此文档类关联的所有子文档对象,然后调用用户可以实现的OnCloseDocument方法。

OnCloseDocument的默认实现仅仅是进行清理而已。

bool wxDocument::Close()
{
    if ( !OnSaveModified() )
        return false;

    DocsList::const_iterator it = m_childDocuments.begin();
    for ( DocsList::const_iterator end = m_childDocuments.end(); it != end; ++it )
    {
        if ( !(*it)->OnSaveModified() )
        {
            return false;
        }
    }

    while ( !m_childDocuments.empty() )
    {
        wxDocument * const childDoc = m_childDocuments.front();
        if ( !childDoc->Close() )
        {
            wxFAIL_MSG( "Closing the child document unexpectedly failed "
                        "after its OnSaveModified() returned true" );
        }
        childDoc->DeleteAllViews();
    }

    return OnCloseDocument();
}

bool wxDocument::OnCloseDocument()
{
    // Tell all views that we‘re about to close
    NotifyClosing();
    DeleteContents();
    Modify(false);
    return true;
}

删除视图

调用wxDocument::DeleteAllViews删除此文档关联的所有视图,对于一个View来说有两个操作步骤:

  1. 调用wxView::Close方法关闭view;
  2. 调用delete删除view对象。
  3. 当存在多个View时,逐个调用删除
bool wxDocument::DeleteAllViews()
{
    wxDocManager* manager = GetDocumentManager();

    // first check if all views agree to be closed
    const wxList::iterator end = m_documentViews.end();
    for ( wxList::iterator i = m_documentViews.begin(); i != end; ++i )
    {
        wxView *view = (wxView *)*i;
        if ( !view->Close() )
            return false;
    }

    // all views agreed to close, now do close them
    if ( m_documentViews.empty() )
    {
        if ( manager && manager->GetDocuments().Member(this) )
            delete this;
    }
    else // have views
    {
        for ( ;; )
        {
            wxView *view = (wxView *)*m_documentViews.begin();
            bool isLastOne = m_documentViews.size() == 1;
            delete view;

            if ( isLastOne )
                break;
        }
    }

    return true;
}

View类的Close方法比较简单,就是调用用户的OnClose方法,当然OnClose也有默认实现,wxView::OnClose的默认实现就是调用wxDocument::Close,前文已经描述过,不赘述:

bool wxView::Close(bool deleteWindow)
{
    return OnClose(deleteWindow);
}
bool wxView::OnClose(bool WXUNUSED(deleteWindow))
{
    return GetDocument() ? GetDocument()->Close() : true;
}

我们还需关注delete view;这行语句,这行实现了view与doc类的脱离关系,如下,最终调用m_viewDocument->RemoveView实现了将自己从doc中移除:

wxView::~wxView()
{
    if (m_viewDocument && GetDocumentManager())
        GetDocumentManager()->ActivateView(this, false);

    if ( m_docChildFrame && m_docChildFrame->GetView() == this )
    {
        m_docChildFrame->SetView(NULL);
        m_docChildFrame->GetWindow()->Destroy();
    }

    if ( m_viewDocument )
        m_viewDocument->RemoveView(this);
}

删除文档对象

跟踪wxDocument::RemoveView,可以看到此时会从doc的m_documentViews列表中删除此view,然后再调用OnChangedViewList进行后处理,OnChangedViewList检查到当前doc不关联任何view的时候,会发起自己的删除delete this

bool wxDocument::RemoveView(wxView *view)
{
    (void)m_documentViews.DeleteObject(view);
    OnChangedViewList();
    return true;
}

void wxDocument::OnChangedViewList()
{
    if ( m_documentViews.empty() && OnSaveModified() )
        delete this;
}

关闭Frame

在前文中我们看到Doc对象的关闭是动态注册的处理函数,由于最后在wxDocParentFrameAny<>::OnCloseWindow方法中调用event.Skip(),这样这个消息还会继续传递处理,接下来继续查找静态消息表,我们可以看到在wxTopLevelWindowBase中有如下定义:

BEGIN_EVENT_TABLE(wxTopLevelWindowBase, wxWindow)
    EVT_CLOSE(wxTopLevelWindowBase::OnCloseWindow)
    EVT_SIZE(wxTopLevelWindowBase::OnSize)
END_EVENT_TABLE()

那么此时会继续静态消息处理表的处理,对应的处理入口消息为wxTopLevelWindowBase::OnCloseWindow

  1. 将自己加入到wxPendingDelete队列中,这样下次IDLE的时候会一并删除;
  2. 隐藏待删除的窗口;
void wxTopLevelWindowBase::OnCloseWindow(wxCloseEvent& WXUNUSED(event))
{
    Destroy();
}

bool wxTopLevelWindowBase::Destroy()
{
    if ( wxWindow* parent = GetParent() )
    {
        if ( parent->IsBeingDeleted() )
            return wxNonOwnedWindow::Destroy();
    }

    if ( !wxPendingDelete.Member(this) )
        wxPendingDelete.Append(this);

    for ( wxWindowList::const_iterator i = wxTopLevelWindows.begin(),
                                     end = wxTopLevelWindows.end();
          i != end;
          ++i )
    {
        wxTopLevelWindow * const win = static_cast<wxTopLevelWindow *>(*i);
        if ( win != this && win->IsShown() )
        {
            Hide();
            break;
        }
    }
    return true;
}

App清理

再回过头来,我们看看APP类的清理,APP类的启动过程可以参考启动代码部分,这里回到调用点wxEntryReal,程序执行完成后会从wxTheApp->OnRun()中返回,也就是退出wxTRY的作用域,我们来重点看下CallOnExit的作用:

在定义CallOnExit类的时候一起创建了一个对象callOnExit,此对象位于栈上,那么退出wxTRY的作用域时必然会调用到此对象的析构函数,参考定义,我们看到析构函数最终调用的是wxTheApp->OnExit()

这样通过临时对象的析构函数调用了APP类的OnExitfangf

int wxEntryReal(int& argc, wxChar **argv)
{
    wxInitializer initializer(argc, argv);
    if ( !initializer.IsOk() )
    {
        return -1;
    }

    wxTRY
    {
        if ( !wxTheApp->CallOnInit() )
        {
            return -1;
        }

        // ensure that OnExit() is called if OnInit() had succeeded
        class CallOnExit
        {
        public:
            ~CallOnExit() { wxTheApp->OnExit(); }
        } callOnExit;

        WX_SUPPRESS_UNUSED_WARN(callOnExit);
        return wxTheApp->OnRun();
    }
    wxCATCH_ALL( wxTheApp->OnUnhandledException(); return -1; )
}

多文档窗口的关闭

多文档父窗口关闭

多文档窗口的父窗口用于容纳一个或者多个子窗口,关闭过程同wxFrame的正常关闭过程。

如何通知到子窗口?注意看Destroy。。。

多文档子窗口关闭

一般使用中,我们会独立关闭多文档APP的子窗口,操作过程是用户点击了子窗口Frame的关闭按钮,消息的产生过程与单文档窗口类似,我们可以找到wxDocChildFrameAny<>类中Create函数中创建了对wxEVT_CLOSE_WINDOW消息的动态绑定wxDocChildFrameAny::OnCloseWindow

bool Create(wxDocument *doc, ...)
{
    if ( !wxDocChildFrameAnyBase::Create(doc, view, this) )
        return false;

    if ( !BaseClass::Create(parent, id, title, pos, size, style, name) )
        return false;

    this->Connect(wxEVT_ACTIVATE,
                  wxActivateEventHandler(wxDocChildFrameAny::OnActivate));
    this->Connect(wxEVT_CLOSE_WINDOW,
                  wxCloseEventHandler(wxDocChildFrameAny::OnCloseWindow));

    return true;
}

OnCloseWindow方法的调用比较简单,我们逐个看实现:

// wxDocChildFrameAny < >
void OnCloseWindow(wxCloseEvent& event)
{
    if ( CloseView(event) )
        Destroy();
    //else: vetoed
}

wxDocChildFrameAnyBase::CloseView用于关闭view,会调用View的Close方法,前文我们知道View在关闭的时候会再次调用doc的Close方法,这样就可以保证视图和文档对象都关闭了。

关闭完成后执行删除视图就可以了,在删除视图的析构函数中会再次调用文档的RemoveView,当文档对象检查到自己没有关联的视图对象时,文档会删除自己。

到此,视图和文档就已经删除了。

bool wxDocChildFrameAnyBase::CloseView(wxCloseEvent& event)
{
    if ( m_childView )
    {
        if ( !m_childView->Close(false) && event.CanVeto() )
        {
            event.Veto();
            return false;
        }

        m_childView->Activate(false);
        m_childView->SetDocChildFrame(NULL);
        wxDELETE(m_childView);
    }

    m_childDocument = NULL;

    return true;
}

继续我们看下wxDocChildFrameAny<>::Destroy方法,这个方法会调用父类实现的Destroy,调用Destroy方法后就会销毁窗口,同普通窗口销毁。

// wxDocChildFrameAny < >
virtual bool Destroy()
{
    // FIXME: why exactly do we do this? to avoid activation events during
    //        destructions maybe?
    m_childView = NULL;
    return BaseClass::Destroy();
}

我们再关注下子窗口是何时与父窗口脱离关系的呢?找到wxMDIChildFrame的析构函数:

  1. 调用GetMDIParent()->RemoveMDIChild方法恢复父类原来的菜单;
  2. 调用MSWDestroyWindow将自己与父类窗口脱离关系
wxMDIChildFrame::~wxMDIChildFrame()
{
    // if we hadn‘t been created, there is nothing to destroy
    if ( !m_hWnd )
        return;

    GetMDIParent()->RemoveMDIChild(this);

    m_frameToolBar = NULL;
    m_frameStatusBar = NULL;

    DestroyChildren();
    MDIRemoveWindowMenu(NULL, m_hMenu);
    MSWDestroyWindow();
}

这样子窗口删除后,父窗口会继续存活。

窗口的正式删除

App处于空闲状态时会调用wxAppConsoleBase::ProcessIdle方法,这个方法进而调用DeletePendingObjects用于清理待删除的窗口对象。

操作过程就是从wxPendingDelete中取得所有的待删除的对象,delete掉。

bool wxAppConsoleBase::ProcessIdle()
{
    // synthesize an idle event and check if more of them are needed
    wxIdleEvent event;
    event.SetEventObject(this);
    ProcessEvent(event);

// Garbage collect all objects previously scheduled for destruction.
    DeletePendingObjects();
    return event.MoreRequested();
}

void wxAppConsoleBase::DeletePendingObjects()
{
    wxList::compatibility_iterator node = wxPendingDelete.GetFirst();
    while (node)
    {
        wxObject *obj = node->GetData();
        if ( wxPendingDelete.Member(obj) )
            wxPendingDelete.Erase(node);

        delete obj;
        node = wxPendingDelete.GetFirst();
    }
}

窗口关闭过程总结

下面对关闭时用户可以捕捉的时机做个总结,用户可以通过下面的方法来截获此消息。

类型 用户重载的方法
wxApp virtual int OnExit ()
wxDocument virtual bool OnCloseDocument ()
wxView virtual bool OnClose (bool deleteWindow)
wxFrame virtual bool Destroy () // 重载时必须要调用父类的方法

如何手工删除view

从上面的分析,我们看到删除view之前,我们需要先调用wxView::Close方法,此时会调用用户的

OnClose方法,然后再delete掉。

void deleteView(wxView *view)
{
    view->Close();
    delete view
}

原文地址:https://www.cnblogs.com/psbec/p/9784635.html

时间: 2024-11-03 04:02:05

wxWidgets源码分析(6) - 窗口关闭过程的相关文章

wxWidgets源码分析(5) - 窗口管理

窗口管理 所有的窗口均继承自wxTopLevelWindows: WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows; wxTopLevelWindows Frame窗口创建过程 在使用Frame窗口的时候,我们一般从wxFrame继承,创建时通过调用Create方法进行创建: debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id) { ... Create(parent, id

wxWidgets源码分析(7) - 窗口尺寸

目录 窗口尺寸 概述 窗口Size消息的处理 用户调整Size消息的处理 调整窗口大小 程序调整窗口大小 wxScrolledWindow设置窗口大小 获取TextCtrl控件最合适大小 窗口尺寸 概述 类型 说明 Size 当前窗口实际大小,通过wxWindow::SetSize()获取: Client Size 客户区大小: Best Size 最合适的大小,用户可以实现wxWindow::DoGetBestSize()方法,自定义返回最合适的大小: Best Client Size 最合适

wxWidgets源码分析(1) - App启动过程

目录 APP启动过程 wxApp入口定义 wxApp实例化准备 wxApp的实例化 wxApp运行 总结 APP启动过程 本文主要介绍wxWidgets应用程序的启动过程,从app.cpp入手. wxApp入口定义 wxApp通过IMPLEMENT_APP宏注册App类,这个宏同时定义了入口,实现在wx/app.h文件中. // wx/app.h 文件中定义 #define IMPLEMENT_APP(app) wxIMPLEMENT_APP(app); // 可以忽略 wxIMPLEMENT_

netty 5 alph1源码分析(服务端创建过程)

参照<Netty系列之Netty 服务端创建>,研究了netty的服务端创建过程.至于netty的优势,可以参照网络其他文章.<Netty系列之Netty 服务端创建>是 李林锋撰写的netty源码分析的一篇好文,绝对是技术干货.但抛开技术来说,也存在一些瑕疵. 缺点如下 代码衔接不连贯,上下不连贯. 代码片段是截图,对阅读代理不便(可能和阅读习惯有关) 本篇主要内容,参照<Netty系列之Netty 服务端创建>,梳理出自己喜欢的阅读风格. 1.整体逻辑图 整体将服务

wxWidgets源码分析(4) - 消息处理过程

目录 消息处理过程 消息如何到达wxWidgets Win32消息与wxWidgets消息的转换 菜单消息处理 消息处理链(基于wxEvtHandler) 消息处理链(基于wxWindow) 总结 消息处理过程 消息如何到达wxWidgets Windows程序有其自身运行的一套规律,::SendMessage是MS提供的windows消息发送接口,用户调用这个接口后会进入到MS系统库程序,此接口指定了目标HWND和消息参数,Windows系统内部会查找指定HWND,然后通过gapfnScSen

wxWidgets源码分析(8) - MVC架构

目录 MVC架构 wxDocManager文档管理器 框架菜单命令的执行过程 SDI消息传递总结 更新视图 MVC架构 wxDocManager文档管理器 wxWidgets使用wxDocManager类来管理MVC中的文档和视图的对应关系,使用方法: 创建一个wxDocManager对象,然后向此对象中增加文档模板wxDocTemplate对象,文档模板对象中说明了文档类型和该文档对应的文档类.视图类: 将此wxDocManager对象传递给wxDocParentFrame类(SDI),这样框

wxWidgets源码分析(9) - wxString

目录 wxString wxString的中文字符支持 wxString与通用字符串的转换 字符集转换 wxString wxString的中文字符支持 中文字符的编码格式如下: 汉字 GBK 区位码 UTF-8 UTF-16 中 D6 D0 54 48 E4 B8 AD 4E 2D 文 CE C4 46 36 E6 96 87 65 87 不同操作系统的默认内码 Windows系统(默认GBK):41 42 d6 d0 ce c4 Linux系统(默认UTF-8):41 42 e4 b8 ad

Netty源码分析之客户端启动过程

一.先来看一下客户端示例代码. 1 public class NettyClientTest { 2 public void connect(int port, String host) throws Exception { 3 EventLoopGroup group = new NioEventLoopGroup();//与服务端不同,客户端只需要一个IO线程组 4 5 try { 6 Bootstrap b = new Bootstrap(); 7 b.group(group) 8 .op

f2fs源码分析之文件读写过程

本篇包括三个部分:1)f2fs 文件表示方法: 2)NAT详细介绍:3)f2fs文件读写过程:4) 下面详细阐述f2fs读写的过程. 管理数据位置关键的数据结构是node,node包括三种:inode.直接node.间接node.其中inode记录了文件的基本信息,包括访问权限.文件大小.修改时间等,也有索引的功能:直接node和间接node单纯负责索引.F2fs的inode中有923个直接数据块索引,2个一级索引,2个二级索引,1个三级索引,文件的逻辑表示如下图: inode中有923个索引项