Search code examples
winformspython-3.xwinapipywin32

Return a list of all files from the selected Explorer Window with pywin32


I'm currently on Python 3 using the Win32 api. For window inspection, I'm using the Microsoft Inspect Tool. Currently, I have the following code that enumerates through all the windows:

def getSelectedFile():

    def callback(handle, hwnds):
        print(str(handle) + " - class name: " + win32gui.GetClassName(handle) + "-- name: " + win32gui.GetWindowText(handle))
        return True

    hwnd = win32gui.GetForegroundWindow()
    if hwnd:
        if win32gui.GetClassName(hwnd) == 'CabinetWClass':  # this is the main explorer window
            win32gui.EnumChildWindows(hwnd, callback, None)

And this would output the following:

19269320 - class name: BrowserFrameGripperClass-- name: 
526990 - class name: WorkerW-- name: 
395922 - class name: ReBarWindow32-- name: 
13371224 - class name: TravelBand-- name: 
2559382 - class name: ToolbarWindow32-- name: 
11076870 - class name: Address Band Root-- name: 
2230638 - class name: msctls_progress32-- name: 
7930970 - class name: Breadcrumb Parent-- name: 
6292500 - class name: ToolbarWindow32-- name: Address: Libraries\Pictures
8980342 - class name: ToolbarWindow32-- name: 
9568934 - class name: UniversalSearchBand-- name: 
11403790 - class name: Search Box-- name: 
7407762 - class name: SearchEditBoxWrapperClass-- name: 
23266054 - class name: DirectUIHWND-- name: 
7078564 - class name: ShellTabWindowClass-- name: Pictures
11732514 - class name: DUIViewWndClassName-- name: 
12584158 - class name: DirectUIHWND-- name: 
1118546 - class name: CtrlNotifySink-- name: 
987636 - class name: NamespaceTreeControl-- name: Namespace Tree Control
8193258 - class name: Static-- name: Namespace Tree Control
24314574 - class name: SysTreeView32-- name: Tree View
21103510 - class name: CtrlNotifySink-- name: 
1642968 - class name: Shell Preview Extension Host-- name: Shell Preview Extension Host
1577368 - class name: CtrlNotifySink-- name: 
2036036 - class name: SHELLDLL_DefView-- name: ShellView
24380214 - class name: DirectUIHWND-- name: 
1969552 - class name: CtrlNotifySink-- name: 
594366 - class name: ScrollBar-- name: 
987466 - class name: CtrlNotifySink-- name: 
17827752 - class name: ScrollBar-- name: 
2035978 - class name: CtrlNotifySink-- name: 
4851916 - class name: Button-- name: Save
13174848 - class name: CtrlNotifySink-- name: 
7145486 - class name: Button-- name: Cancel
1509810 - class name: WorkerW-- name: 
12781114 - class name: ReBarWindow32-- name: 
11405468 - class name: ToolbarWindow32-- name: 
1315080 - class name: msctls_statusbar32-- name: 

Which is great. But also notice that these objects have a frameworkId of ONLY "Win32" by looking at the Inspect tool (as you can see in the picture).

enter image description here

From the inspector, I noticed that some objects have a different frameworkId named "DirectUI", and they don't seem to show up from the EnumChildWindows function. This is a problem, because the object that contains all the files is actually called "Items View" pane, and it is "DirectUI" (refer to the second picture). So it's not even getting detected. If it's not detected, how can I read all the files inside it? I know that the names are there because you can see them in the tree (in the picture below)

enter image description here

How can I get Win32API to work with DirectUI in order to read the file names?
Is there an easier way of retrieving a list of names of all the files?


Solution

  • The shell has a dedicated COM API for things like this, which can be accessed through pywin32.

    Here is working code I've come up with:

    import os
    import sys
    import win32con
    import win32api
    import win32gui
    import win32com.client
    import pythoncom
    from win32com.shell import shell, shellcon
    
    # Get list of paths from given Explorer window or from all Explorer windows.
    def get_explorer_files( hwndOfExplorer = 0, selectedOnly = False ):
        paths = []
    
        # Create instance of IShellWindows (I couldn't find a constant in pywin32)
        CLSID_IShellWindows = "{9BA05972-F6A8-11CF-A442-00A0C90A8F39}"
        shellwindows = win32com.client.Dispatch(CLSID_IShellWindows)
    
        # Loop over all currently open Explorer windows
        for window in shellwindows:
            # Skip windows we are not interested in.
            if hwndOfExplorer != 0 and hwndOfExplorer != window.HWnd:
                continue
    
            # Get IServiceProvider interface
            sp = window._oleobj_.QueryInterface( pythoncom.IID_IServiceProvider )
    
            # Query the IServiceProvider for IShellBrowser
            shBrowser = sp.QueryService( shell.SID_STopLevelBrowser, shell.IID_IShellBrowser )
    
            # Get the active IShellView object
            shView = shBrowser.QueryActiveShellView()
    
            # Get an IDataObject that contains the items of the view (either only selected or all). 
            aspect = shellcon.SVGIO_SELECTION if selectedOnly else shellcon.SVGIO_ALLVIEW
            items = shView.GetItemObject( aspect, pythoncom.IID_IDataObject )
    
            # Get the paths in drag-n-drop clipboard format. We don't actually use 
            # the clipboard, but this format makes it easy to extract the file paths.
            # Use CFSTR_SHELLIDLIST instead of CF_HDROP if you want to get ITEMIDLIST 
            # (aka PIDL) format, but you can't use the simple DragQueryFileW() API then. 
            data = items.GetData(( win32con.CF_HDROP, None, pythoncom.DVASPECT_CONTENT, -1, pythoncom.TYMED_HGLOBAL ))
    
            # Use drag-n-drop API to extract the individual paths.
            numPaths = shell.DragQueryFileW( data.data_handle, -1 )
            paths.extend([
                shell.DragQueryFileW( data.data_handle, i ) \
                    for i in range( numPaths )
            ])
    
            if hwndOfExplorer != 0:
                break
    
        return paths
    
    try:
        # Use hwnd value of 0 to list files of ALL explorer windows...
        hwnd = 0  
        # ... or restrict to given window:
        #hwnd = win32gui.GetForegroundWindow()
        selectedOnly = False
        print( *get_explorer_files( hwnd, selectedOnly ), sep="\n" )
    except Exception as e:
        print( "ERROR: ", e )
    

    Wow, that was a nice puzzle (as I'm actually a C++ guy)!

    To understand that stuff I suggest studying the original MSDN documentation, then try to map that to pywin32 code.

    Shell API (and COM in general) can be a little bit overwhelming at first but it's usually not that difficult to adapt existing example code. A great source for that is the blog of Raymond Chen.

    For pywin32 samples, there are some demos in this folder of the pywin32 installation:

    Lib\site-packages\win32comext\shell\demos\