如何将WPF启动窗口强制显示在特定屏幕上?

10

我有一个WPF应用程序,它将通过专用窗口在投影仪上显示信息。 我想配置哪个屏幕用于投影仪显示,哪个屏幕用于主应用程序窗口。

以下代码将在指定的屏幕上生成投影仪输出:

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];
}

我试图在启动窗体中尝试相同的技巧,但迄今为止没有成功。 我尝试在MainWindow构造函数中设置Top和Left属性,但这并没有起作用。

启动窗口是通过在App.xaml.cs中设置StartupUri来启动的:

StartupUri = new Uri("Windows/MainWindow.xaml", UriKind.Relative);

有没有其他方法可以启动启动窗体? 我尝试直接调用构造函数,但由于某些资源不再加载,这会导致崩溃。


有一个主要的问题浮现出来 - 你怎么知道电脑连接了投影仪?如果没有投影仪会发生什么? - ChrisF
2个回答

10

我搞定了。在设置窗口位置之前必须将WindowState设置为Normal。并且在创建窗口后才能生效,即在构造函数调用后。因此,我在Windows_Loaded事件中调用了显式设置。如果需要移动窗口,则可能会闪烁,但这对我来说是可以接受的。

如果用户手动更改了屏幕设置,则还应该调用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;
    }
}

备份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];
    }
}

7

原先被接受的答案在Windows 10上已经失效,特别是在应用程序清单中使用每个显示器DPI。

以下方法适用于我:

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;
    }
}

仅仅设置Left/Top属性并不能起到作用。根据我的测试,仅当窗口已经被创建并放置在某个监视器上时,才会启用每个监视器的DPI感知。在此之前,窗口的Left/Top属性显然与主监视器的DPI成比例。
对于某些组合的每个监视器的DPI和监视器布局,这会导致一个错误:将Left/Top属性设置为System.Windows.Forms.Screen矩形的像素值会导致窗口定位到其他位置。
上述解决方法仅适用于最大化,它并不总是正确地设置窗口的大小。但至少它能够正确地设置左上角位置,从而使最大化正常工作。

为避免使用窗体引用符合初始要求,请使用接受的答案获取可用屏幕列表。使用投影仪,您可以使用IsPrimary标志作为指示器。投影仪通常不是主屏幕。 - benJima

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接