WPFでウィンドウを前面に移動する
質問
WPFアプリケーションをデスクトップの前面に表示するにはどうすればよいですか?これまで私は試しました:
SwitchToThisWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle, true);
SetWindowPos(new WindowInteropHelper(Application.Current.MainWindow).Handle, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
SetForegroundWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle);
ジョブを実行しているものはありません( Marshal.GetLastWin32Error()
はこれらの操作が正常に完了したと言っており、各定義のP / Invoke属性には SetLastError = true
)。
新しい空のWPFアプリケーションを作成し、タイマーを使用して SwitchToThisWindow
を呼び出すと、期待どおりに動作するため、元のケースで動作しない理由がわかりません。
編集:グローバルホットキーと組み合わせてこれを実行しています。
解決 11
まあ、私は回避策を見つけました。ホットキーの実装に使用されるキーボードフックから呼び出しを行っています。一時停止してBackgroundWorkerに呼び出した場合、呼び出しは期待どおりに機能します。それは手間がかかりますが、元々機能していなかった理由がわかりません。
void hotkey_execute()
{
IntPtr handle = new WindowInteropHelper(Application.Current.MainWindow).Handle;
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(delegate
{
Thread.Sleep(10);
SwitchToThisWindow(handle, true);
});
bg.RunWorkerAsync();
}
他のヒント
myWindow.Activate();
ウィンドウを最前面に移動してアクティブにします。
それは、私が誤解し、常に手前に表示する動作を望まない限り、トリックを行う必要があります。その場合、次のようにします。
myWindow.TopMost = true;
ウィンドウを一番上に表示するソリューションを見つけましたが、通常のウィンドウとして動作します:
if (!Window.IsVisible)
{
Window.Show();
}
if (Window.WindowState == WindowState.Minimized)
{
Window.WindowState = WindowState.Normal;
}
Window.Activate();
Window.Topmost = true; // important
Window.Topmost = false; // important
Window.Focus(); // important
最初にロードするときにウィンドウを前面に表示する必要がある場合は、次を使用する必要があります。
private void Window_ContentRendered(object sender, EventArgs e)
{
this.Topmost = false;
}
private void Window_Initialized(object sender, EventArgs e)
{
this.Topmost = true;
}
これを簡単にコピーアンドペーストするには-
このクラスの DoOnProcess
メソッドを使用して、プロセスのメインウィンドウをフォアグラウンドに移動します(ただし、他のウィンドウからフォーカスを奪うことはありません)
public class MoveToForeground
{
[DllImportAttribute("User32.dll")]
private static extern int FindWindow(String ClassName, String WindowName);
const int SWP_NOMOVE = 0x0002;
const int SWP_NOSIZE = 0x0001;
const int SWP_SHOWWINDOW = 0x0040;
const int SWP_NOACTIVATE = 0x0010;
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
public static void DoOnProcess(string processName)
{
var allProcs = Process.GetProcessesByName(processName);
if (allProcs.Length > 0)
{
Process proc = allProcs[0];
int hWnd = FindWindow(null, proc.MainWindowTitle.ToString());
// Change behavior by settings the wFlags params. See http://msdn.microsoft.com/en-us/library/ms633545(VS.85).aspx
SetWindowPos(new IntPtr(hWnd), 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
}
}
}
HTH
この質問はかなり古いことはわかっていますが、この正確なシナリオに出会ったばかりで、実装したソリューションを共有したいと考えました。
このページのコメントで述べたように、提案されたソリューションのいくつかはXPで動作しません。これは私のシナリオでサポートする必要があります。 @Matthew Xavierによる一般的にこれは悪いUXプラクティスであるという意見には同意しますが、完全に妥当なUXである場合があります。
WPFウィンドウを最上部に表示するソリューションは、グローバルホットキーを提供するために使用しているのと同じコードによって実際に提供されました。 ジョセフクーニーのブログ記事には、元のコードを含む彼のコードサンプルへのリンクが含まれています。
コードを少しクリーンアップして変更し、System.Windows.Windowの拡張メソッドとして実装しました。 XP 32ビットとWin7 64ビットでテストしましたが、どちらも正常に動作します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace System.Windows
{
public static class SystemWindows
{
#region Constants
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_SHOWWINDOW = 0x0040;
#endregion
/// <summary>
/// Activate a window from anywhere by attaching to the foreground window
/// </summary>
public static void GlobalActivate(this Window w)
{
//Get the process ID for this window's thread
var interopHelper = new WindowInteropHelper(w);
var thisWindowThreadId = GetWindowThreadProcessId(interopHelper.Handle, IntPtr.Zero);
//Get the process ID for the foreground window's thread
var currentForegroundWindow = GetForegroundWindow();
var currentForegroundWindowThreadId = GetWindowThreadProcessId(currentForegroundWindow, IntPtr.Zero);
//Attach this window's thread to the current window's thread
AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, true);
//Set the window position
SetWindowPos(interopHelper.Handle, new IntPtr(0), 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW);
//Detach this window's thread from the current window's thread
AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, false);
//Show and activate the window
if (w.WindowState == WindowState.Minimized) w.WindowState = WindowState.Normal;
w.Show();
w.Activate();
}
#region Imports
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
[DllImport("user32.dll")]
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
#endregion
}
}
この問題が発生した他の人にこのコードが役立つことを願っています。
ユーザーが別のアプリケーションと対話している場合、自分のアプリケーションを前面に表示できない場合があります。一般的なルールとして、プロセスは、そのプロセスが既にフォアグラウンドプロセスである場合にのみフォアグラウンドウィンドウを設定することを期待できます。 (Microsoftは SetForegroundWindow()に制限を文書化しています。 MSDNエントリ。)これは次の理由によります。
- ユーザー&quot; owns&quot;前景。たとえば、ユーザーが入力している最中に別のプログラムがフォアグラウンドを盗み、少なくともワークフローを中断すると、変更に気付くまで1つのアプリケーションを対象としたキーストロークが犯罪者によって誤って解釈されるため、意図しない結果を引き起こす可能性があります。
- 2つのプログラムのそれぞれが、ウィンドウがフォアグラウンドかどうかを確認し、そうでない場合はフォアグラウンドに設定しようとすることを想像してください。 2番目のプログラムが実行されると、タスクスイッチごとにフォアグラウンドが2つの間でバウンドするため、コンピューターは役に立たなくなります。
Shellオブジェクトを介してAccessアプリケーションから呼び出されるWPFアプリケーションでも同様の問題が発生しました。
私の解決策は以下のとおりです-XPおよびWin7 x64で、x86ターゲットにコンパイルされたアプリで動作します。
Alt-Tabをシミュレートするよりも、これを実行したいです。
void Window_Loaded(object sender, RoutedEventArgs e)
{
// make sure the window is normal or maximised
// this was the core of the problem for me;
// even though the default was "Normal", starting it via shell minimised it
this.WindowState = WindowState.Normal;
// only required for some scenarios
this.Activate();
}
これは回答が遅く、研究者に役立つかもしれないことを知っています
if (!WindowName.IsVisible)
{
WindowName.Show();
WindowName.Activate();
}
このページのいくつかの回答が間違っている理由
-
window.Focus()
を使用する回答はすべて間違っています。- なぜですか?通知メッセージがポップアップした場合、
window.Focus()
は、ユーザーがその時点で入力しているものからフォーカスを奪います。これは、特にポップアップが非常に頻繁に発生する場合、エンドユーザーにとって非常にイライラします。
- なぜですか?通知メッセージがポップアップした場合、
-
window.Activate()
を使用する回答はすべて間違っています。- なぜですか?親ウィンドウも表示されます。
-
window.ShowActivated = false
を省略した回答はすべて間違っています。- なぜですか?メッセージがポップアップすると、別のウィンドウからフォーカスを奪ってしまい、非常に迷惑です!
- ウィンドウの非表示/表示に
Visibility.Visible
を使用しない回答はすべて間違っています。- なぜですか? Citrixを使用している場合、ウィンドウを閉じたときにウィンドウが折りたたまれないと、画面に奇妙な黒い長方形のホールドが残ります。したがって、
window.Show()
およびwindow.Hide()
は使用できません。
- なぜですか? Citrixを使用している場合、ウィンドウを閉じたときにウィンドウが折りたたまれないと、画面に奇妙な黒い長方形のホールドが残ります。したがって、
本質的に:
- ウィンドウは、アクティブになったときに他のウィンドウからフォーカスを奪ってはいけません。
- ウィンドウは、表示されているときに親をアクティブにしないでください;
- ウィンドウはCitrixと互換性があります。
MVVMソリューション
このコードは、Citrixと100%互換性があります(画面の空白部分はありません)。通常のWPFとDevExpressの両方でテストされています。
この回答は、他のウィンドウの前に常に小さな通知ウィンドウが必要なユースケースを対象としています(ユーザーが設定でこれを選択した場合)。
この回答が他の回答よりも複雑に思える場合、それは堅牢なエンタープライズレベルのコードであるためです。このページの他の回答のいくつかは単純ですが、実際には機能しません。
XAML-添付プロパティ
この添付プロパティをウィンドウ内の任意の UserControl
に追加します。添付プロパティは次のようになります。
-
Loaded
イベントが発生するまで待機します(そうしないと、ビジュアルツリーを検索して親ウィンドウを見つけることができません)。 - ウィンドウが表示されるかどうかを確認するイベントハンドラを追加します。
任意の時点で、添付プロパティの値を反転することにより、ウィンドウを前面または前面に設定できます。
<UserControl x:Class="..."
...
attachedProperties:EnsureWindowInForeground.EnsureWindowInForeground=
"{Binding EnsureWindowInForeground, Mode=OneWay}">
C#-ヘルパーメソッド
public static class HideAndShowWindowHelper
{
/// <summary>
/// Intent: Ensure that small notification window is on top of other windows.
/// </summary>
/// <param name="window"></param>
public static void ShiftWindowIntoForeground(Window window)
{
try
{
// Prevent the window from grabbing focus away from other windows the first time is created.
window.ShowActivated = false;
// Do not use .Show() and .Hide() - not compatible with Citrix!
if (window.Visibility != Visibility.Visible)
{
window.Visibility = Visibility.Visible;
}
// We can't allow the window to be maximized, as there is no de-maximize button!
if (window.WindowState == WindowState.Maximized)
{
window.WindowState = WindowState.Normal;
}
window.Topmost = true;
}
catch (Exception)
{
// Gulp. Avoids "Cannot set visibility while window is closing".
}
}
/// <summary>
/// Intent: Ensure that small notification window can be hidden by other windows.
/// </summary>
/// <param name="window"></param>
public static void ShiftWindowIntoBackground(Window window)
{
try
{
// Prevent the window from grabbing focus away from other windows the first time is created.
window.ShowActivated = false;
// Do not use .Show() and .Hide() - not compatible with Citrix!
if (window.Visibility != Visibility.Collapsed)
{
window.Visibility = Visibility.Collapsed;
}
// We can't allow the window to be maximized, as there is no de-maximize button!
if (window.WindowState == WindowState.Maximized)
{
window.WindowState = WindowState.Normal;
}
window.Topmost = false;
}
catch (Exception)
{
// Gulp. Avoids "Cannot set visibility while window is closing".
}
}
}
使用法
これを使用するには、ViewModelにウィンドウを作成する必要があります。
private ToastView _toastViewWindow;
private void ShowWindow()
{
if (_toastViewWindow == null)
{
_toastViewWindow = new ToastView();
_dialogService.Show<ToastView>(this, this, _toastViewWindow, true);
}
ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(_toastViewWindow);
HideAndShowWindowHelper.ShiftWindowIntoForeground(_toastViewWindow);
}
private void HideWindow()
{
if (_toastViewWindow != null)
{
HideAndShowWindowHelper.ShiftWindowIntoBackground(_toastViewWindow);
}
}
追加リンク
通知ウィンドウが常に表示画面に戻るようにするためのヒントについては、私の回答をご覧ください: WPFで、ウィンドウが画面外にある場合にウィンドウを画面上に移動する方法は?。
まあ、これは非常にホットなトピックなので...ここに私のために働くものです。ウィンドウを表示できない場合、Activate()がエラーを出すため、このようにしないとエラーになります。
Xaml:
<Window ....
Topmost="True"
....
ContentRendered="mainWindow_ContentRendered"> .... </Window>
コードビハインド:
private void mainWindow_ContentRendered(object sender, EventArgs e)
{
this.Topmost = false;
this.Activate();
_UsernameTextBox.Focus();
}
これは、ウィンドウを一番上に表示するための唯一の方法でした。次に、マウスでフォーカスを設定せずにボックスに入力できるようにアクティブにします。 control.Focus()は、ウィンドウがActive()でない限り機能しません;
現在開いているウィンドウを表示するには、これらのDLLをインポートします。
public partial class Form1 : Form
{
[DllImportAttribute("User32.dll")]
private static extern int FindWindow(String ClassName, String WindowName);
[DllImportAttribute("User32.dll")]
private static extern int SetForegroundWindow(int hWnd);
およびプログラム内で指定されたタイトルのアプリを検索します(最初の文字なしでタイトルを書き込みます(インデックス&gt; 0))
foreach (Process proc in Process.GetProcesses())
{
tx = proc.MainWindowTitle.ToString();
if (tx.IndexOf("Title of Your app WITHOUT FIRST LETTER") > 0)
{
tx = proc.MainWindowTitle;
hWnd = proc.Handle.ToInt32(); break;
}
}
hWnd = FindWindow(null, tx);
if (hWnd > 0)
{
SetForegroundWindow(hWnd);
}
問題は、フックからコードを呼び出すスレッドがランタイムによって初期化されていないため、ランタイムメソッドの呼び出しが機能しないことです。
おそらく、Invokeを実行してコードをUIスレッドにマーシャリングして、ウィンドウを前面に表示するコードを呼び出すことができます。
これらのコードは常に正常に機能します。
最初に、XAMLでアクティブ化されたイベントハンドラーを設定します:
Activated="Window_Activated"
メインウィンドウのコンストラクタブロックに次の行を追加します。
public MainWindow()
{
InitializeComponent();
this.LocationChanged += (sender, e) => this.Window_Activated(sender, e);
}
アクティブ化されたイベントハンドラー内で次のコードをコピーします:
private void Window_Activated(object sender, EventArgs e)
{
if (Application.Current.Windows.Count > 1)
{
foreach (Window win in Application.Current.Windows)
try
{
if (!win.Equals(this))
{
if (!win.IsVisible)
{
win.ShowDialog();
}
if (win.WindowState == WindowState.Minimized)
{
win.WindowState = WindowState.Normal;
}
win.Activate();
win.Topmost = true;
win.Topmost = false;
win.Focus();
}
}
catch { }
}
else
this.Focus();
}
これらの手順は正常に機能し、他のすべてのウィンドウを親ウィンドウに前面に表示します。
ウィンドウを非表示にしようとしている場合、たとえばウィンドウを最小化する場合、使用していることがわかりました
this.Hide();
それを正しく非表示にしてから、単に使用する
this.Show();
ウィンドウが再び最上位のアイテムとして表示されます。
この質問に別の解決策を追加したかっただけです。この実装は、CaliBurnがメインウィンドウの表示を担当する私のシナリオで機能します。
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<IMainWindowViewModel>();
Application.MainWindow.Topmost = true;
Application.MainWindow.Activate();
Application.MainWindow.Activated += OnMainWindowActivated;
}
private static void OnMainWindowActivated(object sender, EventArgs e)
{
var window = sender as Window;
if (window != null)
{
window.Activated -= OnMainWindowActivated;
window.Topmost = false;
window.Focus();
}
}
アクティブなウィンドウがイベントを処理したウィンドウに戻るため、PreviewMouseDoubleClickハンドラー内にそのウィンドウを表示するコードを配置しないことを忘れないでください。 MouseDoubleClickイベントハンドラーに配置するか、e.HandledをTrueに設定してバブリングを停止します。
私の場合、リストビューでPreviewMouseDoubleClickを処理し、e.Handled = trueを設定していなかったため、元のウィンドウにフォーカスを戻すMouseDoubleClickイベントが発生しました。
再利用しやすいように拡張メソッドを作成しました。
using System.Windows.Forms;
namespace YourNamespace{
public static class WindowsFormExtensions {
public static void PutOnTop(this Form form) {
form.Show();
form.Activate();
}// END PutOnTop()
}// END class
}// END namespace
フォームコンストラクターでの呼び出し
namespace YourNamespace{
public partial class FormName : Form {
public FormName(){
this.PutOnTop();
InitalizeComponents();
}// END Constructor
} // END Form
}// END namespace