Validation in QSpinBox - c++

I have QSpinBox which should take only odd numbers, so I've set initial value to 3 and step to 2.
QSpinBox* spinBox = new QSpinBox;
spinBox->setValue(3);
spinBox->setSingleStep(2);
When I'm using spin box arrows to modify value everything is ok. But when I input value from keyboard it can take not odd numbers to.
So is it possible to set validation which fulfills my requirements without inheriting QSpinBox and redefining its validate method?
My current solution is checking in slot if the value is odd:
void MyWidget::slotSetSpinBoxValue(int value)
{
if(value%2 != 0)
{
//call function which takes only odd values
}
else
{
//here I want to show some kind off message that value can only be odd
//call function with --value parameter
}
}
Second question is how to show some tip for QSpinBox? I would like to show tip like tool tip is shown with message that QSpinBox value should be odd. I've found statusTip property in QWidget but cant find example how to use it.

You can connect to the editingFinished signal and fix it:
void Obj::onSpinEditFinished()
{
int val = ui->spinPoints->value();
if(val % 2 == 0)
ui->spinPoints->setValue(val-1);
}

With respect to Richard's comment, I think that ctrl->setKeyboardTracking(false) would get around the checking that would otherwise happen on each keystroke, and allow the validation only to happen at the end.

I think the correct answer doesn't work perfectly. It makes you unable to input values like "12", because the value changed singal will be triggered when "1" was input and it will be corrected to "0" as 1 is an odd numer.
The fix could be using a timer to correct the values in the spinbox. E.g. we restart a timer with (500ms) timout once we received the valueChanged signal(The timer will only triggered once if you type quickly enough). And we check and correct the input in the timers timeout slot.

In python I solved this as follows using the editingFinished property of the spinbox. This only fires a signal when you hit enter or move the focus away from the box. In python I had to pass the function references or the value passed is not the updated one but the one at initialization.:
class SpinboxExample:
def __init__(self)
spinbox = QSpinBox()
spinbox.editingFinished.connect(
lambda value=spinbox.value, set_spinbox_val=spinbox.setValue:
self.value_changed_spinbox(value,set_spinbox_val, set_slider_val)
)
I then had a callback function which checked whether number was odd and if not altered the value in the spinbox to make it odd.
def value_changed_spinbox(self, get_value,set_spinbox_value):
value=get_value()
if value % 2 == 0:
value += 1
set_spinbox_value(value)
Hope that helps.

Well you can make a workaround using the valueChanged() slot:
void MainWindow::on_spinBox_valueChanged(int arg1)
{
if( arg1 % 2 == 0)
{
//for even values, show a message
QMessageBox b;
b.setText("Only odd values allowed!");
b.exec();
//and then decrease the value to make it odd
ui.spinBox->setValue( arg1 - 1 );
}
}
Now if you want to keep the old value in case the used enters an even number, you will have to either inherit from QSpinBox, or use an event filter to catch key press events, and act before the value gets changed.
To show the message when the user hovers his/her mouse over the spinbox, you will need to set the box's toolTip, which holds the string that will be shown:
UPDATE:
If you don't want a message box, you can:
use QStatusBar. It can display messages which only last for some amount of time (that you pass it). The downside of it is that the message will appear on the bar on the bottom of the window, instead of being close to the spinbox.
Place a label under the spinbox. Set the label's text to something like "only odd values are allowed" when the user enters an invalid value, and set an empty string when the user enters a good value. You could also do this dynamically:
The user inputs a wrong value
Create a QLabel with the warning text, and set Qt::WA_DeleteOnClose flag, so the label will delete itself when closed.
Create a QTimer with singleShot, and set it to fire after a couple of seconds (when you want the message to dissapear)
Connect the timer's signal to the label's close() slot. When the timer will expire, the label will be closed, and, thanks to WA_DeleteOnClose, will be deleted.

Related

C++ QT Creator create slot function for QTextEdit::verticalSlideBar?

I'm using QT Creator to create a simple Application that has two textEdit fields next to each oteher. I want both fields to be linked when it comes to scrolling so that when one field scrolls up or down, the other one will as well automatically and vice versa. For this, I need a callback function that is triggered whenever the user moves the slideBar of one of the fields. Unfortunately, when I right click the textEdit fields and press "Go to slots" I can not find an event for the movement of the slideBar.
How can I achieve this?
QTextEdit does not have a signal for when the sliderbar in it changes, since it is not a scrollbar. However QScrollBar has the sliderMoved(int value) signal which is emitted when the slider moves. QScrollBar also has a way to set its scroll value via slots (with setValue(int value))
We can therefore tie two scrollbars together using signals and slots very easily.
For example:
...
// Get easy pointers to the scrollbars
QScrollBar* textbx_slider_1 = ui->textbx1->verticalScrollBar();
QScrollBar* textbx_slider_2 = ui->textbx2->verticalScrollBar();
// Connect them too each other
connect(textbx_slider_1, &QScrollBar::sliderMoved, textbx_slider_2, &QScrollBar::setValue); // Connect the first scrollbar to the second
connect(textbx_slider_2, &QScrollBar::sliderMoved, textbx_slider_1, &QScrollBar::setValue); // Connect the second scrollbar to the first
...
(This assumes that your QTextEdit widgets have ids' of textbx1 and textbx2)
Edit:
It is worth mentioning that sliderMoved will not be emitted when using the scroll wheel on the text box. To detect those inputs you must use something like QScrollBar::valueChanged. You have to be careful with this however since setValue emits valueChanged, meaning you will get an infinite feedback loop if you simply modify the above code.
To prevent this you could use a lambda, something like this:
...
int old_pos = textbx_slider_1->value()
std::function<void(int, QScrollBar*)> f = [old_pos](int new_pos, QScrollBar* slider){
if (new_pos != old_pos) {
// Only trigger if the value has changed
slider->setValue(new_pos);
old_pos = new_pos;
};
connect(textbx_slider_1, &QScrollBar::sliderMoved, std::bind(f, std::placeholders::_1, textbx_slider_2)); // Connect the first scrollbar to the second
connect(textbx_slider_2, &QScrollBar::sliderMoved, std::bind(f, std::placeholders::_1, textbx_slider_1)); // Connect the second scrollbar to the first
...
(The weirdness with std::bind() is simply so we don't repeat virtually the same lambda twice)

Qt find out if QSpinBox was changed by user

Let's suppose I have a QSpinBox, how I can find out if the value was changed manually from user or from a other function?
EDIT: I want to do some actions only when user change values but if your program does it (setValue) I don't want do this actions.
Possible solution:
ui->spinBox->blockSignals(true);
ui->spinBox->setValue(50);
ui->spinBox->blockSignals(false);
In this case, signal will not be emitted, so all what you can catch by valueChanged() signal is only user's action.
For example:
void MainWindow::on_spinBox_valueChanged(int arg1)
{
qDebug() << "called";
}
When user change value by mouse or type by keybord, you see "called", but when you setValue with blocking signals, you don't see "called".
Another approach is to provide some bool variable and set it to true before the setValue and check this variable in slot. If it is false(user action) - do some action, if not - don't do(change bool to false). Advantages: you don't block signal. Disadvantages: maybe hard readable code, if slot calls many times, you will many time do this unnecessary checking.

Proper way to get 'infinite loading' progress bar with minimum duration delay using QProgressDialog

Problem
I am trying to use QProgressDialog to display a modal dialog window that shows an 'infinite loading' progress bar, but which also takes advantage of the minimum duration which can be specified before showing the window. I am able to do it using a bit of hacky code, but wanted to ask if there is a more correct way to do it.
Use case
A query is made to some back-end service and the results may come quickly or may take a little more time. If the results are returned 'quickly', eg < 500ms, no dialog should show (to avoid a dialog box flickering on screen). If the results take 'more time', the modal dialog should appear (allowing the user to potentially cancel the operation, etc).
Code
Initialization
m_progress = new QProgressDialog(this);
m_progress->setWindowModality(Qt::WindowModal);
m_progress->setLabelText("Looking up data...");
m_progress->setMinimumDuration(500);
connect(m_progress, SIGNAL(canceled()), this, SLOT(onProgressCanceled()));
'Look up data' button onClicked slot (sends request to back-end and shows dialog)
// HACKY CODE ----------
//
m_progress->setMaximum(100); // some value != 0 (0 is the value passed to setValue)
m_progress->setValue(0); // starts the minimum duration timer
m_progress->setMaximum(0); // set maximum to 0 to get 'infinite loading' progress bar
//
// HACKY CODE ----------
onProgressCanceled slot
m_progress->reset();
onResults method (callback that handles returned results)
m_progress->reset();
That is the entirety of the relevant code.
The reason for the hack is that in the Qt source, there is this explicit check:
(d->bar->value() == -1 && progress == d->bar->maximum())
If the maximum is set to 0 (for 'infinite loading') before the value is set to 0 (which starts the minimum duration timer), then QProgressDialog::setValue simply returns. The d->bar->value is initialized to -1 and reset to -1 when QProgressDialog::reset is called. It's almost as if this specific use case of QProgressDialog ('infinite loading' but with a minimum duration delay) is being singled out.
Question
Is there a way to achieve the same result but without the use of the hacky code that I'm missing? I feel like I'm missing something simple.

"Smart" Linked Scrollbar and Edit Controls?

I hope that I can explain my problem well enough for someone to help.
Basically, I have a horizontal scrollbar (ranged 0 to 1000) and an edit control that represents the position of the scrollbar divided by 1000, so that the user can use either the scrollbar to select a range of numbers between 0 and 1 up to a 3 decimal precision (.001, .002, ..., .987, etc.), or enter their own number in the edit box. As they scroll the scrollbar, the number in the edit control changes to reflect the new scroll position. When a new number is entered, the scrollbar sets itself to a new position reflecting the number entered. Meanwhile I also perform some calculations with this number as it changes (through either the scrollbar or the edit control) and display the results in another dialog.
Here is my problem: I'm having trouble deciding which event handlers to use to produce the proper behavior when a user enters a number into the edit control.
I'm using a double value variable called fuelMargin to handle my edit control and a CScrollBar control variable called fuelScroll to handle the scrollbar.
In my HSCROLL event I set the edit control to the scroll position / 1000. No problems there; when the user scrolls the scrollbar the edit box is correctly updated.
As for the edit box, my first attempt was an ONCHANGE event:
void MarginDlg::OnEnChangeFueledit()
{
CEdit* editBox;
editBox = (CEdit*)GetDlgItem(IDC_FUELEDIT);
CString editString;
editBox->GetWindowText(editString);
if (editString.Compare(".") != 0 && editString.Compare("0.") != 0
&& editString.Compare(".0") != 0 && editString.Compare("0.0") != 0
&& editString.Compare(".00") != 0 && editString.Compare("0.00") != 0)
{
UpdateData();
UpdateData(FALSE);
if (fuelMargin > 1)
{
UpdateData();
fuelMargin = 1;
UpdateData(FALSE);
}
if (fuelMargin < 0)
{
UpdateData();
fuelMargin = 0;
UpdateData(FALSE);
}
fuelScroll.SetScrollPos(int(fuelMargin*1000));
}
}
I needed that first if statement in there to keep from doing an UpdateData() when the user is trying to type a number like .5 or .05 or .005. It does produce a few wonky behaviors, though; when the user tries to type something like .56, after the .5 an UpdateData() is performed, the number becomes 0.5, and the cursor is moved to the far left, so if they tried to type .56 they would accidentally end up typing 60.5 -- which goes to 1, since I won't let them enter numbers lower than 0 or higher than 1. If they enter 0.56, however, this behavior is avoided.
For my second attempt, I commented out my ONCHANGE event and put in an ONKILLFOCUS event instead:
void MarginDlg::OnEnKillfocusFueledit()
{
UpdateData();
UpdateData(FALSE);
if (fuelMargin > 1)
{
UpdateData();
fuelMargin = 1;
UpdateData(FALSE);
}
if (fuelMargin < 0)
{
UpdateData();
fuelMargin = 0;
UpdateData(FALSE);
}
fuelScroll.SetScrollPos(int(fuelMargin*1000));
}
So now the user can finish typing their number and all is hunky dory--as long as they click out of the edit box. The scrollbar won't move and results won't be calculated until the box loses focus.
I want the results to be calculated as the numbers in the box are being typed; I want the scrollbar to move as the numbers are being typed. But I don't want typing to be disrupted, i.e. the actual numbers in the box changed or the cursor moved in any way.
Suggestions?
Thanks!
With the first approach, it looks like you're almost there: the only really significant problem is that the repeated calls to UpdateData() mess with the cursor position as the user is typing.
Given that you're trying to have a reasonably complex interaction between the controls, what I'd suggest is not to do validation in the OnChange() at all, so that as the user is typing he can type what he wants (which is how most numeric edit controls work anyway). When the user closes the dialog the controls are on (or clicks a button that uses the data in some way) then validation should be triggered, and a suitable error shown.
Once you're free from validating in OnChange(), you can fix the "cursor moves" problem by simply not calling UpdateData() in OnChange(). Instead, just parse the number from "editString" and, if it's in the valid range, update the scrollbar. That way, the scrollbar updates as the user types, and if they type in an invalid value the scrollbar stays put, and they'll get an error when they move to whatever the next stage is. Something like this (not tested):
void MarginDlg::OnEnChangeFueledit()
{
CString editString;
GetDlgItem(IDC_FUELEDIT)->GetWindowText(editString);
double editValue;
if ((sscanf(editString,"%lf",&editValue) == 1)
{
if (editValue >= 0.0) && (editValue <= 1.0))
fuelScroll.SetScrollPos(int(editValue*1000));
}
}
The only remaining important problem to note is that, if the user types some invalid value, or a number out of the valid range, then the edit control and the scrollbar will be out of sync. The simplest way to deal with that is to just decide that the edit control is the "master" value: that is, when we want to know what the user entered, we always look at the edit control, not the scrollbar, and validate data.
As for your second approach, one possible solution might be to implement a timer message handler: in the timer handler you could say the equivalent of "if the user hasn't typed anything for a second, I'll assume they're done, and parse the number and update the scrollbar". I'm not so keen on that as a solution, though.
I'd suggest watching for the Enter key, and performing UpdateData() then as well as OnKillFocus and OnChange. The more user-friendly the better.
Next, make sure your UpdateData() routine only works in the direction you need:
If ".5" is entered in the edit control, run your UpdateData() routine when the OnChange event is raised, but make sure to update your scrollbar only. Don't worry about updating the edit control to "0.5" until OnKillFocus is raised. Any updates in the reverse direction (to the edit control) will mess with your cursor. If your edit control is somehow bound to this double variable and auto updates when the var changes, consider leaving the double and your edit control seperate from each other until the OnKillFocus event is raised.
The same concept applies in the other direction as well. When the user scrolls, don't mess with the scrollbar. Just update the edit control and leave it at that.
I should add that XAML's data-binding features really help in situations like this, if you know how to use them properly. It's unfortunate for us native-type developers that it's so difficult to implement similar functionality using only event handlers.

Programmatically change combobox

I need to update a combobox with a new value so it changes the reflected text in it. The cleanest way to do this is after the comboboxhas been initialised and with a message.
So I am trying to craft a postmessage to the hwnd that contains the combobox.
So if I want to send a message to it, changing the currently selected item to the nth item, what would the postmessage look like?
I am guessing that it would involve ON_CBN_SELCHANGE, but I can't get it to work right.
You want ComboBox_SetCurSel:
ComboBox_SetCurSel(hWndCombo, n);
or if it's an MFC CComboBox control you can probably do:
m_combo.SetCurSel(2);
I would imagine if you're doing it manually you would also want SendMessage rather than PostMessage. CBN_SELCHANGE is the notification that the control sends back to you when the selection is changed.
Finally, you might want to add the c++ tag to this question.
A concise version:
const int index = 0;
m_comboBox.PostMessage(CBN_SELCHANGE, index);
What might be going wrong is the selection is being changed inside the selection change message handler, which result in another selection change message.
One way to get around this unwanted feedback loop is to add a sentinel to the select change message handler as shown below:
void onSelectChangeHandler(HWND hwnd)
{
static bool fInsideSelectChange = 0;
//-- ignore the change message if this function generated it
if (fInsideSelectChange == 0)
{
//-- turn on the sentinel
fInsideSelectChange = 1;
//-- make the selection changes as required
.....
//-- we are done so turn off the sentinel
fInsideSelectChange = 0;
}
}
if you fx want to change the title - which is the line shown when combobox is closed, then you can do following:
m_ComboBox.DeleteString(0); // first delete previous if any, 0 = visual string
m_ComboBox.AddString(_T("Hello there"));
put this in fx. in OnCloseupCombo - when event close a dropdownbox fires
ON_CBN_CLOSEUP(IDC_COMBO1, OnCloseupCombo)
This change is a new string not a selection of already assigned combobox elements