MFC DestroyWindow、OnDestroy、OnClose 程序關閉相關


一、DestroyWindow:

The MFC framework manages window destruction as well as creation for those windows associated with framework documents and views. If you create additional windows, you are responsible for destroying them.

In the framework, when the user closes the frame window, the window's default OnClose handler calls DestroyWindow. The last member function called when the Windows window is destroyed is OnNcDestroy, which does some cleanup, calls the Default member function
to perform Windows cleanup, and lastly calls the virtual member function PostNcDestroy. The CFrameWnd implementation ofPostNcDestroy deletes the C++ window object. You should never use the C++delete operator on a frame window.
Use DestroyWindow instead.

When the main window closes, the application closes. If there are modified unsaved documents, the framework displays a message box to ask if the documents should be saved and ensures that the appropriate documents are saved if necessary.

MFC框架管理窗口破壞以及創建那些窗口與框架相關的文檔和視圖。如果您創建新的窗口,你負責摧毀它們。
在框架中,當用戶關閉框架窗口,窗口的默認OnClose處理 程序調用DestroyWindow。當窗口被摧毀,成員函數OnNcDestroy 被調用,OnNcDestroy做一些清理,調用默認的成員函數來執行清理,最后調用虛函數PostNcDestroy。框架用PostNcDestroy實現刪除C
+ +窗口對象。你不應該使用C + + delete操作符在框架窗口。使DestroyWindow代替。
當主窗口關閉,應用程序關閉。如果有修改還未保存的文檔,這個框架將顯示一個消息框詢問是否應該保存文件並確保適當的文檔保存如果必要的話。

總之:函數的功能就是銷毀指定的窗口。通過發送WM_DESTROY 消息和 WM_NCDESTROY 消息使窗口無效並移除其鍵盤焦點。這個函數還銷毀窗口的菜單,清空線程的消息隊列,銷毀與窗口過程相關的定時器,解除窗口對剪貼板的擁有權,打斷剪貼板器的查看鏈。

原型:

BOOL DestroyWindow(HWND hWnd);

返回值:

如果函數成功,返回值為非零:如果函數失敗,返回值為零。若想獲得更多錯誤信息,請調用GetLastError函數。

備注說明:

一個線程不能使用本函數銷毀別的線程創建的窗口

如果這個窗口是一個不具有WS_EX_NOPARENTNOTIFY 樣式的子窗口,則銷毀窗口時將發WM_PARENTNOTIFY 消息給其父窗口。

===========================================================================================

二、CWnd::DestroyWindow

MSDN:

            Destroys the Windows window attached to the CWnd object.

         銷毀Windows窗口附加到 CWnd 對象。

原型:

           
virtual BOOL DestroyWindow( );

返回值:

             非零,則銷毀窗口;否則為0

備注說明:

            1、DestroyWindow 成員函數將相應信息到窗口停用並移除輸入焦點。 如果 CWnd 是在瀏覽器鏈的頂部,它銷毀windows的菜單中,對於應用程序隊列,銷毀處理計時器,還移除剪貼板所有權,以及中斷剪貼板瀏覽器鏈。 其發送 WM_DESTROY 和 WM_NCDESTROY 信息到窗口。 但不銷毀 CWnd 對象。

            2、DestroyWindow 是進行清理一個位置容納器。 由於 DestroyWindow 是虛函數,在所有 CWnd-在選件類視圖的派生類公開。 但,即使您重寫在您的 CWnd的此功能的派生類,DestroyWindow 不一定調用。 如果 DestroyWindow 在MFC代碼未調用,則必須顯式調用它在代碼中,如果您希望此調用。

           3、假定,例如,重寫了 CView的 DestroyWindow 派生類。 因為MFC源代碼不對 DestroyWindow 其任何 CFrameWnd派生類,重寫的 DestroyWindow 不會調用,除非您顯式調用它。

         4、如果窗口是任何窗口的父級,自動銷毀。這些子窗口,當銷毀時父窗口。 DestroyWindow 成員函數首先然后銷毀子窗口。

         5、DestroyWindow 成員函數來銷毀 CDialog::Create創建的無模式對話框。

         6、如果銷毀的 CWnd 是子窗口,並沒有 WS_EX_NOPARENTNOTIFY 樣式設置,則 WM_PARENTNOTIFY 發送到父級。

三、CWnd::OnClose    

WM_CLOSE  的響應函數

afx_msg void OnClose( );

說明:

The default implementation calls DestroyWindow.

