【问题标题】:Python - Get Path of Selected File in Current Windows ExplorerPython - 在当前 Windows 资源管理器中获取所选文件的路径
【发布时间】:2016-09-26 19:09:38
【问题描述】:

我正在尝试在 Python 2.7 中执行 this。我在 C#here 中找到了答案,但在 Python 中重新创建它时遇到了麻烦。建议的答案here 确实解释了我理解的概念,但我不知道如何实现它。

基本上我只想标记一个文件,按 Winkey+C 并复制其路径。我知道如何做热键部分(pyhk,win32 [RegisterHotKey]),但我的问题是解决文件路径。

提前致谢!

【问题讨论】:

    标签: python python-2.7 operating-system pywin32


    【解决方案1】:

    这需要大量的修改,但粗略的解决方案如下:

    #!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')
    

    所以它的作用是不断检查活动窗口是否属于资源管理器窗口使用的类,然后遍历子小部件以查找地址栏和文件列表视图。然后它从列表视图中提取文件列表并请求选定的索引。它还从地址栏中获取并解码文本。
    然后将底部的信息组合为您提供完整路径、文件夹路径、文件名或其任意组合。

    我在 windows xp 上用 python3.4 测试过这个,但是你需要安装 win32gui 和 win32 conn 包。

    【讨论】:

    • 还要注意,因为这会从地址栏中提取路径,所以这在桌面上不起作用,除非您打开文件资源管理器并将其指向桌面。目前只处理单个文件选择
    • 对于任何感兴趣的人,我现在改进了获得多个文件选择的答案并清理了一些不必要的注释掉代码
    • 我必须更改代码,如下所述....EnumChildWindows(currentHwnd,EnumWindowsChildProc(_windowEnumerationHandler),childWindows) 我收到错误 ctypes.ArgumentError: argument 3: : 不知道如何使用 Python 3.7、Windows 10 转换参数 3。任何建议都会有所帮助。
    【解决方案2】:

    如果您希望它用于 Windows Vista 或更高版本(不确定 Vista),请在此处查看我的答案:https://stackoverflow.com/a/52959617/8228163。我混合了一个更正的 James Kent 的答案以使用较新的 Windows 和 Olav 的答案(在该线程上),我把它与较新的 Windows 一起使用并只选择活动窗口。我也解释了如何从所有 Windows 资源管理器窗口中获取文件(Olav 自己说过,我只是在我的回答中再次解释了它)。对于 XP,只需将原始 James Kent 的答案与 Olav 的答案混合即可。我的回答也更好地解释了这一点。

    可能 OP 不再需要这个,但也许其他人会 - 我需要它并且没有完整的答案,所以这可能会对某人有所帮助。

    感谢 Olav 和 James Kent 的回答,因为我会花更多的时间来尝试找出如何做到这一点(我是 Python/任何语言的初学者 - 只编码了一年,所以需要真的很多时间,也许我不得不将它与另一种语言混合)。再次感谢 OP,感谢他们提出问题并在正确的时间让正确的人回答! (因为 Olav 在链接中引用的来源不再存在)。

    希望这会有所帮助!干杯!

    【讨论】:

    • 这非常适合我的需求。谢谢!
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      抱歉,您尝试实现的目标没有多大意义,因为您可以拥有多个资源管理器窗口,其中选择了文件,多个显示器甚至多个终端会话在同一台机器上运行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-07-07
        • 1970-01-01
        • 2017-01-08
        • 1970-01-01
        • 1970-01-01
        • 2023-01-22
        • 1970-01-01
        • 2016-04-13
        相关资源
        最近更新 更多