如何强制 WPF 启动窗口到特定屏幕?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13230151/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to force WPF startup window to specific screen?
提问by Jakob Lithner
I have a WPF application that will show information on a projector through a dedicated window. I would like to configure what screen to be used for projector display and what to be used for main application window.
我有一个 WPF 应用程序,它将通过专用窗口显示投影仪上的信息。我想配置用于投影仪显示的屏幕以及用于主应用程序窗口的屏幕。
This code will generate projector output on specified screen:
此代码将在指定屏幕上生成投影仪输出:
var screen = GetProjectorScreen();
_projectorWindow = new ProjectorWindow();
_projectorWindow.Left = screen.WorkingArea.Left;
_projectorWindow.Top = screen.WorkingArea.Top;
_projectorWindow.Owner = _parentWindow;
_projectorWindow.Show();
public static Screen GetProjectorScreen()
{
var screens = Screen.AllScreens;
if (screens.Length > 1 && Settings.Default.DisplayScreen < screens.Length)
{
return screens[Settings.Default.DisplayScreen];
}
return screens[0];
}
I have tried to do the same trick with startup form, but so far without success. I tried to set Top and Left properties in MainWindow constructor but that did not work.
我试图用启动形式做同样的技巧,但到目前为止没有成功。我试图在 MainWindow 构造函数中设置 Top 和 Left 属性,但这不起作用。
The startup window is launched from App.xaml.cs by setting StartupUri:
通过设置 StartupUri 从 App.xaml.cs 启动启动窗口:
StartupUri = new Uri("Windows/MainWindow.xaml", UriKind.Relative);
Is there any other way to launch startup form? I tried to just call the constructor but that causes a crash because some resources are no longer loaded.
有没有其他方法可以启动启动表单?我试图只调用构造函数,但这会导致崩溃,因为某些资源不再加载。
回答by Jakob Lithner
I got it working. It is necessary to set WindowState to Normal before setting window location. And the setting will not work at all until the window is created, i.e. after constructor call. I therefore call the explicit setting in Windows_Loaded event. That might cause a flickering if window need to be moved, but that is acceptable to me.
我让它工作了。在设置窗口位置之前,需要将 WindowState 设置为 Normal。并且在创建窗口之前,即在构造函数调用之后,设置将根本不起作用。因此,我在 Windows_Loaded 事件中调用了显式设置。如果需要移动窗口,这可能会导致闪烁,但这对我来说是可以接受的。
The SetScreen method should also be called after screen settings have changed manually by user.
在用户手动更改屏幕设置后,也应调用 SetScreen 方法。
private void SetScreen()
{
var mainScreen = ScreenHandler.GetMainScreen();
var currentScreen = ScreenHandler.GetCurrentScreen(this);
if (mainScreen.DeviceName != currentScreen.DeviceName)
{
this.WindowState = WindowState.Normal;
this.Left = mainScreen.WorkingArea.Left;
this.Top = mainScreen.WorkingArea.Top;
this.Width = mainScreen.WorkingArea.Width;
this.Height = mainScreen.WorkingArea.Height;
this.WindowState = WindowState.Maximized;
}
}
The backup ScreenHandler utility is very simple:
备份 ScreenHandler 实用程序非常简单:
public static class ScreenHandler
{
public static Screen GetMainScreen()
{
return GetScreen(Settings.Default.MainScreenId);
}
public static Screen GetProjectorScreen()
{
return GetScreen(Settings.Default.ProjectorScreenId);
}
public static Screen GetCurrentScreen(Window window)
{
var parentArea = new Rectangle((int)window.Left, (int)window.Top, (int)window.Width, (int)window.Height);
return Screen.FromRectangle(parentArea);
}
private static Screen GetScreen(int requestedScreen)
{
var screens = Screen.AllScreens;
var mainScreen = 0;
if (screens.Length > 1 && mainScreen < screens.Length)
{
return screens[requestedScreen];
}
return screens[0];
}
}
回答by Soonts
The accepted answer no longer works on Windows 10 with per-monitor DPI in the app's manifest.
接受的答案不再适用于应用程序清单中带有每个显示器 DPI 的 Windows 10。
Here's what worked for me:
这是对我有用的:
public partial class MyWindow : Window
{
readonly Rectangle screenRectangle;
public MyWindow( System.Windows.Forms.Screen screen )
{
screenRectangle = screen.WorkingArea;
InitializeComponent();
}
[DllImport( "user32.dll", SetLastError = true )]
static extern bool MoveWindow( IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint );
protected override void OnSourceInitialized( EventArgs e )
{
base.OnSourceInitialized( e );
var wih = new WindowInteropHelper( this );
IntPtr hWnd = wih.Handle;
MoveWindow( hWnd, screenRectangle.Left, screenRectangle.Top, screenRectangle.Width, screenRectangle.Height, false );
}
void Window_Loaded( object sender, RoutedEventArgs e )
{
WindowState = WindowState.Maximized;
}
}
Just setting Left/Top doesn't work. Based on my tests, per-monitor DPI awareness only kicks in after window is already created and placed on some monitor. Before that, apparently Left/Top properties of the window scale with DPI of the primary monitor.
只是设置左/上不起作用。根据我的测试,每个显示器的 DPI 感知仅在窗口已经创建并放置在某个显示器上之后才会启动。在此之前,显然窗口的 Left/Top 属性随主监视器的 DPI 缩放。
For some combinations of per-monitor DPI and monitors layout, this caused a bug where setting Left/Top properties to the pixels values of System.Windows.Forms.Screen rectangle caused the window to be positioned somewhere else.
对于每显示器 DPI 和显示器布局的某些组合,这会导致错误,将 Left/Top 属性设置为 System.Windows.Forms.Screen 矩形的像素值会导致窗口位于其他位置。
The above workaround is only suitable for maximizing, it does not always sets the correct size of the window. But at least it sets correct top-left corner which is enough for the maximize to work correctly.
上述解决方法仅适用于最大化,它并不总是设置正确的窗口大小。但至少它设置了正确的左上角,这足以使最大化正常工作。

