如何检查窗口在 Windows 窗体中是否确实可见?

发布于 2024-08-09 22:23:27 字数 210 浏览 3 评论 0原文

通常,您使用 Form.Visible 来检查 Window 是否完全可见。但有时屏幕上的窗口位于其他窗口下方,因此它实际上是不可见的。

那么如何在 C# Windows 窗体中检查窗口是否确实可见?

我想实现这一点:当我单击键盘上的 CTRL+K 并且我的窗口在屏幕上可见时,它什么也不做。但当它位于其他窗口下方时,它会弹出到顶部(置于前面)。

亲切的问候

Normally you use Form.Visible to check if Window is visible at all. But sometimes on the screen window is below other windows so it's really invisible.

So how to check in c# Windows Forms if window is really visible or not?

I would like to accomplish this: when I click CTRL+K on my keyboard and my window is visible on my screen it does nothing. But when it's underneath other windows it pops to the top (Bring to front).

kind regards

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

执手闯天涯 2024-08-16 22:23:27

我通过网络搜索,但无法找到任何直接答案来查看窗口的一部分是否真正对用户可见。如果鼠标当前位于窗口可见部分的顶部,我实际上需要一种“点击测试”表单的方法。我想我应该分享花了几天时间才能完成的代码:

public class VisibilityTester
{
    private delegate bool CallBackPtr(int hwnd, int lParam);
    private static CallBackPtr callBackPtr;

    /// <summary>
    /// The enumerated pointers of actually visible windows
    /// </summary>
    public static List<IntPtr> enumedwindowPtrs = new List<IntPtr>();
    /// <summary>
    /// The enumerated rectangles of actually visible windows
    /// </summary>
    public static List<Rectangle> enumedwindowRects = new List<Rectangle>();

    /// <summary>
    /// Does a hit test for specified control (is point of control visible to user)
    /// </summary>
    /// <param name="ctrlRect">the rectangle (usually Bounds) of the control</param>
    /// <param name="ctrlHandle">the handle for the control</param>
    /// <param name="p">the point to test (usually MousePosition)</param>
    /// <param name="ExcludeWindow">a control or window to exclude from hit test (means point is visible through this window)</param>
    /// <returns>boolean value indicating if p is visible for ctrlRect</returns>
    public static bool HitTest(Rectangle ctrlRect, IntPtr ctrlHandle, Point p, IntPtr ExcludeWindow)
    {
        // clear results
        enumedwindowPtrs.Clear();
        enumedwindowRects.Clear();

        // Create callback and start enumeration
        callBackPtr = new CallBackPtr(EnumCallBack);
        EnumDesktopWindows(IntPtr.Zero, callBackPtr, 0);

        // Go from last to first window, and substract them from the ctrlRect area
        Region r = new Region(ctrlRect);

        bool StartClipping = false;
        for (int i = enumedwindowRects.Count - 1; i >= 0; i--)
        {
            if (StartClipping && enumedwindowPtrs[i] != ExcludeWindow)
            {
                r.Exclude(enumedwindowRects[i]);
            }

            if (enumedwindowPtrs[i] == ctrlHandle) StartClipping = true;
        }

        // return boolean indicating if point is visible to clipped (truly visible) window
        return r.IsVisible(p);
    }

    /// <summary>
    /// Window enumeration callback
    /// </summary>
    private static bool EnumCallBack(int hwnd, int lParam)
    {
        // If window is visible and not minimized (isiconic)
        if (IsWindow((IntPtr)hwnd) && IsWindowVisible((IntPtr)hwnd) && !IsIconic((IntPtr)hwnd))
        { 
            // add the handle and windowrect to "found windows" collection
            enumedwindowPtrs.Add((IntPtr)hwnd);

            RECT rct;

            if (GetWindowRect((IntPtr)hwnd, out rct))
            {
                // add rect to list
                enumedwindowRects.Add(new Rectangle(rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top));
            }
            else
            {
                // invalid, make empty rectangle
                enumedwindowRects.Add(new Rectangle(0, 0, 0, 0));
            }
        }

        return true;
    }


    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindow(IntPtr hWnd);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsIconic(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern int EnumDesktopWindows(IntPtr hDesktop, CallBackPtr callPtr, int lPar);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left;        // x position of upper-left corner
        public int Top;         // y position of upper-left corner
        public int Right;       // x position of lower-right corner
        public int Bottom;      // y position of lower-right corner

        public override string ToString()
        {
            return Left + "," + Top + "," + Right + "," + Bottom;
        }
    }
}

