Search code examples
vb.netformspanelrect

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

Solution

  • 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