在 WxPython 应用程序中创建 Explorer.exe 的文件 - 上下文菜单

问题描述 投票:0回答:2

我有一个 WxPython 应用程序,除其他外,它还具有集成的文件浏览器。

当用户右键单击我的应用程序中的某个项目时,我希望能够创建系统默认文件上下文菜单(例如,在 Windows 资源管理器中右键单击文件时会得到什么)。

注意:我已经知道如何创建自己的上下文菜单(例如 wx.EVT_LIST_ITEM_RIGHT_CLICK),我想要 Windows 上下文菜单。

澄清一下,我不想或需要修改现有的系统上下文菜单,我希望能够为我的应用程序中的特定文件“显示”它。 基本上,我知道右键单击了什么,以及鼠标指针在哪里(如果需要)。我想在那里创建系统上下文菜单,就像它在 Windows 资源管理器中一样。

python user-interface windows-xp wxpython
2个回答
0
投票
<PYTHON>/lib/site-packages/win32comext/shell/demos/servers

下查看。其中包含一个文件

context_menu.py
,其中包含用于创建 shell 扩展的示例代码。

更新:我想你想要

folder_view.py

样本。

    


0
投票
终于

找到了一个可行的解决方案。一个大问题是大多数搜索都询问如何将自定义项目添加到上下文菜单中,奇怪的是,这是我在该主题上找到的少数 stackoverflow 线程之一。 我最初尝试使用

ctypes

,但它变得太大而难以追踪。因此,我的解决方案需要

pip install pywin32
在 Python 3.8 上测试。据推测,您可以删除/修改类型提示以支持旧版本。

要使用,只需调用

windows_context_menu(path)

,请参阅底部的示例。

from __future__ import annotations

import ctypes
import os
import sys

from ctypes import windll
from pathlib import WindowsPath
from typing import TYPE_CHECKING, Sequence

import win32com.client
import win32con
import win32gui

if TYPE_CHECKING:
    from _win32typing import PyResourceId, PyWNDCLASS
    from win32com.client import DispatchBaseClass
    from win32com.client.dynamic import CDispatch

try:
    from ctypes.wintypes import HWND, LPARAM
except Exception:  # noqa: BLE001  # pyinstaller frozen-related issue observed.
    # just grab from ctypes.wintypes src I suppose
    # WPARAM is defined as UINT_PTR (unsigned type)
    # LPARAM is defined as LONG_PTR (signed type)
    if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
        WPARAM = ctypes.c_ulong
        LPARAM = ctypes.c_long
    elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
        WPARAM = ctypes.c_ulonglong
        LPARAM = ctypes.c_longlong
    HWND = ctypes.c_void_p

# Load libraries
user32: ctypes.WinDLL = windll.user32
kernel32: ctypes.WinDLL = windll.kernel32

# Define window class and procedure
WNDPROC: ctypes._FuncPointer = ctypes.WINFUNCTYPE(
    ctypes.c_long,
    HWND,
    ctypes.c_uint,
    ctypes.c_uint,
    LPARAM,
)


def wnd_proc(
    hwnd: int | None,
    message: int,
    wparam: float | None,
    lparam: float | None,
) -> int:
    if message == win32con.WM_DESTROY:
        win32gui.PostQuitMessage(0)
        return 0
    return win32gui.DefWindowProc(hwnd, message, wparam, lparam)


class RobustInvisibleWindow:
    """Context manager for creating and destroying an invisible window."""
    CLASS_NAME: str = "RobustInvisibleWindow"
    DISPLAY_NAME: str = "Robust Invisible Window"

    def __init__(self):
        self.hwnd: int | None = None

    def __enter__(self) -> int:
        self.register_class()
        self.hwnd = self.create_window()
        return self.hwnd

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.hwnd is not None:
            win32gui.DestroyWindow(self.hwnd)
        self.unregister_class()

    def register_class(self):
        """Register the window class."""
        wc: PyWNDCLASS = win32gui.WNDCLASS()
        wc.lpfnWndProc = WNDPROC(wnd_proc)
        wc.lpszClassName = self.CLASS_NAME
        wc.hInstance = kernel32.GetModuleHandleW(None)
        wc.hCursor = user32.LoadCursorW(None, 32512)
        try:
            self._class_atom: PyResourceId = win32gui.RegisterClass(wc)
        except Exception as e:  # Don't import pywintypes here, unsafe.
            if getattr(e, "winerror", None) != 1410:  # class already registered
                raise

    def unregister_class(self):
        """Unregister the window class."""
        win32gui.UnregisterClass(self.CLASS_NAME, kernel32.GetModuleHandleW(None))

    def create_window(self) -> int:
        """Create an invisible window."""
        return user32.CreateWindowExW(
            0, self.CLASS_NAME, self.DISPLAY_NAME, 0, 0, 0, 0, 0, None, None,
            kernel32.GetModuleHandleW(None), None)


def _safe_path_parse(file_path: os.PathLike | str) -> WindowsPath:
    return WindowsPath(os.fspath(file_path))


def safe_isfile(path: WindowsPath) -> bool | None:
    try:
        result: bool = path.is_file()
    except (OSError, ValueError):
        return None
    else:
        return result


def safe_isdir(path: WindowsPath) -> bool | None:
    try:
        result: bool = path.is_dir()
    except (OSError, ValueError):
        return None
    else:
        return result


# Function to display context menu
def windows_context_menu_file(file_path: os.PathLike | str):
    """Opens the default windows context menu for a filepath at the position of the cursor."""
    # Normalize filepath for safety
    parsed_filepath: WindowsPath = _safe_path_parse(file_path)
    hwnd = None

    shell: CDispatch = win32com.client.Dispatch("Shell.Application")
    folder: CDispatch = shell.NameSpace(str(parsed_filepath.parent))
    item: CDispatch = folder.ParseName(parsed_filepath.name)
    context_menu: CDispatch = item.Verbs()
    hmenu: int = win32gui.CreatePopupMenu()
    for i, verb in enumerate(context_menu):
        if verb.Name:
            win32gui.AppendMenu(hmenu, win32con.MF_STRING, i + 1, verb.Name)
    pt: tuple[int, int] = win32gui.GetCursorPos()

    with RobustInvisibleWindow() as hwnd:
        cmd: int = win32gui.TrackPopupMenu(hmenu, win32con.TPM_LEFTALIGN | win32con.TPM_RETURNCMD,
                                        pt[0], pt[1], 0, hwnd, None)
        if cmd:
            verb: DispatchBaseClass = context_menu.Item(cmd - 1)
            if verb:
                verb.DoIt()


def windows_context_menu_folder(folder_path: os.PathLike | str):
    """Opens the default windows context menu for a folderpath at the position of the cursor."""
    # Normalize folder path for safety
    parsed_folderpath: WindowsPath = _safe_path_parse(folder_path)
    hwnd = None

    shell: CDispatch = win32com.client.Dispatch("Shell.Application")
    folder: CDispatch = shell.NameSpace(str(parsed_folderpath))
    item: CDispatch = folder.Self
    context_menu: CDispatch = item.Verbs()
    hmenu: int = win32gui.CreatePopupMenu()
    for i, verb in enumerate(context_menu):
        if verb.Name:
            win32gui.AppendMenu(hmenu, win32con.MF_STRING, i + 1, verb.Name)

    pt: tuple[int, int] = win32gui.GetCursorPos()

    with RobustInvisibleWindow() as hwnd:
        cmd: int = win32gui.TrackPopupMenu(hmenu, win32con.TPM_LEFTALIGN | win32con.TPM_RETURNCMD,
                                    pt[0], pt[1], 0, hwnd, None)
        if cmd:
            verb: DispatchBaseClass = context_menu.Item(cmd - 1)
            if verb:
                verb.DoIt()


def windows_context_menu_multiple(paths: Sequence[os.PathLike | str]):
    """Opens the default windows context menu for multiple files/folder paths at the position of the cursor."""
    # Normalize paths for safety
    parsed_paths: list[WindowsPath] = [_safe_path_parse(path) for path in paths]
    hwnd = None

    shell: CDispatch = win32com.client.Dispatch("Shell.Application")
    folders_items: list[CDispatch] = [
        shell.NameSpace(str(path.parent if safe_isfile(path) else path)).ParseName(path.name)
        for path in parsed_paths
    ]
    context_menu: CDispatch = folders_items[0].Verbs()
    hmenu: int = win32gui.CreatePopupMenu()
    for i, verb in enumerate(context_menu):
        if verb.Name:
            win32gui.AppendMenu(hmenu, win32con.MF_STRING, i + 1, verb.Name)

    pt: tuple[int, int] = win32gui.GetCursorPos()

    with RobustInvisibleWindow() as hwnd:
        cmd: int = win32gui.TrackPopupMenu(hmenu, win32con.TPM_LEFTALIGN | win32con.TPM_RETURNCMD,
                                        pt[0], pt[1], 0, hwnd, None)
        if cmd:
            verb: DispatchBaseClass = context_menu.Item(cmd - 1)
            if verb:
                verb.DoIt()


def windows_context_menu(path: os.PathLike | str):
    """Opens the default windows context menu for a folder/file path at the position of the cursor."""
    parsed_path: WindowsPath = _safe_path_parse(path)
    if safe_isfile(parsed_path):
        windows_context_menu_file(parsed_path)
    elif safe_isdir(parsed_path):
        windows_context_menu_folder(parsed_path)
    else:
        msg = f"Path is neither file nor folder: {path}"
        raise ValueError(msg)


# Example usage
if __name__ == "__main__":
    filepath = r"C:\Users\Wizard\test_folder\City.sol"
    windows_context_menu(filepath)

    multiple_files = [
        r"C:\Users\Wizard\test_folder\RestoreBackup.ps1",
        r"C:\Users\Wizard\test_folder\City.sol",
    ]
    windows_context_menu_multiple(multiple_files)

    folderpath = r"C:\Users\Wizard\test_folder"
    windows_context_menu_folder(folderpath)

© www.soinside.com 2019 - 2024. All rights reserved.