默認實現調用 DestroyWindow

四、CWnd::OnDestroy  框架調用該成員函數通知 CWnd 對象銷毀它。

WM_DESTROY 的響應函數

 

afx_msg void OnDestroy( );

說明:

OnDestroy is called after the CWnd object is removed from the screen.

OnDestroy is called first for the CWnd being destroyed, then for the child windows ofCWnd as they are destroyed. It can be assumed that all child windows still exist whileOnDestroy runs.

If the CWnd object being destroyed is part of the Clipboard-viewer chain (set by calling the SetClipboardViewer member function), theCWnd must remove itself from the Clipboard-viewer chain by calling the ChangeClipboardChain
member function before returning from theOnDestroy function.

OnDestroy在窗口對象離開屏幕后調用

當銷毀時,OnDestroy 首先調用為銷毀的 CWnd,然后為子窗口 CWnd 它們。 可以假定,所有子窗口仍存在,則 OnDestroy 運行時。

如果銷毀的 CWnd 對象是剪貼板瀏覽器鏈的一部分(設置通過調用 SetClipboardViewer 成員函數),CWnd 必須從剪貼板瀏覽器鏈中移除其自身通過調用 ChangeClipboardChain 成員函數中返回從 OnDestroy 功能之前。

=============================================================================================

下面介紹下三個關閉消息:
WM_CLOSE:
  在系統菜單里選擇了“關閉”或者點擊了窗口右上角的“X”按鈕,你的窗口過程就會收到WM_CLOSE。DefWindowProc對WM_CLOSE的處理是調用DestroyWindow。當然,你可以不讓DefWindowProc處理,而是自己處理,例如詢問用戶是否保存更改等。如果用戶選擇“取消”,
你忽略此消息,那么程序照常運行;如果用戶確認要退出,你就調用DestroyWindow。
WM_DESTROY:
  接下來,DestroyWindow完成窗口的清理工作,最后像窗口過程發送WM_DESTROY。對於 WM_DESTROY,DefWindowProc不會處理。也就是說,你如果不處理這個消息,雖然你的窗口已經銷毀,但進程並不會結束。一般處理 WM_DESTROY時都是釋放資源(例如申請的內存等),
然后調用PostQuitMessage。
WM_QUIT:
  PostQuitMessage會發送WM_QUIT給消息隊列。注意,WM_QUIT永遠不會到達窗口過程,因為GetMessage得到WM_QUIT后就會返回FALSE,從而結束消息循環,最后進程結束,程序退出。
   假設使用者執行HELLOWIN,並且使用者最終單擊了 Close按鈕,或者假設用鍵盤或鼠標從系統菜單中選擇了Close,DefWindowProc處理這一鍵盤或者鼠標輸入,在檢測到使用者選擇了Close選項之后,它給窗口消息處理程序發送一條WM_SYSCOMMAND消息。WndProc將這個消息
傳給DefWindowProc。 DefWindowProc給窗口消息處理程序發送一條WM_CLOSE消息來響應之。WndProc再次將它傳給DefWindowProc。 DestroyWindow呼叫DestroyWindow來響應這條WM_CLOSE消息。DestroyWindow導致Windows給窗口消息處理程序發送一條WM_DESTROY消息。WndProc再呼叫PostQuitMessage,將一條WM_QUIT消息放入消息隊列中,以此來響應此消息。這個消息導致WinMain中的消息循環終止,然后程序結束。

言而簡之:

(1)用戶點擊退出按鈕,發送了WM_CLOSE消息
(2)在WM_CLOSE消息的處理函數中,調用DestroyWindow()
(3)在DestroyWindow()中發送了WM_DESTROY消息
(4)在WM_DESTROY消息中調用PostQuitMessage(),發送WM_QUIT消息,結束消息循環

綜上,程序先調用OnClose()(也可能不調用),然后調用OnDestroy()(必調用),

所以,如果要進行程序結束時的清理工作,應該在OnDestroy()中,而不是在OnClose(),否則就有可能會出現內存泄漏的危險了!

 

=============================================================================================

五、WM_QUIT  

The WM_QUIT message indicates a request to terminate an application and is generated when the application calls thePostQuitMessage function. It causes theGetMessage
function to return zero.

wParam

        Specifies the exit code given in the PostQuitMessage function.

lParam

       This parameter is not used.

Return Value

        This message does not have a return value because it causes the message loop to terminate before the message is sent to the application's window procedure.

        這個消息沒有返回值,因為它使消息循環終止在消息被發送到應用程序的窗口程序

