Восстановление Размера/Положения Окна С Помощью Нескольких Мониторов

многие сообщения о восстановлении позиции и размера WinForm.

примеры:

но мне еще предстоит найти код для этого с несколькими мониторами.

то есть, если я закройте мое приложение .NET Winform с окном на мониторе 2, я хочу, чтобы он сохранил размер, местоположение и состояние windows в настройках приложения, чтобы он мог позже восстановить монитор 2 при перезапуске приложения. Было бы неплохо, если бы, как в Примере codeproject выше, он включал некоторые проверки здравомыслия, например, если сохраненное местоположение в основном за пределами экрана, оно "исправляет" его. Или если сохраненное местоположение находится на мониторе, которого больше нет (например, мой ноутбук теперь сам по себе без моего второго монитора), то это правильно перемещает его на монитор 1.

какие мысли?

моя среда: C#, .NET 3.5 или ниже, VS2008

6 ответов


попробуйте этот код. Достопримечательности:

  • проверяет, является ли окно (частично) видимым в рабочей области любого экрана. Е. Г. перетащив его за панель задач или перемещение его полностью закадровым сбрасывает установки по умолчанию Windows.
  • сохраняет правильные границы, даже если форма минимизирована или максимизирована (общая ошибка)
  • сохраняет WindowState правильно. Экономия FormWindowState.Минимизация отключена по дизайну.

границы и состояние хранятся в appsettings с их соответствующим типом, поэтому нет необходимости выполнять синтаксический анализ строк. Пусть фреймворк творит свою магию сериализации.

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();

        // this is the default
        this.WindowState = FormWindowState.Normal;
        this.StartPosition = FormStartPosition.WindowsDefaultBounds;

        // check if the saved bounds are nonzero and visible on any screen
        if (Settings.Default.WindowPosition != Rectangle.Empty &&
            IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
        {
            // first set the bounds
            this.StartPosition = FormStartPosition.Manual;
            this.DesktopBounds = Settings.Default.WindowPosition;

            // afterwards set the window state to the saved value (which could be Maximized)
            this.WindowState = Settings.Default.WindowState;
        }
        else
        {
            // this resets the upper left corner of the window to windows standards
            this.StartPosition = FormStartPosition.WindowsDefaultLocation;

            // we can still apply the saved size
            this.Size = Settings.Default.WindowPosition.Size;
        }
    }

    private bool IsVisibleOnAnyScreen(Rectangle rect)
    {
        foreach (Screen screen in Screen.AllScreens)
        {
            if (screen.WorkingArea.IntersectsWith(rect))
            {
                return true;
            }
        }

        return false;
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);

        // only save the WindowState if Normal or Maximized
        switch (this.WindowState)
        {
            case FormWindowState.Normal:
            case FormWindowState.Maximized:
                Settings.Default.WindowState = this.WindowState;
                break;

            default:
                Settings.Default.WindowState = FormWindowState.Normal;
                break;
        }

        // reset window state to normal to get the correct bounds
        // also make the form invisible to prevent distracting the user
        this.Visible = false;
        this.WindowState = FormWindowState.Normal;

        Settings.Default.WindowPosition = this.DesktopBounds;
        Settings.Default.Save();
    }
}

файл настроек для справки:

<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="ScreenTest" GeneratedClassName="Settings">
    <Profiles />
    <Settings>
        <Setting Name="WindowPosition" Type="System.Drawing.Rectangle" Scope="User">
            <Value Profile="(Default)">0, 0, 0, 0</Value>
        </Setting>
        <Setting Name="WindowState" Type="System.Windows.Forms.FormWindowState" Scope="User">
            <Value Profile="(Default)">Normal</Value>
        </Setting>
    </Settings>
</SettingsFile>

ответ VVS был большим подспорьем! Я нашел две незначительные проблемы с ним, поэтому я перепечатываю основную часть его кода с этими изменениями:

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

(2) Если приложение закрыто при минимизации или максимизации кода в OnClosing не удается запомнить размеры окна в нормальном состоянии. (3 строки кода,которые я сейчас прокомментировал, кажутся разумными, но по какой-то причине просто не работают.) К счастью, я уже решил эту проблему и включили этот код в новый регион в конце кода, чтобы отслеживать состояние окна, как это происходит, а не ждать закрытия.


С этими двумя исправлениями на месте, я проверил:

A. заключение в нормальном состоянии--восстанавливает к таким же размеру / положению и государство

