Destroying Window Objects(销毁窗口对象)

TN017: Destroying Window Objects
销毁窗口对象
此章节讲述了CWnd::PostNcDestroy成员函数的使用。如果你想对派生自CWnd的对象做自定义的处理,你可以使用这个函数。
此章节也阐述了“要销毁一个C++的Windows对象,使用DestroyWindow而不是delete”这个最重要规则的原因。
这是非常重要的。如果你遵照以下的指导方法,你就会很少碰到清除方面的问题(例如忘记删除/释放C++内存,忘记释放系统资源比如说HWNDs,或者释放对象太多次)。
问题
Windows对象(CWnd派生类的对象)既代表一个C++对象(在应用程序的堆中分配)也代表了一个HWND(由窗口管理器在系统资源里分配)。由于存在多种途径来销毁一个窗口对象,我们必须提供一组规则以防止系统资源或者应用程序的内存泄露问题,同时也防止对象和Windows句柄被多次销毁。
这不仅仅是一个内存管理的问题。一个Windows窗口的存在会对用户界面产生影响:窗口绘制在屏幕上;一旦它被销毁,同样对系统资源也会产生影响。在你的应用程序地址空间里泄漏C++内存并不会像泄漏系统资源那样糟糕。
销毁窗口

有两种方法被允许来销毁一个Windows对象:
l 调用CWnd::DestroyWindow或Windows API ::DestroyWindow.
l 利用delete操作符来进行明确的删除工作。
第一种方法是迄今为止最常用的。即使DestroyWindow没有在你的代码里被直接调用,此方法也照常适用。这种情况就是,当用户直接关闭一个框架窗口时(缺省的WM_CLOSE行为主是调用DestroyWindow),当一个父窗口(框架窗口)被销毁时,Windows会调用DestroyWindow来销毁它的所有的子窗口。
Auto Cleanup with CWnd::PostNcDestroy
When destroying a Windows window, the last Windows message sent to the window is WM_NCDESTROY. The default CWnd handler for that message (CWnd::OnNcDestroy) will detach the HWND from the C++ object and call the virtual function PostNcDestroy. Some classes override this function to delete the C++ object.
利用CWnd::PostNcDestroy进行自动清除
当销毁一个Windows窗口时,最后发送给此窗口的Windows消息是WM_NCDESTROY。CWnd对此消息的缺省处理(CWnd::OnNcDestroy)会将C++对象与HWND分离,并调用虚函数PostNcDestroy。一些类重载这个函数来删除C++对象。
The default implementation of CWnd::PostNcDestroy does nothing which is appropriate for window objects allocated on the stack frame or embedded in other objects. This is not appropriate for window objects that are designed to be allocated by themselves on the heap (not embedded in other C++ object).
CWnd::PostNcDestroy的缺省操作是什么也不做,这适合于那些分配在堆栈或者嵌在其他对象里面的窗口对象。这不适合于那些设计来分配在堆上的窗口对象(不嵌在其他C++对象中)。
Those classes that are designed to be allocated by themselves on the heap override the PostNcDestroymember function to perform a "delete this". This statement will free any C++ memory associated with the C++ object. Even though the default CWnd destructor calls DestroyWindow if m_hWnd is non-NULL, this does not lead to infinite recursion since the handle will be detached and NULL during the cleanup phase.
那些设计来分配在堆上的类可以重载成员函数PostNcDestroy以执行“delete this”操作。它将会释放任何与此C++对象相关的C++内存。尽管缺省的CWnd析构函数会在m_hWnd不为空的情况下调用DestoryWindow,但这不会导致无穷递归,因为此句柄在清除阶段将会处于分离状态并为空。
Note CWnd::PostNcDestroy is normally called after the Windows WM_NCDESTROY message is processed, as part of window destruction, and the HWND and the C++ window object are no longer attached. CWnd::PostNcDestroy will also be called in the implementation of most Create calls if failure occurs (see below for auto cleanup rules).
注意CWnd::PostNcDestroy一般会在Windows消息WM_NCDESTORY处理后被调用,把它作为窗口销毁的一部分,同时HWND和C++窗口对象不再关联。
CWnd::PostNcDestroy也会在大部分Create调用的执行部分被调用,如果错误发生的话(自动清理规则如下)
Auto Cleanup Classes
The following classes are not designed for auto-cleanup. They are normally embedded in other C++ object or on the stack:
All the standard Windows controls (CStatic, CEdit, CListBox, and so on).
Any child windows derived directly from CWnd (for example, custom controls)
Splitter windows (CSplitterWnd)
Default control bars (classes derived from CControlBar, see Technical Note 31 for enabling auto-delete for control bar objects)
Dialogs (CDialog) - designed for modal dialogs on the stack frame
All the standard dialogs except CFindReplaceDialog
The default dialogs created by ClassWizard
自动清理类
以下的这些类不是设计来做自动清理的。他们通常嵌在其他C++对象或者在堆栈上:
l 所有的标准Windows控件(CStatic, CEdit, ClistBox等)
l 所有从CWnd直接派生来的子窗口(比例,自定义控件)
l 拆分窗口(CSplitterWnd)
l 缺省控制条(从CcontrolBar派生的类,查看TN31来了解能够自动删除的控制条对象)
l 对话框(CDialog)设计来在堆栈上创建模态对话框
l 所有的标准对话框,除了CfindReplaceDialog
l 由ClassWizard创建的缺省对话框
The following classes are designed for auto-cleanup. They are normally allocated by themselves on the heap:
Main frame windows (derived directly or indirectly from CFrameWnd)
View windows (derived directly or indirectly from CView)
以下这些类设计来做自动清理。他们一般单独分配在堆上:
l 主框架窗口(直接或间接派生于CFrameWnd)
l 视图窗口(直接或间接派生于CView)
If you wish to break any of these rules, you must override the PostNcDestroy member function in your derived class. To add auto-cleanup to your class, simply call your base class and then do a delete this. To remove auto-cleanup from your class, call CWnd::PostNcDestroy directly instead of thePostNcDestroy member in your direct base class.
如果你想打破任何一条规则,你就必须在你的派生类中重载PostNcDestroy成员函数。为了增加自动清理到你的类,只需要调用你的基类并做delete this操作。为了将自动清理从你的类中移出,直接调用CWnd::PostNcDestroy来代替你基类的成员函数PostNcDestory.
The most common use of the above is to create a modeless dialog that can be allocated on the heap.
以上内容常用在创建一个能在堆上分配的非模态的对话框
When to Call 'delete'
The recommended way to destroy a Windows object is to call DestroyWindow, either the C++ member function or the global ::DestroyWindow API.
何时调用delete
销毁一个窗口最好是调用DestoryWindow,不管是C++的成员函数还是全局的::DestoryWindow API.
Do not call the global ::DestroyWindow API to destroy an MDI Child window, use the virtual member function CWnd::DestroyWindow instead.
不要调用全局的API ::DestroyWindow来销毁一个MDI子窗口,使用虚拟成员函数CWnd::DestroyWindow来代替它。
For C++ Window objects that don't perform auto-cleanup, using DestroyWindow instead of delete avoids problems of having to call DestroyWindow in the CWnd::~CWnd destructor where the VTBL is not pointing to the correctly derived class. This can lead to subtle bugs so the diagnostic (debug) version of MFC will warn you with
Warning: calling DestroyWindow in CWnd::~CWnd
OnDestroy or PostNcDestroy in derived class will not be called
对于那些不执行自动清理的C++窗口对象,使用DestoryWindow来代替delete以避免你必须在CWnd::~CWnd析构函数中调用DestoryWindow的问题,而在此处VTBL并没有指向正确的派生类。这可能会导致许多bugs,所以MFC诊断版本(调试)中会警告你:
Warning: calling DestroyWindow in CWnd::~CWnd
OnDestroy or PostNcDestroy in derived class will not be called
In the case of C++ Windows objects that do perform auto-cleanup, you must call DestroyWindow. If you use operator delete directly, the MFC diagnostic memory allocator will alert you that you are freeing memory twice (the first call to delete as well as the indirect call to "delete this" in the auto-cleanup implementation of PostNcDestroy).
对于执行自动清理工作的C++Windows对象,你必须调用DestroyWindow。如果你直接使用操作符delete,MFC的诊断内存分配算符将会警告你:你正在第二次释放内存(第一次调用delete,还有在PostNcDestroy的自动清理执行过程中调用delete this)。
After calling DestroyWindow on a non-auto-cleanup object, the C++ object will still be around, butm_hWnd will be NULL. After calling DestroyWindow on an auto-cleanup object, the C++ object will be gone, freed by the C++ delete operator in the auto-cleanup implementation of PostNcDestroy..
对于一个不执行自动清理的对象,在调用DestroyWindow之后,这个C++对象仍然存在,但是m_hWnd会为空。对一个执行自动清理的对象,在调用DestroyWindow之后,此C++对象就不存在了,它被PostNcDestroy的自动清理执行过程里的delete操作符释放。

