2

我们有一个 Delphi 6 应用程序,它使用非模态表单和网格内编辑。在 FormClose 事件中,我们检查条目是否为正方形,如果不是,则阻止关闭。

但是,如果用户单击后面的主表单,则原始表单会消失在后面(如您所料),但这允许用户移动到主屏幕上的新记录,而无需验证网格中的更改。

我已经尝试过 FormDeactivate 事件,它确实会触发,但似乎没有任何机制来防止停用(与 FormClose 事件 Action 参数不同)。我从网格中尝试了 OnExit,但它不会在停用时触发。我尝试捕获 WM_ACTIVATE 消息并设置 Msg.Result = 1 但这没有效果(可能是因为另一个 WM_ACTIVATE 消息正在发送到主窗体?)。

因此,我正在寻找有关如何(有条件地)防止在用户单击另一个表单时停用表单的想法。(PS我不想把表单样式改成fsStayOnTop)

谢谢

4

6 回答 6

2

Windows 中的一条经典规则是,您不能在焦点更改事件期间更改焦点。该OnDeactivate事件发生在焦点更改事件期间。您的表单被告知它正在被停用——操作系统没有请求许可——同时,另一个表单被告知它正在被激活。两个窗口在这件事上都没有任何发言权,在这些事件发生时试图改变焦点只会让所有的窗口感到困惑。症状包括让两个窗口自己绘制,好像它们有焦点一样,并且尽管输入光标闪烁,但键盘消息却无处可去。MSDN更可怕,虽然我从未目睹过这么糟糕的事情:

在处理此消息 [ WM_KILLFOCUS ] 时,不要进行任何显示或激活窗口的函数调用。这会导致线程让出控制并可能导致应用程序停止响应消息。有关详细信息,请参阅消息死锁

由于您无法在焦点更改已经开始后否认它,因此要做的是将事件处理延迟到事情稳定下来之后。当您的编辑表单被停用且其中的数据无效时,请在表单上发布一条消息。Posting 将消息放在消息队列的末尾,因此在所有先前的消息(尤其是焦点更改通知)都已处理之前,它不会被处理。当消息到达时,指示数据无效并将焦点重新设置回编辑表单:

const
  efm_InvalidData = wm_User + 1;

type
  TEditForm = class(TForm)
  ...
  private
    procedure EFMInvalidData(var Msg: TMessage); message efm_InvalidData;
  end;

procedure TEditForm.FormDeactivate(Sender: TObject);
begin
  if DataNotValid then
    PostMessage(Handle, efm_InvalidData, 0, 0);
end;

procedure TEditForm.EFMInvalidData(var Msg: TMessage);
begin
  Self.SetFocus;
  ShowMessage('Invalid data');
end;

我应该指出,这个答案在技术上并不能回答您的问题,因为它不会阻止表单停用,但是您拒绝了我的另一个确实可以防止停用的答案。

于 2009-10-05T16:38:27.573 回答
1

当您调用 时ShowModal,除显示的表单之外的所有表单都将被禁用。它们在ShowModal返回之前重新启用。

以非模态方式显示您的编辑表单,当数据开始被编辑时,通过禁用其他表单使表单本身成为模态。编辑完成后启用其他表单。显然,禁用窗口并不总是像设置Enabled属性那么简单。我建议使用DisableTaskWindows,但它会禁用所有窗口,包括您的编辑表单。尽管如此,看看它是如何在Forms.pas中实现的。它保留了它禁用的所有窗口的列表,以便之后只有它们才能重新启用。

于 2009-10-05T15:22:34.670 回答
0

您还可以在模型中引入一个状态,以跟踪窗口是否需要您在此处描述的焦点,并在其他窗体上使用 onFocus 处理程序以编程方式将焦点设置回网格窗口。

[编辑] 我的评论副本:

您可以使用网格注册表单的 onShow 事件。(如果您实现它,请务必使其以某种方式可配置,以最大限度地减少网格对应用程序当前布局的依赖。也许通过提供一个由表单调用的方法,该方法反过来在调用表单触发网格的事件注册对于 onShow 事件)

详细说明事件注册:

您可以以编程方式附加事件处理程序。网上有很多关于这个的方法。我这里没有可用的 Delphi,所以我现在不能复制工作代码。

用于以编程方式附加事件的伪代码!

myform.onShow=myGrid.formOnShowHandler;

formOnShowHandler 与 IDE 为 onShow 事件生成的函数具有相同的签名。它有一个参数,您可以使用该参数来确定哪个表单调用了处理程序,这样您就可以重用该函数并将表单放在后台并再次显示您的网格表单(例如,它将是网格的父级)。

于 2009-10-05T11:23:25.883 回答
0

Delphi 2006 引入了 OnMouseActivate 事件。如果另一个窗体可见,则主窗体的 OnMouseActivate 将允许您阻止激活主窗体。

这当然不适用于 D6。

于 2009-10-05T12:09:23.453 回答
0

大卫这不是一个有用的答案,但我认为我必须同意其他受访者的观点,即这不是正确的方法。有很多方法都可能出错,所以停下来看看另一种方法来解决你的问题可能会更好。

即使您确实设法找到了符合您要求的事件/方法/消息,您仍然需要能够处理断电的情况。

在一个稍微有用的说明中,您是否尝试过禁用主窗体直到准备好?您可以将所有控件放在面板上,然后执行

panel1.enabled := false;
于 2009-10-05T19:45:18.367 回答
0

感谢大家的帮助和建议。

这是我采用的解决方案:在“网格表单”(例如 Form2)中......

public  
    PricesNotSquare: boolean;  

在 FormDeactivate 事件中,如果价格不匹配,请将它们设置为 true。

在主窗体的 OnActivate 事件中,...

  if Assigned(Form2) and (Form2.PricesNotSquare) then  
  begin  
      ShowMessage( 'Please ensure the total Prices match before leaving the form' );  
      Form2.Show;  
      exit;  
  end;  
  // other form activate stuff here  

原来是一个简单的解决方案,只是花了一段时间才得到它。

似乎工作正常,但如果它有问题,那么我会结合发送消息的想法。

于 2009-10-06T10:28:37.187 回答