问题描述
昨天同事修改一个bug,请我审查代码时我发现这个bug很有意思,值得深入研究一下。
问题是这样的,弹出一个模态窗口后,在窗口内切换树节点,发现主窗口也可以接受鼠标和键盘消息了——模态窗口不模态了!
跟踪发现,在窗口内切换树节点会间接调用到进度条窗口(我们自己写的一个窗口),ShowProgress时会调用Application.MainForm.Enabled := False,HideProgress是会调用Application.MainForm.Enabled := True,问题就出现在这个地方。
ShowModal实现
TCustomForm.ShowModal begin //... WindowList := DisableTaskWindows(0); Show; SendMessage(Handle, CM_ACTIVATE, 0, 0); repeat Application.HandleMessage; if Application.Terminated then ModalResult := mrCancel else if ModalResult <> 0 then CloseModal; until ModalResult <> 0; //... end; DisableTaskWindows begin //... EnumThreadWindows(GetCurrentThreadID, @DoDisableWindow, 0); //... end DoDisableWindow begin //... EnableWindow(Window, False); //... end
ShowModal的实际上只让模态接收键盘和鼠标信息,而禁止其他的窗口接收键盘和鼠标消息,这是通过调用EnableWindow这个Windows API实现的。
SetEnabled实现
TControl.SetEnabled begin //... Perform(CM_ENABLEDCHANGED, 0, 0) //... end TWinControl.CMEnabledChanged begin //... EnableWindow(FHandle, Enabled) //... end
当调用Application.MainForm.Enabled := True时就会使得MainForm的可以接收消息了,之前的模态窗口就不再模态了。
修改bug的方法:判断Application.ModalLevel > 0是否为真,为真的时候就不在做上述调用。
Windows API
1、EnableWindow:该函数允许/禁止指定的窗口或控件接受鼠标和键盘的输入,当输入被禁止时,窗口不响应鼠标和按键的输入,输入允许时,窗口接受所有的输入。
2、IsWindowEnabled:用于判断指定的窗口是否允许接受键盘或鼠标输入。
3、EnumThreadWindows:枚举所有与一个线程相关联的非子窗口,办法是先将句柄传送给每一个窗口,随后传送给应用程序定义的回调函数。EnumThreadWindows函数继续直到所有窗口枚举完为止或回调函数返回FALSE为止。
VCL:DisableTaskWindows:{ EnumThreadWindows(GetCurrentThreadID, @DoDisableWindow, 0); } //DoDisableWindow为回调函数
反思
1、遇到问题后要深入了解其内在过程,知其所以然;
2、不断总结,博客化;