Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force WPF startup window to specific screen?

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.

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.

The startup window is launched from App.xaml.cs by setting StartupUri:

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.

like image 227
Jakob Lithner Avatar asked Nov 05 '12 10:11

Jakob Lithner


People also ask

How do I change a WPF window to a page?

Answers. You can either just host the Page in a Frame. Alternatively, just make a new Window, and put the same xaml content in the Window class. Migrate any code in the code behind to your new Window class.

How do I change the startup form in WPF?

You are looking for way of changing startup form. Application. Run(new MyForm()); Change MyForm to your other form.

How do I open a WPF window in full screen?

Just set the WindowState to Maximized , and the WindowStyle to None . Also setting the Window as topmost will make sure no other Window shows up over your window.


2 Answers

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.

The SetScreen method should also be called after screen settings have changed manually by user.

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:

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];
    }
}
like image 87
Jakob Lithner Avatar answered Oct 10 '22 13:10

Jakob Lithner


The accepted answer no longer works on Windows 10 with per-monitor DPI in the app’s manifest.

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.

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.

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.

like image 38
Soonts Avatar answered Oct 10 '22 13:10

Soonts