7

With this code:

internal static List<DetectedWindow> EnumerateWindows()
{
    var shellWindow = GetShellWindow();

    var windows = new List<DetectedWindow>();

    EnumWindows(delegate (IntPtr handle, int lParam)
    {
        if (handle == shellWindow)
            return true;

        if (!IsWindowVisible(handle))
            return true;

        if (IsIconic(handle))
            return true;

        var length = GetWindowTextLength(handle);

        if (length == 0)
            return true;

        var builder = new StringBuilder(length);

        GetWindowText(handle, builder, length + 1);
        GetWindowRect(handle, out Rect rect);

        windows.Add(new DetectedWindow(handle, rect.ToRect(), builder.ToString()));

        return true;
    }, IntPtr.Zero);

    return windows;
}

Auxiliar class:

public class DetectedWindow
{
    public IntPtr Handle { get; private set; }

    public Rect Bounds { get; private set; }

    public string Name { get; private set; }

    public DetectedWindow(IntPtr handle, Rect bounds, string name)
    {
        Handle = handle;
        Bounds = bounds;
        Name = name;
    }
}

I'm getting this list of applications (Window text - Rect bounds):

Microsoft Visual Studio  - -8;-8;1936;1056
Microsoft Edge - 0;77;1920;963
EnumWindows - Stack Overflow and 7 more pages ‎- Microsoft Edge - -8;-8;1936;1056
Microsoft Edge - 0;77;1920;963
Microsoft Edge - 0;77;1920;963
Microsoft Edge - 0;0;1920;1080
Microsoft Edge - 0;0;1920;1080
Microsoft Edge - 0;8;1920;1040
Microsoft Edge - 0;85;1920;963
Microsoft Edge - 150;79;1532;42
Microsoft Edge - 0;85;1920;963
Microsoft Edge - 0;77;1920;963
Microsoft Edge - 0;85;1920;963
Microsoft Edge - 0;213;1920;964
Microsoft Edge - 0;0;1920;1080
Microsoft Edge - 484;208;952;174
Microsoft Edge - 0;84;1920;964
Microsoft Edge - 0;84;1920;964
Microsoft Edge - 0;84;1920;964 
Microsoft Edge - 0;0;1920;1080
Mail - 0;32;1356;693
Mail - 278;252;1372;733
OneNote - 0;8;1920;1040
My notes - OneNote - -8;-8;1936;1056
Photos - 0;32;1920;1008
Photos - -8;-8;1936;1056
Skype - 0;40;1920;1008
Skype - -8;-8;1936;1056
Store - 0;40;1920;1008
Store - -8;-8;1936;1056
Movies & TV - 0;0;1920;1080
Movies & TV - -8;-8;1936;1056
Groove Music - 0;32;1466;712
Groove Music - -7;3;1372;733
Settings - 0;40;1920;1008
Settings - -8;-8;1936;1056
Windows Shell Experience Host - 0;0;1920;1080

My current not minimized windows are Visual Studio and two Edge windows (with several tabs each). I can understand the fact that only one Edge item was listing the title of the current page. Because I recently recovered from a crash and only that page was loaded.

My questions are:

  1. Why are my closed Windows Store apps being listed? (and even twice)
  2. Why are my Edge tabs being listed?
  3. How can I filter the Edge tabs and the closed Windows Store apps?

EDIT:

  1. By "Filter": Only retrieve the apps with visible window. With my use case, only 3 windows are visible.

I tried to get the WsStyle and WsEXStyle of each window to compare, but I couldn't find any difference.

The method IsWindowVisible() fails filter out the Windows Store apps that are not visible.

Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
  • 2
    [Windows 10 universal Windows platform (UWP) app lifecycle](https://learn.microsoft.com/en-us/windows/uwp/launch-resume/app-lifecycle). – IInspectable May 12 '17 at 07:37
  • I understand about the lifecycle of Store apps. But not about why EnumWindow (+ my checks) returns that kind of app even when the window is not visible. Also it returns 2 items of each. – Nicke Manarin May 12 '17 at 15:34

3 Answers3

16

Why are my closed Windows Store apps being listed?

Because they are not actually closed. Easy to see with Task Manager, Processes tab. You'll see that the process that owns these windows is suspended. Part of the WinRT (aka UWP, aka Store, aka Modern UI, aka Metro) programming framework approach, modern machines have enough RAM to make it feasible to keep processes running even if the user doesn't interact with them. Brings them back quickly again and saves battery life. If RAM is needed elsewhere then the OS will salvage it by killing such a process.

Why are my Edge tabs being listed?

Because Edge is a WinRT app as well.

How can I filter the Edge tabs and the closed Windows Store apps?

It is not entirely clear by which property you want to filter, given that the window is in fact not closed. GetWindowThreadProcessId() and IsImmersiveProcess() can tell you that you are dealing with such a process. Consider IsWindowVisible(). Maybe this post can help, also tells you why you see multiple windows.


Edit (Nicke Manarin):

By checking the Cloacked attribute, it is possible to ignore the hidden/background Store apps:

DwmGetWindowAttribute(handle, (int)DwmWindowAttribute.Cloaked, out bool isCloacked, Marshal.SizeOf(typeof(bool)));

if (isCloacked)
    return true;

Edit 2 (Nicke Manarin):

Each Edge tab behaves like a window (I believe it has something to do with the fact that you can drag the tab to create a new window).

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1) Yes, but why twice? 2) Maybe I'm getting the a lot of Edge items because each tab acts like a window, so it can be dragged... 3) I want to remove those entries from my list, I only want to get the visible windows. As you can see, I'm already using IsWindowVisible(), yet I'm getting the those apps. – Nicke Manarin May 14 '17 at 02:32
  • 3
    Cloaked windows return TRUE from IsWindowVisible(), yet aren't actually visible. Unfortunately. – Eric Brown May 17 '17 at 17:24