B. закрытие в свернутом состоянии--восстановление в нормальное состояние с последним нормальным размером / положением

C. закрытие в максимизированном состоянии -- восстанавливает в максимизированное состояние и запоминает его последний размер/положение, когда позже настраивается на нормальное состояние.

D. закрытие монитора 2--восстановление монитора 2.

E. закрытие монитора 2, затем отсоединение монитора 2 -- восстановление в том же положении на мониторе 1

Дэвид: ваш код позволил мне достичь точки D и E почти без усилий-вы не только предоставили решение для моего вопроса, но и предоставили его в полной программе, поэтому я запустил его почти через несколько секунд после вставки в Visual Studio. Большое спасибо за это!

public partial class MainForm : Form
{
    bool windowInitialized;

    public MainForm()
    {
        InitializeComponent();

        // this is the default
        this.WindowState = FormWindowState.Normal;
        this.StartPosition = FormStartPosition.WindowsDefaultBounds;

        // check if the saved bounds are nonzero and visible on any screen
        if (Settings.Default.WindowPosition != Rectangle.Empty &&
            IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
        {
            // first set the bounds
            this.StartPosition = FormStartPosition.Manual;
            this.DesktopBounds = Settings.Default.WindowPosition;

            // afterwards set the window state to the saved value (which could be Maximized)
            this.WindowState = Settings.Default.WindowState;
        }
        else
        {
            // this resets the upper left corner of the window to windows standards
            this.StartPosition = FormStartPosition.WindowsDefaultLocation;

            // we can still apply the saved size
            // msorens: added gatekeeper, otherwise first time appears as just a title bar!
            if (Settings.Default.WindowPosition != Rectangle.Empty)
            {
                this.Size = Settings.Default.WindowPosition.Size;
            }
        }
        windowInitialized = true;
    }

    private bool IsVisibleOnAnyScreen(Rectangle rect)
    {
        foreach (Screen screen in Screen.AllScreens)
        {
            if (screen.WorkingArea.IntersectsWith(rect))
            {
                return true;
            }
        }

        return false;
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);

        // only save the WindowState if Normal or Maximized
        switch (this.WindowState)
        {
            case FormWindowState.Normal:
            case FormWindowState.Maximized:
                Settings.Default.WindowState = this.WindowState;
                break;

            default:
                Settings.Default.WindowState = FormWindowState.Normal;
                break;
        }

        # region msorens: this code does *not* handle minimized/maximized window.

        // reset window state to normal to get the correct bounds
        // also make the form invisible to prevent distracting the user
        //this.Visible = false;
        //this.WindowState = FormWindowState.Normal;
        //Settings.Default.WindowPosition = this.DesktopBounds;

        # endregion

        Settings.Default.Save();
    }

    # region window size/position
    // msorens: Added region to handle closing when window is minimized or maximized.

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        TrackWindowState();
    }

    protected override void OnMove(EventArgs e)
    {
        base.OnMove(e);
        TrackWindowState();
    }

    // On a move or resize in Normal state, record the new values as they occur.
    // This solves the problem of closing the app when minimized or maximized.
    private void TrackWindowState()
    {
        // Don't record the window setup, otherwise we lose the persistent values!
        if (!windowInitialized) { return; }

        if (WindowState == FormWindowState.Normal)
        {
            Settings.Default.WindowPosition = this.DesktopBounds;
        }
    }

    # endregion window size/position
}

большинство других решений здесь полагаются на ручное вычисление текущего позиционирования каждого монитора. Край случаи чрезвычайно трудно понять, и очень немногие приложения могут получить это право прокатки свои собственные.

функция SetWindowPlacement в самой Windows корректно обрабатывает все крайние случаи - если окно будет расположено вне видимого экрана, оно регулирует его соответствующим образом.

лучший пример, который я видел в C#, находится в блоге Дэвида Рикарда. Он не только показывает, как использовать SetWindowPlacement, он также показывает, как сериализовать весь результат. http://blogs.msdn.com/b/davidrickard/archive/2010/03/09/saving-window-size-and-location-in-wpf-and-winforms.aspx


это отлично, я думаю на основе ваших ответов и комментариев.


это решение для сохранить / восстановить размер и положение формы С мульти мониторы + мульти документе, мульти форма или multi основная форма поддержка. Это не MDI форма, но Microsoft Word, как мульти документ с другим экземпляром основной формы.

благодаря VVS, msorens и Ян Голдби. Я сливаюсь решение от VVS, msorens и MSDN приложение.Метод Run (ApplicationContext) пример, чтобы сделать multi MainForm, но не MDI.

