Python: Get a list of selected files in Explorer (WIndows 7)

前端 未结 4 1871
谎友^
谎友^ 2020-12-21 05:48

At work I can select multiple .xlsx files, and right clicking on one file will give me the option of combining the files to make a .pdf. Now I want to use the same functiona

相关标签:
4条回答
  • 2020-12-21 05:59

    This is really a Windows question and not very specific to Python. You want the windows shell to show a menu item for your script in the shell context menu.

    To accomplish this, you can add some keys to the registry. See Add menu item to windows context menu only for specific filetype for an explanation of how to add your menu item.

    After that, when you select multiple files and send them to your script, you will see the files as command line arguments. If you select 10 files, the script will be ran 10 times.

    0 讨论(0)
  • 2020-12-21 06:03

    Are you asking about how to get the list of files, or are you asking about how to do the conversion?

    If you are asking about selecting files (which is what is sounds like to me), are you looking for a GUI solution or a command line solution?

    you can show all the .xlsx files in a folder by using the os.listdir() function is the os library and then filtering them to just the files containing .xlsx like so:

    files = [ fi for fi in os.listdir(folder) if fi.endswith(suffix) ]
    

    you could then print the list of files with their indices next to them and ask the user to enter the indices of the files that they want to select like so:

    for fInd,f in enumerate(files):
        print '%s) %s' %(fInd, f)
    
    response = raw_input('select files by indices (coma separated)')
    keeperInds = response.split(',')
    keeperInds = [int(keeperInd) for keeperInd in keeperInds]
    # you should also check to make sure that the selected inds are valid...
    selectedFiles = [files[ind] for ind in keeperInds]
    

    that will give you a list of selected files that you can pass into your script.

    If you actually need help with the conversion from the .xlsx files into a pdf you could have a look at this - you might be able to change it to save .pdfs by changing the file format. Converting .XLSX to .XLS in Python with win32com.client module

    0 讨论(0)
  • 2020-12-21 06:07

    I know this is a "bit" late to post an answer here, but I had tried Olav's solution some months ago and it didn't work completely: the working directory was the script's working directory, so I had to delete the if condition for it to work, but it selected all the files in all Windows Explorer windows (which I wanted too, so it worked partially for me). But now I came back to continue my project (an assistant) and I found out I really needed this working, so I thought on this idea (which is not that hard to think of, but took months for me to have it...). I don't know if this answer worked for anyone else, but for me it didn't completely so I thought I could improve it and post my solution here. This code is a mix of this answer (which I've been using too in the same script, but never thought of getting them to work together): https://stackoverflow.com/a/43892579/8228163 (corrected by me to work at least under Windows 7) and Olav's answer and the result worked for me - the script detects the files only in the current Windows Explorer window. I think all of this works from Vista (maybe, I don't know as it's older than 7) to 10, but I'm not completely sure. The other answer was made to work with XP. When I started this script on Windows 10, I think it worked, but I don't have 10 anymore so I don't know for sure (I'm using 7 again, so for 7 this works).

    import win32gui, time
    from win32con import PAGE_READWRITE, MEM_COMMIT, MEM_RESERVE, MEM_RELEASE, PROCESS_ALL_ACCESS, WM_GETTEXTLENGTH, WM_GETTEXT
    from commctrl import LVS_OWNERDATA, LVM_GETITEMCOUNT, LVM_GETNEXTITEM, LVNI_SELECTED
    import os
    import struct
    import ctypes
    import win32api
    import datetime
    import win32com.client as win32
    import win32ui
    import psutil
    import subprocess
    import time
    import urllib.parse
    
    clsid = '{9BA05972-F6A8-11CF-A442-00A0C90A8F39}' #Valid for IE as well!
    
    def getEditText(hwnd):
        # api returns 16 bit characters so buffer needs 1 more char for null and twice the num of chars
        buf_size = (win32gui.SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0) +1 ) * 2
        target_buff = ctypes.create_string_buffer(buf_size)
        win32gui.SendMessage(hwnd, WM_GETTEXT, buf_size, ctypes.addressof(target_buff))
        return target_buff.raw.decode('utf16')[:-1]# remove the null char on the end
    
    def _normaliseText(controlText):
        '''Remove '&' characters, and lower case.
        Useful for matching control text.'''
        return controlText.lower().replace('&', '')
    
    def _windowEnumerationHandler(hwnd, resultList):
        '''Pass to win32gui.EnumWindows() to generate list of window handle,
        window text, window class tuples.'''
        resultList.append((hwnd, win32gui.GetWindowText(hwnd), win32gui.GetClassName(hwnd)))
    
    def searchChildWindows(currentHwnd,
                   wantedText=None,
                   wantedClass=None,
                   selectionFunction=None):
        results = []
        childWindows = []
        try:
            win32gui.EnumChildWindows(currentHwnd,
                          _windowEnumerationHandler,
                          childWindows)
        except win32gui.error:
            # This seems to mean that the control *cannot* have child windows,
            # i.e. not a container.
            return
        for childHwnd, windowText, windowClass in childWindows:
            descendentMatchingHwnds = searchChildWindows(childHwnd)
            if descendentMatchingHwnds:
                results += descendentMatchingHwnds
    
            if wantedText and \
                not _normaliseText(wantedText) in _normaliseText(windowText):
                    continue
            if wantedClass and \
                not windowClass == wantedClass:
                    continue
            if selectionFunction and \
                not selectionFunction(childHwnd):
                    continue
            results.append(childHwnd)
        return results
    
    
    def explorer_fileselection():
        global clsid
        address_1=""
        files = []
        shellwindows = win32.Dispatch(clsid)
        w=win32gui
        window = w.GetForegroundWindow()
        #print("window: %s" % window)
        if (window != 0):
            if (w.GetClassName(window) == 'CabinetWClass'): # the main explorer window
                #print("class: %s" % w.GetClassName(window))
                #print("text: %s " %w.GetWindowText(window))
                children = list(set(searchChildWindows(window)))
                addr_edit = None
                file_view = None
                for child in children:
                    if (w.GetClassName(child) == 'WorkerW'): # the address bar
                        addr_children = list(set(searchChildWindows(child)))
                        for addr_child in addr_children:
                            if (w.GetClassName(addr_child) == 'ReBarWindow32'):
                                addr_edit = addr_child
                                addr_children = list(set(searchChildWindows(child)))
                                for addr_child in addr_children:
                                    if (w.GetClassName(addr_child) == 'Address Band Root'):
                                        addr_edit = addr_child
                                        addr_children = list(set(searchChildWindows(child)))
                                        for addr_child in addr_children:
                                            if (w.GetClassName(addr_child) == 'msctls_progress32'):
                                                addr_edit = addr_child
                                                addr_children = list(set(searchChildWindows(child)))
                                                for addr_child in addr_children:
                                                    if (w.GetClassName(addr_child) == 'Breadcrumb Parent'):
                                                        addr_edit = addr_child
                                                        addr_children = list(set(searchChildWindows(child)))
                                                        for addr_child in addr_children:
                                                            if (w.GetClassName(addr_child) == 'ToolbarWindow32'):
                                                                text=getEditText(addr_child)
                                                                if "\\" in text:
                                                                    address_1=getEditText(addr_child)[text.index(" ")+1:]
                                                                    print("Address --> "+address_1)
    
        for window in range(shellwindows.Count):
            window_URL = urllib.parse.unquote(shellwindows[window].LocationURL,encoding='ISO 8859-1')
            window_dir = window_URL.split("///")[1].replace("/", "\\")
            print("Directory --> "+window_dir)
            if window_dir==address_1:
                selected_files = shellwindows[window].Document.SelectedItems()
                for file in range(selected_files.Count):
                    files.append(selected_files.Item(file).Path)
                print("Files --> "+str(files))
    
    while True:
        explorer_fileselection()
        time.sleep(1)
    

    This looks for the active Windows Explorer window, gets that window's address and then the address is used on Olav's answer for it to check if that address is equal to one of the addresses opened in Windows Explorer, getting the files from the active window. Btw, as this is script is a modified copy of both answers, it has the limitations from these. So, like it's on Olav's answer "Edit: Doesn't work yet, at least when using context menu", then this won't probably work either, as it's the same code - it's just the working directory which is different (though, I don't know what he meant with that, but for what I've tested, it worked). And like it's on James Kent's answer, this doesn't work for desktop, only for opened windows using Windows Explorer. The encoding='ISO 8859-1' is because I'm portuguese, but it can be changed, JUST make sure both directories are equal without %?s or that won't work!

    As this question has only almost 5 years, the OP probably won't need it anymore, but I needed it and didn't have it anywhere, so I thought I could post this here and maybe help others who want to do this. The code in the script can be used to know the files on the current Windows Explorer window and to get the current Windows Explorer window path on Windows above XP (not sure about Vista). For XP, see the the original answer (https://stackoverflow.com/a/43892579/8228163) and to get the files from all Windows Explorer windows, just remove the if condition from Olav's answer.

    Thanks Olav and James Kent for the answers, because I would have taken MUCH more time trying to find out how to do this (I'm a Python/any language begginner - just coding for a year, so it would have taken really much time, maybe I'd have to mix it with another laguage). Thanks again, and to the OPs too for asking the questions and having the right people answering at the right time! (as the source which Olav quoted on the link no longer exists).

    Hope this helps! Cheers!

    0 讨论(0)
  • 2020-12-21 06:16

    Edit: Doesn't work yet, at least when using context menu

    I found a partial solution here. However it doesn't work if Internet Explorer is open (how do I detect this?). Also, if multiple instances of Windows Explorer is open, the selected files in all windows are counted. I added a check for this.

    import win32com.client as win32
    import os
    import win32ui
    
    
    def explorer_fileselection():
        working_dir = os.getcwd()
    
        clsid = '{9BA05972-F6A8-11CF-A442-00A0C90A8F39}' #Valid for IE as well!
        shellwindows = win32.Dispatch(clsid)
    
        files = []
        try:
            for window in range(shellwindows.Count):
                window_URL = shellwindows[window].LocationURL
                window_dir = window_URL.split("///")[1].replace("/", "\\")
                if window_dir == working_dir:
                    selected_files = shellwindows[window].Document.SelectedItems()
                    for file in range(selected_files.Count):
                        files.append(selected_files.Item(file).Path)
        except:   #Ugh, need a better way to handle this one
            win32ui.MessageBox("Close IE!", "Error")
        del shellwindows
    
        return files
    
    
    print(*explorer_fileselection(), sep="\n")
    
    --- prints the selected files:
    
    C:\Users\oe\Python\ssa\util\test3.docx
    C:\Users\oe\Python\ssa\util\__init__.py
    C:\Users\oe\Python\ssa\util\explorer_filer.py
    C:\Users\oe\Python\ssa\util\test1.xlsx
    C:\Users\oe\Python\ssa\util\test2.xls
    

    I think I will add a *valid_ext parameter to the function, so I can do calls like explorer_fileselection("xlsx", "xls") to get Excel-files only.

    0 讨论(0)
提交回复
热议问题