Topmost window over everything, except child processes - vb.net

My program is full screen and topmost, and I would like all the child processes of that window to be above my program's main window. The processes are unknown, and external.
I can launch the process using System.Diagnostics.Process.Start(exeName,procArgs).WaitForExit(), but from there I am stuck.

Basically you use the SetParent() API to make the external app a child of yours. Here I'm also using the GetWindowRect() and SetWindowPos() APIs to keep the window in the same launch position after its parent is changed. Finally, you need to keep track of the processes and close them manually so they do not become orphaned when the form is closed:
Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Text.RegularExpressions
Public Class Form1
Private Const SWP_NOSIZE As Integer = &H0001
<StructLayout(LayoutKind.Sequential)>
Public Structure RECT
Public Left As Integer, Top As Integer, Right As Integer, Bottom As Integer
End Structure
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Shared Function SetParent(ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As IntPtr
End Function
<DllImport("user32.dll")>
Private Shared Function GetWindowRect(ByVal hWnd As IntPtr, ByRef lpRect As RECT) As Boolean
End Function
<DllImport("user32.dll", SetLastError:=True)>
Private Shared Function SetWindowPos(ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As Integer) As Boolean
End Function
Private Ps As New List(Of Process)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim exeName As String = "Notepad"
Dim procArgs As String = ""
LaunchExe(exeName, procArgs)
End Sub
Private Sub LaunchExe(ByVal exeName As String, ByVal procArgs As String)
Try
Dim p As Process = System.Diagnostics.Process.Start(exeName, procArgs)
If Not IsNothing(p) Then
p.WaitForInputIdle()
Dim rc As RECT
GetWindowRect(p.MainWindowHandle, rc)
Dim pt As New Point(rc.Left, rc.Top)
pt = Me.PointToClient(pt)
SetParent(p.MainWindowHandle, Me.Handle)
SetWindowPos(p.MainWindowHandle, 0, pt.X, pt.Y, 0, 0, SWP_NOSIZE)
Ps.Add(p)
End If
Catch ex As Exception
MessageBox.Show(exeName & vbCrLf & procArgs, "Error Starting Application")
End Try
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Me.Close()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
For Each P As Process In Ps
If Not P.HasExited Then
P.CloseMainWindow()
End If
Next
End Sub
End Class

Related

How to resize a parent form and panel to fit a child window

I use the code below to start a cmd.exe windows and move it into a panel after execution. At this moment, the child window is resized to fit into Panel1 on my Form1. I would like it to be the other way around: my Form1 (and thus Panel1) should resize to fit the size of the child window, so no matter what the size of the default cmd.exe window is on the local computer.
Does anybody know how I should do that?
Thanks for any help in advance!
Kind regards,
Eric
Imports System.Runtime.InteropServices
Public Class Form1
Private WithEvents Tmr As New Timer With {.Interval = 100}
Private Const HWND_BOTTOM As Integer = &H1
Private WithEvents proc As New Process
<DllImport("user32.dll", EntryPoint:="SetParent")>
Private Shared Function SetParent(ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As IntPtr
End Function
<DllImport("user32.dll", EntryPoint:="SetWindowPos")>
Private Shared Function SetWindowPos(ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As UInteger) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Text = "My title"
proc.EnableRaisingEvents = True
proc.StartInfo.FileName = "cmd"
proc.Start()
Tmr.Start()
End Sub
Private Sub Tmr_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Tmr.Tick
If SetParent(proc.MainWindowHandle, Panel1.Handle) <> IntPtr.Zero Then
Tmr.Stop()
SetWindowPos(proc.MainWindowHandle, New IntPtr(HWND_BOTTOM), 0, 0, Panel1.ClientSize.Width, Panel1.ClientSize.Height, 0)
End If
End Sub
Private Sub Proc_Exited(ByVal sender As Object, ByVal e As System.EventArgs) Handles proc.Exited
Invoke(Sub() Close())
End Sub
End Class
As suggested by #Jimi, I used DwmGetWindowAttribute to retrieve the size of the child, before moving it into the panel. Afterwards I use ShowWindow to maximize it, to get rid of the borders.
The changed code can be found in a different post of mine.

Unable to find RECT size with DwmGetWindowAttribute

I'm trying to find to find the size of the cmd.exe window which is started as a child. I like to use this size to resize my form accordingly. For some reason the size returned by DwmGetWindowAttribute is always zero, so I must be doing something wrong here, but I can't find it. Any help will be greatly appreciated.
Kind regards,
Eric
Imports System.Runtime.InteropServices
Public Class Form1
Private WithEvents Tmr As New Timer With {.Interval = 100}
Private Const HWND_BOTTOM As Integer = &H1
Private WithEvents proc As New Process
Public Const DWMWA_EXTENDED_FRAME_BOUNDS As Integer = 9
<DllImport("user32.dll", EntryPoint:="SetParent")>
Private Shared Function SetParent(ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As IntPtr
End Function
<DllImport("user32.dll", EntryPoint:="SetWindowPos")>
Private Shared Function SetWindowPos(ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As UInteger) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("dwmapi.dll")>
Shared Function DwmGetWindowAttribute(ByVal hwnd As IntPtr, ByVal dwAttribute As Integer, ByRef pvAttribute As RECT, ByVal cbAttribute As Integer) As Integer
End Function
Public Structure RECT
Public left, top, right, bottom As Integer
End Structure
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Text = "My title"
proc.EnableRaisingEvents = True
proc.StartInfo.FileName = "cmd"
proc.Start()
Tmr.Start()
End Sub
Private Sub Tmr_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Tmr.Tick
If SetParent(proc.MainWindowHandle, Panel1.Handle) <> IntPtr.Zero Then
Tmr.Stop()
Dim Width As Integer
Dim Hight As Integer
Dim WindowRect As New RECT
DwmGetWindowAttribute(proc.MainWindowHandle, DWMWA_EXTENDED_FRAME_BOUNDS, WindowRect, Marshal.SizeOf(WindowRect))
Width = WindowRect.right - WindowRect.left
Hight = WindowRect.bottom - WindowRect.top
MessageBox.Show("Hight: " & Hight & " Width: " & Width)
'Me.Size = New Size(Width, Hight)
SetWindowPos(proc.MainWindowHandle, New IntPtr(HWND_BOTTOM), 0, 0, Panel1.ClientSize.Width, Panel1.ClientSize.Height, 0)
End If
End Sub
Private Sub Proc_Exited(ByVal sender As Object, ByVal e As System.EventArgs) Handles proc.Exited
Invoke(Sub() Close())
End Sub
End Class
After implementing all valuable additions of #Jimi, this is the code that works:
Imports System.Runtime.InteropServices
Public Class Form1
Private WithEvents proc As New Process
Public Const WM_NCLBUTTONDOWN As Long = &HA1
Public Const SW_SHOWMAXIMIZED As UInt32 = 3
Public Const WM_CLOSE = &H10
Public Const DWMWA_EXTENDED_FRAME_BOUNDS As Integer = 9
'Function to set the parent window
Private Declare Function SetParent Lib "user32" (ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As IntPtr
'Function to set the child window position
Private Declare Function SetWindowPos Lib "user32" (ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As UInteger) As <MarshalAs(UnmanagedType.Bool)> Boolean
'Function to allow the child to be maximized
Private Declare Function ShowWindow Lib "user32" (ByVal hwnd As IntPtr, ByVal nCmdShow As Int32) As Boolean
'Function to retrieve the initail child size
Private Declare Function DwmGetWindowAttribute Lib "dwmapi" (ByVal hwnd As IntPtr, ByVal dwAttribute As Integer, ByRef pvAttribute As RECT, ByVal cbAttribute As Integer) As Integer
'Function to set focus to the child window
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As IntPtr) As Long
'Function used to set terminate child window
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
'Rectangle size, used later to resize the form
Public Structure RECT
Public left, top, right, bottom As Integer
End Structure
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Text = "My title"
proc.EnableRaisingEvents = True
proc.StartInfo.FileName = "cmd"
proc.Start()
End Sub
Private Sub Tmr_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Shown
'Wait for 200 ms for the form to load
proc.WaitForExit(200)
Dim WindowRect As New RECT
Dim Width, Hight As Integer
'Retrieve the initial size of the child window
DwmGetWindowAttribute(proc.MainWindowHandle, DWMWA_EXTENDED_FRAME_BOUNDS, WindowRect, Marshal.SizeOf(WindowRect))
Width = WindowRect.right - WindowRect.left
Hight = WindowRect.bottom - WindowRect.top + 23
'Set the form size to the initial size of the child window
Me.Size = New Size(Width, Hight)
'When the child is started, move the child into the panel and maximize it
If SetParent(proc.MainWindowHandle, Panel1.Handle) <> IntPtr.Zero Then
SetWindowPos(proc.MainWindowHandle, IntPtr.Zero, 0, 0, Width, Height, 0)
ShowWindow(proc.MainWindowHandle, SW_SHOWMAXIMIZED)
End If
End Sub
'Exit form when child terminates
Private Sub Proc_Exited(ByVal sender As Object, ByVal e As System.EventArgs) Handles proc.Exited
Invoke(Sub() Close())
End Sub
'Set focus on child when the form is activated
Private Sub Form1_UnFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Activated
SetForegroundWindow(proc.MainWindowHandle)
End Sub
'Set focus on child when the parent titlebar is clicked
Protected Overrides Sub DefWndProc(ByRef m As System.Windows.Forms.Message)
If CLng(m.Msg) = WM_NCLBUTTONDOWN Then
SetForegroundWindow(proc.MainWindowHandle)
End If
MyBase.DefWndProc(m)
End Sub
'Properly terminate child when the form is closed by the user
Private Sub Form1_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Closed
SendMessage(proc.MainWindowHandle, WM_CLOSE, 0, 0)
End Sub
End Class
Kind regards,
Eric

SystemMenu add click event

I have a code that append menu in the system menu(right click form title bar). How will i add click event in the two menus that i appended? this is the code i use:
Public Class AppendFormSysMenu
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function AppendMenu(ByVal hMenu As IntPtr, ByVal uFlags As Int32, ByVal uIDNewItem As IntPtr, ByVal lpNewItem As String) As Boolean
End Function
Private Declare Function GetSystemMenu Lib "user32" (ByVal hWnd As IntPtr, ByVal bRevert As Boolean) As IntPtr
Private SYSMENU_SAVE_FORM_ID As Integer = &H1
Private SYSMENU_RESTORE_FORM_ID As Integer = &H2
<Flags()>
Public Enum MenuFlags As Integer
MF_BYPOSITION = 1024
MF_REMOVE = 4096
MF_SEPARATOR = 2048
MF_STRING = 0
End Enum
Public Sub insertSeparator(frm As Form)
Dim hMenu = GetSystemMenu(frm.Handle, False)
AppendMenu(hMenu, MenuFlags.MF_SEPARATOR, 0, Nothing)
End Sub
Public Sub insertSaveMenu(frm As Form, ByVal strMenuItem As String)
Dim hMenu = GetSystemMenu(frm.Handle, False)
AppendMenu(hMenu, MenuFlags.MF_STRING, SYSMENU_SAVE_FORM_ID, strMenuItem)
End Sub
Public Sub insertRestoreMenu(frm As Form, ByVal strMenuItem As String)
Dim hMenu = GetSystemMenu(frm.Handle, False)
AppendMenu(hMenu, MenuFlags.MF_STRING, SYSMENU_RESTORE_FORM_ID, strMenuItem)
End Sub
End Class
This is the code i use in the form:
Dim AppendFormMenu As New AppendFormSysMenu
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AppendFormMenu.insertSeparator(Me)
AppendFormMenu.insertSaveMenu(Me, "Save Form Position")
AppendFormMenu.insertRestoreMenu(Me, "Restore Form Position")
End Sub

GetWindowText is returning nothing

In one of my project I have to get the title of foreground window so I called GetForegroundWindow() Entry Point form User32.dll for getting the windows Handle then I called GetWindowText() for the title everything goes error less but the output comes nothing, here is the code I am using in my VB.NET program.
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("user32.dll")> _
Private Shared Function GetForegroundWindow() As IntPtr
End Function
<DllImport("user32.dll")> _
Private Shared Function GetWindowText(ByVal hwnd As Long, ByVal lpString As System.Text.StringBuilder, ByVal cch As Long) As Integer
End Function
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim hWnd As IntPtr
hWnd = GetForegroundWindow()
Dim title As New System.Text.StringBuilder(256)
GetWindowText(hWnd, title, title.Capacity)
Me.Text = title.ToString
End Sub
End Class
I found the solution myself, It was the fault in the hWnd parameter as Long value for proper functioning of the program it has to be IntPtr. The new correct code looks something like this.
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("user32.dll")> _
Private Shared Function GetForegroundWindow() As IntPtr
End Function
<DllImport("user32.dll")> _
Private Shared Function GetWindowText(ByVal hwnd As IntPtr, ByVal lpString As System.Text.StringBuilder, ByVal cch As Long) As Integer
End Function
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim hWnd As IntPtr
hWnd = GetForegroundWindow()
Dim title As New System.Text.StringBuilder(256)
GetWindowText(hWnd, title, title.Capacity)
Me.Text = title.ToString
End Sub
End Class

Reducing all sound volumes except for my own app

I'm wondering if it would be possible to simulate iPhone notification sounds on VB.NET, in the sense of how they are handled.
For example, if you get a SMS message on iPhone, while listening to music, the music volume is lowered to about 25% while the alert is played, then the volume is restored.
Could this be done with vb.net?
I've seen ways to reduce the system volume, but wouldn't this reduce the volume for my app as well?
I found this which works great, but I'd like to know if I can separate my app from this
Imports System
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Namespace WindowsFormsApplication1
Partial Public Class Form1
Inherits Form
Private Const APPCOMMAND_VOLUME_MUTE As Integer = &H80000
Private Const APPCOMMAND_VOLUME_UP As Integer = &HA0000
Private Const APPCOMMAND_VOLUME_DOWN As Integer = &H90000
Private Const WM_APPCOMMAND As Integer = &H319
<DllImport("user32.dll")> _
Public Shared Function SendMessageW(ByVal hWnd As IntPtr, _
ByVal Msg As Integer, ByVal wParam As IntPtr, _
ByVal lParam As IntPtr) As IntPtr
End Function
Private Sub btnMute_Click(ByVal sender As Object, ByVal e As EventArgs)
SendMessageW(Me.Handle, WM_APPCOMMAND, _
Me.Handle, New IntPtr(APPCOMMAND_VOLUME_MUTE))
End Sub
Private Sub btnDecVol_Click(ByVal sender As Object, ByVal e As EventArgs)
SendMessageW(Me.Handle, WM_APPCOMMAND, _
Me.Handle, New IntPtr(APPCOMMAND_VOLUME_DOWN))
End Sub
Private Sub btnIncVol_Click(ByVal sender As Object, ByVal e As EventArgs)
SendMessageW(Me.Handle, WM_APPCOMMAND, _
Me.Handle, New IntPtr(APPCOMMAND_VOLUME_UP))
End Sub
End Class
End Namespace