35

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

例:

しかし、複数のモニターでこれを行うコードをまだ見つけていません。

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

何かご意見は?

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

4

6 に答える 6

39

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

  • 任意の画面の作業領域でウィンドウが (部分的に) 表示されているかどうかを確認します。たとえば、タスク バーの後ろにドラッグするか、完全に画面外に移動すると、位置が Windows のデフォルトにリセットされます。
  • フォームが最小化または最大化されている場合でも、正しい境界を保存します (一般的なエラー)
  • 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>
于 2009-06-02T15:44:04.753 に答える
29

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
}
于 2009-06-02T21:48:15.340 に答える
9

ここでの他のソリューションのほとんどは、各モニターの現在の位置を手動で把握することに依存しています。エッジ ケースを把握するのは非常に難しく、独自に展開できるアプリはほとんどありません。

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

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

于 2011-05-19T17:09:37.457 に答える
1

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

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]));
    }
于 2009-06-02T00:39:05.603 に答える
0

これは古い質問ですが、これは以前の回答に基づく 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
于 2014-07-24T01:32:33.687 に答える