AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 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

Abrir arquivo do QFileDialog no explorador de arquivos nativo por meio de clique com o botão direito no PyQt5?

  • 772

No Firefox, se eu baixar um arquivo, há um ícone de pasta "Mostrar na pasta":

Firefox Mostrar na pasta

... que, quando clicado, abre o explorador de arquivos nativo do sistema operacional no diretório Downloads, com o arquivo de download de destino selecionado:

Mostrar na pasta aberta no Explorador de Arquivos nativo

Eu gostaria do mesmo tipo de funcionalidade - exceto que eu quero isso em um aplicativo PyQt5, quando o QFileDialog é aberto, ao escolher uma ação no menu de contexto do botão direito ativado quando o arquivo de destino é selecionado; por exemplo, com o exemplo do PyQt5 (abaixo), posso obter esta caixa de diálogo do Qt5:

Qt5 QFileDialog

... então, quando clico com o botão direito em um arquivo de destino (como test.txtna imagem), gostaria que uma ação "Mostrar na pasta" fosse adicionada ao menu de contexto e, quando ela fosse escolhida, gostaria que o explorador de arquivos nativo fosse aberto no diretório que contém o arquivo de destino e o arquivo de destino selecionado, como o Firefox faz.

Como posso fazer isso no PyQt5?

Código de exemplo:

# 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 respostas
  • 40 Views

2 respostas

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

    Conforme observado nos comentários, não há suporte Qt integrado para isso. Abrir e selecionar um arquivo no gerenciador de arquivos do sistema é bem complicado e não há soluções multiplataforma perfeitas. No entanto, há um pacote Python show-in-file-manager que faz um trabalho razoável se você não quiser desenvolver sua própria solução. Resta então subclassificar QFileDialoge reimplementar o tratamento do menu de contexto. (NB: isso significa que não será mais possível usar funções estáticas como getOpenFileName, que usam uma instância interna do Qt de QFileDialog- a menos, é claro, que você escolha reimplementar essas funções também).

    Aqui está uma demonstração básica (testada apenas no 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

    Bem, graças aos comentários de @ekhumoro e @musicamante, finalmente cheguei a uma solução funcional, com classe e exemplo de uso - mas cara, isso foi incrível:

    • Um bom documento para consultar sobre menus de contexto é https://wiki.python.org/moin/PyQt/Handling%20context%20menus , mesmo que seja para PyQt5; isso me ajudou (junto com fontes C++) a passar pela conversão PyQt5 de um exemplo que postei aqui: menu de contexto em QFileDialog
    • Então tive que consultar as fontes C++ especialmente sobre como recuperar o "arquivo selecionado atualmente"
    • Finalmente usei show_in_file_manager, porém como uso MINGW64 Python3 no Windows 10, os.path.normpathetc. todos convertem caminhos para barras (mesmo se você fornecer manualmente um caminho do Windows com barras invertidas) o que quebra show_in_file_manager- felizmente, há uma solução alternativa aqui: forneça path as_uri()e use allow_conversion=Falseemshow_in_file_manager

    Então, aqui está como o exemplo abaixo se parece na minha máquina:

    Exemplo de saída GUI

    Aqui está o código:

    # 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

relate perguntas

  • Como divido o loop for em 3 quadros de dados individuais?

  • Como verificar se todas as colunas flutuantes em um Pandas DataFrame são aproximadamente iguais ou próximas

  • Como funciona o "load_dataset", já que não está detectando arquivos de exemplo?

  • Por que a comparação de string pandas.eval() retorna False

  • Python tkinter/ ttkboostrap dateentry não funciona quando no estado somente leitura

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

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

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve