Как проверить, действительно ли окно видно в Windows Forms? - PullRequest
9 голосов
/ 30 октября 2009

Обычно вы используете Form.Visible, чтобы проверить, видно ли окно вообще. Но иногда на экране окно находится ниже других окон, поэтому оно действительно невидимо.

Так как же проверить в c # Windows Forms, действительно ли окно действительно видно или нет?

Я хотел бы добиться этого: когда я нажимаю CTRL + K на клавиатуре и мое окно видно на экране, оно ничего не делает. Но когда оно находится под другими окнами, оно всплывает наверх (на передний план).

С уважением

Ответы [ 9 ]

6 голосов
/ 06 декабря 2010

Я погуглил в Интернете, но не смог найти прямого ответа, чтобы увидеть, действительно ли часть окна действительно видна пользователю. Я действительно нуждался в способе «проверить себя» на форме, если мышь в данный момент находится в верхней части видимой части окна. Я думал, что поделюсь кодом, который занял несколько дней:

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;
        }
    }
}
5 голосов
/ 30 октября 2009

Вы можете вызвать метод Activate в форме, чтобы вывести его на передний план, если это еще не сделано.

Однако учтите, что если активна другая программа, она обычно просто мигает кнопкой на рабочем столе (в зависимости от того, откуда вы ее вызываете). Это стандартная защита Windows от кражи фокуса , и вы не должны пытаться обойти ее .

4 голосов
/ 30 октября 2009

Вы можете использовать Windows API для перечисления всех окон, получения их Z-порядка и сравнения его с Z-порядком вашего окна. Я думаю, что кто-то уже сделал это здесь .

3 голосов
/ 30 октября 2009

Чтобы ответить на заданный вопрос, вы можете попробовать вызвать API-функцию WindowFromPoint, чтобы найти окно в различных точках формы и проверить, возвращает ли оно дескриптор того, что вы ожидаете увидеть в этот момент.

1 голос
/ 26 мая 2013

Я на самом деле пытался реализовать предложение 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
1 голос
/ 24 мая 2011

Вы также можете .. :) получить свойство ClickablePoint из элемента AutomationElement, соответствующего окну. Я не уверен на 100%, является ли это полностью точным, хотя ... это сработало для меня в 99% случаев, и я все еще проверяю другие 1%, где проблема (может быть на моей стороне или плохом пользователе) обработка или.)

1 голос
/ 30 октября 2009

Хм ... странный вопрос. : P

Возможно, вы могли бы спросить расположение форм, и если две формы пересекаются (выяснить их координаты и создать простой метод), проверить, есть ли у одной формы Focus (). Если он имеет фокус, то другие должны быть «невидимыми» ( в том смысле, что пользователь не может их видеть, потому что он находится под другой формой ).

Очевидно, этот метод хакерский в лучшем случае , но это то, с чем вы можете начать работать.

0 голосов
/ 02 апреля 2012

Просто установите для свойства Form.AlwaysOnTop значение true.

0 голосов
/ 31 октября 2009

Вы должны быть в состоянии выяснить, является ли ваше окно видимым, переопределив метод OnPaint. Вы захотите передать управление базовому классу, чтобы выполнить рисование, но сможете определить, получено ли сообщение рисования. Обновление: нет, это не работает, извините!

В принципе, метод Activate должен вывести ваше окно на передний план, но на практике я всегда находил это проблематичным, если другие процессы имеют фокус ввода. Если вы действительно хотите, чтобы кто-то увидел окно, установите верхний бит, но ожидайте, что он будет раздражен! Один верный способ привлечь внимание к окну - это закрыть его и снова открыть, если вам это сойдет с рук.

Один из способов добиться того, что вы ищете, - использовать значок уведомления, который привлечет внимание пользователя в соответствии с рекомендациями по пользовательскому интерфейсу Windows.

...