注意事項:

         The WM_QUIT message is not associated with a window and therefore will never be received through a window's window procedure. It is retrieved only by theGetMessage orPeekMessage
functions.

         Do not post the WM_QUIT message using the PostMessage function; usePostQuitMessage.

六、總結,比較異同

1、對話框中  點“確定”、“取消”時的關閉路由為 
      OnOK()或OnCancel() ---> EndDialog() ---> DestroyWindow() ---> OnDestroy() ---> PostNcDestroy()
                       點“關閉”標題欄按鈕的關閉路由為 
      OnClose()---> DestroyWindow() ---> OnDestroy() ---> PostNcDestroy()

      所以OnClose()並不是關閉路由的必經路徑,OnDestroy() 才是程序關閉的必經路徑,因此重寫OnDestroy() ,把我需要在程序結束的操作全部放到了這個函數里面。

 2、在 OnDestroy 中調用 PostQuitMessage() 發送 WM_QUIT 消息,結束消息循環,結束程序。

 3、DestroyWindow  在運用過程分析

      1、【單窗口】通過new創建了一個窗口對象pWnd,然后pWnd->Create。則銷毀窗口的調用次序

            1.1、手動調用  pWnd->DestroyWindow(),DestroyWindow會發送WM_DESTROY

            1.2、WM_DESTROY對應的消息處理函數是OnDestroy()

            1.3、DestroyWindow會發送WM_NCDESTROY,WM_NCDESTROY對應的消息處理函數是OnNcDestroy

            1.4、OnNcDestroy最后會調用PostNcDestroy-------------------------------可以在 \src\WINCORE.CPP中 查看源碼
             通過這種方式,窗口對象對應的窗口和窗口對象本身都被釋放了

       2、【含子窗口】則調用父窗口的DestroyWindow時,它會向子窗口發送WM_DESTROY和WM_NCDESTROY消息。

 PostNcDestroy:源碼

  1.  
    void CWnd::PostNcDestroy()
  2.  
    {
  3.  
    // default to nothing
  4.  
    }
  1.  
    void CView::PostNcDestroy()
  2.  
    {
  3.  
    // default for views is to allocate them on the heap
  4.  
    // the default post-cleanup is to 'delete this'.
  5.  
    // never explicitly call 'delete' on a view
  6.  
    delete this;
  7.  
    }
  1.  
    void CFrameWnd::PostNcDestroy()
  2.  
    {
  3.  
    // default for frame windows is to allocate them on the heap
  4.  
    // the default post-cleanup is to 'delete this'.
  5.  
    // never explicitly call 'delete' on a CFrameWnd, use DestroyWindow instead
  6.  
    delete this;
  7.  
    }

很明顯:delete會對DestroyWindow產生影響

delete會引起析構函數,CWnd的析構函數中有對DestroyWindow的調用

  1.  
    CWnd::~CWnd()
  2.  
    {
  3.  
    if (m_hWnd != NULL &&
  4.  
    this != (CWnd*)&wndTop && this != (CWnd*)&wndBottom &&
  5.  
    this != (CWnd*)&wndTopMost && this != (CWnd*)&wndNoTopMost)
  6.  
    {
  7.  
    TRACE(_T( "Warning: calling DestroyWindow in CWnd::~CWnd; ")
  8.  
    _T( "OnDestroy or PostNcDestroy in derived class will not be called.\n"));
  9.  
    DestroyWindow();
  10.  
    }
  11.  
     
  12.  
    #ifndef _AFX_NO_OCC_SUPPORT
  13.  
    // cleanup control container,
  14.  
    // including destroying controls
  15.  
     
  16.  
    delete m_pCtrlCont;
  17.  
     
  18.  
    // cleanup control site
  19.  
    if (m_pCtrlSite != NULL && m_pCtrlSite->m_pWndCtrl == this)
  20.  
    m_pCtrlSite->m_pWndCtrl = NULL;
  21.  
    #endif
  22.  
    }

CDialog的析構函數 有對DestroyWindow的調用,但條件比較松,只需要m_hWnd != NULL。DoModal也會調用DestroyWindow。

  1.  
    CDialog::~CDialog()
  2.  
    {
  3.  
    if (m_hWnd != NULL)
  4.  
    {
  5.  
    TRACE0( "Warning: calling DestroyWindow in CDialog::~CDialog --\n");
  6.  
    TRACE0( "\tOnDestroy or PostNcDestroy in derived class will not be called.\n");
  7.  
    DestroyWindow();
  8.  
    }
  9.  
    }
  1.  
    int CDialog::DoModal()
  2.  
    {
  3.  
    ...
  4.  
    // destroy modal window
  5.  
    DestroyWindow();
  6.  
    PostModal();
  7.  
    ...
  8.  
    }

