WinForms 应用程序之间的 SendMessage - 窗体需要焦点

4
我正在使用Windows消息API在两个Windows Forms应用程序之间进行通信。我将应用程序的一部分表单取出并制作成自己的应用程序,这样当它正在加载时,用户仍然可以在另一个表单中工作,该表单现在是从一个独立的应用程序中而不是原来的应用程序中打开的。
我需要能够在两个应用程序之间通信,以便现在单独的应用程序表单可以告诉主应用程序要打开什么。
我在主应用程序的主表单上使用此代码,它运行得很好...除非主表单没有焦点。
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    Try
        Select Case m.Msg
            Case &H400
                BS.BuildString(m.LParam)
            Case Else
                MyBase.WndProc(m)
        End Select
    Catch ex As Exception

    End Try
End Sub

我以前从未使用过Windows消息API,我只是通过查找如何在窗体之间通信而得到了这个。所以我对此还很陌生。经过进一步的研究,我知道我需要一个仅用于处理消息的窗口。但我不知道该怎么做。我查看了一些文章和解决方案,如这篇,我认为我遇到的问题是我不知道如何实现它。
编辑1
以下是第二个应用程序如何查找并发送给主应用程序的方法。
                'used to send a message using SendMessage API to the 
                'main app to open the ID
                Private WithEvents BS As New BuildString 
                'get this running process
                Dim proc As Process = Process.GetCurrentProcess()
                'get all other (possible) running instances
                Dim processes As Process() = Process.GetProcessesByName("ProcessName")

                If processes.Length > 0 Then
                    'iterate through all running target applications
                    For Each p As Process In processes
                        'now send the ID to the running instance of the main app
                        BS.PostString(p.MainWindowHandle, &H400, 0, "ID:" & ID)
                    Next
                Else
                    MessageBox.Show("Main application not running")
                End If

这是两个应用中都有的 BuildString 类的代码。
Imports System.Text

Public Class BuildString

