Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Window Top and Left values are not updated correctly when maximizing a Window in .NET 4

Tags:

c#

.net

wpf

I am trying to center a Window to the owner window. I also need the child window to move along the owner window. A cross-post on the MSDN WPF forum's can be found here.

To achieve this I subscribe to the LocationChanged and SizeChanged events (and also the StateChanged event) of the owner of my child window. When those events are triggered I recompute the location of child window. I do this in the code-behind of the child window.

The code is very straight forward:

Top = Owner.Top + ((Owner.ActualHeight - ActualHeight) / 2);
Left = Owner.Left + ((Owner.ActualWidth - ActualWidth) / 2);

If you compile and run the sample program I provided you will see that it works when the main window is as-is, and moved around. So that part works.

The problem arises when the owner window is maximized. (And after being maximized, set back to normal.) Because I subscribe to three events I enter the relocate function three times. After printing out the owner data I get different results. Most annoyingly the Top and Left values of the owner window are off. It seems it gets the correct Top and Left values when the state changes, but then the ActualWidth and ActualHeight values are wrong. When the LocationChanged or SizeChanged events are triggered the ActualWidth and ActualHeight values are OK, but the Top and Left values are incorrect. It seems these are the previous values. How can this be? What is causing this? And is there a proper fix for this?

Since the same code seemed to have worked in .net 3.5 I am under the impression something changed in .net 4. (Or I had a weird timing issue which caused the problem not to appear.) But I cannot find any documented change in this part.

.NET 3.5:

OnOwnerLocationChanged
T: -8; L: -8; W: 640; H: 480
OnOwnerStateChanged
T: -8; L: -8; W: 640; H: 480
OnOwnerSizeChanged
T: -8; L: -8; W: 1936; H: 1066

.NET 4.0:

OnOwnerLocationChanged
T: -8; L: -8; W: 640; H: 480
OnOwnerStateChanged
T: 494; L: 33; W: 640; H: 480
OnOwnerSizeChanged
T: 494; L: 33; W: 1936; H: 1066

So the main question remains: why are the Top and Left values of the owner incorrect?

like image 478
Jensen Avatar asked Mar 21 '12 20:03

Jensen


2 Answers

The comment from Mataniko regarding the migration issues in .NET 4.0 was correct. Since my code was working when the WindowState was set to Normal, I could keep that. I just had to foresee something when the WindowState was Maximized.

I implemented the native GetWindowRect() function to achieve this, as it could give me the proper dimensions.

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

// Make sure RECT is actually OUR defined struct, not the windows rect.
public static RECT GetWindowRectangle(Window window)
{
    RECT rect;
    GetWindowRect((new WindowInteropHelper(window)).Handle, out rect);

    return rect;
}

Next, when the Owner.WindowState is Maximized, we use the GetWindowRectangle function to get the actual dimensions. We don't care about a border at this point, but if needed it can be incorporated using the GetSystemMetrics function.

if (Owner.WindowState == WindowState.Maximized)
{
    var rect = GetWindowRectangle(Owner);

    Top = rect.Top + ((rect.Bottom - ActualHeight) / 2);
    Left = rect.Left + ((rect.Right - ActualWidth) / 2);
}
like image 92
Jensen Avatar answered Oct 17 '22 16:10

Jensen


I don't know if it's a bug in .NET 4.0 or intended behavior. My suspicion is that there is a race condition between the events registered and actually firing. Even though LocationChanged is fired first, SizeChanged is registered first with the still incorrect values of the Location. You can easily get around this by creating a local variable in the child window that registers the Owner top and left in the LocationChanged event.

Example:

private Point _ownerLocation;

private void OnOwnerLocationChanged(object sender, EventArgs e)
    {
        Console.WriteLine("OnOwnerLocationChanged");
        _ownerLocation = new Point(Owner.Top, Owner.Left);
        SetLocationToOwner();
    }

private void SetLocationToOwner()
    {
        if (IsVisible && (Owner != null))
        {
            Console.WriteLine("T: {0}; L: {1}; W: {2}; H: {3}", Owner.Top, Owner.Left, Owner.ActualWidth, Owner.ActualHeight);

            Top = _ownerLocation.X + ((Owner.ActualHeight - ActualHeight) / 2);
            Left = _ownerLocation.Y + ((Owner.ActualWidth - ActualWidth) / 2);
        }
    }
like image 31
Mataniko Avatar answered Oct 17 '22 15:10

Mataniko