Win32项目中的WM_CREATE消息何时被调用的

本文通过实验详细解析了在VC中WM_CREATE与WM_INITDIALOG消息的发送顺序及其调用方式。揭示了CreateWindow()如何发送WM_CREATE消息,并强调了WM_CREATE消息直接调用WndProc()的特点。

(本文转载自http://bbs.ednchina.com/BLOG_ARTICLE_3005454.HTM

VC中WM_CREATE、WM_INITDIALOG消息出现顺序及调用方式

wxleasyland@sina.com

2012.7

 

wxleasyland试验:

 

VC建的标准WIN32-HELLO WORLD程序中:

1.在CreateWindow()前面加上语句WinExec("cmd /k echo now call CreateWindow()",SW_SHOW);   Sleep(5000);

2.在CreateWindow()后面加上语句WinExec("cmd /k echo CreateWindow() RETURN",SW_SHOW);

3.在WndProc()中加上

case WM_CREATE:

           WinExec("cmd /k echo WndProc(): WM_CREATE: appear!",SW_SHOW);

           Sleep(5000);

           break;

4.运行,发现,先出现now call CreateWindow()、再WndProc(): WM_CREATE: appear!、最后CreateWindow() RETURN。

 

结论:

CreateWindow()是在运行后、返回前,发送消息WM_CREATE,并阻塞!

WM_CREATE消息直接发给消息处理函数WndProc()执行!而不是通过消息队列(GetMessage、DispatchMessage等)!!

其实就是CreateWindow()直接拿消息WM_CREATE去调用WndProc()!!!!!

如果WndProc()返回0,CreateWindow()正常返回hWnd。

如果WndProc()返回-1,CreateWindow()销毁窗口,并返回NULL。

 

所以WM_CREATE时,CreateWindow()内部已经创建好窗口了,但还没有返回,还没有hWnd!即窗口真正还没有创建结束。这时这里不能有对窗口上的控件操作的函数,不然就会“程序执行了非法操作”。比如GetDlgItem等。

 

 

标准WIN32程序中,是先RegisterClassEx()注册窗口类->再CreateWindow()创建窗口->再ShowWindow()显示出窗口->再UpdateWindow()立即用WM_PAINT刷新窗口->再LoadAccelerators()->再进入消息循环(GetMessage、DispatchMessage等)

 

 

在MFC对话框中,试验,是:先WM_CREATE,这时窗口未显示出来->再WM_INITDIALOG,这时窗口仍未显示出来-> OnInitDialog()运行完毕后,窗口才显示出来(应是默认通过ShowWindow())。

<think>我们遇到了两个问题:1.使用win32con.NIF_ICON时出现AttributeError:module'win32con'hasnoattribute'NIF_ICON'2.使用Shell_NotifyIcon时可能出现的未指定错误(如气球通知不显示等)根据引用[1]和引用[3]中的信息,我们可以知道:-NIF_ICON等常量实际上在win32gui模块中定义,而不是win32con。-使用Shell_NotifyIcon时,需要注意结构体NOTIFYICONDATA的成员设置,特别是cbSize、uFlags等。-在修改托盘图标时,不能重新定义结构体,而应该使用同一个结构体实例。另外,引用[4]提到在Windows10及以上版本中,气球通知可能需要修改注册表才能显示。因此,解决方案如下:###1.解决AttributeError:使用win32gui中的常量在Python中,我们应该使用`win32gui`模块来访问`NIF_ICON`、`NIF_MESSAGE`、`NIF_TIP`等常量,而不是`win32con`。###2.创建托盘图标的正确步骤-定义NOTIFYICONDATA结构(在pywin32中,我们通过一个元组来表示)-设置正确的cbSize(在pywin32中,我们不需要显式设置,因为元组已经固定)-设置hWnd(窗口句柄)-设置uID(图标ID,可以任意指定,但需保证唯一)-设置uFlags(标志位,指明哪些字段有效)-设置uCallbackMessage(回调消息,当托盘图标有事件时,会发送此消息到hWnd指定的窗口)-设置hIcon(图标句柄)-设置szTip(提示文本)###3.修改托盘图标要修改托盘图标(比如更换图标或提示文字),应该使用同一个NOTIFYICONDATA结构(在Python中即同一个元组),然后修改需要改变的字段,并重新调用`Shell_NotifyIcon`,指定操作类型为`NIM_MODIFY`。###4.处理气球通知(BalloonTooltip)如果要显示气球通知,需要:-设置`uFlags`包含`win32gui.NIF_INFO`-设置`szInfo`(通知内容)和`szInfoTitle`(通知标题)-注意在Windows10及以上版本,可能需要修改注册表启用旧版气球通知(引用[4])###5.错误处理当`Shell_NotifyIcon`返回0(失败)时,可以调用`win32api.GetLastError()`获取错误代码,但注意引用[3]提到不能完全依赖错误代码。###具体代码实现####修正后的创建托盘图标代码```pythonimportwin32guiimportwin32apiimportwin32conclassSysTrayIcon:def__init__(self,hwnd,icon_path,tooltip="TrayIcon",callback_message=None):self.hwnd=hwndself.icon_path=icon_pathself.tooltip=tooltipself.callback_message=callback_messageor(win32con.WM_USER+100)#自定义消息IDself.nid=Noneself.create_tray_icon()defcreate_tray_icon(self):#加载图标icon_flags=win32con.LR_LOADFROMFILE|win32con.LR_DEFAULTSIZEhicon=win32gui.LoadImage(0,self.icon_path,win32con.IMAGE_ICON,0,0,icon_flags)#定义托盘图标的数据结构(元组形式)#注意:在pywin32中,NOTIFYICONDATA用元组表示,具体结构为:#(hwnd,id,flags,callback_message,hicon,tooltip)flags=win32gui.NIF_ICON|win32gui.NIF_MESSAGE|win32gui.NIF_TIPself.nid=(self.hwnd,0,flags,self.callback_message,hicon,self.tooltip)#添加托盘图标win32gui.Shell_NotifyIcon(win32gui.NIM_ADD,self.nid)defmodify_icon(self,new_icon_path=None,new_tooltip=None):ifnotself.nid:return#如果提供了新图标路径,则加载新图标ifnew_icon_path:icon_flags=win32con.LR_LOADFROMFILE|win32con.LR_DEFAULTSIZEhicon=win32gui.LoadImage(0,new_icon_path,win32con.IMAGE_ICON,0,0,icon_flags)else:hicon=self.nid[4]#保持原图标new_tooltip=new_tooltiporself.tooltip#更新nid元组(注意:元组不可变,所以新建一个元组)#结构:(hwnd,id,flags,callback_message,hicon,tooltip)flags=win32gui.NIF_ICON|win32gui.NIF_MESSAGE|win32gui.NIF_TIPself.nid=(self.nid[0],self.nid[1],flags,self.nid[3],hicon,new_tooltip)#修改托盘图标win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY,self.nid)defshow_balloon_tip(self,title,message,timeout=10,icon=0):"""显示气球通知:paramtitle:标题:parammessage:消息内容:paramtimeout:超时时间(秒):paramicon:图标类型(0:无图标,1:信息图标,2:警告图标,3:错误图标):return:"""ifnotself.nid:return#构建新的nid元组,包含气球通知所需的字段#注意:pywin32中用于气球通知的元组结构有更多字段,需要使用版本5的结构#这里我们使用扩展的元组结构,具体为:#(hwnd,id,flags,callback_message,hicon,tooltip,info,info_title,info_flags,timeout)flags=win32gui.NIF_ICON|win32gui.NIF_MESSAGE|win32gui.NIF_TIP|win32gui.NIF_INFO#注意:在气球通知中,我们通常不需要改变图标和提示,所以可以复用原来的#但为了确保气球通知显示,我们必须设置info(消息内容)和info_title(标题)#构建新元组(注意:原nid只有6个元素,现在需要10个元素)#如果原nid长度不够,我们需要扩展iflen(self.nid)<10:#扩展元组,缺少的部分用0或空字符串填充nid_list=list(self.nid)#扩展到10个元素:第7-10个元素分别是:szInfo,szInfoTitle,dwInfoFlags,uTimeoutnid_list+=[""]*(10-len(nid_list))self.nid=tuple(nid_list)#更新需要的气球通知字段nid_list=list(self.nid)nid_list[2]=flags#更新flagsnid_list[6]=message#szInfonid_list[7]=title#szInfoTitlenid_list[8]=icon#dwInfoFlags:0=None,1=Info,2=Warning,3=Errornid_list[9]=timeout*1000#uTimeout(毫秒)self.nid=tuple(nid_list)#修改托盘图标以显示气球通知win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY,self.nid)defremove_icon(self):ifself.nid:win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE,self.nid)self.nid=None```####在Tkinter窗口中使用```pythonimporttkinterastkimportosclassApp(tk.Tk):def__init__(self):super().__init__()self.title("TrayIconExample")self.geometry("300x200")self.protocol("WM_DELETE_WINDOW",self.hide_to_tray)#绑定最小化事件self.bind("<Unmap>",self.on_minimize)#创建托盘图标对象(先不创建,等窗口隐藏后再创建)self.tray_icon=None#获取图标路径(使用绝对路径)self.icon_path=os.path.abspath("icon.ico")defon_minimize(self,event):ifself.state()=="iconic":self.hide_to_tray()defhide_to_tray(self):self.withdraw()#隐藏窗口ifnotself.tray_icon:#创建托盘图标,并指定回调消息处理函数self.tray_icon=SysTrayIcon(self.winfo_id(),self.icon_path,"MyTrayIcon",callback_message=win32con.WM_USER+100)#绑定消息处理win32gui.PumpMessages()#注意:这会进入消息循环,阻塞当前线程defshow_window(self):self.deiconify()ifself.tray_icon:self.tray_icon.remove_icon()self.tray_icon=Noneif__name__=="__main__":app=App()app.mainloop()```###注意:1.上述代码中,`win32gui.PumpMessages()`会进入消息循环,导致阻塞。在Tkinter的主线程中使用会阻塞整个Tkinter事件循环。因此,更好的做法是使用单独的线程来处理托盘图标的窗口消息。2.在Tkinter中处理托盘图标事件(如点击)需要将消息传递给Tkinter窗口。我们可以通过`win32gui.PostMessage`将消息转发到Tkinter窗口,然后在Tkinter中处理。###高级:使用线程处理托盘消息为了避免阻塞Tkinter主循环,我们可以创建一个线程来运行消息循环。```pythonimportthreadingclassSysTrayIconThreaded(SysTrayIcon):def__init__(self,*args,**kwargs):super().__init__(*args,**kwargs)self.thread=threading.Thread(target=self.run_message_loop)self.thread.daemon=Trueself.thread.start()defrun_message_loop(self):#进入消息循环win32gui.PumpMessages()```然后在Tkinter中,我们使用这个线程化的托盘图标类。###关于气球通知在Windows10上不显示的问题如果气球通知无法显示,请参考引用[4]中的方法修改注册表:1.打开注册表编辑器(regedit)2.导航到:`HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\Windows\Explorer`3.创建DWORD值:`EnableLegacyBalloonNotifications`,并设置为14.重启计算机(或重启资源管理器)###相关问题1.如何在Tkinter中实现托盘图标的右键菜单?2.如何动态改变托盘图标的提示信息?3.如何让托盘图标在程序退出时自动移除?4.在Windows10中如何避免气球通知被系统转换为Toast通知?[^1]:参考托盘图标的创建和修改方法[^3]:参考Shell_NotifyIcon的错误处理[^4]:参考Windows10气球通知的注册表设置</think>### 解决 Python 中托盘图标创建问题 针对你遇到的 `AttributeError: module 'win32con' has no attribute 'NIF_ICON'` 和 `Shell_NotifyIcon` 相关错误,以下是完整的解决方案: --- ### 问题原因分析 1. **常量引用错误** `NIF_ICON`, `NIF_MESSAGE` 等常量实际定义在 `win32gui` 模块而非 `win32con`[^1] 2. **Shell_NotifyIcon 使用不当** - 未正确处理返回值和错误码 - Windows 10+ 系统需要特殊处理气球通知[^4] 3. **图标路径问题** 相对路径可能导致加载失败 --- ### 完整解决方案 #### 1. 修正常量引用 ```python import win32gui import win32api import win32con # 正确引用常量 NIF_ICON = win32gui.NIF_ICON # 0x00000002 NIF_MESSAGE = win32gui.NIF_MESSAGE # 0x00000001 NIF_TIP = win32gui.NIF_TIP # 0x00000004 NIF_INFO = win32gui.NIF_INFO # 0x00000010 (气球通知) NIM_ADD = win32gui.NIM_ADD # 0x00000000 NIM_MODIFY = win32gui.NIM_MODIFY # 0x00000001 NIM_DELETE = win32gui.NIM_DELETE # 0x00000002 ``` #### 2. 创建托盘图标的正确实现 ```python import os class SysTrayIcon: def __init__(self, hwnd, icon_path, tooltip="My App"): self.hwnd = hwnd self.icon_path = os.path.abspath(icon_path) # 使用绝对路径 self.tooltip = tooltip self.nid = None self.create_tray_icon() def create_tray_icon(self): # 加载图标 hicon = win32gui.LoadImage( 0, self.icon_path, win32con.IMAGE_ICON, 0, 0, win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE ) # 构建 NOTIFYICONDATA 结构 flags = NIF_ICON | NIF_MESSAGE | NIF_TIP message_id = win32con.WM_USER + 100 # 自定义消息ID # 使用元组表示 NOTIFYICONDATA self.nid = ( self.hwnd, # hWnd 0, # uID flags, # uFlags message_id, # uCallbackMessage hicon, # hIcon self.tooltip, # szTip "", # szInfo (气球通知内容) "", # szInfoTitle (气球通知标题) 0, # dwInfoFlags (气球通知图标类型) 10000 # uTimeout (气球通知超时时间) ) # 添加托盘图标 try: result = win32gui.Shell_NotifyIcon(NIM_ADD, self.nid) if not result: error_code = win32api.GetLastError() raise Exception(f"Shell_NotifyIcon failed with error: {error_code}") except Exception as e: print(f"Error creating tray icon: {e}") def show_balloon_tip(self, title, message, icon_type=1): """显示气球通知 (icon_type: 0=无图标, 1=信息, 2=警告, 3=错误)""" if not self.nid: return # 更新 nid 元组 nid_list = list(self.nid) nid_list[2] = NIF_INFO # uFlags nid_list[6] = message # szInfo nid_list[7] = title # szInfoTitle nid_list[8] = icon_type # dwInfoFlags self.nid = tuple(nid_list) try: result = win32gui.Shell_NotifyIcon(NIM_MODIFY, self.nid) if not result: self._handle_balloon_failure() except Exception as e: print(f"Balloon tip error: {e}") def _handle_balloon_failure(self): """处理气球通知失败""" error_code = win32api.GetLastError() print(f"Balloon tip failed (Error: {error_code})") # Windows 10+ 需要特殊处理 if win32api.GetVersion() >= (10, 0): print("Windows 10+ detected, checking registry settings...") self._enable_legacy_balloons() def _enable_legacy_balloons(self): """启用传统气球通知 (需要管理员权限)""" try: import winreg key_path = r"SOFTWARE\Policies\Microsoft\Windows\Explorer" with winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_path) as key: winreg.SetValueEx(key, "EnableLegacyBalloonNotifications", 0, winreg.REG_DWORD, 1) print("Registry updated. Please restart your computer.") except Exception as e: print(f"Registry update failed: {e}. Run as administrator?") def remove_icon(self): """移除托盘图标""" if self.nid: win32gui.Shell_NotifyIcon(NIM_DELETE, self.nid) ``` #### 3. 在 Tkinter 中使用 ```python import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self.title("Tray Icon Demo") self.geometry("400x300") # 托盘图标设置 self.tray_icon = None self.protocol("WM_DELETE_WINDOW", self.hide_to_tray) # 测试按钮 btn = tk.Button(self, text="Show Balloon", command=self.show_balloon) btn.pack(pady=20) def hide_to_tray(self): """隐藏到托盘""" self.withdraw() if not self.tray_icon: self.tray_icon = SysTrayIcon( self.winfo_id(), "icon.ico", # 替换为你的图标路径 "My Application" ) def show_balloon(self): """显示测试气球通知""" if self.tray_icon: self.tray_icon.show_balloon_tip( "Notification", "This is a balloon tip from your app!", icon_type=1 ) def show_window(self): """从托盘恢复窗口""" self.deiconify() if self.tray_icon: self.tray_icon.remove_icon() self.tray_icon = None if __name__ == "__main__": app = App() app.mainloop() ``` --- ### 关键注意事项 1. **图标路径** 使用 `os.path.abspath` 确保图标路径正确: ```python icon_path = os.path.abspath("icon.ico") ``` 2. **Windows 10+ 气球通知** 如引用[4]所述,需修改注册表: ```reg [HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\Windows\Explorer] "EnableLegacyBalloonNotifications"=dword:00000001 ``` 修改后需重启资源管理器或系统 3. **错误处理** - 检查 `Shell_NotifyIcon` 返回值 - 使用 `win32api.GetLastError()` 获取详细错误码 - 常见错误: - `2`: 文件未找到(图标路径错误) - `5`: 拒绝访问(权限问题) - `87`: 参数错误(结构体配置错误) --- ### 相关问题 1. 如何为托盘图标添加右键菜单? 2. 如何实现托盘图标的双击事件处理? 3. 如何让托盘程序在系统启动时自动运行? 4. 如何为不同状态(如网络连接状态)动态更换托盘图标? 5. 在 Windows 11 中如何处理新版通知系统与托盘图标的兼容性? [^1]: 常量引用位置修正 [^3]: Shell_NotifyIcon 错误处理参考 [^4]: Windows 10+ 气球通知注册表设置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值