【问题标题】:SetWindowPlacement won't correct placement for WPF tool windowsSetWindowPlacement 不会正确放置 WPF 工具窗口
【发布时间】:2013-10-05 21:40:42
【问题描述】:

我正在我的 WPF 应用程序中调用 SetWindowPlacement 以保存和恢复窗口位置。这很好用,但是当窗口是工具窗口而不是标准窗口时,确保窗口永远不会完全隐藏的宣传能力似乎不起作用。您使用负的 Left 和 Right 展示位置调用 SetWindowPlacement,它会很高兴地在屏幕外打开它,而无法重新打开它。

有没有办法让 SetWindowPlacement 更正这些工具窗口的位置(对于缺少的监视器等)?

如果做不到这一点,有没有好的手动方法呢?供参考,代码:

// RECT structure required by WINDOWPLACEMENT structure
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;

    public RECT(int left, int top, int right, int bottom)
    {
        this.Left = left;
        this.Top = top;
        this.Right = right;
        this.Bottom = bottom;
    }
}

// POINT structure required by WINDOWPLACEMENT structure
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int X;
    public int Y;

    public POINT(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}

// WINDOWPLACEMENT stores the position, size, and state of a window
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
    public int length;
    public int flags;
    public int showCmd;
    public POINT minPosition;
    public POINT maxPosition;
    public RECT normalPosition;
}

public static class WindowPlacement
{
    private static Encoding encoding = new UTF8Encoding();
    private static XmlSerializer serializer = new XmlSerializer(typeof(WINDOWPLACEMENT));

    [DllImport("user32.dll")]
    private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);

    [DllImport("user32.dll")]
    private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);

    private const int SW_SHOWNORMAL = 1;
    private const int SW_SHOWMINIMIZED = 2;

    public static void SetPlacement(IntPtr windowHandle, string placementXml)
    {
        if (string.IsNullOrEmpty(placementXml))
        {
            return;
        }

        WINDOWPLACEMENT placement;
        byte[] xmlBytes = encoding.GetBytes(placementXml);

        try
        {
            using (MemoryStream memoryStream = new MemoryStream(xmlBytes))
            {
                placement = (WINDOWPLACEMENT)serializer.Deserialize(memoryStream);
            }

            placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
            placement.flags = 0;
            placement.showCmd = (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd);

            SetWindowPlacement(windowHandle, ref placement);
        }
        catch (InvalidOperationException)
        {
            // Parsing placement XML failed. Fail silently.
        }
    }

    public static string GetPlacement(IntPtr windowHandle)
    {
        WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
        GetWindowPlacement(windowHandle, out placement);

        using (MemoryStream memoryStream = new MemoryStream())
        {
            using (XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8))
            {
                serializer.Serialize(xmlTextWriter, placement);
                byte[] xmlBytes = memoryStream.ToArray();
                return encoding.GetString(xmlBytes);
            }
        }
    }
}

使用顶部:200,底部:600,左侧:-1000,右侧:-300 调用 SetPlacement。

【问题讨论】:

  • 在 WINDOWPLACEMENT 的 MSDN 文章中有非常明确的警告,说明工作区和屏幕坐标之间的差异以及工具窗口的不同之处。没什么可看的,所以我只能假设您以某种方式弄错了。不要对您的代码保密。
  • 工作空间和屏幕坐标之间的差异不是这里的问题。我附上了代码,但问题的核心是在一个工具窗口上调用 SetPlacement,该工具窗口的正常放置 RECT 位于正常屏幕区域之外。
  • 实际上您不需要显式的 XML 序列化:您可以按原样将 WINDOWPLACEMENT 的实例存储在设置中。顺便说一句,如果它目前在可见区域之外,则 WIndows 7 似乎会调整位置。否则,感谢您分享您的代码!
  • @Vlad 当您尝试按原样保存设置 UI 时,它的行为很奇怪。同样在我的情况下,我不使用内置设置保存它;我存储在一个 sqlite 数据库中。另外当你看到它在可视区域之外调整窗口的位置时,你使用的是普通窗口还是工具窗口?
  • @RandomEngy:我没有注意到 VS 2019 的设置有任何奇怪的行为。数据库当然是另一回事。

标签: c# wpf winapi


【解决方案1】:

从乔纳森的回答中,我想出了这个代码来手动拯救窗口:

[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
    public int cbSize;
    public RECT rcMonitor;
    public RECT rcWork;
    public uint dwFlags;
}

...

[DllImport("user32.dll")]
private static extern IntPtr MonitorFromRect([In] ref RECT lprc, uint dwFlags);

[DllImport("user32.dll")]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);


private const uint MONITOR_DEFAULTTONEAREST = 0x00000002;

...

IntPtr closestMonitorPtr = MonitorFromRect(ref placement.normalPosition, MONITOR_DEFAULTTONEAREST);
MONITORINFO closestMonitorInfo = new MONITORINFO();
closestMonitorInfo.cbSize = Marshal.SizeOf(typeof (MONITORINFO));
bool getInfoSucceeded = GetMonitorInfo(closestMonitorPtr, ref closestMonitorInfo);

if (getInfoSucceeded && !RectanglesIntersect(placement.normalPosition, closestMonitorInfo.rcMonitor))
{
    placement.normalPosition = PlaceOnScreen(closestMonitorInfo.rcMonitor, placement.normalPosition);
}

...

private static bool RectanglesIntersect(RECT a, RECT b)
{
    if (a.Left > b.Right || a.Right < b.Left)
    {
        return false;
    }

    if (a.Top > b.Bottom || a.Bottom < b.Top)
    {
        return false;
    }

    return true;
}

private static RECT PlaceOnScreen(RECT monitorRect, RECT windowRect)
{
    int monitorWidth = monitorRect.Right - monitorRect.Left;
    int monitorHeight = monitorRect.Bottom - monitorRect.Top;

    if (windowRect.Right < monitorRect.Left)
    {
        // Off left side
        int width = windowRect.Right - windowRect.Left;
        if (width > monitorWidth)
        {
            width = monitorWidth;
        }

        windowRect.Left = monitorRect.Left;
        windowRect.Right = windowRect.Left + width;
    }
    else if (windowRect.Left > monitorRect.Right)
    {
        // Off right side
        int width = windowRect.Right - windowRect.Left;
        if (width > monitorWidth)
        {
            width = monitorWidth;
        }

        windowRect.Right = monitorRect.Right;
        windowRect.Left = windowRect.Right - width;
    }

    if (windowRect.Bottom < monitorRect.Top)
    {
        // Off top
        int height = windowRect.Bottom - windowRect.Top;
        if (height > monitorHeight)
        {
            height = monitorHeight;
        }

        windowRect.Top = monitorRect.Top;
        windowRect.Bottom = windowRect.Top + height;
    }
    else if (windowRect.Top > monitorRect.Bottom)
    {
        // Off bottom
        int height = windowRect.Bottom - windowRect.Top;
        if (height > monitorHeight)
        {
            height = monitorHeight;
        }

        windowRect.Bottom = monitorRect.Bottom;
        windowRect.Top = windowRect.Bottom - height;
    }

    return windowRect;
}

【讨论】:

    【解决方案2】:

    您可以使用MONITOR_DEFAULTTONEAREST 标志将建议的窗口矩形传递给MonitorFromRect()。这将返回一个 HMONITOR 代表与窗口最相交(打开)的监视器 - 或者如果窗口完全不在屏幕上,则表示离建议坐标最近的监视器。

    然后您可以调用GetMonitorInfo() 来查找显示器的显示和工作区矩形,并检查您建议的窗口坐标以确保窗口在显示之前完全在屏幕上。

    【讨论】:

      【解决方案3】:

      我能够提出一个解决方案(基于 RandomEngy 的代码),该解决方案使用 KeepInsideNearestMonitor() 方法扩展 Window 类,确保 WPF 窗口放置在当前可见范围内。它没有 Win32,并且适用于您可以通过右键单击桌面>显示设置在 Windows 10 中设置的调用因素。

      public static class WindowExtension
      {
          [StructLayout(LayoutKind.Sequential)]
          internal struct RECT
          {
              public int Left;
              public int Top;
              public int Right;
              public int Bottom;
      
              public RECT(int left, int top, int right, int bottom)
              {
                  this.Left = left;
                  this.Top = top;
                  this.Right = right;
                  this.Bottom = bottom;
              }
          }
      
          internal static void KeepInsideNearestMonitor(this Window floatingWindow)
          {
              RECT normalPosition = new RECT();
              normalPosition.Left = (int)floatingWindow.FloatingLeft;
              normalPosition.Top = (int)floatingWindow.FloatingTop;
              normalPosition.Bottom = normalPosition.Top + (int)floatingWindow.FloatingHeight;
              normalPosition.Right = normalPosition.Left + (int)floatingWindow.FloatingWidth;
      
              // Are we using only one monitor?
              if (SystemParameters.PrimaryScreenWidth == SystemParameters.VirtualScreenWidth &&
                  SystemParameters.PrimaryScreenHeight == SystemParameters.VirtualScreenHeight)
              {
                  RECT primaryscreen = new RECT(0,0, (int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight);
      
                  if (!RectanglesIntersect(normalPosition, primaryscreen))
                  {
                      normalPosition = PlaceOnScreen(primaryscreen, normalPosition);
      
                      floatingWindow.FloatingLeft = normalPosition.Left;
                      floatingWindow.FloatingTop = normalPosition.Top;
                      floatingWindow.FloatingHeight = normalPosition.Bottom - normalPosition.Top;
                      floatingWindow.FloatingWidth = normalPosition.Right - normalPosition.Left;
                  }
      
                  return;
              }
              else
              {
                  RECT primaryscreen = new RECT(0, 0, (int)SystemParameters.VirtualScreenWidth, (int)SystemParameters.VirtualScreenHeight);
      
                  if (!RectanglesIntersect(normalPosition, primaryscreen))
                  {
                      normalPosition = PlaceOnScreen(primaryscreen, normalPosition);
      
                      floatingWindow.FloatingLeft = normalPosition.Left;
                      floatingWindow.FloatingTop = normalPosition.Top;
                      floatingWindow.FloatingHeight = normalPosition.Bottom - normalPosition.Top;
                      floatingWindow.FloatingWidth = normalPosition.Right - normalPosition.Left;
                  }
      
                  return;
              }
          }
      
          /// <summary>
          /// Determine whether <paramref name="a"/> and <paramref name="b"/>
          /// have an intersection or not.
          /// </summary>
          /// <param name="a"></param>
          /// <param name="b"></param>
          /// <returns></returns>
          private static bool RectanglesIntersect(RECT a, RECT b)
          {
              if (a.Left > b.Right || a.Right < b.Left)
              {
                  return false;
              }
      
              if (a.Top > b.Bottom || a.Bottom < b.Top)
              {
                  return false;
              }
      
              return true;
          }
      
          /// <summary>
          /// Determine the place where <paramref name="windowRect"/> should be placed
          /// inside the <paramref name="monitorRect"/>.
          /// </summary>
          /// <param name="monitorRect"></param>
          /// <param name="windowRect"></param>
          /// <returns></returns>
          private static RECT PlaceOnScreen(RECT monitorRect, RECT windowRect)
          {
              int monitorWidth = monitorRect.Right - monitorRect.Left;
              int monitorHeight = monitorRect.Bottom - monitorRect.Top;
      
              if (windowRect.Right < monitorRect.Left)
              {
                  // Off left side
                  int width = windowRect.Right - windowRect.Left;
                  if (width > monitorWidth)
                  {
                      width = monitorWidth;
                  }
      
                  windowRect.Left = monitorRect.Left;
                  windowRect.Right = windowRect.Left + width;
              }
              else if (windowRect.Left > monitorRect.Right)
              {
                  // Off right side
                  int width = windowRect.Right - windowRect.Left;
                  if (width > monitorWidth)
                  {
                      width = monitorWidth;
                  }
      
                  windowRect.Right = monitorRect.Right;
                  windowRect.Left = windowRect.Right - width;
              }
      
              if (windowRect.Bottom < monitorRect.Top)
              {
                  // Off top
                  int height = windowRect.Bottom - windowRect.Top;
                  if (height > monitorHeight)
                  {
                      height = monitorHeight;
                  }
      
                  windowRect.Top = monitorRect.Top;
                  windowRect.Bottom = windowRect.Top + height;
              }
              else if (windowRect.Top > monitorRect.Bottom)
              {
                  // Off bottom
                  int height = windowRect.Bottom - windowRect.Top;
                  if (height > monitorHeight)
                  {
                      height = monitorHeight;
                  }
      
                  windowRect.Bottom = monitorRect.Bottom;
                  windowRect.Top = windowRect.Bottom - height;
              }
      
              return windowRect;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-27
        • 1970-01-01
        • 1970-01-01
        • 2017-03-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多