【问题标题】:Does FindWindow work in FMX?FindWindow 在 FMX 中工作吗?
【发布时间】:2015-08-17 19:32:00
【问题描述】:

我尝试在 Windows 中的两个应用程序之间交换数据。我使用来自Zarko Gajic 的示例。它使用 Windows 消息传递,示例效果很好。有一个发送者和一个接收应用程序以及一些共享数据:全部为 VCL 编码。代码如下所示。

unit SenderMain;

{ How to send information (String, Image, Record) between two Delphi applications
http://delphi.about.com/od/windowsshellapi/a/wm_copydata.htm

Learn how to send the WM_CopyData message between two Delphi
applications to exchange information and make two applications
communicate. The accompanying source code demonstrates how to
send a string, record (complex data type) and even graphics
to another application.

~Zarko Gajic
About Delphi Programming
http://delphi.about.com
}
interface

uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls, ExtCtrls, TlHelp32,
   shared_data;

type
  TSenderMainForm = class(TForm)
    Button_Send_Data: TButton;
    Log: TListBox;

    procedure Button_Send_DataClick   (Sender: TObject);

   protected
      procedure Loaded; override;
      procedure SendString (send_string: aString);
   end; // Class: TSenderMainForm //

var
   SenderMainForm: TSenderMainForm;

implementation

{$R *.dfm}

{**************** NextWindow ****************}
function NextWindow (wnd: Thandle; list: Tstringlist):boolean; stdcall;
{This is the callback function which is called by EnumWindows procedure
 for each top-level window.  Return "true" to keep retrieving, return
 "false" to stop EnumWindows  from calling}
var
   title: array [0..255] of char;
   receiverHandle: HWND;//THandle;
   win_name: PChar;
   s: AnsiString;
begin
   getwindowtext (wnd, title, 256);
   s := AnsiString (pchar(@title));
   if (s <> '') and (list.indexof (string (s)) < 0) then
   begin
      win_name := PaString (s);
      receiverHandle := FindWindow (win_name, nil); // Find receiving app
      s := AnsiString (Format ('%s (%d)', [s, receiverHandle]));
      list.add (string (s));
   end; // if
   result:=true;
end;

procedure TSenderMainForm.Loaded;
begin
   inherited Loaded;

   enumwindows (@nextwindow, lparam (Log.Items)); {pass the list as a parameter}
end;

procedure TSenderMainForm.SendString (send_string: aString);
var copyDataStruct: TCopyDataStruct; { Declared in Windows.pas: TCopyDataStruct}
    receiverHandle: THandle;
    res: integer;
begin
// Copy string to CopyDataStruct
   copyDataStruct.dwData := 1; //use it to identify the message contents
   copyDataStruct.cbData := (1 + Length (send_string)) * SizeOf (Char);
   copyDataStruct.lpData := PaString (send_string);

   receiverHandle := FindWindow (PaString (cClassName), nil); // Find receiving app
   if receiverHandle = 0 then // not found
   begin
      Log.Items.Add ('CopyData Receiver NOT found!');
   end else // found, send message
   begin
      res := SendMessage (receiverHandle, WM_COPYDATA, Integer(Handle), Integer(@copyDataStruct));
      Log.Items.Add (Format ('String sent, len = %d, result = %d', [copyDataStruct.cbData, res]));
      Log.Items.Add ('"' + PaString (copyDataStruct.lpData) + '"');
   end; // if
end; // SendString

procedure TSenderMainForm.Button_Send_DataClick (Sender: TObject);
begin
   SendString (ParamStr (0));
end;
    ====================== Unit copyDataReceiver ================
