5

在我的 GUI 应用程序中,我使用 C#Process类来生成可能启动窗口的外部进程。子进程窗口可能通过第三方 API 调用显示,因此并不总是可以获得窗口句柄。有什么方法可以确保子进程的窗口显示在主应用程序窗口的前面?

4

4 回答 4

6

通常的方法是:

1. 获取 Process.Start()
2 返回的 Process 类实例。查询 Process.MainWindowHandle
3 。调用非托管 Win32 API 函数“ShowWindow”或“SwitchToThisWindow”

您的问题的诀窍是“子进程窗口可能会通过第三方 API 调用显示”。在这种情况下,您将需要获取生成的 exe 的窗口句柄和枚举子窗口。一旦您拥有 API 调用后显示的表单的句柄,您就可以使用BringWindowToTop API

我使用How To Enumerate Windows Using the WIN32 API作为灵感进行了一个小测试。创建一个包含 1 个表单和 2 个按钮的 Windows 应用程序:

public partial class Form1 : Form
{
[DllImport("user32.dll", SetLastError = true)]
static extern bool BringWindowToTop(IntPtr hWnd);

[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);

public Form1()
{
    InitializeComponent();
}

private System.IntPtr hWnd;
private void button1_Click(object sender, EventArgs e)
{
    Process p = Process.Start(@"C:\TFS\Sandbox\3rdPartyAppExample.exe");         
    try
    {
        do
        {
            p.Refresh();
        }
        while (p.MainWindowHandle.ToInt32() == 0);

        hWnd = new IntPtr(p.MainWindowHandle.ToInt32());
    }
    catch (Exception ex)
    {
        //Do some stuff...
        throw;
    }
}

private void button2_Click(object sender, EventArgs e)
{
    3rdPartyAppExample.Form1 f = new 3rdPartyAppExample.Form1();
    f.ShowForm2();
    //Bring main external exe window to front
    BringWindowToTop(hWnd);
    //Bring child external exe windows to front
    BringExternalExeChildWindowsToFront(hWnd);
}

private void BringExternalExeChildWindowsToFront(IntPtr parent)
{
    List<IntPtr> childWindows = GetChildWindows(hWnd);
    foreach (IntPtr childWindow in childWindows)
    {
        BringWindowToTop(childWindow);
    }
}

// <summary>
/// Returns a list of child windows
/// </summary>
/// <param name="parent">Parent of the windows to return</param>
/// <returns>List of child windows</returns>
public static List<IntPtr> GetChildWindows(IntPtr parent)
{
    List<IntPtr> result = new List<IntPtr>();
    GCHandle listHandle = GCHandle.Alloc(result);
    try
    {
        EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
        EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
    }
    finally
    {
        if (listHandle.IsAllocated)
            listHandle.Free();
    }
    return result;
}

/// <summary>
/// Callback method to be used when enumerating windows.
/// </summary>
/// <param name="handle">Handle of the next window</param>
/// <param name="pointer">Pointer to a GCHandle that holds a reference to the list to fill</param>
/// <returns>True to continue the enumeration, false to bail</returns>
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
    GCHandle gch = GCHandle.FromIntPtr(pointer);
    List<IntPtr> list = gch.Target as List<IntPtr>;
    if (list == null)
    {
        throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
    }
    list.Add(handle);
    //  You can modify this to check to see if you want to cancel the operation, then return a null here
    return true;
}

/// <summary>
/// Delegate for the EnumChildWindows method
/// </summary>
/// <param name="hWnd">Window handle</param>
/// <param name="parameter">Caller-defined variable; we use it for a pointer to our list</param>
/// <returns>True to continue enumerating, false to bail.</returns>
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);

}

3rdPartyAppExample 是一个带有 2 个表单的 Winform 应用程序。我引用这个应用程序并调用 Form1 的 Public 方法来显示 Form2:

public partial class Form1 : Form
{        
    public Form1()
    {
        InitializeComponent();
    }
    public void ShowForm2()
    {
        var f = new Form2();
        f.Show();
    }

或者,您可能希望检查 Windows 标题

  hInst = ProcessStart("calc.exe")

  // Begin search for handle
  hWndApp = GetWinHandle(hInst)

  If hWndApp <> 0 Then
   // Init buffer
   buffer = Space$(128)

   // Get caption of window
   numChars = GetWindowText(hWndApp, buffer, Len(buffer))

另一个解决方案(不是很稳定)在这里讨论:http: //www.shloemi.com/2012/09/solved-setforegroundwindow-win32-api-not-always-works/

诀窍是通过附加线程(使用 AttachThreadInput API)使窗口“认为”我们的进程和目标窗口(hwnd)相关。

关于myTopForm.TopMost = true答案,这不适用于外部应用程序。

于 2012-12-19T04:15:42.830 回答
1

它与“过程”无关,与 Windows 焦点有关。

您可能可以执行以下操作:

http://msdn.microsoft.com/en-us/library/3saxwsad.aspx

public void MakeOnTop()
{
  myTopForm.TopMost = true;
}

但总的来说,您需要 Window 句柄(生成的进程可以很容易地找出自己的句柄)并且您需要这样的东西:

http://support.microsoft.com/kb/186431

于 2012-12-14T15:34:39.387 回答
0

您可以尝试使用 SetWindowPos api 调用将窗口发送到窗口中 z 顺序的后面。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms633545(v=vs.85).aspx

[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, uint wFlags);

const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;

static readonly IntPtr HWND_BOTTOM = new IntPtr(1);

static void SendWindowBack()
{
    var hWnd = this.Handle;
    SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}

(用于 winforms 而不是 wpf 的一些编辑。)

于 2012-12-14T15:36:34.203 回答
0

没有直接且 100% 可靠的方法可以做到这一点。我会以一种有点迂回的方式来解决这个问题:

获取您启动的进程的进程 ID,然后使用该EnumWindows函数的 C# 等效项。在回调中,调用GetWindowThreadProcessId获取窗口的进程ID,将其与刚刚启动的窗口的进程ID进行比较。如果它们匹配,那么您可以使用 将该窗口置于前台SetForegroundWindow

如果有问题的应用程序呈现多个顶级窗口,这将找到第一个,这可能不是正确的。但是在没有来自应用程序的一些“合作”的情况下,我认为我概述的内容是你能做的最好的。

于 2012-12-18T16:19:07.437 回答