Sunday 17 December 2017

WinAPI - there's a difference between Child windows and Popup windows - for controlling visibility at least

So there is currently a SO bounty where poster wants to hide all windows for a program. They launched the program using the Windows Script Host library (c:\Windows\SysWOW64\wshom.ocx). They use the Run method of the Shell object (another link). But despite specifying WshWindowStyle.WshHide, popups still appear. Because of the bounty I decided to investigate, but I started with C# (see bottom of page), then I translated my findings into VBA (see first listing ).

Firstly, a key reading resource is Windows Features which tells that all windows are created with CreateWindowEx but popups are create by specifying WS_POPUP and child windows are created by specifying WS_CHILD. So popups and child windows are different.

On the same page in the section Window Visibility it explains that we can set the visibility of a main window and the change will cascade down to all child windows but there is no mention of this cascade affecting popups.

The short answer to the investigation is that to hide popups it is required to call ShowOwnedPopups(hwnd,0). The VBA declaration is given here


Declare Function ShowOwnedPopups Lib "user32" Alias "ShowOwnedPopups" _
       (ByVal hwnd As Long, ByVal fShow As Long) As Long

And here is some final VBA code but which depends upon a simple C# demo program called VisibilityExperiment

Option Explicit

Private Declare Function ShowOwnedPopups Lib _
    "user32" (ByVal hwnd As Long, _
    ByVal fShow As Long) As Long

Private Declare Function EnumWindows _
    Lib "user32" ( _
        ByVal lpEnumFunc As Long, _
        ByVal lParam As Long) _
        As Long
        
Private Declare Function GetWindowThreadProcessId _
    Lib "user32" (ByVal hwnd As Long, lpdwprocessid As Long) As Long
    

    
Private mlPid As Long
Private mlHWnd As Variant


Private Function EnumAllWindows(ByVal hwnd As Long, ByVal lParam As Long) As Long
    
    Dim plProcID As Long
    GetWindowThreadProcessId hwnd, plProcID
    If plProcID = mlPid Then
        If IsEmpty(mlHWnd) Then
            mlHWnd = hwnd
            Debug.Print "HWnd:&" & Hex$(mlHWnd) & "  PID:&" & Hex$(mlPid) & "(" & mlPid & ")"
        End If
    End If

    EnumAllWindows = True
End Function

Private Function GetPID(ByVal sExe As String) As Long

    Static oServ As Object
    If oServ Is Nothing Then Set oServ = GetObject("winmgmts:\\.\root\cimv2")
    
    Dim cProc As Object
    Set cProc = oServ.ExecQuery("Select * from Win32_Process")
    
    Dim oProc As Object
    For Each oProc In cProc
        If oProc.Name = sExe Then
            Dim lPid As Long
            GetPID = oProc.ProcessID
        End If
    Next

End Function


Private Sub Test()
    '* Tools->References   "Windows Script Host library"

    Dim wsh As IWshRuntimeLibrary.WshShell
    Set wsh = New IWshRuntimeLibrary.WshShell

    Dim lWinStyle As WshWindowStyle
    lWinStyle = WshNormalFocus

    Dim sExe As String
    sExe = "VisibilityExperiment.exe"

    Dim sExeFullPath As String
    sExeFullPath = Environ$("USERPROFILE") & "\source\repos\VisibilityExperiment\VisibilityExperiment\bin\Debug\" & sExe

    Dim x As Long
    x = wsh.Run(sExeFullPath, lWinStyle, False)

    mlPid = GetPID(sExe)

    mlHWnd = Empty
    Call EnumWindows(AddressOf EnumAllWindows, 0)


    Stop
    Call ShowOwnedPopups(mlHWnd, 0)  '* o to hide, 1 to show

End Sub



To repeat, to hide popups one must call ShowOwnedPopups(). Sadly, I cannot see around this restriction. Even if we tried to use the Windows API directly to spawn the process there is nothing in the STARTUPINFO structure (Windows) which looks like it will help, there is nothing to specify the visibility of popups.

Here is the source code of the form to the C# program VisibilityExperiment.exe, it is a normal Windows Forms application and I have added a single button called button1,


using System;
using System.Windows.Forms;

namespace VisibilityExperiment
{
    public partial class VisibilityExperiment : Form
    {
        public VisibilityExperiment()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("message", "caption", MessageBoxButtons.YesNo);
        }
    }
}

And here is my experiment program call VisibilityExperiment2.exe . It is again a simple WinForms Application. You run VisibilityExperiment manually and you can see the effect of calling different Windows API functions. You will see that the message box does will not be hidden unless you use ShowOwnedPopups.