3

I can't reproduce the behavior in my W10 Desktop. After using your code, also filtering out the WsEXStyle WS_EX_TOOLWINDOW, shows the same apps than alt+tab does. I opened and closed Edge and Photos and they didn't appear when closed anymore. Maybe it was tinkering with P/Invoke that triggered that behavior. Does it continue with the code below after a restart?.

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{

    public class EnumerateWindowsTest
    {

        [StructLayout(LayoutKind.Sequential)]
        public 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
        }
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool GetWindowRect(IntPtr hWnd, out Rect lpRect);

        [DllImport("user32.dll")]
        static extern IntPtr GetShellWindow();


        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern int GetWindowTextLength(IntPtr hWnd);
        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
        public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
        [DllImport("user32.dll")]
        static extern int EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool IsWindowVisible(IntPtr hWnd);
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool IsIconic(IntPtr hWnd);
        [DllImport("user32.dll", SetLastError = true)]
        static extern System.UInt32 GetWindowLong(IntPtr hWnd, int nIndex);


        internal static List<DetectedWindow> EnumerateWindows()
        {
            var shellWindow = GetShellWindow();
            var windows = new List<DetectedWindow>();
            EnumWindows(delegate (IntPtr handle, IntPtr lParam)
            {

                if (handle == shellWindow)
                    return true;

                if (!IsWindowVisible(handle))
                    return true;

                if (IsIconic(handle))
                    return true;

                if (HasSomeExtendedWindowsStyles(handle))
                    return true;

                var length = GetWindowTextLength(handle);

                if (length == 0)
                    return true;

                var builder = new StringBuilder(length);

                GetWindowText(handle, builder, length + 1);
                GetWindowRect(handle, out Rect rect);
                windows.Add(new DetectedWindow(handle, rect, builder.ToString()));

                return true;
            }, IntPtr.Zero);

            return windows;
        }

        static bool HasSomeExtendedWindowsStyles(IntPtr hwnd)
        {
            const int GWL_EXSTYLE = -20;
            const uint WS_EX_TOOLWINDOW = 0x00000080U;

            uint i = GetWindowLong(hwnd, GWL_EXSTYLE);
            if ((i & (WS_EX_TOOLWINDOW)) != 0)
            {
                return true;
            }

            return false;
        }

    }

    public class DetectedWindow
    {
        public IntPtr Handle { get; private set; }

        public EnumerateWindowsTest.Rect Bounds { get; private set; }

        public string Name { get; private set; }

        public DetectedWindow(IntPtr handle, EnumerateWindowsTest.Rect bounds, string name)
        {
            Handle = handle;
            Bounds = bounds;
            Name = name;
        }
    }



    class Program
    {

        static void DetectWindows()
        {
            foreach (DetectedWindow w in EnumerateWindowsTest.EnumerateWindows())
            {
                Console.WriteLine("{0} - {1};{2};{3};{4}",w.Name,w.Bounds.Left,w.Bounds.Top,w.Bounds.Right,w.Bounds.Bottom);
            }
        }

        static void Main(string[] args)
        {
            DetectWindows();
            Console.ReadLine();
        }
    }


}
xyq.384.b
  • 2,086
  • 1
  • 13
  • 4
  • The problem is that I want tool windows too, not just normal windows. – Nicke Manarin May 21 '17 at 00:46
  • What do you mean by tool windows, any examples? – xyq.384.b May 21 '17 at 01:12
  • A window with the extended style WS_EX_TOOLWINDOW enabled. This setting changes the title bar UI of the window. Example: http://i.stack.imgur.com/TYYek.jpg (background: normal window, foreground: tool window) – Nicke Manarin May 21 '17 at 01:38
  • If I comment out the lines "if (HasSomeExtendedWindowsStyles(handle)) return true;", and so including WS_EX_TOOLWINDOW , other windows will appear but still I can't see the behavior you were mentioning. – xyq.384.b May 21 '17 at 01:49
  • Right now me neither. I'm not sure what triggers the Cloaked behavior. But it looks like it only happens with Store apps when they are running in background. – Nicke Manarin May 21 '17 at 02:04
0

I have made simple window filter method that include Microsoft store apps that are cloaked. They are also identified by the class names ApplicationFrameWindow and Windows.UI.Core.CoreWindow

public static class WindowFilter
{
    public static bool NormalWindow(IWindow window)
    {
        if (IsHiddenWindowStoreApp(window,  window.ClassName)) return false;

        return !window.Styles.IsToolWindow && window.IsVisible;
    }

    private static bool IsHiddenWindowStoreApp(IWindow window, string className) 
        => (className == "ApplicationFrameWindow" || className == "Windows.UI.Core.CoreWindow") && window.IsCloaked;
}

The above example is part of a project of github, were you can see the rest of the code. https://github.com/mortenbrudvik/WindowExplorer

Morten Brudvik
  • 439
  • 5
  • 10