4

I encountered a problem in WPF (C#), I need to position a popup on a certain point within the parent window. I get the parent position with Application.Current.MainWindow.Left and Application.Current.MainWindow.Top, it works as long as I don't move the window from one monitor to the other with the Windows shortcut Windows + Shift + /. If I use the shortcut the properties stay the same as they were before moving the window.

The window has to be WindowState.Maximized, it works if it is not maximized.

I also tried using Window.GetWindow(this) instead of Application.Current.MainWindow, result is the same.

It seems as if the positionchanged event doesn't occur for the Application.Current.MainWindow and it doesn't update the Left and Top properties.

I didn't find anything on this on SO or Google. A workaround, hint or solution are greatly appreciated.

0009laH
  • 1,960
  • 13
  • 27
kirbby
  • 226
  • 3
  • 8
  • 1
    I'm not sure I understand the `Windows + Shift + Cursor Left/Right` shortcut, do you mean `Windows + Shift + Left/Right arrow`? Also I cant' reproduce the issue when using this shortcut. Maybe your `Application.Current.MainWindow` is not set to the correct `Window`? – Corentin Pane Nov 18 '19 at 14:07
  • yes I mean the arrow keys, my bad. It is the correct window, if I drag and drop with the mouse it changes the Left as expected. – kirbby Nov 18 '19 at 14:10
  • Can you please create a new empty WPF application and replace the default constructor of your `MainWindow` with the following debug code: ``public MainWindow() { InitializeComponent(); async Task Test() { while (true) { Console.WriteLine((Left, Top)); await Task.Delay(500); } }; Test(); }``. It displays the current location of the `Window` in the output window and it works for me even when using the shortcut. Does it not work for you? – Corentin Pane Nov 18 '19 at 14:14
  • I reproduced the error with your test scenario, the problem only occurs when the WindowState is Maximized. – kirbby Nov 18 '19 at 14:28
  • 1
    Try to use __actualLeft_ and __actualTop_ fields. The code for this particular case can be found [here](https://stackoverflow.com/questions/3600874/window-actualtop-actualleft). – Jackdaw Nov 18 '19 at 14:46
  • These _actualLeft and _actualTop fields should be used only when a window in the maximized state. – Jackdaw Nov 18 '19 at 14:55

2 Answers2

4

Try to use this:

    WindowInteropHelper windowInteropHelper = new WindowInteropHelper(Application.Current.MainWindow);
    Screen screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);

The Screen Bounds property provides the coordinates of the whole window and the WorkingArea the boundaries of the area without titlebar and docked windows.

Mihaeru
  • 533
  • 2
  • 6
  • I went with your answer because it creates less overhead than @Corentin Pane 's answer, I am already referencing System.Window.Forms and need to check the position only once and not track the window or anything. Now I only check the screen bounds if the `Application.Current.MainWindow` is in state maximized, otherwise i use the MainWindow itself. Works like a charme now. Thanks to you both for your help. – kirbby Nov 19 '19 at 09:55
  • @kirbby yeah I didn't realize tracking was not required:) – Corentin Pane Nov 19 '19 at 16:40
  • I didn't mention it explicitly in the post, your answer is better if you need tracking, that's why I upvoted your answer aswell, thanks :) – kirbby Nov 20 '19 at 07:23
2

You should use the win32 API to retrieve the information you want as the events exposed by .NET aren't enough to detect this scenario.

There are two things you have to do:

  1. Listen to the WndProc message that corresponds to a window movement (full list here). The one we want is WM_MOVE and is equal to 0x0003. See this thread for details.
  2. Be able to determine the real location of the Window even when it's in Maximized state, which we can do by using the GetWindowRect method. See this thread for details.

Here would be the assembled code that prints the top-left location of your Window when it is moved, including using the shortcut you describe.

public partial class MainWindow : Window {

    HwndSource source;

    const short WM_MOVE = 0x0003;

    public MainWindow() {
        InitializeComponent();
        // Loaded event needed to make sure the window handle has been created.
        Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e) {
        // Subscribe to win32 level events
        source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        source.AddHook(new HwndSourceHook(WndProc));
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
        if (msg == WM_MOVE) {
            Console.WriteLine("Window has moved!");
            GetWindowRect(new HandleRef(this, new WindowInteropHelper(this).Handle), out RECT rect);
            Console.WriteLine("New location is " + (rect.Left, rect.Top));
        }
        return IntPtr.Zero;
    }

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
}
Corentin Pane
  • 4,794
  • 1
  • 12
  • 29