表单如何向其所有者发送消息?

时间:2010-01-23 15:03:28

标签: delphi mdi

我编写了一个基于MDI的应用程序,其中子表单的类型不同。我现在遇到一种情况,我需要一个子表单将消息发送到另一个子表单,告诉它自己更新。第一个子表单不知道此时是否正在显示第二个子表单。

我曾想过让第一个子表单(表单A)向主MDI表单(表单0)发送一条消息,然后可以检查当前正在屏幕上显示的MDI子表单列表。如果正在显示所需的表格(表格B),那么主表格可以向该表格发送第二条消息(表格B)。

不幸的是,我无法成功编写能够使第一个子表单发信号通知主表单的代码。儿童表格如何向其所有者发送信息?

TIA, No'am

4 个答案:

答案 0 :(得分:4)

表单的所有者不一定是另一种形式。 Owner属性只是TComponent,可以是任何东西,包括nil 。但如果所有者 表单,您可以向其发送如下消息:

if Owner is TForm then
  SendMessage(TForm(Owner).Handle, am_Foo, 0, 0);

但您可能不需要了解所有者。 MDI父表单始终是项目的主要表单,主表单始终由Application.MainForm指定。发送消息到该表单的句柄。

SendMessage(Application.MainForm.Handle, am_Foo, 0, 0);

MDI儿童列表将位于Application.MainForm.MDIChildren。您的子表单可以自己检查该列表,而不是让MDI父表单执行此操作。这是一个函数,您的表单可以用来查找任何MDI子类的实例。 (如果要与通信的表单不是 MDI子项,您仍然可以使用此技术,而不是Application.MainForm.MDIChildren,搜索Screen.Forms列表。)

function FindMDIChild(ChildClass: TFormClass): TForm;
var
  i: Integer;
begin
  for i := 0 to Pred(Application.MainForm.MDIChildCount) do begin
    if Application.MainForm.MDIChild[i].InheritsFrom(ChildClass) then begin
      Result := Application.MainForm.MDIChildren[i];
      exit;
    end;
  end;
  Result := nil;
end;

你的第一个孩子班可以这样使用它:

var
  SecondChild: TForm;
begin
  SecondChild := FindMDIChild(TSecondChild);
  if Assigned(SecondChild) then begin
    SendMessage(SecondChild.Handle, am_Foo, 0, 0);
  end;
end;

当窗口消息发送到与发件人相同的线程中的窗口时(任何两个VCL表单都是如此),当发件人等待响应时,会立即调用其处理程序。这就像普通的函数调用一样,所以你可能希望跳过消息并在表单类中创建常规函数。然后你可以使用这样的代码:

var
  SecondForm: TSecondForm;
begin
  SecondForm := TSecondForm(FindMDIChild(TSecondForm));
  if Assigned(SecondForm) then begin
    SecondForm.Foo(0, 0);
  end;
end;

答案 1 :(得分:2)

这里值得使用的另一种方法是使用接口而不是消息。优点是接口更具体......消息很容易被意外重复使用,除非您非常具体地了解消息常量所在的位置。

要使用此模型,首先要创建一个全局单元(或添加到现有)以下接口声明:

type
  ISpecificSignal = interface
    {type CTRL-SHIFT-G here to generate a new guid}
    procedure PerformSignal(Handle:Integer);
  end;

然后修改MAIN表单界面,添加以下内容:

TYPE
  TMainForm = Class(TForm,ISpecificSignal)
    :
  private
    Procedure PerformSignal(Handle:Integer);
  end;

并且在PerformSignal过程的实现中如下所示:

Procedure TMainForm.PerformSignal(Handle:Integer);
var
  i: Integer;
  Intf : ISpecificSignal;
begin
  for i := 0 to Pred(Application.MainForm.MDIChildCount) do begin      
    if Supports(Application.MainForm.MDIChild[i],ISpecificSignal,Intf) and
      (Application.MainForm.MDIChild[i].Handle <> Handle) then 
      Intf.PerformSignal(Handle);
end;

在最终必须处理信号的子表单中,执行与主表单相同的步骤,但更改PerformSignal以调用所需的代码。根据需要重复。

在需要实际启动流程的表单中添加以下代码:

procedure DoSomething;
var
  Intf : ISpecificSignal;
begin
  if Supports(Application.MainForm,ISpecificSignal,Intf) then
    Intf.PerformSignal(Handle);
end; 

这种方法的最大优点是您不限于传递的参数(接口可以包含任意数量的参数,或者没有参数),并且无需使用消息泵即可运行。

编辑添加了句柄,以避免现有表单也需要来自其他表单的相同通知。

答案 2 :(得分:2)

我不知道您的具体问题,因此这可能不适用于您的情况。

我猜你的情况是FormA编辑一些影响FormB“渲染”的。我通常处理这种情况的方法是通过使值更改来触发事件。如果需要修改系统中的多个组件,我会使用“多播”事件。

简单的多机构机制如下所示。

TMultiCastNotifyEventReceiver = class(TComponent)
private
  FEvent : TNotifyEvent
public
  property Event : TNotifyEvent read FEvent write FEvent; 
end;

TMultiCastNotifyEvent = class(TComponent)
private
  //TList or TObjectList to keep a list of Listener.
  //Housekeeping code to make sure you don't keep reference to dangling pointers (I derived from TComponent to have access to the FreeNotification mechanis
public
  procedure DoEvent(Sender : Tobject); //Same parameters as TNotifyEvent
  function AddListener(NotifyEvent : TNotifyEvent) : TMultiCastNotifyEventReceiver
end;

这样,你的formA不需要知道它的父...不需要知道FormB。 FormB不需要知道FormA。要求它的工作原理是FormA和FormB必须知道 Value ,而 Value 需要知道它需要在修改时发送通知。通常会导致更好的模块化和封装。

然后,我对你试图解决的问题的性质做了很多假设。无论如何,我希望这会有所帮助。

答案 3 :(得分:0)

为什么不将消息发送到Application.Mainform.Handle,然后在Main表单循环中从0到MDIChildcount并将消息重新发送到每个消息。然后,仅在您想要的表单类中响应特定消息。我希望这符合您的需求。