это исправление включает комментарий от Яна Голдби, который использует Form.RestoreBounds ликвидировать OnResize(), OnMove() и TrackWindowState().

Я также фиксирую, чтобы помнить монитор, когда форма переходит на другой монитор и максимизируется перед выходом, потому что я не отслеживаю OnResize, OnMove. Этим исправлением эта поддержка решения функция Оснастки Windows 7 который вы можете перетащить заголовок или Win + Стрелка, чтобы привязать окно формы к любому краю монитора или сделать его максимальным/нормальным, а также минимизированным.

это решение реализовано в программе, но не в основной форме для поддержки нескольких основных форм. Однако вы можете использовать и для одной основной формы.

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using SimpleTestForm.Properties;
using System.Drawing;

namespace SimpleTestForm
{
    static class Program
    {
        static MultiMainFormAppContext appContext;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            appContext = new MultiMainFormAppContext();
            Application.Run(appContext);
        }

        /// <summary>
        /// Create a new MainForm and restore the form size and position if necessary. This method can be called like from Menu File > New click event.
        /// </summary>
        /// <returns></returns>
        public static MainForm createNewMainForm()
        {
            return appContext.createNewMainForm();
        }

        /// <summary>
        /// Get the current active MainForm event if a dialog is opened. Useful to create Dictionary (MainForm, T) to store Form/document dependent field. Please set the Owner of child form to prevent null reference exception.
        /// </summary>
        /// <returns></returns>
        public static MainForm GetCurrentMainFormInstance()
        {
            Form mainForm = Form.ActiveForm;
            while (!(mainForm is MainForm) && mainForm.Owner != null)
                mainForm = mainForm.Owner;
            return mainForm as MainForm;
        }
    }

    class MultiMainFormAppContext : ApplicationContext
    {
        List<MainForm> mainForms = new List<MainForm>();
        Point newRestoredLocation = Point.Empty;

        internal MultiMainFormAppContext()
        {
            createNewMainForm();
        }

        internal MainForm createNewMainForm()
        {
            MainForm mainForm = new MainForm();
            mainForm.FormClosed += new FormClosedEventHandler(mainForm_FormClosed);
            mainForm.LocationChanged += new EventHandler(mainForm_LocationChanged);
            RestoreFormSizeNPosition(mainForm);
            PreventSameLocation(mainForm);
            mainForms.Add(mainForm);
            mainForm.Show();
            return mainForm;
        }

        private void PreventSameLocation(MainForm mainForm)
        {
            const int distance = 20;
            foreach (MainForm otherMainForm in mainForms)
            {
                if (Math.Abs(otherMainForm.Location.X - mainForm.Location.X) < distance &&
                    Math.Abs(otherMainForm.Location.Y - mainForm.Location.Y) < distance)
                    mainForm.Location = new Point(mainForm.Location.X + distance, mainForm.Location.Y + distance);
            }
        }

        /// <summary>
        /// Restore the form size and position with multi monitor support.
        /// </summary>
        private void RestoreFormSizeNPosition(MainForm mainForm)
        {
            // this is the default
            mainForm.WindowState = FormWindowState.Normal;
            mainForm.StartPosition = FormStartPosition.WindowsDefaultBounds;

            // check if the saved bounds are nonzero and visible on any screen
            if (Settings.Default.WindowPosition != Rectangle.Empty &&
                IsVisibleOnAnyScreen(Settings.Default.WindowPosition))
            {
                // first set the bounds
                mainForm.StartPosition = FormStartPosition.Manual;
                mainForm.DesktopBounds = Settings.Default.WindowPosition;

                // afterwards set the window state to the saved value (which could be Maximized)
                mainForm.WindowState = Settings.Default.WindowState;
            }
            else
            {
                // this resets the upper left corner of the window to windows standards
                mainForm.StartPosition = FormStartPosition.WindowsDefaultLocation;

                // we can still apply the saved size if not empty
                if (Settings.Default.WindowPosition != Rectangle.Empty)
                {
                    mainForm.Size = Settings.Default.WindowPosition.Size;
                }
            }
        }

        private void SaveFormSizeNPosition(MainForm mainForm)
        {
            // only save the WindowState as Normal or Maximized
            Settings.Default.WindowState = FormWindowState.Normal;
            if (mainForm.WindowState == FormWindowState.Normal || mainForm.WindowState == FormWindowState.Maximized)
                Settings.Default.WindowState = mainForm.WindowState;

            if (mainForm.WindowState == FormWindowState.Normal)
            {
                Settings.Default.WindowPosition = mainForm.DesktopBounds;
            }
            else
            {
                if (newRestoredLocation == Point.Empty)
                    Settings.Default.WindowPosition = mainForm.RestoreBounds;
                else
                    Settings.Default.WindowPosition = new Rectangle(newRestoredLocation, mainForm.RestoreBounds.Size);
            }

            Settings.Default.Save();
        }

        private bool IsVisibleOnAnyScreen(Rectangle rect)
        {
            foreach (Screen screen in Screen.AllScreens)
            {
                if (screen.WorkingArea.IntersectsWith(rect))
                    return true;
            }
            return false;
        }

        void mainForm_LocationChanged(object sender, EventArgs e)
        {
            MainForm mainForm = sender as MainForm;
            if (mainForm.WindowState == FormWindowState.Maximized)
            {
                // get the center location of the form incase like RibbonForm will be bigger and maximized Location wll be negative value that Screen.FromPoint(mainForm.Location) will going to the other monitor resides on the left or top of primary monitor.
                // Another thing, you might consider the form is in the monitor even if the location (top left corner) is on another monitor because majority area is on the monitor, so center point is the best way.
                Point centerFormMaximized = new Point (mainForm.DesktopBounds.Left + mainForm.DesktopBounds.Width/2, mainForm.DesktopBounds.Top + mainForm.DesktopBounds.Height/2);
                Point centerFormRestored = new Point(mainForm.RestoreBounds.Left + mainForm.RestoreBounds.Width / 2, mainForm.RestoreBounds.Top + mainForm.RestoreBounds.Height / 2);
                Screen screenMaximized = Screen.FromPoint(centerFormMaximized);
                Screen screenRestored = Screen.FromPoint(centerFormRestored);
                // we need to change the Location of mainForm.RestoreBounds to the new screen where the form currently maximized.
                // RestoreBounds does not update the Location if you change the screen but never restore to FormWindowState.Normal
                if (screenMaximized.DeviceName != screenRestored.DeviceName)
                {
                    newRestoredLocation = mainForm.RestoreBounds.Location;
                    int screenOffsetX = screenMaximized.Bounds.Location.X - screenRestored.Bounds.Location.X;
                    int screenOffsetY = screenMaximized.Bounds.Location.Y - screenRestored.Bounds.Location.Y;
                    newRestoredLocation.Offset(screenOffsetX, screenOffsetY);
                    return;
                }
            }
            newRestoredLocation = Point.Empty;
        }

        void mainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            MainForm mainForm = sender as MainForm;
            SaveFormSizeNPosition(mainForm);
            mainForm.FormClosed -= new FormClosedEventHandler(mainForm_FormClosed);
            mainForm.LocationChanged -= new EventHandler(mainForm_LocationChanged);
            mainForm.Dispose();
            mainForms.Remove(mainForm);
            if (mainForms.Count == 0) ExitThread();
        }
    }
}

