Python - Get Path of Selected File in Current Windows Explorer

假如想象 提交于 2019-11-30 17:46:48

问题


I am trying to do this in Python 2.7. I have found an answer for it in C# here, but I am having trouble recreating it in Python. The answer suggested here does explain the concept which I understand, but I have no idea how to get it going.

Basically I just want to mark a file, press Winkey+C and have its path copied. I know how to do the hotkey part (pyhk, win32 [RegisterHotKey]), but my trouble is working around with the filepath.

Thanks in advance!


回答1:


it takes a lot of hacking around, but a rough solution is below:

#!python3
import win32gui, time
from win32con import PAGE_READWRITE, MEM_COMMIT, MEM_RESERVE, MEM_RELEASE, PROCESS_ALL_ACCESS, WM_GETTEXTLENGTH, WM_GETTEXT
from commctrl import LVM_GETITEMTEXT, LVM_GETITEMCOUNT, LVM_GETNEXTITEM, LVNI_SELECTED
import os
import struct
import ctypes
import win32api

GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId
VirtualAllocEx = ctypes.windll.kernel32.VirtualAllocEx
VirtualFreeEx = ctypes.windll.kernel32.VirtualFreeEx
OpenProcess = ctypes.windll.kernel32.OpenProcess
WriteProcessMemory = ctypes.windll.kernel32.WriteProcessMemory
ReadProcessMemory = ctypes.windll.kernel32.ReadProcessMemory
memcpy = ctypes.cdll.msvcrt.memcpy

def readListViewItems(hwnd, column_index=0):
    # Allocate virtual memory inside target process
    pid = ctypes.create_string_buffer(4)
    p_pid = ctypes.addressof(pid)
    GetWindowThreadProcessId(hwnd, p_pid) # process owning the given hwnd
    hProcHnd = OpenProcess(PROCESS_ALL_ACCESS, False, struct.unpack("i",pid)[0])
    pLVI = VirtualAllocEx(hProcHnd, 0, 4096, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE)
    pBuffer = VirtualAllocEx(hProcHnd, 0, 4096, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE)

    # Prepare an LVITEM record and write it to target process memory
    lvitem_str = struct.pack('iiiiiiiii', *[0,0,column_index,0,0,pBuffer,4096,0,0])
    lvitem_buffer = ctypes.create_string_buffer(lvitem_str)
    copied = ctypes.create_string_buffer(4)
    p_copied = ctypes.addressof(copied)
    WriteProcessMemory(hProcHnd, pLVI, ctypes.addressof(lvitem_buffer), ctypes.sizeof(lvitem_buffer), p_copied)

    # iterate items in the SysListView32 control
    num_items = win32gui.SendMessage(hwnd, LVM_GETITEMCOUNT)
    item_texts = []
    for item_index in range(num_items):
        win32gui.SendMessage(hwnd, LVM_GETITEMTEXT, item_index, pLVI)
        target_buff = ctypes.create_string_buffer(4096)
        ReadProcessMemory(hProcHnd, pBuffer, ctypes.addressof(target_buff), 4096, p_copied)
        item_texts.append(target_buff.value)

    VirtualFreeEx(hProcHnd, pBuffer, 0, MEM_RELEASE)
    VirtualFreeEx(hProcHnd, pLVI, 0, MEM_RELEASE)
    win32api.CloseHandle(hProcHnd)
    return item_texts

def getSelectedListViewItem(hwnd):
    return win32gui.SendMessage(hwnd, LVM_GETNEXTITEM, -1, LVNI_SELECTED)

def getSelectedListViewItems(hwnd):
    items = []
    item = -1
    while True:
        item = win32gui.SendMessage(hwnd, LVM_GETNEXTITEM, item, LVNI_SELECTED)
        if item == -1:
            break
        items.append(item)
    return items

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

w=win32gui

while True:
    time.sleep(5)
    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) == 'ComboBoxEx32'): # the address bar
                    addr_children = list(set(searchChildWindows(child)))
                    for addr_child in addr_children:
                        if (w.GetClassName(addr_child) == 'Edit'):
                            addr_edit = addr_child
                    pass
                elif (w.GetClassName(child) == 'SysListView32'): # the list control within the window that shows the files
                    file_view = child
            if addr_edit:
                path = getEditText(addr_edit)
            else:
                print('something went wrong - no address bar found')
                path = ''

            if file_view:
                files = [item.decode('utf8') for item in readListViewItems(file_view)]
                indexes = getSelectedListViewItems(file_view)
                print('path: %s' % path)
                print('files: %s' % files)
                print('selected files:')
                for index in indexes:
                    print("\t%s - %s" % (files[index], os.path.join(path, files[index])))
            else:
                print('something went wrong - no file view found')

so what this does is keep checking if the active window is of the class the explorer window uses, then iterates through the children widgets to find the address bar and the file list view. Then it extracts the list of files from the listview and requests the selected indexes. it also gets and decodes the text from the address bar.
at the bottom the info is then combined to give you the complete path, the folder path, the file name or any combination thereof.

I have tested this on windows xp with python3.4, but you will need to install the win32gui and win32 conn packages.




回答2:


Sorry but what you are trying to achieve does not make much sense because you can have multiple Explorer Windows with files selected in them, multiple displays and even multiple terminal Sessions running on the same machine.




回答3:


If you want it for Windows Vista or above (not sure about Vista), see my answer here: https://stackoverflow.com/a/52959617/8228163. I mixed a corrected James Kent's answer to work with newer Windows and Olav's answer (which is on that thread) and I put it working with newer Windows and selecting just the active window. I explain too how to put it getting the files from all Windows Explorer windows (Olav said it himself, I just explained it again on my answer). For XP, just mix the original James Kent's answer with Olav's answer. I explain this better on my answer too.

Probably the OP won't need this anymore, but maybe someone else will - I needed it and didn't have the complete answer, so this might help someone.

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!




回答4:


# Import Statement. import subprocess # Trigger subprocess. subprocess.popen(r'explorer /select,"C:\path\of\folder\file"'



来源:https://stackoverflow.com/questions/37500504/python-get-path-of-selected-file-in-current-windows-explorer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!