AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79192562
Accepted
sdbbs
sdbbs
Asked: 2024-11-15 21:22:40 +0800 CST2024-11-15 21:22:40 +0800 CST 2024-11-15 21:22:40 +0800 CST

在 PyQt5 中通过右键单击从本机文件资源管理器中的 QFileDialog 打开文件?

  • 772

在 Firefox 中,如果我下载文件,则会有一个文件夹图标“在文件夹中显示”:

Firefox 在文件夹中显示

...单击后,将打开下载目录中的本机操作系统文件资源管理器,并选择目标下载文件:

在本机文件资源管理器中打开的文件夹中显示

我想要相同类型的功能 - 除了我希望它在 PyQt5 应用程序中,当打开 QFileDialog 时,在选择目标文件时激活的右键单击上下文菜单中选择一个操作;例如使用 PyQt5 示例(如下),我可以得到这个 Qt5 对话框:

Qt5 QFileDialog

...因此,当我右键单击目标文件(如图所示test.txt)时,我希望在上下文菜单中添加“在文件夹中显示”操作,当选择该操作时,我希望在包含目标文件的目录中打开本机文件资源管理器,并选择目标文件 - 就像 Firefox 所做的那样。

我如何在 PyQt5 中做到这一点?

示例代码:

# started from https://pythonspot.com/pyqt5-file-dialog/
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QInputDialog, QLineEdit, QFileDialog
from PyQt5.QtGui import QIcon

class App(QWidget):

    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 file dialogs - pythonspot.com'
        self.left = 10
        self.top = 10
        self.width = 640
        self.height = 480
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.openFileNameDialog()

        self.show()

    def openFileNameDialog(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileName()", "","Text Files (*.txt)", options=options)
        if fileName:
            print(fileName)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())
python
  • 2 2 个回答
  • 40 Views