CFrameWnd------------OnClose中會調用DestroyWindow,但其析構中不會調用DestroyWindow

  1.  
    void CFrameWnd::OnClose()
  2.  
    {
  3.  
    ....
  4.  
    // then destroy the window
  5.  
    DestroyWindow();
  6.  
    }
  1.  
    CFrameWnd::~CFrameWnd()
  2.  
    {
  3.  
    RemoveFrameWnd();
  4.  
     
  5.  
    if( AfxGetThreadState()->m_pRoutingFrame == this )
  6.  
    AfxGetThreadState()->m_pRoutingFrame = NULL;
  7.  
     
  8.  
    if (m_phWndDisable != NULL)
  9.  
    delete[] (void*)m_phWndDisable;
  10.  
    }

CView的析構沒有調用DestroyWindow。

  1.  
    CView::~CView()
  2.  
    {
  3.  
    if (AfxGetThreadState()->m_pRoutingView == this)
  4.  
    AfxGetThreadState()->m_pRoutingView = NULL;
  5.  
     
  6.  
    if (m_pDocument != NULL)
  7.  
    m_pDocument->RemoveView( this);
  8.  
    }

以SDI程序的銷毀過程為例:

點擊退出按鈕,CMainFrame會收到WM_CLOSE消息。

CFrameWnd(CMainFrame的父類)間接會調用CWnd::DestroyWindow;

它首先向CMyView發送WM_DESTORY和WM_NCDESTROY消息,並引發相應的處理函數;

然后向CXXXDlg發送WM_DESTORY和WM_NCDESTROY消息,並引發相應的處理函數;

然后向CMyWnd發送WM_DESTORY和WM_NCDESTROY消息,並引發相應的處理函數。
DestroyWindow   執行過程:

1、調用CMainFrame::DestroyWindow     調用CWnd::DestroyWindow  (虛函數)

2、調用基類的 CFrameWnd::OnDestroy

3、 CMyView::OnDestroy
4、 CMyWnd::OnDestroy
5、 CXXXDlg::OnDestroy
6、CMyWnd::PostNcDestroy
7、CMyWnd的析構
8、CXXXDlg::OnDestroy             //  執行基類 CWnd::PostNcDestroy 虛函數  也可以重寫
9、CXXXDlg的析構
10、CMyView::PostNcDestroy
11、CMyView的析構
12、CMainFrame的析構
13、CMainFrame::DestroyWindow退出

上面情況是假設我們在CMyWnd和CXXXDlg的PostNcDestroy中添加了delete this。如果沒有添加,則7,9不會執行。
         因為CView::PostNcDestroy中調用了delete this,所以然后會執行CMyView的析構操作。

         因為CFrameWnd::PostNcDestroy中調用了delete this,所以最后執行CMainFrame的析構操作。

       如果自己的CMyDlg和CMyWnd在PostNcDestroy中有delete this;則二者會被析構。否則內存泄漏。當然delete也可以放在CMyView的析構中做,只是不夠好。
總結:
       可以有兩種方法銷毀窗口對象對應的窗口和釋放窗口對象指針。一種是通過DestroyWindow。這是比較好的方法,因為最后MFC會自動相應WM_CLOSE導致CFrameWnd::DestroyWindow被調用,然后會一次釋放所有子窗口的句柄。用戶需要做的是在PostNcDestroy中釋放堆窗口對象指針。但因為某些對象是在棧中申請的,所以delete this可能出錯。這就要保證寫程序時自己創建的窗口盡量使用堆申請。
       另一種是delete。Delete一個窗口對象指針,有的窗口類(如CWnd,CDialog)會間接調用DestroyWindow,有的窗口類(如CView,CFrameWnd)不會調用DestroyWindow。所以要小心應對。

===========================================================================================================================

