Prevent “inherited” signal handlers from executing

前端 未结 2 1826
孤独总比滥情好
孤独总比滥情好 2021-01-14 05:35

Defining a signal handler in a \"base\" component is pretty nifty when that functionality is going to be frequently used by many derived components.

However, in QML

相关标签:
2条回答
  • 2021-01-14 05:55

    As peppe mentioned, one solution would be to instead of installing the handler directly, just make the handler a call to a function that can be overriden. However, function overriding itself is a mixed bag, when there is intent to reuse base implementations in the derived components, not necessarily in the order handlers stack with component inheritance.

    I actually came up with a flexible, albeit a little clunky solution. It is to manually disconnect the previous installed handler and manually connect a new one. This has two implications:

    1. The handlers cannot be anonymous expressions, they have to implemented as functions so they can be referenced for disconnect.

    2. The handlers cannot be bound using the declarative syntax (onSignal: handler()), as this doesn't connect to the handler function, but to an anonymous expression which invokes the handler function. So you can't disconnect.

    So it looks something like this:

    //BaseComp.qml
    QtObject {
        signal sig(int i)
        function baseHandler(i) {...}
        Component.onCompleted: sig.connect(baseHandler)
    }
    
    //DerivedComp.qml
    BaseComp {
        function derivedHandler(i) {...}
        Component.onCompleted: {
            sig.disconnect(baseHandler)
            sig.connect(derivedHandler)
        }
    }
    

    The basic pattern is disconnect the previous base handler in every derived component which overrides it. This way you get to access the base handlers from the derived components if there is a need to do that, if instead there is only one overridden handler function, the base implementations will not be accessible from the derived classes due to how overriding is implemented in QML (there will be two identically named functions as members of the object, but both of them will refer to the derived component override).

    It would be niceand useful if QML provided a pretty way to make a "unique" binding - something that purges all previous connections before making the new one. Then all that workaround code would not be needed.

    0 讨论(0)
  • 2021-01-14 06:07

    Given that when overriding functions in QML, the base implementation is no more available such that there must be a distinct name for each implementation.

    First define a naming scheme for slot handler functions, let's say onSomethingHappened executes handleOnSomethingHappened. And ComponentA's implementation is handleOnSomethingHappened_ComponentA. In handleOnSomethingHappened there is superHandleOnSomethingHappened, which executes the base class' implementation.

    With these naming conventions we can achieve a setup with a bunch of nice properties:

    1. Multiple inheritance possible
    2. base class' implementation can optionally be called at any specific point
    3. A class must only know its direct base class
    4. Caller code is nicer than implementation
    5. It is complex but not complicated (and Complex is better than complicated.)
    6. I guess it can be code-generated easily

    In the first example we have 3 Buttons that handle clicks, 1. using the default implementation, 2. using a custom implementation and 3. using a custom implementation plus the base implementation (at any point):

    BaseButton {
        text: "Button 1"
    }
    
    BaseButton {
        text: "Button 2"
        handleOnClicked: function() {
            console.log("Custom click handler")
        }
    }
    
    BaseButton {
        text: "Button 3"
        handleOnClicked: function() {
            console.log("Custom click handler")
            superHandleOnClicked()
        }
    }
    

    This can be done by defining BaseButton like this

    Button {
        property var handleOnClicked: superHandleOnClicked
    
        // "super" from the instance's perspective. Use this in implementations of handleOnDoubleClicked
        property var superHandleOnClicked: handleOnClicked_BaseButton
    
        function handleOnClicked_BaseButton() {
            console.log("BaseButton clicked.")
        }
    
        onClicked: handleOnClicked()
    }
    

    The base implementation is available in the function superHandleOnClicked.

    Slots with arguments

    When arguments are used, nothing changes:

    Rectangle {
        width: 100
        height: 40
        color: "green"
        BaseMouseArea {
        }
    }
    
    Rectangle {
        width: 100
        height: 40
        color: "green"
        BaseMouseArea {
            handleOnDoubleClicked: function(mouse) {
                console.log("Custom click handler", mouse.x, mouse.y)
            }
        }
    }
    
    Rectangle {
        width: 100
        height: 40
        color: "green"
        BaseMouseArea {
            handleOnDoubleClicked: function(mouse) {
                console.log("Custom click handler", mouse.x, mouse.y)
                superHandleOnDoubleClicked(mouse)
            }
        }
    }
    

    with BaseMouseArea defined as

    MouseArea {
        anchors.fill: parent
    
        property var handleOnDoubleClicked: superHandleOnDoubleClicked
    
        // "super" from the instance's perspective. Use this in implementations of handleOnDoubleClicked
        property var superHandleOnDoubleClicked: handleOnDoubleClicked_BaseMouseArea
    
        function handleOnDoubleClicked_BaseMouseArea(mouse) {
            console.log("BaseMouseArea clicked", mouse.x, mouse.y, ".")
        }
    
        onDoubleClicked: handleOnDoubleClicked(mouse)
    }
    

    Multiple inheritance

    Now we have instance is a PointerMouseArea is a BaseMouseArea, with instance being defined as

    Rectangle {
        width: 100
        height: 40
        color: "blue"
        PointerMouseArea {
        }
    }
    
    Rectangle {
        width: 100
        height: 40
        color: "blue"
        PointerMouseArea {
            handleOnDoubleClicked: function(mouse) {
                console.log("Don't tell father and grandfather", mouse.x, mouse.y)
            }
        }
    }
    
    Rectangle {
        width: 100
        height: 40
        color: "blue"
        PointerMouseArea {
            handleOnDoubleClicked: function(mouse) {
                console.log("Tell father and grandfather", mouse.x, mouse.y)
                superHandleOnDoubleClicked(mouse)
            }
        }
    }
    

    which requires the following definition of PointerMouseArea:

    BaseMouseArea {
        cursorShape: Qt.PointingHandCursor
    
        superHandleOnDoubleClicked: handleOnDoubleClicked_PointerMouseArea
    
        function handleOnDoubleClicked_PointerMouseArea(mouse, superImplementation) {
            console.log("Pointed at something") // I just add my comment and then call super
            handleOnDoubleClicked_BaseMouseArea(mouse)
        }
    }
    

    What you see in PointerMouseArea is

    1. It adds some properties over BaseMouseArea (cursor shape)
    2. It overrides the super* methods to it's concrete implementation
    3. It implements its concrete implementation and calls the parents implementation

    The foll sample project is available here: https://github.com/webmaster128/derived-qml-slots

    0 讨论(0)
提交回复
热议问题