如何检查窗口在 Windows 窗体中是不是真的可见?

Posted

技术标签:

【中文标题】如何检查窗口在 Windows 窗体中是不是真的可见?【英文标题】:How to check if window is really visible in Windows Forms?如何检查窗口在 Windows 窗体中是否真的可见? 【发布时间】:2010-12-11 14:53:19 【问题描述】:

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

那么如何在 c# Windows Forms 中检查窗口是否真的可见?

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

亲切的问候

【问题讨论】:

【参考方案1】:

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

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;
        
    

【讨论】:

【参考方案2】:

如果还没有,您可以调用表单上的Activate 方法将其置于最前面。

但是,请注意,如果另一个程序处于活动状态,它通常只会闪烁桌面按钮(取决于您从哪里调用它)。这是 Windows 的 standard protection against focus-stealing 而你是 should not try to work around it。

【讨论】:

【参考方案3】:

您可以使用 Windows API 枚举所有窗口,检索它们的 Z-Order 并将其与窗口的 Z-Order 进行比较。我想有人已经这样做了here。

【讨论】:

【参考方案4】:

要回答所提出的问题,您可以尝试调用WindowFromPoint API 函数以在表单的不同位置查找窗口,并检查它是否返回您希望在该位置出现的任何句柄。

【讨论】:

这对我来说是搜索网络和 SO 之后的最佳答案。 SLaks来救援!这样的信誉不言而喻。【参考方案5】:

我实际上尝试实施 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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

嗯……奇怪的问题。 :P

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

显然,这种方法充其量很老套,但你可以开始使用它。

【讨论】:

【参考方案8】:

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

原则上,Activate 方法应该将您的窗口置于前台,但在实践中,如果其他进程具有输入焦点,我总是发现这个问题。如果你真的想让某人看到一个窗口,设置最高位,但希望他们会生气!让窗口引起注意的一种可靠方法是关闭它并重新打开它,如果你能侥幸逃脱的话。

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

【讨论】:

【参考方案9】:

这是@MarvinDickhaus' answer中代码的修改C#版本。

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

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

它使用 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;

【讨论】:

【参考方案10】:

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

【讨论】:

如果另一个窗口也具有相同的属性将无济于事

以上是关于如何检查窗口在 Windows 窗体中是不是真的可见?的主要内容,如果未能解决你的问题,请参考以下文章

如何从文本框(windows窗体)检查主键是不是存在于我的数据库中,c#

如何在windows窗体里面添加窗口

如何在 Windows 窗体应用程序设置中记录窗口位置

wpf窗体中如何调用windows窗口?

如何在鼠标位置打开具有 Windows 窗体父级的 WPF 窗口?

C#。当我尝试检查用户是不是存在于我的 SQL 数据库中时,我的 Windows 窗体应用程序崩溃