I googled trough the web, but coudn't find any straight answer to see if a part of a window is truly visible to the user. I actually needed a way to "hittest" the form, if the mouse is currently on top of the visible part of the window. I thought I'd share the code which took several days to accomplish:

public class VisibilityTester
{
    private delegate bool CallBackPtr(int hwnd, int lParam);
    private static CallBackPtr callBackPtr;

    /// <summary>
    /// The enumerated pointers of actually visible windows
    /// </summary>
    public static List<IntPtr> enumedwindowPtrs = new List<IntPtr>();
    /// <summary>
    /// The enumerated rectangles of actually visible windows
    /// </summary>
    public static List<Rectangle> enumedwindowRects = new List<Rectangle>();

    /// <summary>
    /// Does a hit test for specified control (is point of control visible to user)
    /// </summary>
    /// <param name="ctrlRect">the rectangle (usually Bounds) of the control</param>
    /// <param name="ctrlHandle">the handle for the control</param>
    /// <param name="p">the point to test (usually MousePosition)</param>
    /// <param name="ExcludeWindow">a control or window to exclude from hit test (means point is visible through this window)</param>
    /// <returns>boolean value indicating if p is visible for ctrlRect</returns>
    public static bool HitTest(Rectangle ctrlRect, IntPtr ctrlHandle, Point p, IntPtr ExcludeWindow)
    {
        // clear results
        enumedwindowPtrs.Clear();
        enumedwindowRects.Clear();

        // Create callback and start enumeration
        callBackPtr = new CallBackPtr(EnumCallBack);
        EnumDesktopWindows(IntPtr.Zero, callBackPtr, 0);

        // Go from last to first window, and substract them from the ctrlRect area
        Region r = new Region(ctrlRect);

        bool StartClipping = false;
        for (int i = enumedwindowRects.Count - 1; i >= 0; i--)
        {
            if (StartClipping && enumedwindowPtrs[i] != ExcludeWindow)
            {
                r.Exclude(enumedwindowRects[i]);
            }

            if (enumedwindowPtrs[i] == ctrlHandle) StartClipping = true;
        }

        // return boolean indicating if point is visible to clipped (truly visible) window
        return r.IsVisible(p);
    }

    /// <summary>
    /// Window enumeration callback
    /// </summary>
    private static bool EnumCallBack(int hwnd, int lParam)
    {
        // If window is visible and not minimized (isiconic)
        if (IsWindow((IntPtr)hwnd) && IsWindowVisible((IntPtr)hwnd) && !IsIconic((IntPtr)hwnd))
        { 
            // add the handle and windowrect to "found windows" collection
            enumedwindowPtrs.Add((IntPtr)hwnd);

            RECT rct;

            if (GetWindowRect((IntPtr)hwnd, out rct))
            {
                // add rect to list
                enumedwindowRects.Add(new Rectangle(rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top));
            }
            else
            {
                // invalid, make empty rectangle
                enumedwindowRects.Add(new Rectangle(0, 0, 0, 0));
            }
        }

        return true;
    }


    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindow(IntPtr hWnd);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsIconic(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern int EnumDesktopWindows(IntPtr hDesktop, CallBackPtr callPtr, int lPar);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left;        // x position of upper-left corner
        public int Top;         // y position of upper-left corner
        public int Right;       // x position of lower-right corner
        public int Bottom;      // y position of lower-right corner

        public override string ToString()
        {
            return Left + "," + Top + "," + Right + "," + Bottom;
        }
    }
}
假情假意假温柔 2024-08-16 22:23:27