2 个回答

  • Voted
  1. Best Answer
    ekhumoro
    2024-11-16T05:10:26+08:002024-11-16T05:10:26+08:00

    正如评论中所述,没有内置的 Qt 支持。在系统文件管理器中打开和选择文件非常棘手,并且没有完美的跨平台解决方案。但是,如果您不想开发自己的解决方案,有一个 Python show-in-file-manager 包QFileDialog可以完成合理的工作。然后剩下的就是子类化和重新实现上下文菜单处理。(注意:这意味着将不再可能使用像这样的静态函数getOpenFileName,它们使用的内部 Qt 实例QFileDialog- 当然,除非您选择也重新实现这些函数)。

    这是一个基本的演示(仅在 Linux 上测试):

    from PyQt5.QtCore import (
        QFile, QFileDevice,
        )
    from PyQt5.QtWidgets import (
        QApplication, QListView, QTreeView, QFileSystemModel, QToolButton,
        QWidget, QFileDialog, QAction, QMenu, QPushButton, QVBoxLayout,
        QMessageBox,
        )
    # from PyQt6.QtCore import (
    #     QFile, QFileDevice,
    #     )
    # from PyQt6.QtGui import (
    #     QAction, QFileSystemModel,
    #     )
    # from PyQt6.QtWidgets import (
    #     QApplication, QListView, QTreeView, QToolButton, QMessageBox,
    #     QWidget, QFileDialog, QMenu, QPushButton, QVBoxLayout,
    #     )
    
    class FileDialog(QFileDialog):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.setOptions(QFileDialog.Option.DontUseNativeDialog)
            self._list_view = self.findChild(QListView, 'listView')
            self._list_view.customContextMenuRequested.disconnect()
            self._list_view.customContextMenuRequested.connect(
                self.showContextMenu)
            self._tree_view = self.findChild(QTreeView, 'treeView')
            self._tree_view.customContextMenuRequested.disconnect()
            self._tree_view.customContextMenuRequested.connect(
                self.showContextMenu)
            self._rename_action = self.findChild(QAction, 'qt_rename_action')
            self._delete_action = self.findChild(QAction, 'qt_delete_action')
            self._hidden_action = self.findChild(QAction, 'qt_show_hidden_action')
            self._folder_action = self.findChild(QAction, 'qt_new_folder_action')
            self._folder_button = self.findChild(QToolButton, 'newFolderButton')
            self._show_in_action = QAction('Show In &Folder')
            self._show_in_action.triggered.connect(self.showInFolder)
            self._model = self.findChild(QFileSystemModel, 'qt_filesystem_model')
    
        def showContextMenu(self, position):
            if self.viewMode() == QFileDialog.ViewMode.Detail:
                view = self._tree_view
            else:
                view = self._list_view
            index = view.indexAt(position)
            index = index.sibling(index.row(), 0)
            if (proxy := self.proxyModel()) is not None:
                index = proxy.mapToSource(index)
            menu = QMenu(view)
            if index.isValid():
                menu.addAction(self._show_in_action)
                menu.addSeparator()
                permissions = QFileDevice.Permission(index.parent().data(
                    QFileSystemModel.Roles.FilePermissions))
                enable = bool(not self._model.isReadOnly() and
                    permissions & QFileDevice.Permission.WriteUser)
                self._rename_action.setEnabled(enable)
                menu.addAction(self._rename_action)
                self._delete_action.setEnabled(enable)
                menu.addAction(self._delete_action)
                menu.addSeparator()
            menu.addAction(self._hidden_action)
            if self._folder_button.isVisible():
                self._folder_action.setEnabled(self._folder_button.isEnabled())
                menu.addAction(self._folder_action)
            menu.exec(view.viewport().mapToGlobal(position))
    
        def showInFolder(self):
            if files := self.selectedFiles():
                try:
                    from showinfm import show_in_file_manager
                except ImportError:
                    QMessageBox.warning(self, 'Show In Folder', (
                        '<br>Please install <a href="https://pypi.org/'
                        'project/show-in-file-manager/">'
                        'show_in_file_manager</a>.<br>'
                        ))
                else:
                    show_in_file_manager(files)
    
    class Window(QWidget):
        def __init__(self):
            super().__init__()
            self.button = QPushButton('Open File')
            self.button.clicked.connect(self.openFileNameDialog)
            layout = QVBoxLayout(self)
            layout.addWidget(self.button)
            self.dialog = FileDialog(self)
    
        def openFileNameDialog(self):
            self.dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
            self.dialog.setNameFilter('Text Files (*.txt);;All Files(*)')
            self.dialog.open()
    
    if __name__ == '__main__':
    
        app = QApplication(['Test'])
        window = Window()
        window.setGeometry(600, 100, 200, 50)
        window.show()
        app.exec()
    
    • 1
  2. sdbbs
    2024-11-16T11:22:29+08:002024-11-16T11:22:29+08:00

    好吧,感谢 @ekhumoro 和 @musicamante 的评论,我终于找到了一个可行的解决方案,其中包含类和用法示例 - 但天哪,这真是太难了:

    • 有关上下文菜单的一个好文档是https://wiki.python.org/moin/PyQt/Handling%20context%20menus,即使它是针对 PyQt5 的;它帮助我(以及 C++ 源代码)完成了我在此处发布的示例的 PyQt5 转换:QFileDialog 中的上下文菜单
    • 然后我不得不查阅 C++ 源代码,特别是关于如何检索“当前选定的文件”
    • 最后我使用了show_in_file_manager,但是由于我在 Windows 10 等上使用 MINGW64 Python3,os.path.normpath所有路径都会转换为正斜杠(即使你手动提供带有反斜杠的 Windows 路径),这会中断show_in_file_manager- 幸运的是,这里有一个解决方法:提供路径as_uri(),并allow_conversion=False在show_in_file_manager

    因此,下面的示例在我的计算机上如下所示:

    示例 GUI 输出

    以下是代码:

    # started from https://stackoverflow.com/q/43890097/79194084#79194084
    import sys
    import os
    import platform
    from pathlib import Path, PureWindowsPath # python 3.4
    from showinfm import show_in_file_manager, valid_file_manager, stock_file_manager # python3 -m pip install show-in-file-manager
    from PyQt5.QtCore import * # QFile
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QTreeView, QListView, QFileDialog, QAction, QMenu, QActionGroup, QPushButton, QLabel, QMainWindow, QDialog, QFileSystemModel
    
    class CustomShowQFileDialog(QFileDialog):
        """
        * https://forum.qt.io/topic/158937: "QFileDialog is not really meant to
          be overriden. Create your own dialog if you want something special." -
          however, there is an example there
        * https://www.qtcentre.org/threads/1827-QFileDialog-and-subclassing: "When
          you subclass QFileDialog, you are subclassing the Qt QFileDialog, not the
          native one. Calling static methods like getOpenFileName() will have no
          other effect than showing the native dialog because they are *static*,
          they will not operate on your instance.
          Therefore, you must use show() or exec() to show your instance of a
          filedialog. I recomend exec() - that way, when the dialog has been closed,
          functions like selectedFiles(), selectedFilter(), etc. will return the
          correct value."
        """
        def __init__(self, parent=None, caption="", directory="", filter=""):
            super(CustomShowQFileDialog,self).__init__(parent, caption, directory, filter)
    
            #print(f"{valid_file_manager()=} {stock_file_manager()=}") # both are 'explorer.exe' for MINGW64 Python
    
            # disable file multi-selection - ensure only a single item (file/dir)
            # selection is possible; see
            # https://www.qtcentre.org/threads/18815-Select-multiple-files-from-QFileDialog
            self.setFileMode(QFileDialog.ExistingFile)
    
            # must have self.show() first, so .children() are instantiated,
            # and .findChild works; but without extern call to parent .show()
            # afterwards, this self.show() on its own does not show a dialog window!
            #self.show()
            # note: QWidget::show() can call QWidget::showFullScreen(), which calls
            # ensurePolished() and setVisible(True); it turns out, to have
            # .findChild work, it is enough to just run .setVisible(True) - note
            # that this also shows the dialog upon instantiation
            #self.ensurePolished()
            self.setVisible(True)
    
            # fetch the QTreeView/QListView in the QFileDialog
            self.myTree = self.findChild(QTreeView, "treeView") # when QFileDialog in Detail View
            self.myList = self.findChild(QListView, "listView") # when QFileDialog in List View
    
            # get the actions for the original QFileDialog context menu, so
            # we can reconstruct it;
            # qt_rename_action and qt_delete_action are setEnabled(False) by default,
            # and should be enabled as per file permissions of selection (see
            # QFileDialogPrivate::showContextMenu, qtbase/src/widgets/dialogs/qfiledialog.cpp)
            # here we just enable them regardless - where app has permissions,
            # they will work:
            self.rename_act = self.findChild(QAction, "qt_rename_action")
            self.rename_act.setEnabled(True)
            self.delete_act = self.findChild(QAction, "qt_delete_action")
            self.delete_act.setEnabled(True)
            self.show_hidden_act = self.findChild(QAction, "qt_show_hidden_action")
            self.new_folder_act = self.findChild(QAction, "qt_new_folder_action")
    
            # Define our custom context menu action, and
            # connect this action to a slot/handler method
            self.show_in_folder_act = QAction( "Show in Folder", self )
            self.show_in_folder_act.triggered.connect(self.handle_show_in_folder_act)
    
            # set up context menu policy
            self.myTree.setContextMenuPolicy( Qt.CustomContextMenu )
            self.myList.setContextMenuPolicy( Qt.CustomContextMenu )
            self.myTree.customContextMenuRequested.disconnect() # OK
            self.myTree.customContextMenuRequested.connect(self.generate_context_menu)
            self.myList.customContextMenuRequested.disconnect() # OK
            self.myList.customContextMenuRequested.connect(self.generate_context_menu)
    
        def generate_context_menu(self, location): # https://stackoverflow.com/q/44666427
            print("generate_context_menu {}".format(self.sender().objectName())) # "treeView" or "listView"
            #
            # as in QFileDialogPrivate::showContextMenu;
            # note QFileDialogPrivate::model is QFileSystemModel*,
            # not accessible in Python - but FilePermissions are
            index = self.myList.currentIndex()
            index = index.sibling(index.row(), 0)
            #ro = (self.model) and (self.model.isReadOnly()) # cannot
            perms = index.parent().data(QFileSystemModel.FilePermissions) # no .toInt(), is int already
            print(f"    {perms=}")
            # renameAction->setEnabled(!ro && p & QFile::WriteUser);
            # deleteAction->setEnabled(!ro && p & QFile::WriteUser);
            self.rename_act.setEnabled(perms & QFile.WriteUser)
            self.delete_act.setEnabled(perms & QFile.WriteUser)
            #
            menu = QMenu()
            menu.addAction(self.rename_act)
            menu.addAction(self.delete_act)
            menu.addSeparator()
            menu.addAction(self.show_hidden_act)
            menu.addAction(self.new_folder_act)
            menu.addSeparator()
            menu.addAction(self.show_in_folder_act)
            #menu.exec_(event.globalPos()) # https://stackoverflow.com/q/65371143 -> not applicable here
            menu.exec_(self.mapToGlobal(location)) # https://stackoverflow.com/q/43820152 -> works
    
        def handle_show_in_folder_act(self):
            # as in QFileDialogPrivate::renameCurrent();
            # self.myList.currentIndex() works for both Detail View and List View;
            # .column() can be > 0 only for Detail view (when right-clicked over
            # e.g. file size column)
            #print(f"{self.myList.currentIndex().row()=} {self.myList.currentIndex().column()=} ")
            index = self.myList.currentIndex()
            index = index.sibling(index.row(), 0)
            # as in QFileDialogPrivate::deleteCurrent();
            # note QFileDialogPrivate::mapToSource is private, and not in Python
            #index = self.mapToSource(index.sibling(index.row(), 0))
            #print(f"{index.isValid()=} {index=}")
            fileName = index.data(QFileSystemModel.FileNameRole) # no .toString(), is str already
            filePath = index.data(QFileSystemModel.FilePathRole) # no .toString(), is str already
            perms = index.parent().data(QFileSystemModel.FilePermissions) # no .toInt(), is int already
            print(f"{fileName=} {filePath=} {perms=}")
            # NOTE: in MINGW64 Python3, filePath='C:/test/test.txt' with forward
            # slashes - this ends up as folder='/test' in launch_file_explorer of
            # showinfm, causing `folder_pidl = shell.SHILCreateFromPath(folder, 0)[0]`
            # to end up being None, causing "TypeError: None is not a valid
            # ITEMIDLIST in this context" in the desktop.BindToObject call;
            # so probably we need to ensure proper Windows line separators before
            # calling show_in_file_manager
            if "win" in platform.system().lower(): # https://stackoverflow.com/q/1387222
              # python pathlib fails to convert these forward slashes to backslashes
              # fpath = Path(filePath) # WindowsPath('C:/test/test.txt')
              # pwPath = PureWindowsPath(fpath) # PureWindowsPath('C:/test/test.txt'); same with filePath as argument
              # apparently, os.path.normpath can do forward slash to backslash? not for me
              #filePath = os.path.normpath(filePath)
              # well then, have to go manual; unfortunately show_in_file_manager
              # calls normpath itself, so we're back at forward slashes, and not
              # even allow_conversion=False helps:
              #filePath = filePath.replace(r"/", "\\")
              # what worked finally for MINGW64 python3 is to:
              # pass filePath as URI, and use allow_conversion=False
              filePath = Path(filePath).as_uri()
              print(f"{filePath=}")
            show_in_file_manager(filePath, allow_conversion=False) # verbose=True, debug=True,
    
    
    
    #class MainWidget(QWidget):
    class MainWindow(QMainWindow):
    
        def __init__(self, parent=None):
            #super(MainWidget,self).__init__(parent)
            super(MainWindow,self).__init__(parent)
    
            central_widget = QWidget(self)        # for MainWindow(QMainWindow)
            self.setCentralWidget(central_widget) # for MainWindow(QMainWindow)
    
            #creation of main layout
            mainLayout = QVBoxLayout()
    
            self.button = QPushButton("Click for file dialog")
            self.button.clicked.connect(self.on_button_click)
            mainLayout.addWidget( self.button )
    
            self.label = QLabel("[path]")
            mainLayout.addWidget( self.label )
    
            # creation of a widget inside
            #self.monWidget = CustomShowQFileDialog()
            #mainLayout.addWidget( self.monWidget )
    
            #self.setLayout( mainLayout ) # for MainWidget(QWidget)
            central_widget.setLayout( mainLayout ) # for MainWindow(QMainWindow)
    
        def on_button_click(self):
            print("on_button_click")
            """
            Note: the "tradidional" use with the static .getOpenFileName(...) is
            like this:
    
            options = QFileDialog.Options() # https://stackoverflow.com/q/63012420
            options |= QFileDialog.DontUseNativeDialog
            load_file_path, _ = QFileDialog.getOpenFileName(self, "Open .txt File", filter="Text Files(*.txt)", options=options)
            if load_file_path: # user has made a choice
                print(f"{load_file_path=}")
    
            However, as per https://www.qtcentre.org/threads/1827-QFileDialog-and-subclassing,
            we cannot use that idiom for a custom subclass; instead we must block
            with .exec(), which will return an int depending on OK/Cancel button
            pressed:
              0: Cancel (QDialog::Rejected),
              1: Open/file double-click) (QDialog::Accepted)
            ... and afterwards use .selectedFiles()
            """
    
            # instantiation alone raises dialog for now;
            self.myqfd = CustomShowQFileDialog(self, "Open .txt File", filter="Text Files(*.txt)")
            retint = self.myqfd.exec_() # exec blocks; however here returns int
            print(f"{retint=}")
            if retint == QDialog.Accepted:
              print(f"{self.myqfd.selectedFiles()}")
              firstfile = self.myqfd.selectedFiles()[0]
              self.label.setText(firstfile)
    
    
    app = QApplication(sys.argv)
    #window = MainWidget()
    window=MainWindow()
    window.show()
    window.resize(180, 160)
    sys.exit(app.exec_())
    
    • -1

相关问题

  • 如何将 for 循环拆分为 3 个单独的数据框?

  • 如何检查 Pandas DataFrame 中的所有浮点列是否近似相等或接近

  • “load_dataset”如何工作,因为它没有检测示例文件?

  • 为什么 pandas.eval() 字符串比较返回 False

  • Python tkinter/ ttkboostrap dateentry 在只读状态下不起作用

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行?

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    何时应使用 std::inplace_vector 而不是 std::vector?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Marko Smith

    我正在尝试仅使用海龟随机和数学模块来制作吃豆人游戏

    • 1 个回答
  • Martin Hope
    Aleksandr Dubinsky 为什么 InetAddress 上的 switch 模式匹配会失败,并出现“未涵盖所有可能的输入值”? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge 为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini 具有指定基础类型但没有枚举器的“枚举类”的用途是什么? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer 何时应使用 std::inplace_vector 而不是 std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB 为什么 GCC 生成有条件执行 SIMD 实现的代码? 2024-02-17 06:17:14 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve