Home > Net >  How to override QML item
How to override QML item

Time:06-22

I have a 'base' component which controls the aspect ratio of the item 'childItem':

//AspectRatio.qml

import QtQuick 2.12

Rectangle
{
  property real targetAspectRatio: 16 / 9

  color: "black"
  anchors.fill: parent

  onWidthChanged:
  {
    var _ratio = parent.width / parent.height
    var _width = 0

    if(_ratio > targetAspectRatio) //Too wide
    {
      _width = parent.height * targetAspectRatio
    }
    else
    {
      _width = parent.width
    }

    childItem.width = _width
  }

  onHeightChanged:
  {
    var _ratio = parent.width / parent.height
    var _height = 0

    if(_ratio > targetAspectRatio) //Too wide
    {
      _height = parent.height
    }
    else
    {
      _height = parent.width / targetAspectRatio
    }

    childItem.height = _height
  }

  Item
  {
    id: childItem
    anchors.centerIn: parent
  }
}

I want to use AspectRatio.qml as a generic component and override 'childItem', depending on the context the component is used. How can 'childItem' be overridden, like this?

//child.qml

AspectRatio
{
  childItem : Rectangle
  {
    color: "red"
  }
}

This mechanism is also used in standard qml components, like here. But it's unclear to me how to achieve this.

CodePudding user response:

There are multiple solutions for your problem.

You could create a childItem property and manually put it in the children property of Item like so:

// AspectRatio.qml
Rectangle {
  property real targetAspectRatio: 16 / 9

  property Item childItem
  children: [childItem]

  color: "black"
  anchors.fill: parent

  onWidthChanged: // ...

  onHeightChanged: // ...
}
// Usage
AspectRatio {
    childItem : Rectangle {
        color: "red"
    }
}

Alternatively you could do it the other way and bind the childItem to the children and not have an explicit setter and your property just being a "view" into children:

readonly property Item childItem: children[0]
// Usage
AspectRatio {
    Rectangle { /* ... */  }
}

To be able to use both syntax you could use a default property, overriding the Item's children default property:

default property Item childItem
children: [childItem]
// Usage
AspectRatio {
    Rectangle { /* ... */  }
}
// OR
AspectRatio {
    childItem: Rectangle { /* ... */  }
}

I'd say this one is my preferred solution.


You didn't ask but I would replace your imperative code in onWidth/heightChanged by declarative bindings (and move the anchors outside):

Rectangle {
    id: root
    property real targetAspectRatio: 16 / 9
    default property Item childItem
    children: [childItem]
    color: "black"
    Binding {
        target: root.childItem
        property: "width"
        value: Math.min(root.height * root.targetAspectRatio, root.width)
    }
    Binding {
        target: root.childItem
        property: "height"
        value: Math.min(root.width / root.targetAspectRatio, root.height)
    }
}

CodePudding user response:

You can't "override" an item, but you can create a property that points to the child list of an item. This is how things like the background property that you mentioned on controls work. By assigning to this property, you're essentially inserting an object as a child of whatever the containing Item is.

//AspectRatio.qml
Rectangle
{
    property alias childItem: childContainer.data

    Item 
    {
        id: childContainer
        anchors.centerIn: parent
    }
}

//child.qml
AspectRatio
{
    childItem : Rectangle
    {
        width: 100
        height: 100
        color: "red"
    }
}
  • Related