[Development] Qt Quick Controls Dialogs -- enabled state of the standard buttons (API choices)

Sze Howe Koh szehowe.koh at gmail.com
Sat Aug 22 04:43:59 CEST 2015


In general, I think we should give higher priority to API simplicity
and flexibility, and lower priority to implementation simplicity.

Note that the QML Dialog is different from the C++ QDialog. QDialog
has no standard buttons of its own. Rather, it's up to the user to
manually add buttons, or to add a QDialogButtonBox (which might be a
better place to attach the "button proxy/delegate" described in
several of the approaches below). I think the C++ way provides better
separation of concerns, where QDialog manages the popup window itself
and reports the user's choice(s), while QDialogButtonBox manages the
buttons (something to consider for Qt Quick 3?)

On 22 August 2015 at 07:22, Vladimir Moolle <vmoolle at ics.com> wrote:
> Hi, I’d like to discuss approaches to API design in the context of https://bugreports.qt.io/browse/QTBUG-47850 (making Quick Controls dialogs’ standard buttons’ enabled properly state accessible).
> It seems, that the desired properties of an API implementing the above would be:
> possibility to declaratively manipulate enabled-ness of the buttons
> lack of risks brought by “signal races” (usually happens when imperative and declarative code is mixed)
> optionally, possibility to tweak more than just the “enabled” property of the buttons (at least the API could allow for such extension in the future).


> Below are some approaches coming to mind (after a discussion we had with colleagues):
> 1. A getter method on Dialog, more or less duplicating that of the QDialog. Pros are the API (and the patch implementing it) being trivial. Cons are necessity to call the getter only after the button is created (by otherwise declarative code) and complexities of button lifetime tracking.

I strongly dislike this. I think imperative methods should be a last
resort in QML, instead of the first thing we turn to.

(Note also that QDialog has no getters/setters for button states)

> 2. A set of applyEnabled, helpEnabled, … properties on the Dialog. Pros is being declarative and API simplicity, cons is the obvious verbosity (especially, if more properties are exposed this way later).

This seems a bit unsustainable.

> 3. An enabledButtons (disabledButtons? disabledStandardButtons? enabledStandardButtons??) property containing an ORed set of button roles (just like the standardButtons property). Pros are things being declarative, and uniform, cons -- that crafting a binding expression for more than a couple buttons may get ugly (and listening  / responding to changes through the single QML signal handler may require having a signal parameter matching the old / previous state of the ORed enum combo -- which is strange; note: something close is discussed by https://bugreports.qt.io/browse/QTBUG-40868?focusedCommentId=253265)

This could work. We have something similar in Window.flags

Is it necessary to have a signal parameter that reports the old state?
AFAIK, most stateChanged()/valueChanged() signals in Qt only report
the new state/value.

> 4. An auxiliary object can be used to make the buttons accessible declaratively:
> Dialog {
>     StandardButtonProxy {
>         id: okProxy
>         role: StandardButton.Ok
>         Binding {
>              target: okProxy.button // here it is
>              property: “enabled”
>              value: <some binding expression>
>         }
>     }
> }
> Pros of this approach would be its simplicity (on both API and implementation sides), and relative safety (the “button” property would be null when the button is not there, incl. temporarily -- i.e. during button model reorganizations, etc.), cons -- that it would be merely a means to expose standard buttons “the QML way”, not addressing the fact that exposing control instances from underneath may not be the best practice by itself (and especially in QML, where a smart user may try to save the reference, thus confusing the GC).

This is the QML way of achieving how dialog buttons are currently
managed in C++.

I currently haven't worked out if I'm for or against this.

> 5. A special ButtonState object type serving as “declarative proxy” for the button’s properties, i.e.:
> Dialog {
>     <...>
>     ButtonState {
>         role: StandardButton.Ok
>         enabled: <some binding expression>
>     }
> }
> Pros of this approach would be “proper” declarativity, and ability to easily extend the exposed property set (adding possibility to override other properties other than “enabled”), cons would be limiting further API changes in this area to being based on this one-way “reflect my properties” approach (and making some bindings track the state of the button by default would complicate things beyond reasonable).

I don't quite understand the cons you've mentioned. Could you please
explain why would it be one-way? Couldn't we do something like this?:

Dialog {
    ButtonState {
        id: okButtonState
        standardTarget: StandardButton.Ok // I think "role" isn't the
best name, as we're not assigning a role to this object
    onSomething: okButtonState.enabled = true;

Also, why would bindings be complicated?

> 6. Finally, Dialog could accept (optional) delegates for the buttons created, allowing for arbitrary customizations, i.e.:
> Dialog {
>     <...>
>     StandardButtonDelegate {       //name arguably could be better
>         role: StandardButton.Apply // could be “roles” here even
>         StandardButton {           // a Button, but with default bindings for “text”, etc.
>             enabled: <some binding expression>
>         }
>     }
>     StandardButtonDelegate {       //name arguably could be better
>         role: StandardButton.Apply // could be “roles” here even
>         Rectangle {                // a very custom “button”
>        <...>
>             signal clicked         // or a warning emitted by Dialog if absent
>             enabled: <some binding expression>
>         }
>     }
> }
> The pros of this approach would be the possibility to customize the buttons (standard or not) freely, including styling, overriding text (when / if necessary) etc., while being fully declarative.
> This way, the Dialog only does button management (i.e. platform-specific layout, creation and destruction), but the aspect of button’s behavior is completely separated (providing for a nice separation of concerns), and there’re no API bottlenecks like proxy property bags (#5 above) and so on. An obvious con would be ease of breaking layouts with geometry bindings  / anchors specified for the button (unless specifically addressed (and unset) by Dialog’s logic).

This one's obviously the most powerful, but also takes the most effort
to set up (especially if the user simply wants to disable one button).

Is it viable to combine 2 or more of the options above? e.g. a simple
API for common use cases, plus option #6 for maximum flexibility.

> Overall, approaches #5 and #6 seem to attract more sympathy among those who discussed the API decisions in question here internally. What would you guys say (most importantly, Qt Quick Controls patch reviewers, but everyone else interested as well)?


More information about the Development mailing list