web-dev-qa-db-ja.com

別のアプリケーションの最小化されたウィンドウを復元する

別のアプリがまだ実行されていない場合に起動する、または実行されている場合は前面に表示するアプリにコードを追加しています。これには少量の相互運用性/ 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でプログラムによって別のインスタンスのウィンドウ状態を変更できないことがわかりました。ここで何が起こっているのですか?

19
KeithS

...どうやら、プロセスが提供する情報を信頼することはできません。

Process.MainWindowHandleは、アプリケーションによって作成された最初のウィンドウのウィンドウハンドルを返します。これは通常、そのアプリケーションのメイントップレベルウィンドウです。ただし、私の場合、FindWindow()の呼び出しは、復元したい実際のウィンドウのハンドルがMainWindowHandleが指しているものではないことを示しています。この場合、プロセスからのウィンドウハンドルは、プログラムがメインフォームを読み込むときに表示されるスプラッシュスクリーンのハンドルであるようです。

FindWindowが返したハンドルに対してShowWindowを呼び出すと、完全に機能します。

さらに珍しいのは、ウィンドウが開いているときに、プロセスのMainWindowHandle(ウィンドウが閉じているため無効である必要があります)が指定されている場合のSetForegroundWindow()の呼び出しが正常に機能することです。したがって、そのハンドルには、ウィンドウが最小化されているときではなく、ある程度の有効性があります。

要約すると、私の窮地に陥った場合は、FindWindowを呼び出し、外部アプリのメインウィンドウの既知の名前を渡して、必要なハンドルを取得します。

11
KeithS

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()を呼び出すことで使用できます。

私は常にアプリケーションの1つのインスタンスを実行しているため、同時に複数のインスタンスを開くことができる場合は、ロジックを少し変更することをお勧めします。

15
Ivan Yurchenko

私も同じ問題を抱えていました。私が見つけた最良の解決策は、フラグ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 

次のコメントから: http://msdn.Microsoft.com/en-us/library/ms633548%28VS.85%29.aspx

10
Pedro77

ShowWindow(handle、SW_RESTORE);を呼び出すトレイSetForegroundWindow(handle);の後

これで問題が解決する可能性があります。

3
bops

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名はプログラムで見つけることができると想定しています。)

1

私はそれが遅すぎることを知っていますが、それでも私の作業コードは次のようになっているので、後で誰かがすばやく助けを得ることができます:)

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が重要です。このメソッドは、アプリがシステムトレイでサイレントで実行されているとき、およびアプリが最小化されているときに、ウィンドウハンドルを正しく収集します。

0
Ronniee