【问题标题】:Restore a minimized window of another application恢复另一个应用程序的最小化窗口
【发布时间】:2012-02-24 08:22:31
【问题描述】:

我正在向一个应用程序添加一些代码,如果该应用程序尚未运行,它将启动另一个应用程序,或者如果它已运行,则将其置于最前面。这需要少量的互操作/WinAPI 代码,我从其他网站获得了示例,但似乎无法在 Win7 中工作。

如果窗口处于某种可见状态,那么 API 的 SetForegroundWindow 方法就像一种享受(这将是主要情况,根据公司政策,如果外部应用程序正在运行,则不应将其最小化)。但是,如果它被最小化(例外但很重要,因为我的应用程序在这种情况下似乎什么都不做),这个方法和 ShowWindow/ShowWindowAsync 都不会真正从任务栏恢复窗口;所有方法都只是突出显示任务栏按钮。

这是代码;大部分都可以正常工作,但是无论我发送什么命令,对 ShowWindow() 的调用(我也尝试过 ShowWindowAsync)永远不会做我想要的:

[DllImport("user32.dll")]
    private static extern int SetForegroundWindow(IntPtr hWnd);

    private const int SW_SHOWNORMAL = 1;
    private const int SW_SHOWMAXIMIZED = 3;
    private const int SW_RESTORE = 9;

    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

...

//The app is named uniquely enough that it can't be anything else,
//and is not normally launched except by this one.
//so this should normally return zero or one instance
var processes = Process.GetProcessesByName("ExternalApp.exe");

        if (processes.Any()) //a copy is already running
        {
            //I can't currently tell the window's state,
            //so I both restore and activate it
            var handle = processes.First().MainWindowHandle;
            ShowWindow(handle, SW_RESTORE); //GRR!!!
            SetForegroundWindow(handle);
            return true;
        }

        try
        {
            //If a copy is not running, start one.
            Process.Start(@"C:\Program Files (x86)\ExternalApp\ExternalApp.exe");
            return true;
        }
        catch (Exception)
        {
            //fallback for 32-bit OSes
            Process.Start(@"C:\Program Files\ExternalApp\ExternalApp.exe");
            return true;
        }

我尝试了 SHOWNORMAL (1)、SHOWMAXIMIZED (3)、RESTORE (9) 和其他几个大小调整命令,但似乎没有任何效果。想法?

编辑:我发现一些我认为可以正常工作的其他代码存在问题。对 GetProcessesByName() 的调用没有找到进程,因为我正在寻找可执行文件名称,它不是进程名称。这导致我认为正在运行的代码实际上根本没有执行。我认为它正在工作,因为外部应用程序显然也会检测到副本已经在运行并尝试激活该当前实例。我从搜索的进程名称中删除了“.exe”,现在代码执行;然而,这似乎是倒退了一步,因为现在当我调用 ShowWindow[Async] 时任务栏按钮甚至没有突出显示。所以,我现在知道我的应用程序和我正在调用的外部应用程序都不能在 Win7 中以编程方式更改不同实例的窗口状态。这是怎么回事?

【问题讨论】:

  • 在尝试恢复它之前,您是否尝试过让窗口可见,例如:ShowWindow(handle, SW_SHOW);
  • 我尝试了很多排列,包括首先调用 ShowWindow。问题是Process.MainWindowHandle提供的线程不是“主窗口”的线程。

标签: c# winapi windows-7 interop


【解决方案1】:

使用FindWindow方法的工作代码:

[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string className, string windowTitle);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, ShowWindowEnum flags);

[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hwnd);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowPlacement(IntPtr hWnd, ref Windowplacement lpwndpl);

private enum ShowWindowEnum
{
    Hide = 0,
    ShowNormal = 1, ShowMinimized = 2, ShowMaximized = 3,
    Maximize = 3, ShowNormalNoActivate = 4, Show = 5,
    Minimize = 6, ShowMinNoActivate = 7, ShowNoActivate = 8,
    Restore = 9, ShowDefault = 10, ForceMinimized = 11
};

private struct Windowplacement
{
    public int length;
    public int flags;
    public int showCmd;
    public System.Drawing.Point ptMinPosition;
    public System.Drawing.Point ptMaxPosition;
    public System.Drawing.Rectangle rcNormalPosition;
}

private void BringWindowToFront()
{
    IntPtr wdwIntPtr = FindWindow(null, "Put_your_window_title_here");

    //get the hWnd of the process
    Windowplacement placement = new Windowplacement();
    GetWindowPlacement(wdwIntPtr, ref placement);

    // Check if window is minimized
    if (placement.showCmd == 2)
    {
        //the window is hidden so we restore it
        ShowWindow(wdwIntPtr, ShowWindowEnum.Restore);
    }

    //set user's focus to the window
    SetForegroundWindow(wdwIntPtr);
}

您可以拨打BringWindowToFront()使用它。

我总是运行一个应用程序实例,因此如果您可以同时打开多个实例,您可能需要稍微更改逻辑。

【讨论】:

  • 请注意...IsIconic()GetWindowPlacement() 一样可以检查窗口是否已最小化...而且处理起来更简单。
  • 如果您尝试恢复的应用程序窗口在提升的权限(即作为管理员)下运行并且当前进程不是,那么ShowWindow() 不起作用(静默失败)毫无价值。要解决此问题,还必须从以相同提升权限运行的进程调用 ShowWindow()
【解决方案2】:

... 显然您不能相信流程提供给您的信息。

Process.MainWindowHandle 返回应用程序创建的第一个窗口的窗口句柄,通常是该应用程序的主顶级窗口。但是,在我的情况下,对 FindWindow() 的调用表明我要恢复的实际窗口的句柄不是 MainWindowHandle 指向的。在这种情况下,Process 的窗口句柄似乎是程序加载主窗体时显示的初始屏幕的句柄。

如果我在 FindWindow 返回的句柄上调用 ShowWindow,它会完美运行。

更不寻常的是,当窗口打开时,对 SetForegroundWindow() 的调用,当给定进程的 MainWindowHandle(该窗口已关闭时应该是无效的)时,工作正常。很明显,这个句柄有一些有效性,只是在窗口最小化时没有。

总而言之,如果您发现自己处于我的困境中,请调用 FindWindow,将您的外部应用程序主窗口的已知名称传递给它,以获得您需要的句柄。

【讨论】:

  • There is no "Main window" concept,该对象的作者只是假设第一个就是它。此外,MSDN 表示“将创建指定窗口的线程带到前台”
  • 好消息。显然我掉进了陷阱。无论如何,FindWindow,只要你知道你想要的窗口的标题,就会给出正确的答案。
  • +1 @KeithS 你在这里使用什么查找窗口方法。我认为您在谈论互操作方法?
  • 是的,在与 ShowWindow 相同的 WinAPI dll 中对 FindWindow 进行 P/Invoke 调用。
  • +1 - FindWindow 也是我的答案。 MainWindowHandle 工作正常,直到我将窗口隐藏在最小化并让 TrayIcon 可见。在这种情况下,MainWindowHandle 返回了 TrayIcon 的句柄......一定会喜欢那个漂亮的 win32 API 吧? :D
【解决方案3】:

我遇到了同样的问题。我找到的最佳解决方案是使用标志SW_MINIMIZE 调用ShowWindow,然后使用SW_RESTORE。 :D

另一种可能的解决方案:

// Code to display a window regardless of its current state
ShowWindow(hWnd, SW_SHOW);  // Make the window visible if it was hidden
ShowWindow(hWnd, SW_RESTORE);  // Next, restore it if it was minimized
SetForegroundWindow(hWnd);  // Finally, activate the window 

来自 cmets:http://msdn.microsoft.com/en-us/library/ms633548%28VS.85%29.aspx

【讨论】:

  • ShowWindow(hWnd, SW_SHOW) 和 `SetForeGroundWindow(hWnd) 对我来说是关键。仅使用“还原”不起作用。太棒了!
【解决方案4】:

托盘调用 ShowWindow(handle, SW_RESTORE); SetForegroundWindow(handle) 之后;

这可能会解决您的问题。

【讨论】:

    【解决方案5】:

    听起来您正在尝试执行与 alt-tabbing 具有相同结果的操作,如果窗口被最小化,它会返回窗口,而如果它被最大化则“记住”它。

    NativeMethods.cs:

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    
    // Specify your namespace here
    namespace <your.namespace>
    {
        static class NativeMethods
        {
            // This is the Interop/WinAPI that will be used
            [DllImport("user32.dll")]
            static extern void SwitchToThisWindow(IntPtr hWnd, bool fUnknown);
        }
    }
    

    主要代码:

    // Under normal circumstances, only one process with one window exists
    Process[] processes = Process.GetProcessesByName("ExternalApp.exe");
    if (processes.Length > 0 && processes[0].MainWindowHandle != IntPtr.Zero)
    {
        // Since this simulates alt-tab, it restores minimized windows to their previous state
        SwitchToThisWindow(process.MainWindowHandle, true);
        return true;
    }
    // Multiple things are happening here
    // First, the ProgramFilesX86 variable automatically accounts for 32-bit or 64-bit systems and returns the correct folder
    // Secondly, $-strings are the C# shortcut for string.format() (It automatically calls .ToString() on each variable contained in { })
    // Thirdly, if the process was able to start, the return value is not null
    try { if (Process.Start($"{System.Environment.SpecialFolder.ProgramFilesX86}\\ExternalApp\\ExternalApp.exe") != null) return true; }
    catch
    {
        // Code for handling an exception (probably FileNotFoundException)
        // ...
        return false;
    }
    // Code for when the external app was unable to start without producing an exception
    // ...
    return false;
    

    我希望这提供了一个更简单的解决方案。

    (一般规则:如果一个字符串值是序数的,即它属于某个东西而不只是一个值,那么最好以编程方式获取它。更改内容时会为自己省去很多麻烦。在在这种情况下,我假设安装位置可以转换为全局常量,并且可以通过编程找到 .exe 名称。)

    【讨论】:

      【解决方案6】:

      我知道为时已晚,我的工作代码仍然如下,以便以后有人可以快速获得帮助:)

      using System.Runtime.InteropServices;
      using System.Diagnostics;
      
      [DllImport("user32.dll")]
      static extern bool SetForegroundWindow(IntPtr hWnd);
      
      [DllImport("user32.dll")]
      [return: MarshalAs(UnmanagedType.Bool)]
      static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
      
      [DllImport("user32.dll", EntryPoint = "FindWindow")]
      public static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
      
      private static void ActivateApp(string processName)
      {
          Process[] p = Process.GetProcessesByName(processName);
      
          if (p.Length > 0)
          {
              IntPtr handle = FindWindowByCaption(IntPtr.Zero, p[0].ProcessName);
              ShowWindow(handle, 9); // SW_RESTORE = 9,
              SetForegroundWindow(handle);
          }
      } 
      
      ActivateApp(YOUR_APP_NAME);
      

      其实FindWindowByCaption是这里的关键,当应用在系统托盘中静默运行以及应用最小化时,该方法会正确收集窗口句柄。

      【讨论】:

        猜你喜欢
        • 2014-06-30
        • 2019-10-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多