web-dev-qa-db-ja.com

複数のモニターを使用したウィンドウサイズ/位置の復元

WinFormの位置とサイズの復元に関する多くの投稿。

例:

しかし、マルチモニターでこれを行うためのコードはまだ見つけていません。

つまり、モニター2のウィンドウで.NET Winformアプリを閉じると、ウィンドウのサイズ、場所、状態をアプリケーション設定に保存して、後でアプリを再起動したときにモニター2に復元できるようにします。上記のcodeprojectの例のように、保存された場所がほとんど画面の外にある場合のように、いくつかの健全性チェックが含まれているといいでしょう。または、保存された場所がもう存在しないモニター上にある場合(たとえば、私のラップトップは2台目のモニターがなくなったため)、モニター1に正しく移動します。

何かご意見は?

私の環境:C#、. NET 3.5以下、VS2008

33
Michael Sorens

VVSによって提供された答えは非常に役に立ちました!ただし、2つの小さな問題が見つかったので、彼のコードの大部分をこれらのリビジョンで再投稿しています。

(1)アプリケーションが初めて実行されるとき、フォームは通常の状態で開かれますが、タイトルバーとして表示されるようなサイズになっています。これを修正するために、コンストラクターに条件を追加しました。

(2)最小化または最大化されているときにアプリケーションが閉じられると、OnClosingのコードは、通常の状態のウィンドウのサイズを記憶できません。 (コメントアウトした3行のコードは妥当なようですが、何らかの理由で機能しません。)幸い、私は以前にこの問題を解決していて、コードの最後の新しい領域にそのコードを含めています。閉じられるのを待つのではなく、ウィンドウの状態を追跡します。


これらの2つの修正を適用して、テストしました。

A.通常の状態で閉じる-同じサイズ/位置と状態に復元

B.最小化された状態で閉じる-最後の通常のサイズ/位置で通常の状態に復元します

C.最大化状態で閉じる-最大化状態に復元し、後で通常の状態に調整したときに最後のサイズ/位置を記憶します。

D.モニター2を閉じる-モニター2に復元します。

E.モニター2を閉じてからモニター2を切断-モニター1の同じ位置に復元

David:あなたのコードでは、ポイント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
}
27
Michael Sorens

このコードを試してください。興味がある点:

  • ウィンドウが(部分的に)画面の作業領域に表示されているかどうかを確認します。例えば。タスクバーの後ろにドラッグするか、完全に画面外に移動すると、位置がウィンドウのデフォルトにリセットされます。
  • フォームが最小化または最大化されている場合でも、正しい境界を保存します(一般的なエラー)。
  • WindowStateを正しく保存します。 FormWindowState.Minimizedの保存は、仕様により無効になっています。

境界と状態は、対応するタイプとともに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>
36
VVS

ここでの他のソリューションのほとんどは、各モニターの現在の位置を手動で把握することに依存しています。 Edgeのケースを理解するのは非常に難しく、ごく少数のアプリで正しく処理できます。

Windows自体のSetWindowPlacement関数は、すべてのEdgeケースを正しく処理します。ウィンドウが可視画面の外に配置される場合、それに応じて調整されます。

C#で見た最も良い例は、David Rickardのブログです。 SetWindowPlacementの使用方法だけでなく、結果全体をシリアル化する方法も示しています。 http://blogs.msdn.com/b/davidrickard/archive/2010/03/09/saving-window-size-and-location-in-wpf-and-winforms.aspx

8
ShadowChaser

これはあなたの答えやコメントに基づいて私が思う完璧なものです。


このソリューションは、保存/復元フォームサイズと位置マルチモニター+マルチドキュメントmulti formまたはmulti main formのサポート。 MDIフォームではありませんが、Microsoft Wordは、メインフォームインスタンスが異なるマルチドキュメントのようです。

VVS、msorens、およびIan Goldbyに感謝します。 VVS、msorens、およびMSDNのソリューションをマージします Application.Run Method(ApplicationContext) サンプルを使用して、MDIではなくマルチMainFormを作成します。

この修正には、_Form.RestoreBounds_を使用してOnResize()OnMove()およびTrackWindowState()を削除するIan Goldbyからのコメントが含まれています。

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

編集:PreventSameLocationメソッドが追加され、2番目のフォームが最初のフォームの上に正確に開かず、ユーザーが新しく開いたフォームに気付くようになりました。

3
CallMeLaNN

複数のモニターを使用している場合は、画面のUI寸法が単純に大きくなると思います。そのため、場所を保存および復元する通常の「1モニター」アプローチが機能します。私はセカンドモニターから離れているのでこれを試していませんが、テストするのは難しくありません。質問の仕方は、まだテストしていないようです。

2番目の要件は、アプリを復元するときに最大シーンサイズを確認し、必要に応じて再配置する必要があることを意味します。この後者のビットを実行するには、次のコードを使用します。

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

フォームを復元するときにこのメソッドを呼び出します。フォームを閉じるときに画面のジオメトリをレジストリに保存し、フォームを開くときにジオメトリを読み取ります。境界を取得しますが、上記の方法を使用して、復元された境界を実際の現在の画面に制限します。

閉じる時に保存:

      // 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]));
    }
1
Cheeso

これは古い質問ですが、ここに以前の回答に基づくVBバージョンがあります。

VVSとMichael Sorensによって提案された回答の1つの問題は、画面上に数ピクセルしか表示されない保存された位置が可視としてカウントされることです。このソリューションでは、以前の場所を復元する前に、交差点に少なくとも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
0
Judah Sali