简体   繁体   中英

How can I open a File Explorer window from a Visual Studio app and set the position and size?

I have a VB.net application that compacts JPG files, renames them, and copies them from one location to another. When the end-user uses the program, they'll open two file explorer windows to get the source and destination locations and drag them onto the text boxes.

I have added code that opens two file explorers to set locations, but I would like one windows to be in the lower left size of the screen and the other to be in the lower right. Each would be sized to take up 1/4 of the screen.

Most of what I have found is very old. I've found people who said it can't be done and others who provide very old code that doesn't seem to play nice with Visual Studio 2019.

Private Sub btnOpenExplorer_Click(sender As Object, e As EventArgs) Handles btnOpenExplorer.Click

    Process.Start("explorer.exe", String.Format("/n, /e, {0}", "C:\Users\" & Environment.UserName & "\Box\Site Visit Photos"))
    Process.Start("explorer.exe", String.Format("/n, /e, {0}", "P:\"))

End Sub

The above code works well. I just need to add sizing and positioning.

You can use MoveWindow() in user32.dll . The window handle can be obtained by proc.MainWindowHandle where proc is the process returned by Process.Start() .

Additionaly this works well for me: https://www.codeproject.com/Tips/1057230/Windows-Resize-and-Move

Solution 1

The Problem

I think it's difficult to do it in the caller routine btnOpenExplorer_Click since it will be too early to get a process object with all of its properties assigned. Mostly, the ProcessMainWindowTitle and the Process.MainWindowHandle properties which are needed to solve this problem. A workaround to do this is to make the caller starts the processes and a Timer to do the positioning and resizing by the SetWindowPos function.

Here's how I'd do it:

The API Functions

<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("user32.dll")>
Private Shared Function IsIconic(hWnd As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

<DllImport("user32.dll")>
Public Shared Function ShowWindow(hWnd As IntPtr, <MarshalAs(UnmanagedType.I4)> nCmdShow As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

Class Level Constants & Variables

Private Const HWND_TOP As Integer = &H0
Private Const SW_SHOWNORMAL As Integer = &H1

Private dir1, dir2 As String
Private WithEvents Timer1 As New Timer With {.Interval = 250}

Process Finder

Private Function GetExplorerProcess(title As String) As Process
    Dim dirName As String = If(IO.Directory.Exists(title), New IO.DirectoryInfo(title).Name, title).ToLower

    Return Process.GetProcesses.Where(
        Function(a) a.ProcessName.ToLower.Equals("explorer") AndAlso
        a.MainWindowTitle.ToLower.Equals(dirName)
        ).FirstOrDefault
End Function

The Caller

Private Sub btnOpenExplorer_Click(sender As Object, e As EventArgs) Handles btnOpenExplorer.Click
    Dim proc1 As Process = GetExplorerProcess(dir1)

    If proc1 Is Nothing Then
        Dim procInfo1 As New ProcessStartInfo With {
        .FileName = "explorer.exe",
        .Arguments = dir1,
        .WindowStyle = ProcessWindowStyle.Normal
        }

        Process.Start(procInfo1)
    End If

    Dim proc2 As Process = GetExplorerProcess(dir2)

    If proc2 Is Nothing Then
        Dim procInfo2 As New ProcessStartInfo With {
        .FileName = "explorer.exe",
        .Arguments = dir2,
        .WindowStyle = ProcessWindowStyle.Normal
        }

        Process.Start(procInfo2)
    End If

    Timer1.Start()
End Sub

The Timer - Activate the Windows, Set Both Size & Location

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    Dim proc1 As Process = GetExplorerProcess(dir1)
    Dim proc2 As Process = GetExplorerProcess(dir2)

    If proc1 IsNot Nothing AndAlso proc2 IsNot Nothing Then
        Timer1.Stop()

        Dim R As Rectangle = Screen.PrimaryScreen.WorkingArea
        Dim R1 As New Rectangle(R.X, R.Height - (R.Height / 3), R.Width / 2, R.Height / 4)
        Dim R2 As New Rectangle(R1.Right, R1.Y, R1.Width, R1.Height)
        Dim hWnd1 As IntPtr = proc1.MainWindowHandle
        Dim hWnd2 As IntPtr = proc2.MainWindowHandle

        'Restore the first window if its minimized.
        If IsIconic(hWnd1) Then
            ShowWindow(hWnd1, SW_SHOWNORMAL)
        End If

        'Set the size and location of the first window.
        SetWindowPos(hWnd1, IntPtr.op_Explicit(HWND_TOP), R1.X, R1.Y, R1.Width, R1.Height, 0)

        'Restore the second window if its minimized.
        If IsIconic(hWnd2) Then
            ShowWindow(hWnd2, SW_SHOWNORMAL)
        End If

        'Set the size and location of the second window.
        SetWindowPos(hWnd2, IntPtr.op_Explicit(HWND_TOP), R2.X, R2.Y, R2.Width, R2.Height, 0)
    End If
End Sub

Solution 2

After some googling here and there, I figured out a better approach (I think) through using the Microsoft Internet Controls - SHDocVw component.

First, we need to add a reference to that COM component:

  • In your project browser, right-click on the References node and select Add Reference .
  • In the Reference Manager Select the COM tab.
  • Find and check the Microsoft Internet Controls and hit OK.

We need from solution1 the APIs and the constants only, and a new function to get the IE window:

Private Function GetIE(dir As String) As SHDocVw.InternetExplorer
    Return (From ie In New SHDocVw.ShellWindows
            Where New Uri(DirectCast(ie, SHDocVw.InternetExplorer).LocationURL).LocalPath.Equals(dir, StringComparison.OrdinalIgnoreCase)
            Select DirectCast(ie, SHDocVw.InternetExplorer)).FirstOrDefault
End Function

And finally, the caller:

Private Sub btnOpenExplorer_Click(sender As Object, e As EventArgs) Handles btnOpenExplorer.Click
    Dim dir1 As String = "FirstPath"
    Dim dir2 As String = "SecondPath"
    Dim ie1, ie2 As SHDocVw.InternetExplorer

    If Not IO.Path.GetPathRoot(dir1).Equals(dir1, StringComparison.OrdinalIgnoreCase) Then
        dir1 = dir1.TrimEnd(IO.Path.DirectorySeparatorChar)
    End If

    If Not IO.Path.GetPathRoot(dir2).Equals(dir2, StringComparison.OrdinalIgnoreCase) Then
        dir2 = dir2.TrimEnd(IO.Path.DirectorySeparatorChar)
    End If

    ie1 = GetIE(dir1)
    ie2 = GetIE(dir2)

    If ie1 Is Nothing OrElse ie2 Is Nothing Then
        Process.Start(dir1)
        Process.Start(dir2)
        Threading.Thread.Sleep(1000)
    End If

    If ie1 Is Nothing Then ie1 = GetIE(dir1)
    If ie2 Is Nothing Then ie2 = GetIE(dir2)

    If ie1 IsNot Nothing AndAlso ie2 IsNot Nothing Then
        Dim hWnd1 = IntPtr.op_Explicit(ie1.HWND)
        Dim hWnd2 = IntPtr.op_Explicit(ie2.HWND)
        Dim R As Rectangle = Screen.PrimaryScreen.WorkingArea
        Dim R1 As New Rectangle(R.X, R.Height - (R.Height \ 3), R.Width \ 2, R.Height \ 4)
        Dim R2 As New Rectangle(R1.Right, R1.Y, R1.Width, R1.Height)

        SetWindowPos(hWnd1, IntPtr.op_Explicit(HWND_TOP), R2.X, R2.Y, R2.Width, R2.Height, 0)
        SetWindowPos(hWnd2, IntPtr.op_Explicit(HWND_TOP), R1.X, R1.Y, R1.Width, R1.Height, 0)

        If IsIconic(hWnd1) Then
            ShowWindow(hWnd1, SW_SHOWNORMAL)
        End If

        If IsIconic(hWnd2) Then
            ShowWindow(hWnd2, SW_SHOWNORMAL)
        End If
    End If
End Sub

Please note, this solution works also with drives (IE: c:\ , d:\ ..etc.) while the first one doesn't.

Here's a demo:

IEDemo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM