Значения Window Top и Left не обновляются корректно при максимизации окна в.NET 4

Я пытаюсь центрировать окно к окну владельца. Мне также нужно, чтобы дочернее окно перемещалось по окну владельца. Перекресток на форуме MSDN WPF можно найти здесь.

Для этого я подписываюсь на события LocationChanged и SizeChanged (а также событие StateChanged) владельца моего дочернего окна. Когда эти события запускаются, я пересчитываю местоположение дочернего окна. Я делаю это в коде для дочернего окна.

Код очень прост:

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

Если вы скомпилируете и запустите программу-образец, которую я предоставил, вы увидите, что она работает, когда основное окно является как есть, и перемещается. Так что эта часть работает.

Проблема возникает, когда окно владельца максимизируется. (И после максимизации, вернитесь в нормальное состояние.) Поскольку я подписываюсь на три события, я ввожу функцию переместить три раза. После распечатки данных владельца я получаю разные результаты. Наиболее раздражает Top и Left значения окна владельца. Кажется, что он получает правильные значения Top и Left при изменении состояния, но тогда значения ActualWidth и ActualHeight ошибочны. Когда SizeChanged события LocationChanged или SizeChanged значения ActualWidth и ActualHeight в порядке, но значения Top и Left неверны. Кажется, это предыдущие значения. Как это может быть? Чем это вызвано? И есть ли правильное решение для этого?

Поскольку такой же код работал в.net 3.5, у меня под впечатлением что-то изменилось в.net 4. (Или у меня была странная проблема времени, из-за которой проблема не появлялась.) Но я не могу найти никаких задокументированных изменений в этом часть.

.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

Таким образом, остается основной вопрос: почему неверные значения Top и Left?

Ответ 1

Комментарий от Mataniko относительно проблем миграции в.NET 4.0 был правильным. Поскольку мой код работал, когда WindowState был установлен в Normal, я мог бы это сохранить. Мне просто нужно было что-то предвидеть, когда WindowState был Maximized.

Я реализовал функцию GetWindowRect() для достижения этой цели, поскольку это могло бы дать мне правильные размеры.

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

Затем, когда Owner.WindowState Maximized, мы используем функцию GetWindowRectangle для получения фактических измерений. На данный момент мы не заботимся о границе, но при необходимости ее можно включить с помощью функции GetSystemMetrics.

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

    Top = rect.Top + ((rect.Bottom - ActualHeight) / 2);
    Left = rect.Left + ((rect.Right - ActualWidth) / 2);
}

Ответ 2

Я не знаю, если это ошибка в.NET 4.0 или предполагаемое поведение. Мое подозрение в том, что между зарегистрированными событиями и фактическим увольнением существует состояние гонки. Несмотря на то, что LocationChanged запускается первым, SizeChanged регистрируется сначала с еще неверными значениями местоположения. Вы можете легко обойти это, создав локальную переменную в дочернем окне, которая регистрирует верхнюю и верхнюю части Владельца в событии LocationChanged.

Пример:

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

Ответ 3

Ты пробовал..

int left, top, width, height;
bool maximised;

if (WindowState == FormWindowState.Maximized)
{
    maximised = true;
    left = RestoreBounds.X;
    top = RestoreBounds.Y;
    width = RestoreBounds.Width;
    height = RestoreBounds.Height;
}
else
{
    maximised = false;
    left = Left;
    top = Top;
    width = Width;
    height = Height;
}

И вернуться (порядок важен)...

StartPosition = FormStartPosition.Manual;
Location = new Point(left, top);
Size = new Size(width, height);

if (maximised)
    WindowState = FormWindowState.Maximized;

Ответ 4

Мне нравится решение, предложенное в этом аналогичном вопросе. Самый опрошенный ответ использовал единственный метод, чтобы получить значение слева от окна, независимо от состояния окна, используя отражение вместо собственного кода. Он также работал на самом верху. Здесь код на всякий случай, если вопрос/ответ удаляется.

public static class WindowExtensions
{
    /// <summary>
    /// Gets the window left.
    /// </summary>
    /// <param name="window">The window.</param>
    /// <returns></returns>
    public static double GetWindowLeft(this Window window)
    {
        if (window.WindowState == WindowState.Maximized)
        {
            var leftField = typeof(Window).GetField("_actualLeft", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            return (double)leftField.GetValue(window);
        }
        else
            return window.Left;
    }

    /// <summary>
    /// Gets the window top.
    /// </summary>
    /// <param name="window">The window.</param>
    /// <returns></returns>
    public static double GetWindowTop(this Window window)
    {
        if (window.WindowState == WindowState.Maximized)
        {
            var topField = typeof(Window).GetField("_actualTop", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            return (double)topField.GetValue(window);
        }
        else
            return window.Top;
    }
}