### 如何在 Tkinter 中检测窗口是否已被销毁 在 Tkinter 的开发过程中,有时需要判断某个窗口是否已经被销毁。这可以通过尝试访问窗口对象的状态来完成。如果窗口已经销毁,则对该窗口的操作会引发异常 `AttributeError` 或 `_tkinter.TclError`。 以下是具体实现方式: #### 使用 try-except 块捕获错误 可以尝试调用窗口的一个属性或方法(例如 `.winfo_exists()`),如果该操作成功,则说明窗口仍然存在;反之则表示窗口已被销毁。 ```python import tkinter as tk def check_window_destroyed(window): try: # 尝试获取窗口是否存在 window.winfo_exists() return False # 如果没有抛出异常,说明窗口未被销毁 except (AttributeError, tk.TclError): return True # 抛出异常意味着窗口已被销毁 if __name__ == "__main__": root = tk.Tk() def destroy_root(): global root root.destroy() # 销毁窗口 button = tk.Button(root, text="Destroy Window", command=destroy_root) button.pack(pady=20) root.update() # 更新 GUI 状态以便测试 print("Before destruction:", not check_window_destroyed(root)) # 输出窗口状态 # 手动触发窗口销毁逻辑 button.invoke() root.update_idletasks() print("After destruction:", check_window_destroyed(root)) # 再次检查窗口状态 root.mainloop() ``` 上述代码展示了如何通过定义一个辅助函数 `check_window_destroyed` 来验证窗口是否仍处于活动状态[^1]。 --- #### 利用 `winfo_exists()` 方法 Tkinter 提供了内置的 `winfo_exists()` 方法用于查询窗口当前的存在状态。此方法返回布尔值:如果窗口尚未销毁,则返回 `True`;否则返回 `False`。 ```python import tkinter as tk root = tk.Tk() def is_window_alive(window): return window.winfo_exists() # 返回窗口是否存在 button = tk.Button( root, text="Check and Destroy", command=lambda: [ print(f"Window alive before destroying: {is_window_alive(root)}"), root.destroy(), print(f"Window alive after destroying: {is_window_alive(root)}") ] ) button.pack(pady=20) root.mainloop() ``` 在此示例中,`winfo_exists()` 被用来动态监控窗口的生命期变化情况[^2]。 --- #### 注意事项 - **多线程环境下的注意事项**:虽然 `mainloop()` 不会在单独的线程中运行,但如果涉及其他并发机制(如 threading 模块中的子线程),需特别小心同步问题以避免竞态条件。 - **资源释放的重要性**:即使窗口被显式销毁 (`destroy`),也应确保所有关联的对象和变量都被清理干净以免内存泄漏[^3]。 --- ### 总结 为了有效监测 Tkinter 应用程序内的窗口销毁状况,推荐采用 `try-except` 结合特定方法(如 `winfo_exists()`)的方式来进行健壮性编程实践。这种方法不仅能够满足基本需求还能增强代码可维护性和鲁棒性[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值