Сбой в QQuickItem destructor/changeListeners при закрытии приложения (Qt 5.6)
У нас есть довольно большое приложение QtQuick с большим количеством модальных диалогов. Все эти модалы разделяют последовательный внешний вид и поведение, а также имеют кнопки leftButtons, rightButtons, содержимое и дополнительные предупреждения. Мы используем следующий базовый класс (PFDialog.qml):
Window {
property alias content: contentLayout.children
ColumnLayout {
id: contentLayout
}
}
и объявить диалоги следующим образом (main.qml):
Window {
visible: true
property var window: PFDialog {
content: Text { text: "Foobar" }
}
}
Проблема заключается в том, что при закрытии приложения в дескрипторе QQuickItem происходит segfault. Этот segfault трудно воспроизвести, но вот верный способ сделать это: с визуальной студией в режиме отладки освобожденная память заполняется 0xDDDDDDD с триггерами segfault каждый раз.
Полное приложение можно найти здесь: https://github.com/wesen/testWindowCrash
Сбой происходит в QQuickItem::~QQuickItem
:
for (int ii = 0; ii < d->changeListeners.count(); ++ii) {
QQuickAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate();
if (anchor)
anchor->clearItem(this);
}
Причиной этого является то, что содержимое нашего диалога (элемент Text в приведенном выше примере) является дочерним объектом QObject основного окна, но является визуальным дочерним элементом диалогового окна. При закрытии приложения диалоговое окно сначала уничтожается, и в то время, когда элемент Text удален, диалоговое окно (все еще зарегистрированное как changeListener) устарело.
Теперь мой вопрос:
- Это ошибка QtQuick? Если диалог отменяет регистрацию как changeListener для своих детей, когда он уничтожается (я думаю, что он должен)
- наш правильный шаблон
property alias content: layout.children
, или есть лучший способ сделать это? Это также происходит при объявлении псевдонима свойства по умолчанию.
Для полноты, вот как мы исправим его в нашем приложении. Когда содержимое изменяется, мы возвращаем все элементы в элемент макета. Элегантность, как вы все согласитесь.
function reparentTo(objects, newParent) {
for (var i = 0; i < objects.length; i++) {
qmlHelpers.qml_SetQObjectParent(objects[i], newParent)
}
}
onContentChanged: reparentTo(content, contentLayout)
Ответы
Ответ 1
У меня была эта проблема много раз, я не думаю, что это ошибка, больше похоже на ограничение дизайна. Чем больше неявного поведения вы получаете, тем меньше у вас контроля, что приводит к неуместным порядкам уничтожения объекта и доступа к оборванным ссылкам.
Существует множество ситуаций, когда это может произойти "по-своему", поскольку вы превышаете границы тривиального "в книге" qml-приложения, но в вашем случае это вы его делаете.
Если вы хотите получить правильное владение, не используйте это:
property var window: PFDialog {
content: Text { text: "Foobar" }
}
Вместо этого используйте это:
property Window window: dlg // if you need to access it externally
PFDialog {
id: dlg
content: Text { text: "Foobar" }
}
Вот хорошая причина, по которой:
property var item : Item {
Item {
Component.onCompleted: console.log(parent) // qml: QQuickItem(0x4ed720) - OK
}
}
// vs
property var item : Item {
property var i: Item {
Component.onCompleted: console.log(parent) // qml: null - BAD
}
}
Ребенок не такой же, как свойство. Свойства все еще собираются, но они не являются родительскими.
Что касается достижения "динамического контента", у меня были хорошие результаты с ObjectModel
:
Window {
property ObjectModel layout
ListView {
width: contentItem.childrenRect.width // expand to content size
height: contentItem.childrenRect.height
model: layout
interactive: false // don't flick
orientation: ListView.Vertical
}
}
Тогда:
PFDialog {
layout: ObjectModel {
Text { text: "Foobar" }
// other stuff
}
}
Наконец, ради выполнения явных очищений перед закрытием приложения в основном файле QML вы можете реализовать обработчик:
onClosing: {
if (!canExit) doCleanup()
close.accepted = true
}
Это гарантирует, что окно не будет уничтожено без предварительной очистки.
Наконец:
- это наш собственный псевдоним свойства: layout.children pattern correct, или есть лучший способ сделать это? Это также происходит при объявлении псевдоним свойства по умолчанию.
В прошлый раз я не заглянул в нее, но это было, по крайней мере, пару лет назад. Было бы неплохо иметь объекты, объявленные как дети, фактически становящиеся детьми какого-либо другого объекта, но в то время это было невозможно, и все еще может и не быть. Таким образом, необходимо немного более подробное решение, включающее объектную модель и представление списка. Если вы исследуете этот вопрос и находите что-то другое, оставьте комментарий, чтобы сообщить мне.
Ответ 2
Я считаю, что вы не можете объявить объект Window в var. В моих тестах SubWindow никогда не открывается и иногда разбивается при запуске.
Окно может быть объявлено внутри элемента или внутри другого окна; в этом случае внутреннее окно автоматически станет "переходным" для внешнего окна См.: http://doc.qt.io/qt-5/qml-qtquick-window-window.html
Измените код следующим образом:
Window {
visible: true
PFDialog {
content: Text { text: "Foobar" }
}
}