Edit: метод PreventSameLocation добавлен, чтобы убедиться, что 2-я форма открыта не точно поверх 1-й формы, и пользователь заметит открывшейся форме.


Если у вас есть несколько мониторов, я считаю, что размеры экрана UI просто больше. Таким образом, обычный подход "1 monitor" для хранения и восстановления местоположения будет просто работать. Я не пробовал это, потому что я далеко от моего второго монитора, но это не должно быть трудно проверить. То, как вы задали вопрос, похоже, что вы его не тестировали.

ваше второе требование будет означать, что вам придется проверить максимальные размеры sceen при восстановлении приложения, а затем переместить как необходимый. Чтобы сделать этот последний бит, я использую этот код:

    private System.Drawing.Rectangle ConstrainToScreen(System.Drawing.Rectangle bounds)
    {
        Screen screen = Screen.FromRectangle(bounds);
        System.Drawing.Rectangle workingArea = screen.WorkingArea;
        int width = Math.Min(bounds.Width, workingArea.Width);
        int height = Math.Min(bounds.Height, workingArea.Height);
        // mmm....minimax            
        int left = Math.Min(workingArea.Right - width, Math.Max(bounds.Left, workingArea.Left));
        int top = Math.Min(workingArea.Bottom - height, Math.Max(bounds.Top, workingArea.Top));
        return new System.Drawing.Rectangle(left, top, width, height);
    }