您可以使用 Windows API 枚举所有窗口,检索它们的 Z 顺序并将其与窗口的 Z 顺序进行比较。我认为有人已经在此处这样做了。

You could use Windows API to enumerate all windows, retrieve their Z-Order and compare it with the Z-Order of your window. I think someone did this already here.

染年凉城似染瑾 2024-08-16 22:23:27

您可以调用激活方法将其置于前面(如果尚未置于前面)。

但是,请注意,如果另一个程序处于活动状态,它通常只会闪烁桌面按钮(取决于您从何处调用它)。这是 Windows 针对焦点窃取的标准保护并且您不应尝试解决此问题

You can call the Activate method on the form to bring it to the front if it isn't already.

However, note that if a different program is active, it will usually simply flash the desktop button (depending where you call it from). This is Windows' standard protection against focus-stealing and you should not try to work around it.

阳光下的泡沫是彩色的 2024-08-16 22:23:27

要回答所提出的问题,您可以尝试调用 WindowFromPoint API 函数,用于查找表单上各个点的窗口,并检查它是否返回您期望在该点的句柄。

To answer the question as asked, you could try calling the WindowFromPoint API function to find the window at various points on your form, and check whether it returns the handle of whatever you expect to be at that point.

旧伤还要旧人安 2024-08-16 22:23:27

我实际上尝试实施 SLAks 的建议。虽然我是用VB.NET写的,而不是C#

Friend Structure PointStruct
    Public x As Int32
    Public y As Int32
End Structure

<System.Runtime.InteropServices.DllImport("user32.dll")> _
Friend Function WindowFromPoint(ByVal Point As PointStruct) As IntPtr
End Function

''' <summary>
''' Checks if a control is actually visible to the user completely
''' </summary>
''' <param name="control">The control to check.</param>
''' <returns>True, if the control is completely visible, false else.</returns>
''' <remarks>This is not 100% accurate, but feasible enough for my purpose.</remarks>
Public Function IsControlVisibleToUser(ByVal control As Windows.Forms.Control) As Boolean
    If Not control.Visible Then Return False

    Dim bAllPointsVisible As Boolean = True
    Dim lPointsToCheck As New List(Of Point)
    'Add the points to check. In this case add the edges and some border points
    'between the edges.
    'Strangely, the exact edge points always return the false handle.
    'So we add a pixel into the control.
    lPointsToCheck.Add(New Point(control.Left + 1, control.Top + 1))
    lPointsToCheck.Add(New Point(control.Right - 1, control.Top + 1))
    lPointsToCheck.Add(New Point(control.Right - 1, control.Bottom - 1))
    lPointsToCheck.Add(New Point(control.Left + 1, control.Bottom - 1))
    lPointsToCheck.Add(New Point(control.Left + control.Width / 2, control.Top + 1))
    lPointsToCheck.Add(New Point(control.Right - 1, control.Top + control.Height / 2))
    lPointsToCheck.Add(New Point(control.Right - control.Width / 2, control.Bottom - 1))
    lPointsToCheck.Add(New Point(control.Left + 1, control.Bottom - control.Height / 2))
    'lPointsToCheck.Add(New Point(control.Left + control.Width / 2, control.Top + control.Height / 2))

    'Check each point. If all points return the handle of the control,
    'the control should be visible to the user.
    For Each oPoint In lPointsToCheck
        Dim sPoint As New PointStruct() With {
            .x = oPoint.X, _
            .y = oPoint.Y _
        }
        bAllPointsVisible = bAllPointsVisible And ( _
            (WindowFromPoint(sPoint) = control.Handle) _
        )
    Next

    Return bAllPointsVisible
End Function

I aktually tried to implement SLaks suggestion. Although I wrote it in VB.NET, not C#

Friend Structure PointStruct
    Public x As Int32
    Public y As Int32
End Structure

<System.Runtime.InteropServices.DllImport("user32.dll")> _
Friend Function WindowFromPoint(ByVal Point As PointStruct) As IntPtr
End Function

''' <summary>
''' Checks if a control is actually visible to the user completely
''' </summary>
''' <param name="control">The control to check.</param>
''' <returns>True, if the control is completely visible, false else.</returns>
''' <remarks>This is not 100% accurate, but feasible enough for my purpose.</remarks>
Public Function IsControlVisibleToUser(ByVal control As Windows.Forms.Control) As Boolean
    If Not control.Visible Then Return False

    Dim bAllPointsVisible As Boolean = True
    Dim lPointsToCheck As New List(Of Point)
    'Add the points to check. In this case add the edges and some border points
    'between the edges.
    'Strangely, the exact edge points always return the false handle.
    'So we add a pixel into the control.
    lPointsToCheck.Add(New Point(control.Left + 1, control.Top + 1))
    lPointsToCheck.Add(New Point(control.Right - 1, control.Top + 1))
    lPointsToCheck.Add(New Point(control.Right - 1, control.Bottom - 1))
    lPointsToCheck.Add(New Point(control.Left + 1, control.Bottom - 1))
    lPointsToCheck.Add(New Point(control.Left + control.Width / 2, control.Top + 1))
    lPointsToCheck.Add(New Point(control.Right - 1, control.Top + control.Height / 2))
    lPointsToCheck.Add(New Point(control.Right - control.Width / 2, control.Bottom - 1))
    lPointsToCheck.Add(New Point(control.Left + 1, control.Bottom - control.Height / 2))
    'lPointsToCheck.Add(New Point(control.Left + control.Width / 2, control.Top + control.Height / 2))

    'Check each point. If all points return the handle of the control,
    'the control should be visible to the user.
    For Each oPoint In lPointsToCheck
        Dim sPoint As New PointStruct() With {
            .x = oPoint.X, _
            .y = oPoint.Y _
        }
        bAllPointsVisible = bAllPointsVisible And ( _
            (WindowFromPoint(sPoint) = control.Handle) _
        )
    Next

    Return bAllPointsVisible
End Function
GRAY°灰色天空 2024-08-16 22:23:27

您还可以.. :) 从与窗口对应的 AutomationElement 获取 ClickablePoint 属性。
我不是 100% 确定这是否完全准确。它对我来说在 99% 的情况下都有效,我仍在检查其他 1% 的问题所在(可能在我这边或坏用户)处理,或。)

You also could.. :) get the ClickablePoint property from the AutomationElement corresponding to the window.
I am not 100%ly sure whether this is completely accurate though.. it has worked in 99% of the cases for me and I am still checking on the other 1%, where the problem lies (might be on my side or bad user handling, or.)

沧桑㈠ 2024-08-16 22:23:27

嗯……奇怪的问题。 :P

也许你可以询问表单的位置,如果两个表单重叠(找出它们的坐标,并制作一个简单的方法)检查​​一个表单是否有 Focus()。如果它具有焦点,那么 other 一定是“不可见的”(在某种意义上,用户看不到它,因为它位于其他表单的下面)。

显然,这种方法充其量很糟糕,但您可以开始使用它。

Hm... weird question. :P

Perhaps you could ask the location of the forms, and if two forms interlap (figure out their coords, and make a simple method) check if one form has Focus(). If it has focus, then other must be "invisible" (in the sense that a user can't see it because it's underneath the other form).

Obviously this method is hacky at best, but it's something you can start working with.

本王不退位尔等都是臣 2024-08-16 22:23:27