附其他資料:作者:聞怡洋

 一個MFC窗口對象包括兩方面的內容:

       一是窗口對象封裝的窗口,即存放在m_hWnd成員中的HWND(窗口句柄),

       二是窗口對象本身是一個C++對象。要刪除一個MFC窗口對象,應該先刪除窗口對象封裝的窗口,然后刪除窗口對象本身。

    刪除窗口最直接方法是調用CWnd::DestroyWindow或::DestroyWindow,前者封裝了后者的功能。前者不僅會調用后者,而且會使成員m_hWnd保存的HWND無效(NULL)。如果DestroyWindow刪除的是一個父窗口或擁有者窗口,則該函數會先自動刪除所有的子窗口或被擁有者,然后再刪除父窗口或擁有者。

       在一般情況下,在程序中不必直接調用DestroyWindow來刪除窗口,因為MFC會自動調用DestroyWindow來刪除窗口。

       例如,當用戶退出應用程序時,會產生WM_CLOSE消息,該消息會導致MFC自動調用CWnd::DestroyWindow來刪除主框架窗口,當用戶在對話框內按了OK或Cancel按鈕時,MFC會自動調用CWnd::DestroyWindow來刪除對話框及其控件。

  窗口對象本身的刪除則根據對象創建方式的不同,分為兩種情況。在MFC編程中,會使用大量的窗口對象,有些窗口對象以變量的形式嵌入在別的對象內或以局部變量的形式創建在堆棧上,有些則用new操作符創建在堆中。

        1>>對於一個以變量形式創建的窗口對象,程序員不必關心它的刪除問題,因為該對象的生命期總是有限的,若該對象是某個對象的成員變量,它會隨着父對象的消失而消失,若該對象是一個局部變量,那么它會在函數返回時被清除。
        2>>對於一個在堆中動態創建的窗口對象,其生命期卻是任意長的。初學者在學習C++編程時,對new操作符的使用往往不太踏實,因為用new在堆中創建對象,就不能忘記用delete刪除對象。讀者在學習MFC的例程時,可能會產生這樣的疑問,為什么有些程序用new創建了一個窗口對象,卻未顯式的用delete來刪除它呢?問題的答案就是有些MFC窗口對象具有自動清除的功能。

  自動清除,當調用CWnd::DestroyWindow或::DestroyWindow刪除一個窗口時,被刪除窗口的PostNcDestroy成員函數會被調用。缺省的PostNcDestroy什么也不干,但有些MFC窗口類會覆蓋該函數並在新版本的PostNcDestroy中調用delete this來刪除對象,從而具有了自動清除的功能。此類窗口對象通常是用new操作符創建在堆中的,但程序員不必操心用delete操作符去刪除它們,因為一旦調用DestroyWindow刪除窗口,對應的窗口對象也會緊接着被刪除。

  不具有自動清除功能的窗口類如下所示。這些窗口對象通常是以變量的形式創建的,無需自動清除功能。

  所有標准的Windows控件類。
  1. 從CWnd類直接派生出來的子窗口對象(如用戶定制的控件)。
  2. 切分窗口類CSplitterWnd。
  3. 缺省的控制條類(包括工具條、狀態條和對話條)。
  4. 模態對話框類。
  具有自動清除功能的窗口類如下所示,這些窗口對象通常是在堆中創建的。
  1. 主框架窗口類(直接或間接從CFrameWnd類派生)。
  2. 視圖類(直接或間接從CView類派生)。
  讀者在設計自己的派生窗口類時,可根據窗口對象的創建方法來決定是否將窗口類設計成可以自動清除的。

        例如,對於一個非模態對話框來說,其對象是創建在堆中的,因此應該具有自動清除功能。

  綜上所述,對於MFC窗口類及其派生類來說,在程序中一般不必顯式刪除窗口對象。也就是說,既不必調用DestroyWindow來刪除窗口對象封裝的窗口,也不必顯式地用delete操作符來刪除窗口對象本身。只要保證非自動清除的窗口對象是以變量的形式創建的,自動清除的窗口對象是在堆中創建的,MFC的運行機制就可以保證窗口對象的徹底刪除。
  如果需要手工刪除窗口對象,則應該先調用相應的函數(如CWnd::DestroyWindow)刪除窗口,然后再刪除窗口對象.

        對於以變量形式創建的窗口對象,窗口對象的刪除是框架自動完成的.

        對於在堆中動態創建了的非自動清除的窗口對象,必須在窗口被刪除后,顯式地調用delete來刪除對象(一般在擁有者或父窗口的析構函數中進行).

        對於具有自動清除功能的窗口對象,只需調用CWnd::DestroyWindow即可刪除窗口和窗口對象。

        注意,對於在堆中創建的窗口對象,不要在窗口還未關閉的情況下就用delete操作符來刪除窗口對象.
=======================================================================================


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM