liuem  

问题描述

  昨天同事修改一个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、不断总结,博客化;

posted on 2013-01-07 09:25  弈名  阅读(3218)  评论(0编辑  收藏  举报