【问题标题】:Restoring Window Size/Position With Multiple Monitors使用多个监视器恢复窗口大小/位置
【发布时间】:2010-10-30 13:17:08
【问题描述】:

许多关于恢复 WinForm 位置和大小的帖子。

例子:

但我还没有找到使用多个监视器执行此操作的代码。

也就是说,如果我在监视器 2 上关闭我的 .NET Winform 应用程序,我希望它将窗口大小、位置和状态保存到应用程序设置中,以便稍后在我重新启动时恢复到监视器 2应用程序。如果像上面的代码项目示例中那样,它包含一些健全性检查,那就太好了,因为如果保存的位置大部分都在屏幕外,它会“修复”它。或者,如果保存的位置位于不再存在的显示器上(例如,我的笔记本电脑现在没有第二台显示器),那么它会正确地将其移动到显示器 1。

有什么想法吗?

我的环境:C#,.NET 3.5 或以下,VS2008

【问题讨论】:

    标签: c# winforms multiple-monitors appsettings


    【解决方案1】:

    如果您有多个显示器,我相信屏幕 UI 尺寸会更大。因此,存储和恢复位置的正常“1 个监视器”方法将起作用。我没有尝试过这个,因为我远离我的第二台显示器,但它应该不难测试。您提出问题的方式似乎您还没有测试过。

    您的第二个要求意味着您必须在恢复应用程序时检查最大屏幕尺寸,然后根据需要重新定位。为此,我使用以下代码:

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

    【讨论】:

      【解决方案2】:

      试试这个代码。兴趣点:

      • 检查窗口是否(部分)在任何屏幕的工作区域中可见。例如。将其拖到任务栏后面或将其完全移出屏幕会将位置重置为 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>
      

      【讨论】:

      • 您使用的Settings 类型是什么?
      • @ColonelPanic 我使用了默认的应用程序设置类。欲了解更多信息,请点击此处:docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/…
      • 效果很好,但在 .NET 4.7 中,我必须在将 DesktopBounds 复制到设置后 将表单设置为不可见,否则它会保持最大化大小..
      【解决方案3】:

      VVS 提供的答案帮了大忙!不过我发现了两个小问题,所以我将他的大部分代码与这些修订一起重新发布:

      (1) 应用程序第一次运行时,表单以正常状态打开,但其大小使其仅显示为标题栏。我在构造函数中添加了一个条件来解决这个问题。

      (2) 如果应用程序在最小化或最大化时关闭,则 OnClosing 中的代码无法记住正常状态下的窗口尺寸。 (这 3 行代码——我现在已经注释掉了——看起来很合理,但由于某种原因根本不起作用。)幸运的是,我之前已经解决了这个问题,并在代码末尾包含了该代码的新区域跟踪窗口状态,而不是等待关闭。


      有了这两个修复,我已经测试了:

      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
      }
      

      【讨论】:

      • 对于那些对实际使用此解决方案感兴趣的人,我已经完成了将示例转换为具有文档化 API (cleancode.sourceforge.net/api/csharp/…) 的库的最后一步。使用该库,您只需要几行代码来加载/保存设置,以及几个单行事件处理程序——总共 7 行代码即可完成所有操作!下载区在cleancode.sourceforge.net/wwwdoc/download.html
      • 也许我的代码的问题是我不应该在改变状态之前让窗口不可见。也许我应该测试它而不是猜测:)
      • 当我删除 base.OnMove() 和 base.OnResize() 行时,我发现它没有堆栈溢出。否则,很好的解决方案。
      • WindowState 不是Normal 时,您应该使用Form.RestoreBounds 来获取表单的位置和大小。那么你就不需要 TrackWindowState()。另请参阅下面 Cheeso 的回答。
      • TrackWindowState 调整大小和移动的目的是什么?这不会增加关闭时可以完成的额外过程吗?
      【解决方案4】:

      根据您的回答和 cmets,我认为这是完美的。


      此解决方案是使用多显示器 + 多文档多表单保存/恢复表单大小和位置 > 或多主窗体支持。它是不是 MDI 表单,而是 Microsoft Word 之类的多文档,具有不同的主表单实例。

      感谢 VVS、msorens 和 Ian Goldby。我将 VVS、msorens 和 MSDN Application.Run Method (ApplicationContext) 示例中的解决方案合并以制作多 MainForm 而不是 MDI。

      此修复包括来自 Ian Goldby 的评论,该评论使用 Form.RestoreBounds 来消除 OnResize()OnMove()TrackWindowState()

      我还修复了当窗体移动到另一个监视器并在退出前最大化时记住监视器,因为我没有跟踪 OnResize、OnMove。 通过此修复,此解决方案支持Windows 7 Snap feature,您可以拖动标题栏或 Win+箭头键将表单窗口捕捉到任何监视器边缘或使其最大化/正常以及最小化。

      此解决方案在 Program 中实现,但不在主窗体中实现,以支持多主窗体。但是,您也可以用于单个主窗体。

      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 方法以确保打开的第二个表单不完全位于第一个表单的顶部,并且用户会注意到新打开的表单。

      【讨论】:

        【解决方案5】:

        这里的大多数其他解决方案都依赖于手动确定每台显示器的当前位置。边缘情况非常难以弄清楚,很少有应用程序能够自行解决。

        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

        【讨论】:

          【解决方案6】:

          这是一个老问题,但这里是基于以前答案的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
          

          【讨论】:

            猜你喜欢
            • 2012-05-28
            • 2012-03-29
            • 2017-06-19
            • 1970-01-01
            • 1970-01-01
            • 2010-09-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多