您应该能够通过重写 OnPaint 方法来确定窗口是否可见。您需要将控制权传递给基类以便进行实际的绘制,但您将能够检测是否收到绘制消息。
更新:不,这不起作用,抱歉!

原则上,Activate 方法应该将您的窗口带到前台,但在实践中,如果其他进程具有输入焦点,我总是发现这个问题。如果您确实希望别人看到窗口,请设置最上面的位,但预计他们会生气!一个让窗口引起注意的可靠方法是关闭它然后重新打开它,如果你能做到的话。

实现您想要的效果的一种方法是使用通知图标,这将以符合 Windows UI 准则的方式引起用户的注意。

You should be able to find out if your window is visible by overriding the OnPaint method. You'll want to pass control to the base class in order to do the actual painting, but you'll be able to detect whether a paint message is received.
Update: no, this doesn't work, Sorry!

In principle, the Activate method should bring your window to the foreground, but in practice I've always found this problematic if other processes have the input focus. If you really want someone to see a window, set the topmost bit, but expect them to be annoyed! One surefire way to get some attention for a window is to close it and reopen it, if you can get away with that.

One way to achieve what you're looking for is to use a notify icon, this will get the user's attention in a way that is compliant with Windows UI guidelines.

原谅我要高飞 2024-08-16 22:23:27

以下是 @MarvinDickhaus 的回答

它允许通过仅检查某些点来测试窗口或任何控件是否可见或部分可见。

主要的基本兴趣是能够将完全或部分覆盖的表格放在前面。

它使用 13 个点,但可以添加更多点,并且可以改进该方法以传递任意数字并根据该数字计算坐标。 Windows 10 表单的默认边距设置为 15。

static partial class NativeMethods
{
  [StructLayout(LayoutKind.Sequential)]
  public struct PointStruct
  {
    public int X;
    public int Y;
    public PointStruct(int x, int y)
    {
      X = x;
      Y = y;
    }
  }
  [DllImport("user32.dll")]
  static public extern IntPtr WindowFromPoint(PointStruct Point);
}
static public IEnumerable<T> GetAllControls<T>(this Control control)
{
  var controls = control.Controls.OfType<T>();
  return control.Controls.Cast<Control>()
                         .SelectMany(c => c.AllControls<T>())
                         .Concat(controls);
}
static public List<Point> GetGridPoints(this Control control, int margin = 15)
{
  int widthDiv2 = control.Width / 2;
  int heightDiv2 = control.Height / 2;
  int widthDiv4 = widthDiv2 / 4;
  int heightDiv4 = heightDiv2 / 4;
  var points = new List<Point>();
  // Center
  points.Add(new Point(control.Left + widthDiv2, control.Top + heightDiv2));
  // Corners
  points.Add(new Point(control.Left + margin, control.Top + margin));
  points.Add(new Point(control.Right - margin, control.Top + margin));
  points.Add(new Point(control.Left + margin, control.Bottom - margin));
  points.Add(new Point(control.Right - margin, control.Bottom - margin));
  // Borders
  points.Add(new Point(control.Left + widthDiv4, control.Top + heightDiv4));
  points.Add(new Point(control.Right - widthDiv4, control.Top + heightDiv4));
  points.Add(new Point(control.Left + widthDiv4, control.Bottom - heightDiv4));
  points.Add(new Point(control.Right - widthDiv4, control.Bottom - heightDiv4));
  // Inner
  points.Add(new Point(control.Left + widthDiv2, control.Top + margin));
  points.Add(new Point(control.Left + widthDiv2, control.Bottom - margin));
  points.Add(new Point(control.Left + margin, control.Top + heightDiv2));
  points.Add(new Point(control.Right - margin, control.Top + heightDiv2));
  return points;
}
static public bool IsVisibleOnTop(this Control control, int requiredPercent = 100, int margin = 15)
{
  if ( !control.Visible ) return false;
  var controls = control.GetAllControls<Control>().Select(c => c.Handle).ToList();
  var points = control.GetGridPoints(margin);
  bool all = requiredPercent == 100;
  int found = 0;
  int required = points.Count();
  if (!all) required = required * requiredPercent / 100;
  foreach ( var point in points )
  {
    var handle = NativeMethods.WindowFromPoint(new NativeMethods.PointStruct(point.X, point.Y));
    if ( handle == control.Handle || controls.Contains(handle) )
    {
      if ( ++found == required ) return true;
    }
    else
    {
      if ( all ) return false;
    }
  }
  return false;
}