Private Declare Function PostMessage Lib "user32.dll" Alias "PostMessageA" (ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
Public Event StringOK(ByVal Result As String)
Private hwnd As Integer = 0
Private wMsg As Integer = 0
Private wParam As Integer = 0
Private lParam As String = ""
Private tempA(-1) As Byte
Private enc As Encoding = Encoding.UTF8

Public Property Encode() As Encoding
    Get
        Return enc
    End Get
    Set(ByVal value As Encoding)
        enc = value
    End Set
End Property

Public Sub BuildString(ByVal b As IntPtr)
    If b <> 0 Then
        'build temp array
        Dim tempB(tempA.Length) As Byte
        tempA.CopyTo(tempB, 0)
        tempB(tempA.Length) = b
        ReDim tempA(tempB.Length - 1)
        tempB.CopyTo(tempA, 0)
    Else
        'decode byte array to string
        Dim s As String
        If enc Is Encoding.UTF8 Then
            s = Encoding.UTF8.GetString(tempA)
        ElseIf enc Is Encoding.Unicode Then
            s = Encoding.Unicode.GetString(tempA)
        ElseIf enc Is Encoding.ASCII Then
            s = Encoding.ASCII.GetString(tempA)
        Else
            s = Encoding.Default.GetString(tempA)
        End If
        'send out result string via event
        RaiseEvent StringOK(s)
        ReDim tempA(-1)
    End If
End Sub

Public Sub PostString(ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As String)
    Me.hwnd = hwnd
    Me.wMsg = wMsg
    Me.wParam = wParam
    Me.lParam = lParam
    'create a new thread to post window message
    Dim t As Threading.Thread
    t = New Threading.Thread(AddressOf SendString)
    t.Start()
End Sub

Private Sub SendString()
    'create byte array
    Dim ba() As Byte
    'encode string to byte array
    If enc Is Encoding.UTF8 Then
        ba = Encoding.UTF8.GetBytes(lParam)
    ElseIf enc Is Encoding.Unicode Then
        ba = Encoding.Unicode.GetBytes(lParam)
    ElseIf enc Is Encoding.ASCII Then
        ba = Encoding.ASCII.GetBytes(lParam)
    Else
        ba = Encoding.Default.GetBytes(lParam)
    End If
    Dim i As Integer
    For i = 0 To ba.Length - 1
        'start post message
        PostMessage(hwnd, wMsg, wParam, ba(i))
    Next
    'post a terminator message to destination window
    PostMessage(hwnd, wMsg, wParam, 0)
End Sub

结束类

这是主应用程序上的代码,一旦消息被解码就运行。

Private Sub SB_StringOK(ByVal Result As String) Handles BS.StringOK
    Dim sArray() As String = Result.Split(";")
    'rest of the code to open the ID
End Sub

1
一个窗体不需要处于活动状态(或拥有焦点)才能接收消息;正如您所提到的,它只需要一个消息队列。请展示您在第二个应用程序中使用的代码来查找第一个应用程序的句柄,并随后发送您的消息。 - Justin Ryan
我已经向@JustinRyan展示了其余的代码。"正如你所提到的",我在哪里提到表单不需要处于活动状态?消息队列?我会查一下。 - Keevan
您的消息代码似乎是正确的,我已经阅读了它。您的接收窗口是否有任何非典型操作,例如最小化到系统托盘,或者在某些时候被隐藏(除了处于非活动状态)?只要窗口存在,它就应该能够接收消息。如果以上情况都不是问题所在,我建议尝试另一种获取窗口句柄的方法(例如FindWindow),而不是通过进程名称搜索。 - Justin Ryan
当另一个窗体具有焦点时,没有任何反应。我必须将WndProc子程序放在具有焦点的其他窗体中才能接收消息,但我不想这样做,因为我必须将其添加到50个窗体中。接收窗口不会最小化或发生任何事情。它始终处于打开状态(与最小化相反),以便查看,通常只是隐藏在另一个窗体后面。 - Keevan
这将向我表明,由于某种原因,当窗体处于非活动状态时,MainWindowHandle 返回了一个不同的句柄(尽管我的理解是这不是预期行为)。我会使用断点来检查返回的句柄(或将它们输出到调试窗口)以确保句柄不会改变。 - Justin Ryan
我不确定句柄,但FindWindow工作得非常完美! - Keevan
3个回答

3

感谢@JustinRyan的帮助,我使用FindWindow找到了解决方法。

我将这个方法添加到了我的第二个应用程序中来发送信息。

Private Declare Function FindWindow1 Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Integer

我不是在查找应用程序的过程中,而是直接将想要的窗口发送到它所在的进程中。

Dim parenthwnd As Integer = FindWindow1(Nothing, "Form Name")
BS.PostString(parenthwnd, &H400, 0, "ID:" & ID)

它正是我需要的。

我不确定如何展示正确答案,因为答案在评论中。 请告诉我在这种情况下给用户应有的信用的适当礼仪。


请务必点击此答案旁边的复选标记,以便其他人知道此问题已解决。 - Justin Ryan
是的,我必须等待24小时才能这样做。谢谢! - Keevan

1

与其使用此API,您可以尝试使用新线程并在该第二线程上创建第二个窗体,以便两个窗体在不同的线程上运行,但仍然可以成为同一项目的一部分。

Dim Form2Thread As New System.Threading.Thread(Address of MethodName)


Sub Form1Load() Handles Form1.Shown
 Form2Thread.Start()
End Sub
Sub MethodName()
 'All your form creation code here
 'Form2.Show()
End Sub

这很简单,但缺点是您不能直接从原始线程上运行的方法编辑Form2的控件或属性。所有对Form2的更改都必须通过第二个线程进行。
(有一个例外,但事情开始变得更加复杂,请搜索如何进行跨线程操作)
另一种解决方案是使用Background Worker组件。有很多教程可以使用它们。

1

[添加更多信息:]

根据Process.MainWindowHandle

主窗口是由当前拥有焦点(顶级表单)的进程打开的窗口。如果主窗口已更改,则必须使用Refresh方法刷新Process对象以获取当前主窗口句柄。

顶级表单在此处定义(作为MainWindowHandle链接),如下所示:

顶级表单是没有父表单或其父表单为桌面窗口的窗口。顶级窗口通常用作应用程序中的主窗体。

这解释了为什么在表单不活动时消息被发送到其他地方。因此,虽然使用Process属性可能适用于获取单个表单应用程序的窗口句柄,但在其他情况下则不可靠。

偶然的是,FindWindow也说明:

检索与指定字符串匹配的类名和窗口名称的顶级窗口的句柄。该函数不搜索子窗口。

然而,FindWindow(和 FindWindowEx)对焦点(激活)不加区分,因此会返回特定窗口的句柄,而不考虑它是否在顶部。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接