Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crash in QQuickItem destructor / changeListeners when closing application (Qt 5.6)

Tags:

qt

qml

We have a fairly big QtQuick application, with a lot of modal dialogs. All of these modals share a consistent look and behaviour, and have leftButtons, rightButtons, a content and additional warning widgets. We use the following base class (PFDialog.qml):

Window {
    property alias content: contentLayout.children
    ColumnLayout {
        id: contentLayout
    }
}

and declare dialogs in the following way (main.qml):

Window {
    visible: true
    property var window: PFDialog {
        content: Text { text: "Foobar" }
    }
}

The problem is that when the application is closed, a segfault happens in the QQuickItem destructor. This segfault is hard to reproduce, but here is a surefire way of making it happen: with visual studio in debug mode, freed memory is filled with 0xDDDDDDD with triggers the segfault every time.

Full example application can be found here: https://github.com/wesen/testWindowCrash

The crash happens in QQuickItem::~QQuickItem:

for (int ii = 0; ii < d->changeListeners.count(); ++ii) {
    QQuickAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate();
    if (anchor)
        anchor->clearItem(this);
}

The reason for this is that the content of our dialog (the Text item in the example above) is a QObject child of the main Window, but a visual child of the dialog window. When closing the application, the dialog window is destroyed first, and at the time the Text item is deleted, the dialog window (still registered as a changeListener) is stale.

Now my question is:

  • is this a QtQuick bug? Should the dialog deregister itself as a changeListener for its children when it is destroyed (I think it should)
  • is our property alias content: layout.children pattern correct, or is there a better way to do this? This also happens when declaring a default property alias.

For the sake of completeness, here is how we hotfix it in our application. When content changes, we reparent all the items to the layout item. A of elegance, as you will all agree.

function reparentTo(objects, newParent) {
    for (var i = 0; i < objects.length; i++) {
        qmlHelpers.qml_SetQObjectParent(objects[i], newParent)
    }
}
onContentChanged: reparentTo(content, contentLayout)
like image 733
Manuel Odendahl Avatar asked Jun 07 '16 18:06

Manuel Odendahl


2 Answers

I have had this problem lots of times, I don't think it is a bug, more like a design limitation. The more implicit behavior you get, the less control you have, leading to inappropriate orders of object destruction and access to dangling references.

There are numerous situations where this can happen "on its own" as you exceed the bounds of a trivial "by the book" qml application, but in your case it is you who's doing it.

If you want proper ownership, don't use this:

property var window: PFDialog {
    content: Text { text: "Foobar" }
}

Instead use this:

property Window window: dlg // if you need to access it externally
PFDialog {
    id: dlg
    content: Text { text: "Foobar" }
}

Here is a good reason why:

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
  }
}

A child is not the same as a property. Properties are still collected but they are not parented.

As for achieving the "dynamic content" thingie, I've had good results with 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
    }
}

Then:

PFDialog {
    layout: ObjectModel {
        Text { text: "Foobar" }
        // other stuff
    }
}

Lastly, for the sake of doing explicit cleanups before closing the application, on your main QML file you can implement a handler:

onClosing: {
    if (!canExit) doCleanup()
    close.accepted = true
}

This ensures the window will not be destroyed without doing the cleanup first.

Finally:

is our property alias content: layout.children pattern correct, or is there a better way to do this? This also happens when declaring a default property alias.

It wasn't last time I looked into it, but it was at least couple of years back. It would certainly be nice to have objects declared as children actually becoming children of some other object, but at the time this was not possible, and still may not be. Thus the need for the slightly more verbose solution involving the object model and the list view. If you investigate the matter and find something different, leave a comment to let me know.

like image 112
dtech Avatar answered Oct 23 '22 09:10

dtech


I believe that you cannot declare a Window Object in a var. In my tests the SubWindow never open and sometimes broken on startup.

A Window can be declared inside an Item or inside another Window; in that case the inner Window will automatically become "transient for" the outer Window See: http://doc.qt.io/qt-5/qml-qtquick-window-window.html

Modify your code to this:

Window {
    visible: true
    PFDialog {
        content: Text { text: "Foobar" }
    }
}
like image 37
rflobao Avatar answered Oct 23 '22 11:10

rflobao