Here is the form layout

.

You will have to rename the controls btnGetMainWindowHandle, btnShow, btnHide, grpShowHide, chkShowOwnedPopups, chkShowWindowAsync, chkSetWindowPos for the following form code to work...


using System;
using System.Diagnostics;
using System.Dynamic;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace VisibilityExperiment2
{


    public partial class ShowerHider : Form
    {
        IntPtr mainWindowHandle;

        [DllImport("user32.dll")]
        static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);

        [DllImport("user32.dll")]
        static extern bool ShowOwnedPopups(IntPtr hWnd, int nCmdShow);

        [DllImport("user32.dll", SetLastError = true)]
        static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);

        #region Constants

        enum SetWindowPosFlags
        {
            NOSIZE = 0x0001,
            NOMOVE = 0x0002,
            NOZORDER = 0x0004,
            NOREDRAW = 0x0008,
            NOACTIVATE = 0x0010,
            DRAWFRAME = 0x0020,
            FRAMECHANGED = 0x0020,
            SHOWWINDOW = 0x0040,
            HIDEWINDOW = 0x0080,
            NOCOPYBITS = 0x0100,
            NOOWNERZORDER = 0x0200,
            NOREPOSITION = 0x0200,
            NOSENDCHANGING = 0x0400,
            DEFERERASE = 0x2000,
            ASYNCWINDOWPOS = 0x4000
        };

        static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
        static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
        static readonly IntPtr HWND_TOP = new IntPtr(0);
        static readonly IntPtr HWND_BOTTOM = new IntPtr(1);

        private const int SW_HIDE = 0;
        private const int SW_SHOWNORMAL = 1;
        private const int SW_SHOW = 5;
        #endregion Constants

        public ShowerHider()
        {
            InitializeComponent();
        }



        private ExpandoObject CheckForPID()
        {
            dynamic retVal = new ExpandoObject();

            Process[] processes = Process.GetProcessesByName("VisibilityExperiment");
            if (processes.Length==0)
            {
                retVal.pid = -1;
                
            } else if (processes.Length == 1) {

                Process proc = processes[0];
                retVal.pid = proc.Id;
                retVal.hWnd = proc.MainWindowHandle;
                

            } else
            {
                retVal.pid = -2;
            }
            return retVal;
        }

        private void btnGetMainWindowHandle_Click(object sender, EventArgs e)
        {
            dynamic retVal = CheckForPID();

            if (retVal.pid>=0)
            {
                mainWindowHandle = retVal.hWnd;
                this.textBox1.Text = retVal.hWnd.ToString("X8");
                this.grpShowHide.Enabled = true;
            }

            
            
        }

        private void btnShow_Click(object sender, EventArgs e)
        {
            if (this.chkSetWindowPos.Checked == true)
            {
                SetWindowPos(mainWindowHandle, IntPtr.Zero, 0, 0, 0, 0,SetWindowPosFlags.SHOWWINDOW | 
                    SetWindowPosFlags.NOMOVE | SetWindowPosFlags.NOSIZE);
            }


            if (this.chkShowWindowAsync.Checked==true)
            { 
                ShowWindowAsync(mainWindowHandle, SW_SHOW);
                //* Need to call this twice of the first time
                //* TODO Eastablish first time versus subsequent time logic 
                ShowWindowAsync(mainWindowHandle, SW_SHOW);
            }

            if (this.chkShowOwnedPopups.Checked == true)
                ShowOwnedPopups(mainWindowHandle, 1);
        }

        private void btnHide_Click(object sender, EventArgs e)
        {

            if (this.chkSetWindowPos.Checked == true)
            {
                SetWindowPos(mainWindowHandle, IntPtr.Zero, 0, 0, 0, 0,SetWindowPosFlags.HIDEWINDOW |
                    SetWindowPosFlags.NOMOVE | SetWindowPosFlags.NOSIZE);
            }

            if (this.chkShowWindowAsync.Checked == true)
            {
                ShowWindowAsync(mainWindowHandle, SW_HIDE);
                //* Need to call this twice of the first time
                //* TODO Eastablish first time versus subsequent time logic 

                ShowWindowAsync(mainWindowHandle, SW_HIDE);
            }
            if (this.chkShowOwnedPopups.Checked == true)
                ShowOwnedPopups(mainWindowHandle, 0);



        }

        private void chkShowWindowAsync_CheckedChanged(object sender, EventArgs e)
        {

        }
    }
}



No comments:

Post a Comment