unit ReceiverMain;
{ How to send information (String, Image, Record) between two Delphi applications
http://delphi.about.com/od/windowsshellapi/a/wm_copydata.htm }
interface

uses
   Windows, Messages,
   SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls, ExtCtrls, shared_data;

type
  TReceiverMainForm = class (TForm)
    Log: TListBox;

    procedure FormCreate(Sender: TObject);

  private
    procedure WMCopyData (var Msg: TWMCopyData); message WM_COPYDATA;
    procedure WMSignalClose (var Msg: TMessage);  message WM_SIGNAL_CLOSE;

    procedure HandleCopyDataString (copyDataStruct: PCopyDataStruct);
  end;

var ReceiverMainForm: TReceiverMainForm;

implementation

{$R *.dfm}

procedure TReceiverMainForm.FormCreate (Sender: TObject);
begin
  Log.Clear;
end;

procedure TReceiverMainForm.WMSignalClose (var Msg: TMessage);
var pfn: PaString;
    fn: aString;
begin
   Log.Items.Add (Format ('Signal received, WParam = %d, LParam = %d', [Msg.WParam, Msg.LParam]));
   pfn := PaString (Msg.LParam);
   fn  := aString (pfn);
   Log.Items.Add (fn);
end;

procedure TReceiverMainForm.WMCopyData (var Msg: TWMCopyData);
var copyDataType: Int32;
begin
   copyDataType := Msg.CopyDataStruct.dwData;

   //Handle of the Sender
   Log.Items.Add (Format ('WM_CopyData (type: %d) from: %d', [copyDataType, msg.From]));
   HandleCopyDataString (Msg.CopyDataStruct);

   //Send something back
   msg.Result := Log.Items.Count;
end;

procedure TReceiverMainForm.HandleCopyDataString (copyDataStruct: PCopyDataStruct);
var mess: aString;
begin
   mess := aString (PaString (copyDataStruct.lpData));
   Log.Items.Add (Format ('Received string of length %d at %s', [Length (mess), DateToStr (Now)]));
   Log.Items.Add ('"' + mess + '"');
end;

end.
    ================ unit shared_data ==========================
unit shared_data;

interface

uses Messages;

const
   WM_SIGNAL_CLOSE = WM_APP + 2012;
   ARG_AMI_1       = 285;
   ARG_AMI_2       = 1;
   cClassName      = 'TReceiverMainForm';

type
   aString  = string;
   PaString = PChar;

implementation

end.

发送方应用程序的关键在于它向接收方发送 WM_COPYDATA。为了找到接收者,FindWindow 与接收应用程序的名称(硬编码)一起使用,它返回窗口句柄。如果句柄为零,则显示错误。

当我在 FMX 应用程序中复制它时,会出现问题。 FMX 接收部分不起作用,而 VCL 接收器可以接收来自 VCL 发送器或 FMX 发送器的消息。 FMX接收器的代码如下所示。

因为我不确定窗口的名称,所以我枚举了所有窗口,将数字句柄添加到每个窗口名称,并将其显示在发送方的列表框中。所有句柄都为零。我有两个问题:

  1. 为什么枚举中的所有句柄都为零?
  2. 为什么我无法向 FMX 接收应用程序发送消息?

任何帮助将不胜感激。

unit copyDataReceiver;

interface

uses
   System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
   FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts, FMX.ListBox,
   Windows, Messages, shared_data;

type
   TReceiverMainForm = class (TForm)
    Log: TListBox;

    procedure FormCreate(Sender: TFMXObject);

  private
    procedure WMCopyData (var Msg: TWMCopyData); message WM_COPYDATA;
    procedure WMSignalClose (var Msg: TMessage);  message WM_SIGNAL_CLOSE;

    procedure HandleCopyDataString (copyDataStruct: PCopyDataStruct);
  end;

var ReceiverMainForm: TReceiverMainForm;

implementation

{$R *.fmx}

procedure TReceiverMainForm.FormCreate (Sender: TFMXObject);
begin
  Log.Clear;
end;

procedure TReceiverMainForm.WMSignalClose (var Msg: TMessage);
var pfn: PaString;
    fn: aString;
begin
   Log.Items.Add (Format ('Signal received, WParam = %d, LParam = %d', [Msg.WParam, Msg.LParam]));
   pfn := PaString (Msg.LParam);
   fn  := aString (pfn);
   Log.Items.Add (fn);
end;

procedure TReceiverMainForm.WMCopyData (var Msg: TWMCopyData);
var copyDataType: Int32;
begin
   copyDataType := Msg.CopyDataStruct.dwData;

   //Handle of the Sender
   Log.Items.Add (Format ('WM_CopyData (type: %d) from: %d', [copyDataType, msg.From]));
   HandleCopyDataString (Msg.CopyDataStruct);

   //Send something back
   msg.Result := Log.Items.Count;
end;

procedure TReceiverMainForm.HandleCopyDataString (copyDataStruct: PCopyDataStruct);
var mess: aString;
begin
   mess := aString (PaString (copyDataStruct.lpData));
   Log.Items.Add (Format ('Received string of length %d at %s', [Length (mess), DateToStr (Now)]));
   Log.Items.Add ('"' + mess + '"');
end;

end.

【问题讨论】:

    标签: windows delphi firemonkey sendmessage


    【解决方案1】:

    FindWindow 在 FMX 下的工作方式相同。问题是向您找到的窗口发送消息不会导致它们被路由到表单的消息处理程序。

    相反,您应该做您应该一直做的事情,即使是使用 VCL。那就是使用您控制其生命周期的已知窗口。请记住,VCL 窗口可以重新创建。换句话说,您可能拥有另一个进程中某个窗口的窗口句柄,但该窗口可能在您有机会向其发送消息之前被销毁。

    通过使用AllocateHWndCreateWindow 创建一个不会重新创建的窗口来解决此问题。您可以控制其生命周期的窗口。您必须设计一种方法让其他进程发现您的窗口。我个人会使用带有已知类名的CreateWindow,然后使用EnumWindows 枚举顶级窗口以查找具有该类名的窗口。

    【讨论】:

    • 您非常肯定消息没有路由到消息句柄:总是这样吗?据我了解:我的应用程序主窗口可以由操作系统重新创建?
    • 是的,对于 FMX 表单,message 指令无效。
    • 有人可能会说这个问题是stackoverflow.com/questions/20109686/…的欺骗
    • 部分:那篇文章解释了为什么我的接收应用程序不起作用。感谢您提供了很多信息的链接。它没有解释第一个问题:为什么在枚举中返回所有 FindWindow 句柄为零?
    • EnumWindows 不会枚举句柄为零的窗口,因为那不是有效的窗口句柄。你的那部分问题对我来说毫无意义。这就是我忽略它的原因。无论如何,很抱歉您觉得这对您没有帮助。
    【解决方案2】:
    1. 为什么我无法向 FMX 接收应用程序发送消息?

    对于 VCL-Forms,ClassName 是从表单名称派生而来的 然后在名称中添加前导“T”。 例如如果您有一个名为 MyForm 的表单,则 ClassName 是 TMyForm。 Self.ClassName 返回此名称并调用 Winapi.Windows.FindWindow(PChar(Self.ClassName), nil) 返回正确的 处理。

    使用 FMX-Forms,您将收到以类似方式构建的 ClassName。 对于 FMX-Forms,ClassName 是从表单的名称派生而来的 在表格名称中添加前导“FMT”。 但是,Self.ClassName 返回的 ClassName 与 VCL-Forms 的相同。

    例如如果您有一个名为 MyFMXForm 的表单,则 ClassName 是 FMTMyFMXForm 但 Self.ClassName 返回TMyFMXForm。 因此,尝试使用该 ClassName 获取窗口句柄会失败。 正确的调用是 Winapi.Windows.FindWindow(PChar('FMTMyFMXForm'), nil));.

    【讨论】:

      猜你喜欢
      • 2018-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-28
      • 1970-01-01
      • 2021-11-26
      • 1970-01-01
      相关资源
      最近更新 更多