Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickbuttongroup.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/private/qobject_p.h>
7#include <QtCore/qmetaobject.h>
8#include <QtCore/qvariant.h>
9#include <QtQml/qqmlinfo.h>
10
12
14
15/*!
16 \qmltype ButtonGroup
17 \inherits QtObject
18//! \nativetype QQuickButtonGroup
19 \inqmlmodule QtQuick.Controls
20 \since 5.7
21 \ingroup utilities
22 \brief Mutually-exclusive group of checkable buttons.
23
24 ButtonGroup is a non-visual, mutually exclusive group of buttons.
25 It is used with controls such as RadioButton, where only one of the options
26 can be selected at a time.
27
28 The most straight-forward way to use ButtonGroup is to assign
29 a list of buttons. For example, the list of children of a
30 \l{Item Positioners}{positioner} or a \l{Qt Quick Layouts}{layout}
31 that manages a group of mutually exclusive buttons.
32
33 \code
34 ButtonGroup {
35 buttons: column.children
36 }
37
38 Column {
39 id: column
40
41 RadioButton {
42 checked: true
43 text: qsTr("DAB")
44 }
45
46 RadioButton {
47 text: qsTr("FM")
48 }
49
50 RadioButton {
51 text: qsTr("AM")
52 }
53 }
54 \endcode
55
56 Mutually exclusive buttons do not always share the same parent item,
57 or the parent layout may sometimes contain items that should not be
58 included in the button group. Such cases are best handled using
59 the \l group attached property.
60
61 \code
62 ButtonGroup { id: radioGroup }
63
64 Column {
65 Label {
66 text: qsTr("Radio:")
67 }
68
69 RadioButton {
70 checked: true
71 text: qsTr("DAB")
72 ButtonGroup.group: radioGroup
73 }
74
75 RadioButton {
76 text: qsTr("FM")
77 ButtonGroup.group: radioGroup
78 }
79
80 RadioButton {
81 text: qsTr("AM")
82 ButtonGroup.group: radioGroup
83 }
84 }
85 \endcode
86
87 Another option is to filter the list of children. This is especially handy
88 if you're using a repeater to populate it, since the repeater will also be
89 a child of the parent layout:
90
91 \code
92 ButtonGroup {
93 buttons: column.children.filter((child) => child !== repeater)
94 }
95
96 Column {
97 id: column
98
99 Repeater {
100 id: repeater
101 model: [ qsTr("DAB"), qsTr("AM"), qsTr("FM") ]
102 RadioButton {
103 required property string modelData
104 text: modelData
105 }
106 }
107 }
108 \endcode
109
110 More advanced use cases can be handled using the \c addButton() and
111 \c removeButton() methods.
112
113 \sa RadioButton, {Button Controls}
114*/
115
116/*!
117 \qmlsignal QtQuick.Controls::ButtonGroup::clicked(AbstractButton button)
118 \since QtQuick.Controls 2.1 (Qt 5.8)
119
120 This signal is emitted when a \a button in the group has been clicked.
121
122 This signal is convenient for implementing a common signal handler for
123 all buttons in the same group.
124
125 \code
126 ButtonGroup {
127 buttons: column.children
128 onClicked: button => {
129 console.log("clicked:", button.text)
130 }
131 }
132
133 Column {
134 id: column
135 Button { text: "First" }
136 Button { text: "Second" }
137 Button { text: "Third" }
138 }
139 \endcode
140
141 \sa AbstractButton::clicked()
142*/
143
145{
146public:
147 Q_DECLARE_PUBLIC(QQuickButtonGroup)
148
149 void clear();
153 void setCheckState(Qt::CheckState state);
154
155 static void buttons_append(QQmlListProperty<QQuickAbstractButton> *prop, QQuickAbstractButton *obj);
156 static qsizetype buttons_count(QQmlListProperty<QQuickAbstractButton> *prop);
157 static QQuickAbstractButton *buttons_at(QQmlListProperty<QQuickAbstractButton> *prop, qsizetype index);
158 static void buttons_clear(QQmlListProperty<QQuickAbstractButton> *prop);
159
160 bool complete = true;
161 bool exclusive = true;
162 bool settingCheckState = false;
166};
167
169{
170 for (QQuickAbstractButton *button : std::as_const(buttons)) {
171 auto *attached = qobject_cast<QQuickButtonGroupAttached *>(
172 qmlAttachedPropertiesObject<QQuickButtonGroup>(button, false));
173 if (attached) {
174 attached->setGroup(nullptr);
175 } else {
176 QQuickAbstractButtonPrivate::get(button)->group = nullptr;
177 QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, this, &QQuickButtonGroupPrivate::buttonClicked);
178 QObjectPrivate::disconnect(button, &QQuickAbstractButton::checkedChanged, this, &QQuickButtonGroupPrivate::_q_updateCurrent);
179 }
180 }
181 buttons.clear();
182}
183
185{
186 Q_Q(QQuickButtonGroup);
187 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(q->sender());
188 if (button)
189 emit q->clicked(button);
190}
191
193{
194 Q_Q(QQuickButtonGroup);
195 if (exclusive) {
196 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(q->sender());
197 if (button && button->isChecked())
198 q->setCheckedButton(button);
199 else if (!buttons.contains(checkedButton))
200 q->setCheckedButton(nullptr);
201 }
203}
204
206{
208 return;
209
210 bool anyChecked = false;
211 bool allChecked = !buttons.isEmpty();
212 for (QQuickAbstractButton *button : std::as_const(buttons)) {
213 const bool isChecked = button->isChecked();
214 anyChecked |= isChecked;
215 allChecked &= isChecked;
216 }
217 setCheckState(Qt::CheckState(anyChecked + allChecked));
218}
219
220void QQuickButtonGroupPrivate::setCheckState(Qt::CheckState state)
221{
222 Q_Q(QQuickButtonGroup);
223 if (checkState == state)
224 return;
225
226 checkState = state;
227 emit q->checkStateChanged();
228}
229
230void QQuickButtonGroupPrivate::buttons_append(QQmlListProperty<QQuickAbstractButton> *prop, QQuickAbstractButton *obj)
231{
232 QQuickButtonGroup *q = static_cast<QQuickButtonGroup *>(prop->object);
233 q->addButton(obj);
234}
235
236qsizetype QQuickButtonGroupPrivate::buttons_count(QQmlListProperty<QQuickAbstractButton> *prop)
237{
238 QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data);
239 return p->buttons.size();
240}
241
242QQuickAbstractButton *QQuickButtonGroupPrivate::buttons_at(QQmlListProperty<QQuickAbstractButton> *prop, qsizetype index)
243{
244 QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data);
245 return p->buttons.value(index);
246}
247
248void QQuickButtonGroupPrivate::buttons_clear(QQmlListProperty<QQuickAbstractButton> *prop)
249{
250 QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data);
251 if (!p->buttons.isEmpty()) {
252 p->clear();
253 QQuickButtonGroup *q = static_cast<QQuickButtonGroup *>(prop->object);
254 // QTBUG-52358: don't clear the checked button immediately
255 QMetaObject::invokeMethod(q, "_q_updateCurrent", Qt::QueuedConnection);
256 emit q->buttonsChanged();
257 }
258}
259
260QQuickButtonGroup::QQuickButtonGroup(QObject *parent)
261 : QObject(*(new QQuickButtonGroupPrivate), parent)
262{
263}
264
265QQuickButtonGroup::~QQuickButtonGroup()
266{
267 Q_D(QQuickButtonGroup);
268 d->clear();
269}
270
271QQuickButtonGroupAttached *QQuickButtonGroup::qmlAttachedProperties(QObject *object)
272{
273 return new QQuickButtonGroupAttached(object);
274}
275
276/*!
277 \qmlproperty AbstractButton QtQuick.Controls::ButtonGroup::checkedButton
278
279 This property holds the currently selected button in an exclusive group,
280 or \c null if there is none or the group is non-exclusive.
281
282 By default, it is the first checked button added to an exclusive button group.
283
284 \sa exclusive
285*/
286QQuickAbstractButton *QQuickButtonGroup::checkedButton() const
287{
288 Q_D(const QQuickButtonGroup);
289 return d->checkedButton;
290}
291
292void QQuickButtonGroup::setCheckedButton(QQuickAbstractButton *checkedButton)
293{
294 Q_D(QQuickButtonGroup);
295 if (d->checkedButton == checkedButton)
296 return;
297
298 if (d->checkedButton)
299 d->checkedButton->setChecked(false);
300 d->checkedButton = checkedButton;
301 if (checkedButton)
302 checkedButton->setChecked(true);
303 emit checkedButtonChanged();
304}
305
306/*!
307 \qmlproperty list<AbstractButton> QtQuick.Controls::ButtonGroup::buttons
308
309 This property holds the list of buttons.
310
311 \code
312 ButtonGroup {
313 buttons: column.children
314 }
315
316 Column {
317 id: column
318
319 RadioButton {
320 checked: true
321 text: qsTr("Option A")
322 }
323
324 RadioButton {
325 text: qsTr("Option B")
326 }
327 }
328 \endcode
329
330 \sa group
331*/
332QQmlListProperty<QQuickAbstractButton> QQuickButtonGroup::buttons()
333{
334 Q_D(QQuickButtonGroup);
335 return QQmlListProperty<QQuickAbstractButton>(this, d,
336 QQuickButtonGroupPrivate::buttons_append,
337 QQuickButtonGroupPrivate::buttons_count,
338 QQuickButtonGroupPrivate::buttons_at,
339 QQuickButtonGroupPrivate::buttons_clear);
340}
341
342/*!
343 \since QtQuick.Controls 2.3 (Qt 5.10)
344 \qmlproperty bool QtQuick.Controls::ButtonGroup::exclusive
345
346 This property holds whether the button group is exclusive. The default value is \c true.
347
348 If this property is \c true, then only one button in the group can be checked at any given time.
349 The user can click on any button to check it, and that button will replace the existing one as
350 the checked button in the group.
351
352 In an exclusive group, the user cannot uncheck the currently checked button by clicking on it;
353 instead, another button in the group must be clicked to set the new checked button for that group.
354
355 In a non-exclusive group, checking and unchecking buttons does not affect the other buttons in
356 the group. Furthermore, the value of the \l checkedButton property is \c null.
357*/
358bool QQuickButtonGroup::isExclusive() const
359{
360 Q_D(const QQuickButtonGroup);
361 return d->exclusive;
362}
363
364void QQuickButtonGroup::setExclusive(bool exclusive)
365{
366 Q_D(QQuickButtonGroup);
367 if (d->exclusive == exclusive)
368 return;
369
370 d->exclusive = exclusive;
371 emit exclusiveChanged();
372}
373
374/*!
375 \since QtQuick.Controls 2.4 (Qt 5.11)
376 \qmlproperty enumeration QtQuick.Controls::ButtonGroup::checkState
377
378 This property holds the combined check state of the button group.
379
380 Available states:
381 \value Qt.Unchecked None of the buttons are checked.
382 \value Qt.PartiallyChecked Some of the buttons are checked.
383 \value Qt.Checked All of the buttons are checked.
384
385 Setting the check state of a non-exclusive button group to \c Qt.Unchecked
386 or \c Qt.Checked unchecks or checks all buttons in the group, respectively.
387 \c Qt.PartiallyChecked is ignored.
388
389 Setting the check state of an exclusive button group to \c Qt.Unchecked
390 unchecks the \l checkedButton. \c Qt.Checked and \c Qt.PartiallyChecked
391 are ignored.
392*/
393Qt::CheckState QQuickButtonGroup::checkState() const
394{
395 Q_D(const QQuickButtonGroup);
396 return d->checkState;
397}
398
399void QQuickButtonGroup::setCheckState(Qt::CheckState state)
400{
401 Q_D(QQuickButtonGroup);
402 if (d->checkState == state || state == Qt::PartiallyChecked)
403 return;
404
405 d->settingCheckState = true;
406 if (d->exclusive) {
407 if (d->checkedButton && state == Qt::Unchecked)
408 setCheckedButton(nullptr);
409 } else {
410 for (QQuickAbstractButton *button : std::as_const(d->buttons))
411 button->setChecked(state == Qt::Checked);
412 }
413 d->settingCheckState = false;
414 d->setCheckState(state);
415}
416
417/*!
418 \qmlmethod void QtQuick.Controls::ButtonGroup::addButton(AbstractButton button)
419
420 Adds a \a button to the button group.
421
422 \note Manually adding objects to a button group is typically unnecessary.
423 The \l buttons property and the \l group attached property provide a
424 convenient and declarative syntax.
425
426 \sa buttons, group
427*/
428void QQuickButtonGroup::addButton(QQuickAbstractButton *button)
429{
430 Q_D(QQuickButtonGroup);
431 if (!button || d->buttons.contains(button))
432 return;
433
434 QQuickAbstractButtonPrivate::get(button)->group = this;
435 QObjectPrivate::connect(button, &QQuickAbstractButton::clicked, d, &QQuickButtonGroupPrivate::buttonClicked);
436 QObjectPrivate::connect(button, &QQuickAbstractButton::checkedChanged, d, &QQuickButtonGroupPrivate::_q_updateCurrent);
437
438 if (d->exclusive && button->isChecked())
439 setCheckedButton(button);
440
441 d->buttons.append(button);
442 d->updateCheckState();
443 emit buttonsChanged();
444}
445
446/*!
447 \qmlmethod void QtQuick.Controls::ButtonGroup::removeButton(AbstractButton button)
448
449 Removes a \a button from the button group.
450
451 \note Manually removing objects from a button group is typically unnecessary.
452 The \l buttons property and the \l group attached property provide a
453 convenient and declarative syntax.
454
455 \sa buttons, group
456*/
457void QQuickButtonGroup::removeButton(QQuickAbstractButton *button)
458{
459 Q_D(QQuickButtonGroup);
460 if (!button || !d->buttons.contains(button))
461 return;
462
463 QQuickAbstractButtonPrivate::get(button)->group = nullptr;
464 QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, d, &QQuickButtonGroupPrivate::buttonClicked);
465 QObjectPrivate::disconnect(button, &QQuickAbstractButton::checkedChanged, d, &QQuickButtonGroupPrivate::_q_updateCurrent);
466
467 if (d->checkedButton == button)
468 setCheckedButton(nullptr);
469
470 d->buttons.removeOne(button);
471 d->updateCheckState();
472 emit buttonsChanged();
473}
474
475void QQuickButtonGroup::classBegin()
476{
477 Q_D(QQuickButtonGroup);
478 d->complete = false;
479}
480
481void QQuickButtonGroup::componentComplete()
482{
483 Q_D(QQuickButtonGroup);
484 d->complete = true;
485 if (!d->buttons.isEmpty())
486 d->updateCheckState();
487}
488
490{
491public:
492 QQuickButtonGroup *group = nullptr;
493};
494
495QQuickButtonGroupAttached::QQuickButtonGroupAttached(QObject *parent)
496 : QObject(*(new QQuickButtonGroupAttachedPrivate), parent)
497{
498}
499
500/*!
501 \qmlattachedproperty ButtonGroup QtQuick.Controls::ButtonGroup::group
502
503 This property attaches a button to a button group.
504
505 \code
506 ButtonGroup { id: group }
507
508 RadioButton {
509 checked: true
510 text: qsTr("Option A")
511 ButtonGroup.group: group
512 }
513
514 RadioButton {
515 text: qsTr("Option B")
516 ButtonGroup.group: group
517 }
518 \endcode
519
520 \sa buttons
521*/
522QQuickButtonGroup *QQuickButtonGroupAttached::group() const
523{
524 Q_D(const QQuickButtonGroupAttached);
525 return d->group;
526}
527
528void QQuickButtonGroupAttached::setGroup(QQuickButtonGroup *group)
529{
530 Q_D(QQuickButtonGroupAttached);
531 if (d->group == group)
532 return;
533
534 auto *button = qobject_cast<QQuickAbstractButton *>(parent());
535 if (d->group)
536 d->group->removeButton(button);
537 d->group = group;
538 if (group)
539 group->addButton(button);
540 emit groupChanged();
541}
542
543QT_END_NAMESPACE
544
545#include "moc_qquickbuttongroup_p.cpp"
Mutually-exclusive group of checkable buttons.
static QQuickAbstractButton * buttons_at(QQmlListProperty< QQuickAbstractButton > *prop, qsizetype index)
void setCheckState(Qt::CheckState state)
QList< QQuickAbstractButton * > buttons
static qsizetype buttons_count(QQmlListProperty< QQuickAbstractButton > *prop)
QPointer< QQuickAbstractButton > checkedButton
static void buttons_clear(QQmlListProperty< QQuickAbstractButton > *prop)
static void buttons_append(QQmlListProperty< QQuickAbstractButton > *prop, QQuickAbstractButton *obj)
Combined button and popup list for selecting options.