Восстановить свернутое окно другого приложения - PullRequest
18 голосов
/ 01 февраля 2012

Я добавляю некоторый код в приложение, которое запустит другое приложение, если оно еще не запущено, или, если оно есть, перенесло его на передний план. Это требует небольшого количества кода взаимодействия / 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. Что здесь происходит?

Ответы [ 5 ]

15 голосов
/ 26 января 2016

Рабочий код с использованием 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().

У меня всегда работает один экземпляр приложения, поэтому, если вы можете иметь несколько открытых экземпляров одновременно, вы можете немного изменить логику.

11 голосов
/ 01 февраля 2012

... Очевидно, вы не можете доверять информации, которую вам предоставляет Процесс.

Process.MainWindowHandle возвращает дескриптор окна первого окна, созданного приложением, которое, как правило, является основным окном верхнего уровня приложения. Однако в моем случае вызов FindWindow () показывает, что дескриптор фактического окна, которое я хочу восстановить, не тот, на который указывает MainWindowHandle. Похоже, что дескриптор окна «Процесс» в данном случае является дескриптором заставки, отображаемой при загрузке основной формы программой.

Если я вызываю ShowWindow для дескриптора, который вернул FindWindow, он работает отлично.

Что еще более необычно, так это то, что когда окно открыто, вызов SetForegroundWindow (), когда ему передан MainWindowHandle процесса (который должен быть недействительным, так как это окно закрыто), работает нормально. Очевидно, что дескриптор имеет НЕКОТОРОЕ значение, но только когда окно свернуто.

Таким образом, если вы оказались в моем затруднительном положении, вызовите FindWindow, передав ему известное имя главного окна вашего внешнего приложения, чтобы получить нужный дескриптор.

10 голосов
/ 21 марта 2012

У меня была такая же проблема.Лучшее решение, которое я нашел, это позвонить ShowWindow с флагом SW_MINIMIZE, а затем с 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 

из комментариев по адресу: http://msdn.microsoft.com/en-us/library/ms633548%28VS.85%29.aspx

3 голосов
/ 09 февраля 2012

трей, вызывающий ShowWindow (handle, SW_RESTORE); после SetForegroundWindow (handle);

Это может решить вашу проблему.

1 голос
/ 20 апреля 2019

Звучит так, будто вы пытаетесь выполнить действие, которое приводит к тому же результату, что и 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 может быть найдено программно.)

...