Почему позиция окна по-прежнему равна нулю после его отображения?

Я пытаюсь получить положение экрана MainWindow после его создания. Из этого и это я узнал, что геометрия окна устанавливается после вызова show() - но в моем случае он сохраняет значения по умолчанию, и поэтому позиция остается нулевой.


Фактическая проблема, которую я пытаюсь решить, заключается в том, чтобы QFileDialog отображалось посередине и перед главным окном. На самом деле это должно быть поведением по умолчанию, но поскольку позиция главного окна равна нулю, диалоговое окно появляется в верхнем левом углу экрана. Пользовательский интерфейс был создан с помощью Qt-дизайнера, и я загружаю файл пользовательского интерфейса во время выполнения через файл QUiLoader. Я заметил несколько вещей:

  • Я могу сделать так, чтобы диалоговое окно появлялось посередине, если я установил положение окна с помощью move() или setGeometry() в его реальное положение на экране после show().
  • Геометрия члена окна ui, где QUiLoader загружает ui-файл, содержит правильные значения, если я взаимодействую с окном (например, внутри QPushButton-обратного вызова), и я могу установить геометрию окна на геометрию ui в вот так: self.setGeometry(self.ui.frameGeometry()) Это то, что я делаю в данный момент, чтобы диалоговое окно появлялось посередине прямо перед отображением диалогового окна в обратном вызове определенной кнопки.

(Но все это только для контекста.)


И все же я все еще хочу знать, могу ли я заставить главное окно иметь правильное положение после show()ing его. По этой причине я написал минимальный пример: одно окно, которое полностью закодировано на Python, одно окно, которое загружает файл пользовательского интерфейса, чтобы он выглядел так же, как первое, и некоторый код для отображения одного из окон:

import sys, os
from PySide2 import QtCore, QtWidgets, QtUiTools

class Window1(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(Window1, self).__init__(parent)
        self.resize(300, 200)
        self.pushButton = QtWidgets.QPushButton(self, text="Print geometry")
        self.pushButton.clicked.connect(self.print_geometry)
        self.show()                                    # (A)
        # self.move(300, 200)
        self.print_geometry()                          # (C)

    def print_geometry(self):
        self.updateGeometry()                          # (E)
        print(self.frameGeometry())
        print(self.geometry())


class Window2(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(Window2, self).__init__(parent)
        loader = QtUiTools.QUiLoader()
        file = QtCore.QFile(os.path.abspath("TestWindow.ui"))
        file.open(QtCore.QFile.ReadOnly)
        self.ui = loader.load(file, parent)
        file.close()
        self.ui.pushButton.clicked.connect(self.print_geometry)
        self.ui.show()                                 # (A)
        # self.ui.move(300, 200)
        self.print_geometry()                          # (C)

    def print_geometry(self):
        self.ui.updateGeometry()                       # (E)
        print(self.ui.frameGeometry())
        print(self.ui.geometry())


app = QtWidgets.QApplication(sys.argv)

window = Window1()
# window = Window2()
# window.show()                                        # (B)
window.print_geometry()                                # (D)

sys.exit(app.exec_())

Сначала хочу отметить:

  • Кажется, не имеет значения, вызываю ли я show() в конце __init__() или после создания окна. (А) или (Б)
  • Кажется, не имеет значения, вызываю ли я print_geometry() внутри __init__() или после создания окна. (С) или (Г)
  • Вызовы updateGeometry(), кажется, ничего не делают. (Э)

Вывод для обоих окон всегда:

PySide2.QtCore.QRect(0, 0, 300, 200)
PySide2.QtCore.QRect(0, 0, 300, 200)
PySide2.QtCore.QRect(0, 0, 300, 200)
PySide2.QtCore.QRect(0, 0, 300, 200)

И когда я нажимаю кнопку, вывод всегда:

PySide2.QtCore.QRect(800, 418, 302, 227)
PySide2.QtCore.QRect(801, 444, 300, 200)

Мой вопрос: почему геометрия окна имеет значения по умолчанию даже после show(), и что происходит между show() и обратным вызовом кнопки, так что геометрия вдруг содержит правильные значения? Могу ли я эмулировать это внутри окна __init__() после show()? Это поведение характерно для оконного менеджера Linux Mint Cinnamon, который я использую?

TestWindow.ui:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>300</width>
    <height>200</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="pushButton">
    <property name="text">
     <string>Print geometry</string>
    </property>
   </widget>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

person S818    schedule 29.07.2019    source источник


Ответы (1)


Это происходит только на платформе X11 (например, Linux) — см. Геометрия окна и раздел Особенности X11.

Ключевым моментом является то, что в X11 некоторые события, связанные с окном, происходят асинхронно. Это означает, что вы должны подождать, пока оконный менеджер не создаст рамку для вашего окна и не разместит его на экране, прежде чем запрашивать геометрию. Если вы нажимаете кнопку для печати геометрии, вы не запрашиваете ее сразу (т. е. синхронно), что объясняет, почему вы видите разные выходные данные.

Если вам нужно выполнить какое-то действие сразу после отображения окна, для которого требуется информация о его геометрии, простой обходной путь — использовать однократный таймер:

class Window1(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        ...
        self.show()
        # self.print_geometry()
        QtCore.QTimer.singleShot(50, self.print_geometry)

На практике фактическая продолжительность задержки будет зависеть от системы и может составлять от 1 мс до 25 мс. Возможно, вам придется немного поэкспериментировать, но 50 мс должны быть безопасными.

person ekhumoro    schedule 30.07.2019