Ошибка сегментации FigureCanvasQTAgg, вложенная в QMdiSubWindow, когда я сворачиваю QMdiSubWindow

Я пытаюсь использовать FigureCanvasQTAgg внутри QMdiSubWindow, чтобы пользователь мог создавать свои собственные графики на лету. Я сделал этот очень маленький автономный код:

from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys

class ExampleApp(QtGui.QMainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.mdiarea = QtGui.QMdiArea()
        self.setCentralWidget(self.mdiarea)
        sub = QtGui.QMdiSubWindow(self.mdiarea)
        fig = Figure()
        p = FigureCanvas(fig)
        sub.layout().addWidget(p)
        sub.show()

def main():
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()

Проблема возникает, когда я запускаю программу и пытаюсь свернуть объект QtGui.QMdiSubWindow. Когда я это делаю, программа выдает ошибку и завершает работу без описания ошибки. Это может быть ошибка в qt, в привязках python или в объекте FigureCanvasQTAgg. Конечно, это мог быть и я, просто неправильно использующий эти объекты. Пожалуйста, помогите мне понять, почему возникает segfault, когда я сворачиваю подокно, и помогите мне понять, как я могу решить эту проблему. Спасибо.

Моя среда - Ubuntu 14.04 и использует версию Qt: 4.8.7 Версия SIP: 4.16.9 Версия PyQt: 4.11.4 Версия MatplotLib: 1.5.0

Вот пример набора свойств перетаскивания. Похоже, что и с этим есть проблемы.

from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys

class QtZListView(QtGui.QListView):
    def __init__(self, *args, **kwargs):
        QtGui.QListView.__init__(self, *args, **kwargs)
        self.model = QtGui.QStringListModel(['a','b','c'])
        self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
        self.setModel(self.model)
        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.setDragEnabled(True)

    def setStringList(self, *args, **kwargs):
        return self.model.setStringList(*args, **kwargs)

class mplsubwindow(QtGui.QMdiSubWindow):

    def __init__(self, *args, **kwargs):
        QtGui.QMdiSubWindow.__init__(self, *args, **kwargs)
        self.setWindowTitle("testing")
        self.setAcceptDrops(True)
        self.resize(400,400)
        self.show()

    def dragEnterEvent(self, event):
        print('entering')
        super(mplsubwindow, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        print('drag moving')
        super(mplsubwindow, self).dragMoveEvent(event)

    def dropEvent(self, event):
        print('dropped')
        super(mplsubwindow, self).dropEvent(event)

class ExampleApp(QtGui.QMainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()
        mainwid = QtGui.QWidget()
        self.mdiarea = QtGui.QMdiArea()

        layout = QtGui.QGridLayout(mainwid)
        layout.addWidget(self.mdiarea)
        sub = mplsubwindow(self.mdiarea)
        sub.show()
        layout.addWidget(QtZListView())
        self.setCentralWidget(mainwid)
        #self.setWidget(mainwid)

def main():
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()

person plaindev    schedule 13.03.2016    source источник
comment
В Windows 10, matplotlib v1.3.1, PyQt v4.11.1 я не получаю segfault, но получаю предупреждения о недопустимой ширине/высоте для средства визуализации matplotlib. Они исчезнут, если я добавлю виджет через sub.setWidget(p), а не через макет. Дайте мне знать, если это решит вашу проблему, чтобы я мог опубликовать это как ответ!   -  person three_pineapples    schedule 14.03.2016
comment
Можете ли вы сообщить об этом как об ошибке в mpl? Проблема, похоже, в том, что когда окно свернуто, запрошенная высота холста является отрицательной, что не нравится Аггу.   -  person tacaswell    schedule 14.03.2016
comment
См. исправление здесь github.com/matplotlib/matplotlib/pull/6152   -  person tacaswell    schedule 14.03.2016
comment
three_pineapples, похоже, это решает проблему. Большое спасибо.   -  person plaindev    schedule 14.03.2016
comment
three_pineapples, однако это означает, что я могу добавить только один виджет на холст, я думаю... потому что, если я хочу добавить 2 или более и вызвать setwidget для каждого, он заменит предыдущий.   -  person plaindev    schedule 14.03.2016
comment
Я добавил ответ, демонстрирующий создание и использование собственного макета. P.S. Добро пожаловать в переполнение стека! Вы должны поставить перед именем пользователя символ @, если хотите, чтобы он получил уведомление о вашем комментарии. Вот так: @plaindev   -  person three_pineapples    schedule 14.03.2016
comment
@three_pineapples спасибо за это. Еще учусь, да. Я обновил приведенный выше вопрос дополнительным фрагментом кода, который также отображает проблему с перетаскиванием. Кажется, даже с предложенным вами setwidget и полным удалением зависимости от matplotlib он по-прежнему не может принимать события отбрасывания.   -  person plaindev    schedule 14.03.2016
comment
@plaindev, вам не следует добавлять больше к этому вопросу. Лучше задать новый вопрос (как вы уже сделали в любом случае) и оставить этот вопрос таким, каким он был изначально, чтобы он мог помочь другим людям в будущем.   -  person three_pineapples    schedule 14.03.2016
comment
@plaindev Можете ли вы отменить изменения, которые задают другой вопрос (и ссылку на то, где когда-либо был новый вопрос)?   -  person tacaswell    schedule 16.03.2016
comment
Да определенно. я сделаю это   -  person plaindev    schedule 16.03.2016


Ответы (2)


Проблема, похоже, в том, что при минимизации виджет имеет отрицательную высоту (я думаю, это имеет смысл, но я не могу найти документацию об этом факте; я заметил это, добавив несколько операторов печати). Решение состоит в том, чтобы просто не рисовать в таких случаях. Я отправил PR, чтобы исправить это в восходящем потоке, но вам может понадобиться монки-патч matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase.__draw_idle_agg с :

def __draw_idle_agg(self, *args):
    if self.height() < 0 or self.width() < 0:
        self._agg_draw_pending = False
        return
    try:
        FigureCanvasAgg.draw(self)
        self.update()
    finally:
        self._agg_draw_pending = False

Обратите внимание, что qt5 в модуле не является опечаткой, функциональность Qt4 получена из поддержки Qt5.

person tacaswell    schedule 14.03.2016
comment
Большое спасибо за быстрый ответ. Кажется, вы уже сообщили об этой ошибке выше по течению, так что мне не нужно предпринимать никаких действий? Вы хоть примерно представляете, сколько времени пройдет до выхода этого патча? - person plaindev; 14.03.2016

Проблема, похоже, связана с неправильным размером, сообщаемым для виджета matplotlib. Как указывает @tcaswell, необходимо исправить matplotlib, чтобы это не вызывало segfault.

Я собираюсь рассмотреть проблему с другой стороны и попытаться помешать Qt сообщать о фиктивных измерениях. Кажется, что использование «встроенного» макета вызывает проблему. Вероятно, это связано с тем, что существование макета наследуется QMdiSubWindow от QWidget, но реализация QMdiSubWindow, вероятно, использует его неправильно. Пока вы используете метод QMdiSubWindow.setWidget() и создаете свои собственные макеты, segfault можно избежать.

Вот пример кода с макетом, которым вы сами управляете:

p = FigureCanvas(fig)
container = QtGui.QWidget()
layout = QtGui.QVBoxLayout(container)
layout.addWidget(p)        
sub.setWidget(container)

ИЗМЕНИТЬ

Если вы посмотрите на лежащую в основе реализацию C++ вы можете видеть, что вызов QMdiSubWindow.setWidget() намного сложнее, чем просто добавление виджета в макет!

person three_pineapples    schedule 14.03.2016