QFileDialog总是在主窗口后面打开

7

我正试图在我的PySide2应用程序中打开一个文件,但是文件对话框总是在主窗口下方打开,并出现在启动器中作为另一个应用程序。该应用程序的名称为“Portal”。

我看到其他答案,其中解决方案是将主窗口作为第一个参数传递给getOpenFileName(),但这对我不起作用。

这里是问题的简单演示:

import sys
from PySide2.QtWidgets import QPushButton, QFileDialog, QApplication


class DemoButton(QPushButton):
    def __init__(self, text):
        super().__init__(text)
        self.clicked.connect(self.on_click)

    def on_click(self):
        file_name, _ = QFileDialog.getOpenFileName(
            self,
            "Open a text file.",
            filter='Text file (*.txt)')
        print(file_name)


def main():
    app = QApplication(sys.argv)
    button = DemoButton("Hello World")
    button.show()
    app.exec_()
    sys.exit()


main()

我以为父窗口必须是 QMainWindow,所以我尝试了一下:

import sys

from PySide2 import QtWidgets


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        main_widget = QtWidgets.QWidget(self)
        self.setCentralWidget(main_widget)

        # layout initialize
        g_layout = QtWidgets.QVBoxLayout()
        layout = QtWidgets.QFormLayout()
        main_widget.setLayout(g_layout)

        # Add Widgets
        self.exec_btn = QtWidgets.QPushButton('Execute')
        self.exec_btn.clicked.connect(self.find_file)

        # global layout setting
        g_layout.addLayout(layout)
        g_layout.addWidget(self.exec_btn)

    def find_file(self):
        file_name, _ = QtWidgets.QFileDialog.getOpenFileName(
            self,
            "Open a text file.",
            filter='Text file (*.txt)')
        print(file_name)


def main():
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()
    sys.exit()


main()

文件对话框的行为完全相同。
我正在使用PySide2 5.12.2,Python 3.6.7,并在Ubuntu 18.04上运行。

2
在我的 Arch Linux 系统上,我无法复现你描述的情况。文档中记录的行为应该是产生一个模态对话框居中于父窗口,这正是我得到的结果。你可以尝试添加 options=QFileDialog.DontUseNativeDialog 来看是否有所改变。(注:我想知道问题是否由xdg-desktop-portal引起。你的系统上安装了它吗?) - ekhumoro
1
我遇到了同样的问题。使用platform.system()检查,以便在Windows上仍然获得本地对话框。 - Jim
2个回答

3
感谢ekhumoro的评论,我学会了如何告诉PySide2不要使用本地文件对话框。
import sys
from PySide2.QtWidgets import QPushButton, QFileDialog, QApplication


class DemoButton(QPushButton):
    def __init__(self, text):
        super().__init__(text)
        self.clicked.connect(self.on_click)

    def on_click(self):
        file_name, _ = QFileDialog.getOpenFileName(
            self,
            "Open a text file.",
            filter='Text file (*.txt)',
            options=QFileDialog.DontUseNativeDialog)
        print(file_name)


def main():
    app = QApplication(sys.argv)
    button = DemoButton("Hello World")
    button.show()
    app.exec_()
    sys.exit()


main()

这样做可以通过将文件对话框移到前台来解决问题,但我认为本地文件对话框看起来更好。希望能有另一种选择使本地文件对话框正常工作。


0

我曾经遇到过类似的问题。问题部分在于默认的Windows行为,不允许应用程序窃取焦点。我通过使用外部上下文处理程序来解决这个问题,该处理程序设置了窗口焦点。这使用了NativeWindow方法。这在Windows 11上测试过,使用Python 3.10.6和PySide6:

需要窗口上下文处理程序才能将QFileDialog置于前台:

import os
import re
import win32gui
import win32com.client 
from PySide6.QtWidgets import QFileDialog, QApplication
from PySide6 import QtCore

class WindowMgr:
    """ Encapsulates calls to the winapi for window management
        Forces context window to take focus
        Based on: 
         - https://dev59.com/ZnI95IYBdhLWcg3w7SlZ
         - https://dev59.com/SmYq5IYBdhLWcg3wrSas
    """

    def __init__ (self):
        self._handle = None

    def find_window(self, class_name, window_name=None):
        """find a window by its class_name"""
        self._handle = win32gui.FindWindow(class_name, window_name)
        return self

    def _window_enum_callback(self, hwnd, wildcard):
        """Pass to win32gui.EnumWindows() to check all the opened windows"""
        if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) is not None:
            self._handle = hwnd

    def find_window_wildcard(self, wildcard):
        """find a window whose title matches the wildcard regex"""
        self._handle = None
        win32gui.EnumWindows(self._window_enum_callback, wildcard)
        return self

    def set_foreground(self):
        """put the window in the foreground"""
        shell = win32com.client.Dispatch("WScript.Shell")
        shell.SendKeys('%')  # left shift key sent, this shifts focus from current window
        win32gui.SetForegroundWindow(self._handle)

接下来,使用基于类的实现(而不是独立的快捷方式)设置文件对话框:
def file_open_save_dialog(
    def_dir: str = ".", 
    title: str = "Open file", 
    filter: str = "file (*.*)", 
    mode: str = 'open'
) -> str:
    if (def_dir is None) or (def_dir == '.'):
        def_dir = os.getcwd()

    app = QApplication.instance()
    if app is None:
        app = QApplication([def_dir])

    file_dialog = QFileDialog()
    if mode == 'open':
        file_dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
    elif mode == 'save':
        file_dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
    elif mode == 'folder':
        file_dialog.setFileMode(QFileDialog.FileMode.Directory)
    else:
        raise NameError(f"Invalid option given to FileDialog mode: {mode}")

    file_dialog.setNameFilter(filter)
    file_dialog.setDirectory(def_dir)
    file_dialog.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal)
    file_dialog.setWindowTitle(title)
    file_dialog.show()
    WindowMgr().find_window_wildcard(title).set_foreground() # <- from above

    if file_dialog.exec():
        filenames = file_dialog.selectedFiles()
        return filenames[0]
    else:
        return ''

使用示例:

文件打开:

myopenfile = file_open_save_dialog(def_dir="C:\Temp", title="Select file Foo", filter="Text (*.txt)", mode='open')

文件保存:

mysavefile = file_open_save_dialog(def_dir="C:\Temp", title="Save to file", filter="Text (*.txt)", mode='save')

选择文件夹:

myfolder = file_open_save_dialog(def_dir="C:\Temp", title="Select Folder", filter="*", mode='folder')

这些是阻塞函数。如果您需要在选择过程中保持UI活动,请使用线程。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接