How to limit the number of QCheckboxes checked at the same time? - qt

I'm in the process of creating a QT application which is using multiple (14) qcheckboxes. I need to have a limit (preferably set as a variable that i can change) to the number of checkboxes that can be checked at the same time, is there any way to achieve this cleanly ? Thanks for your time.

There is no simple way of doing this, you have to write your code to do it.
I suppose you have the checkboxes in some parent widget class. So I would create a slot which looks like this.
void SomeParentWidget::onCheckBoxToggled(bool value)
{
// when we unchecked the checkbox,
// we do not need to count the number of checked ones
if (!value)
return;
int total = 0;
int limit = 15; // your "magic" number of maximum checked checkboxes
for (auto chb : allCheckBoxes()) // allCheckBoxes() is some method which returns all the checkboxes in consideration
{
if (chb->isChecked())
{
++total;
if (total > limit)
{
// too many checkboxes checked! uncheck the sender checkbox
// Note: you may want to add some nullptr checks or asserts to the following line for better robustness of your code.
qobject_cast<QCheckBox*>(sender())->setChecked(false);
return;
}
}
}
}
And when creating each of your checkboxes inside some parent widget, connect this slot to their signal:
auto chb = new QCheckBox();
connect(chb, &QCheckBox::toggled, this, &SomeParentWidget::onCheckBoxToggled);
Implementation of allCheckBoxes() is up to you, I do not know how you can retrieve the collection of all your check boxes. Depends on your design.

I found another, even simpler solution. Use this slot.
void SomeParentWidget::onCheckBoxToggled(bool value)
{
static int totalChecked = 0; // static! the value is remembered for next invocation
totalChecked += value ? 1 : -1;
Q_ASSERT(totalChecked >= 0);
int maxChecked = 15; // any number you like
if (value && totalChecked > maxChecked)
{
qobject_cast<QCheckBox*>(sender())->setChecked(false);
}
}
... and connect it to checkboxes' toggled() signal. Note that in order to work correctly, all check boxes must be unchecked at the time when you make the signal-slot connection because this function starts counting from zero (0 is the initial value of the static variable).

You can store all your checkboxes in a map (either in an std::map, an std::unordered_map or an QMap). Your keys will be your checkboxes, and your values will be their states, so something like this:
std::unordered_map<QCheckBox*, bool> m_checkBoxStates;
Here's what your connected to your toggled signal of all your checkboxes look like (keep in mind that all the signals will be connected to the same slot):
void MainWindow::onToggled(bool checked) {
QCheckBox* checkBox = sender(); //the checkbox that has been toggled
m_checkBoxStates[checkBox] = checked;
if (!checked) {
return;
}
const int count = std::count_if(m_checkBoxStates.begin(), m_checkBoxStates.end(),
[](const auto pair) {
return pair.second == true;
});
if (count > maxCount) {
checkBox->setChecked(false);
}
}

Related

After I declare an object from a class, and try to set a variable to that object inly, why does it say that it does not declare a type?

I am writing code for a school project that will be used for a Chromebook charging station with security. The problem I am having now is when I am detecting if a Chromebook is actually in the slot after the user has been assigned one, I am using a rocker switch to simulate this but when I am declaring the pin to the rocker, the arduino verfier comes up with that
"'slot1' does not name a type".
Code is below:
//class
class Chromebook_slot {
public:
String Name = "";
String RFID_tag = "";
int rocker = 0;
boolean chromebook_in = false;
//class function to check if chromebook is in.
//if not, redirect already to reassigning so chromebook slot is entered as open and free.
void set_if_in()
{
int momen_1_state = digitalRead(momen_1);
int momen_2_state = digitalRead(momen_2);
// the button has been pushed down and the previous process has been completed
// eg. servos would have been reset if there was a previous user
if (momen_1_state == HIGH || momen_2_state == HIGH)
{
chromebook_in = digitalRead(this->rocker);
if (chromebook_in == 0)
{
re_assigning();
}
else
{
return;
}
}
}
};
//this is now outside the class..
//class declarations
Chromebook_slot slot1;
Chromebook_slot slot2;
//variables for rocker switches which will act for detecting chromebooks.
// in my final version, this will replaced by a photoresistor and laser.
slot1.rocker = 3;
slot2.rocker = 2;
Where the function re_assigning() is a separate function declared further in the code and just resets the slot as open for future use.
slot1.rocker = 3;
slot2.rocker = 2;
These are statements that cannot be at the top level of a C++ (or .ino) file. They need to be inside of a function. What's happening is the compiler is looking looking at the slot1 identifier through the lens of potential valid constructions. It sees an identifier, and about the only thing that could legally exist at this point in the code that starts with an identifier like that is some declaration, e.g. int a = 7;, or more abstractly some_type some_more_stuff. So it expects slot1 to be a type, which it isn't, hence the message.
If you want an assignment like those to happen early on in an Arduino program, the simplest thing you could do is put them in setup():
void setup() {
slot1.rocker = 3;
slot2.rocker = 2;
// ...
}
Or, you'd make these part of the Chromebook_slot's constructor, such that they could be given in slot1 and slot2's declaration:
class Chromebook_slot {
public:
Chromebook_slot(int rocker_init_value) {
rocker = rocker_init_value;
}
// ...
Or in a maybe less familiar but more proper form, using the constructor's initialization list:
class Chromebook_slot {
public:
Chromebook_slot(int rocker_init_value)
: rocker(rocker_init_value) {}
// ...
Once you have a constructor for Chromebook_slot, your variables can become:
Chromebook_slot slot1(3);
Chromebook_slot slot2(2);

How to pin a tab in Qt

Is it possible to pin a tab with Qt?
I want a tab to always stay in place (index 0) while still able to move other tabs.
So far I tried to listen to QTabBar::tabMoved and revert the move but that's too late. I don't want it even to attempt to move.
Worst case for me would be to be forced to change the mouse handling. Let me know please if there is an other way.
I have never found a nice way to do that. But, I used the fact that you can store raw data in the QTabBar to pin the tabs and undo a move if it was a pinned tab. It's not perfect and I still have some ugly behavior, but I didn't want to use mouse events, neither.
First, create a struct to store the current state of a tab:
struct PinnedTab
{
bool isPinned;
int currentIndex;
};
Q_DECLARE_METATYPE(PinnedTab); // For QVariant
Then, create a custom QTabBar to handle the move and use QTabWidget to replace the current tab bar (you have to do that before inserting the tabs):
class Bar: public QTabBar
{
public:
void pin(int const index)
{
PinnedTab info;
info.isPinned = true;
info.currentIndex = index; // TODO: move the tab to the left and do not use current index
setTabData(index, QVariant::fromValue(info));
}
Bar(QWidget* parent=nullptr): QTabBar(parent)
{}
virtual void tabLayoutChange() override
{
for (int i = 0; i != count(); ++i) // Check if a pinned tab has moved
{
if (tabData(i).isValid())
{
PinnedTab const info = tabData(i).value<PinnedTab>();
if (info.isPinned == true && i != info.currentIndex) {
rollbackLayout();
return;
}
}
}
for (int i = 0; i != count(); ++i)
{
if (tabData(i).isValid())
{
PinnedTab info = tabData(i).value<PinnedTab>();
info.currentIndex = i;
setTabData(i, QVariant::fromValue(info));
}
else
{
PinnedTab info;
info.isPinned = false;
info.currentIndex = i;
setTabData(i, QVariant::fromValue(info));
}
}
}
void rollbackLayout() {
for (int i = 0; i != count(); ++i)
{
if (tabData(i).isValid())
{
PinnedTab const info = tabData(i).value<PinnedTab>();
if (i != info.currentIndex) {
moveTab(i, info.currentIndex);
}
}
}
}
};
tabLayoutChange is called when the layout has changed. So, it will be called when you move a tab.
the rollbackLayout method is used to move each tab to the last position stored in the tab data.
Call pin to pin a tab with the given index.
I simplified my code for more clarity and you may have to redefine some behavior (for now, if you pin a tab, it will keep its current position and it will not handle the insert/remove tabs).

How to store button value by pushing only once

I am trying to make a program that stores my button digital input so that I don't have to keep holding the button for it to work.
The algorithm I am trying to develop is when the button is pressed, it it executes the servoMomvement() function then increments count by one. If count is even then program runs, but then if the button is pressed again it will not be even and would stop working.
void loop() {
while(true){
int count = 0;
bool isEven = count%2;
bool condition = digitalRead(4);
if(condition == true && isEven == false){
servoMovement();
count++;
}
}
}
It is not working as intended. I still have to hold to push button for it to not stop executing.
You can use timer to incremenet count by one.This timer function check periodically button. And when you press it will work as you want. But first of all you have to know timers ability and capacity.
The problem is that the loop method is executed in a loop (like one could guess by the name of the method...) and therefore it's checked in every loop whether the button is pressed.
Because in your if statement it says condition == true && ... this will only be true if the condition is true (the button is pressed).
If I understood your question correctly you want to have some kind of start-/stop-button. If so you could try like this:
//global variable run
bool run;
void setup() {
run = false;
}
void loop() {
while(true){
bool condition = digitalRead(4);
if (condition) {
run = !run;//switch the state of the run variable
delay(50);//some delay to debounce the button; see https://www.brainy-bits.com/arduino-switch-debounce/ for more information
}
if(run){
servoMovement();
}
}
}
I have a quite simple solution for that.
You can compare the actual buttonstate with the buttonstate the "round" before.
bool Button = false;
bool ButtonBefore = false;
bool help = false;
void loop()
{
Button = digitalRead(ButtonPin);
if(Button > ButtonBefore) help = !help;
if(help) { do stuff; }
ButtonBefore = Button;
}
When you press the button, "Button" becomes true while "ButtonBefore" is still false. So "Button" is bigger than "ButtonBefore" so "help" changes to true. In the next cycle "ButtonBefore" is even to "Button" so "help" won't change its state. When the button is released, "ButtonBefore" is bigger than "Button" so it "help" won't change too. So the state from "help" is changed when the button is pressed.
I hope I could help you with this.

How to draw dash instead of null in QSpinBox?

I need to draw dash instead of null in QSpinBox. Also I need to make dash key pressing equalling null key pressing.
How can I do this?
You can use setSpecialValueText();
QSpinBox spinBox;
spinBox->setSpecialValueText(tr("-"));
You can then check if the special value is selected by connecting valueChanged(QString) function. Note that this is different from valueChanged(int) You can then check the value of the passed string in a slot, and if it is equal to special text, you can do something.
main()
{
connect(spinBox, SIGNAL(valueChanged(QString)), this, SLOT(doSomething(QString)));
}
void doSomething(QString valueStr)
{
if(valueStr == spinBox->specialValueText())
// Do something
else
//Convert valueStr to int and do other stuff
}
Or you could do something like this:
main()
{
connect(spinBox, SIGNAL(valueChanged()), this, SLOT(doSomething()));
}
void doSomething()
{
if(spinBox->value() == 0)
// Do something with dash
else
//Do something with the value
}
For your other question, you need to create a keyPressEvent and check if pressed key is dash or not. If it's dash you can call another function to do something. Edit: BTW, the index of specialValueText() is 0.
Edit: Or you can create a QShortcut in your main function.
new QShortcut(QKeySequence(Qt::Key_Minus), this, SLOT(doSomething()));
Edit continued: doSomething() is a slot function. Put, for example void doSomething(); in the private slots: section of your header file. And in the cpp file define a function similar to this:
void MainWindow::doSomething()
{
ui->spinBox->setValue(0);
//This is the slot called when you press dash.
}
Edit still continued:
You need to declare a protected: function in the header like this:
virtual void keyPressEvent(QKeyEvent *event);
Then you need to define this function in your cpp file. Like this:
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if(event->key() == Qt::Key_Minus)
ui->spinBox->setValue(0);
}
You don't have to connect any signals or slots for this function. It's an event.
That means when dash is pressed ui->spinBox->setValue(0);
Because of that, you need to create a spinBox with a range starting from 0.
spinBox->setRange(0, 100);
That means,
if(spinBox->value() == 0)
//Then specialValueText is selected.

HowTo find Subitem in QAbstractItemModel and QTreeView class?

Question: how to find sub item, in a QTreeView loaded QAbstractItemModel model with model->match() method?
Problem: model->match() can't find sub items, wtf?!
Here is the example:
As you can see from the picture, I'm trying to expand Layouts sub item with this code:
void Dialog::restoreState(void)
{
// get list
QSettings settings("settings.ini", QSettings::IniFormat);
settings.beginGroup("MainWindow");
QStringList List = settings.value("ExpandedItems").toStringList();
settings.endGroup();
foreach (QString item, List)
{
if (item.contains('|'))
item = item.split('|').last();
// search `item` text in model
QModelIndexList Items = model->match(model->index(0, 0), Qt::DisplayRole, QVariant::fromValue(item));
if (!Items.isEmpty())
{
// Information: with this code, expands ONLY first level in QTreeView
view->setExpanded(Items.first(), true);
}
}
}
Where settings.ini file contains:
[MainWindow]
ExpandedItems=Using Containers, Connection Editing Mode, Form Editing Mode, Form Editing Mode|Layouts
PS: root items successfully expands on start!
Here is the solution:
QModelIndexList Items = model->match(
model->index(0, 0),
Qt::DisplayRole,
QVariant::fromValue(item),
2, // look *
Qt::MatchRecursive); // look *
* Without that argument match() function searches only 1 level
My working example on QTreeView.
QModelIndexList Indexes = this->ui->treeView->selectionModel()->selectedIndexes();
if(Indexes.count() > 0)
{
QStandardItemModel *am = (QStandardItemModel*)this->ui->treeView->model();
QStack<QModelIndex> mis;
QModelIndex mi = Indexes.at(0);
while(mi.isValid())
{
mis.push(mi);
mi = mi.parent();
}
QStandardItem *si;
bool FirstTime = true;
while (!mis.isEmpty())
{
mi = mis.pop();
if(FirstTime)
{
FirstTime = false;
si = am->item(mi.row());
}
else
{
si = si->child(mi.row());
}
}
// "si" - is selected item
}
Wanted to add to the answer that #mosg gave
The forth parameter is actually the hits parameters.
It decides ho many matches one wants to return.
For all matches specify -1 as can be seen
here:
QModelIndexList Items = model->match(
model->index(0, 0),
Qt::DisplayRole,
QVariant::fromValue(item),
-1, // any number of hits
Qt::MatchRecursive); // look *

Resources