Here is a modified C# version of the code in @MarvinDickhaus' answer.

It allows to test if a window or any control is visible or partially visible by checking only certain points.

The main basic interest is to be able to bring to front a fully or partially covered form.

It uses 13 points but more can be added, and the method can be improved to pass an arbitrary number and calculate the coordinates from that number. The default margin is set to 15 for Windows 10 forms.

static partial class NativeMethods
{
  [StructLayout(LayoutKind.Sequential)]
  public struct PointStruct
  {
    public int X;
    public int Y;
    public PointStruct(int x, int y)
    {
      X = x;
      Y = y;
    }
  }
  [DllImport("user32.dll")]
  static public extern IntPtr WindowFromPoint(PointStruct Point);
}
static public IEnumerable<T> GetAllControls<T>(this Control control)
{
  var controls = control.Controls.OfType<T>();
  return control.Controls.Cast<Control>()
                         .SelectMany(c => c.AllControls<T>())
                         .Concat(controls);
}
static public List<Point> GetGridPoints(this Control control, int margin = 15)
{
  int widthDiv2 = control.Width / 2;
  int heightDiv2 = control.Height / 2;
  int widthDiv4 = widthDiv2 / 4;
  int heightDiv4 = heightDiv2 / 4;
  var points = new List<Point>();
  // Center
  points.Add(new Point(control.Left + widthDiv2, control.Top + heightDiv2));
  // Corners
  points.Add(new Point(control.Left + margin, control.Top + margin));
  points.Add(new Point(control.Right - margin, control.Top + margin));
  points.Add(new Point(control.Left + margin, control.Bottom - margin));
  points.Add(new Point(control.Right - margin, control.Bottom - margin));
  // Borders
  points.Add(new Point(control.Left + widthDiv4, control.Top + heightDiv4));
  points.Add(new Point(control.Right - widthDiv4, control.Top + heightDiv4));
  points.Add(new Point(control.Left + widthDiv4, control.Bottom - heightDiv4));
  points.Add(new Point(control.Right - widthDiv4, control.Bottom - heightDiv4));
  // Inner
  points.Add(new Point(control.Left + widthDiv2, control.Top + margin));
  points.Add(new Point(control.Left + widthDiv2, control.Bottom - margin));
  points.Add(new Point(control.Left + margin, control.Top + heightDiv2));
  points.Add(new Point(control.Right - margin, control.Top + heightDiv2));
  return points;
}
static public bool IsVisibleOnTop(this Control control, int requiredPercent = 100, int margin = 15)
{
  if ( !control.Visible ) return false;
  var controls = control.GetAllControls<Control>().Select(c => c.Handle).ToList();
  var points = control.GetGridPoints(margin);
  bool all = requiredPercent == 100;
  int found = 0;
  int required = points.Count();
  if (!all) required = required * requiredPercent / 100;
  foreach ( var point in points )
  {
    var handle = NativeMethods.WindowFromPoint(new NativeMethods.PointStruct(point.X, point.Y));
    if ( handle == control.Handle || controls.Contains(handle) )
    {
      if ( ++found == required ) return true;
    }
    else
    {
      if ( all ) return false;
    }
  }
  return false;
}
罪歌 2024-08-16 22:23:27

只需将 Form.AlwaysOnTop 属性设置为 true 即可。

Simply set the Form.AlwaysOnTop property to true.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文