Я вызываю этот метод при восстановлении формы. Я храню геометрию экрана в реестре при закрытии формы, а затем читаю геометрию при открытии формы. Я получаю границы, но затем ограничиваю восстановленные границы фактическим текущим экраном, используя метод выше.

сохранить на close:

      // store the size of the form
      int w = 0, h = 0, left = 0, top = 0;
      if (this.Bounds.Width < this.MinimumSize.Width || this.Bounds.Height < this.MinimumSize.Height)
      {
          // The form is currently minimized.  
          // RestoreBounds is the size of the window prior to last minimize action.
          w = this.RestoreBounds.Width;
          h = this.RestoreBounds.Height;
          left = this.RestoreBounds.Location.X;
          top = this.RestoreBounds.Location.Y;
      }
      else
      {
          w = this.Bounds.Width;
          h = this.Bounds.Height;
          left = this.Location.X;
          top = this.Location.Y;
      }
      AppCuKey.SetValue(_rvn_Geometry,
        String.Format("{0},{1},{2},{3},{4}",
              left, top, w, h, (int)this.WindowState));

восстановить в открытой форме:

    // restore the geometry of the form
    string s = (string)AppCuKey.GetValue(_rvn_Geometry);
    if (!String.IsNullOrEmpty(s))
    {
        int[] p = Array.ConvertAll<string, int>(s.Split(','),
                         new Converter<string, int>((t) => { return Int32.Parse(t); }));
        if (p != null && p.Length == 5)
            this.Bounds = ConstrainToScreen(new System.Drawing.Rectangle(p[0], p[1], p[2], p[3]));
    }

Это старый вопрос, но вот версия VB, основанная на предыдущих ответах.

одна проблема с ответами, предложенными VVS и Michael Sorens, заключается в том, что сохраненная позиция, которая показывает только пару пикселей на экране, считается видимой. Это решение требует по крайней мере 50x50 пикселей в пересечении перед восстановлением предыдущего местоположения.

параметры:

<Settings>
  <Setting Name="WindowState" Type="System.Windows.Forms.FormWindowState" Scope="User">
    <Value Profile="(Default)">Normal</Value>
  </Setting>
  <Setting Name="WindowBounds" Type="System.Drawing.Rectangle" Scope="User">
    <Value Profile="(Default)">10, 10, 800, 600</Value>
  </Setting>
</Settings>

форма:

Partial Public Class MainForm

    Private loadingComplete As Boolean = False

    Public Sub New()

        InitializeComponent()
        RestoreWindowLocation()

    End Sub

    Private Sub MainForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load

        loadingComplete = True

    End Sub

    Private Sub MainForm_Resize(sender As System.Object, e As System.EventArgs) Handles MyBase.Resize

         TrackWindowLocation()

     End Sub

     Private Sub MainForm_Move(sender As System.Object, e As System.EventArgs) Handles MyBase.Move

         TrackWindowLocation()

     End Sub

    Private Sub MainForm_FormClosing(sender As System.Object, e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing

        SaveWindowLocation()

     End Sub


    Private Sub RestoreWindowLocation()

        If IsRectangleVisible(My.Settings.WindowBounds) Then
            Me.StartPosition = FormStartPosition.Manual
            Me.DesktopBounds = My.Settings.WindowBounds
        End If

        If Not My.Settings.WindowState = FormWindowState.Minimized Then
            Me.WindowState = My.Settings.WindowState
        End If

    End Sub

    Private Sub TrackWindowLocation()

        If loadingComplete Then
            If Me.WindowState = FormWindowState.Normal Then
                My.Settings.WindowBounds = Me.DesktopBounds
                My.Settings.WindowState = Me.WindowState
            End If
        End If

    End Sub

    Private Sub SaveWindowLocation()

        If Not Me.WindowState = FormWindowState.Minimized Then
            My.Settings.WindowState = Me.WindowState
        End If

        If Me.WindowState = FormWindowState.Normal Then
            My.Settings.WindowBounds = Me.DesktopBounds
        End If

        My.Settings.Save()

    End Sub

    Private Function IsRectangleVisible(Rectangle As Rectangle) As Boolean

        For Each screen As Screen In screen.AllScreens
            Dim r As Rectangle = Rectangle.Intersect(Rectangle, screen.WorkingArea)
            If Not r.IsEmpty Then
                If r.Width > 50 And r.Height > 50 Then Return True
            End If
        Next

        Return False

    End Function

End Class