私のGUIアプリケーションでは、C#Process
クラスを使用して、ウィンドウを起動する可能性のある外部プロセスを生成しています。サブプロセスウィンドウはサードパーティのAPI呼び出しを介して表示される場合があるため、ウィンドウハンドルを取得できるとは限りません。サブプロセスのウィンドウがメインアプリケーションウィンドウの前に表示されるようにする方法はありますか?
4 に答える
通常の方法は次のとおりです。
1。Process.Start()
2によって返されるProcessクラスインスタンスを取得します。クエリProcess.MainWindowHandle3
。管理されていないWin32API関数「ShowWindow」または「SwitchToThisWindow」を呼び出します
あなたの質問の秘訣は、「サブプロセスウィンドウはサードパーティのAPI呼び出しを介して表示される可能性がある」ということです。その場合、生成されたexeのウィンドウハンドルを取得し、子ウィンドウを列挙する必要があります。API呼び出しの後に表示されるフォームのハンドルを取得したら、BringWindowToTopAPIを使用できます。
インスピレーションとしてWIN32APIを使用してWindowsを列挙する方法を使用して小さなテストをまとめました。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
、それは外部アプリケーションでは機能しません。
それは「プロセス」とは何の関係もなく、すべてがWindowsフォーカスと関係があります。
あなたはこのようなことをすることができるかもしれません:
http://msdn.microsoft.com/en-us/library/3saxwsad.aspx
public void MakeOnTop()
{
myTopForm.TopMost = true;
}
ただし、一般的には、ウィンドウハンドルが必要であり(生成されたプロセスはそれ自体のハンドルを簡単に把握できます)、次のようなものが必要になります。
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);
}
(wpfの代わりにwinformsに使用する編集のカップル。)
これを行うための直接かつ100%信頼できる方法はありません。私はこれについていくぶん回りくどい方法で行きます:
起動したプロセスのプロセスIDを取得し、EnumWindows
関数と同等のC#を使用します。コールバックで、を呼び出しGetWindowThreadProcessId
てウィンドウのプロセスIDを取得し、起動したばかりのウィンドウのプロセスIDと比較します。それらが一致した場合は、。を使用してそのウィンドウを前面に表示できますSetForegroundWindow
。
問題のアプリケーションが複数のトップレベルウィンドウを表示する場合、これは最初のウィンドウを検索しますが、これは正しいウィンドウではない可能性があります。しかし、アプリケーションからの「協力」がない場合、私が概説したことはあなたができる最善のことだと思います。