QProgressDialog: problems about MinimumDuration - c++

I'm using QT 4.8.5. I met some problems on QProgressDialog with MinimumDuration.
Here is the documentation: http://doc.qt.io/qt-4.8/qprogressdialog.html#minimumDuration-prop.
1.Test with the following code. The dialog is not displayed at all. But the documentation says: "the dialog will pop up after the minimumDuration time or as soon as any progress is set".
QProgressDialog* dlg = new QProgressDialog("Test", "cancel", 0, 10);
dlg->setMinimumDuration(8000);
2.Test with the following code. The dialog is displayed in 8 seconds. But the documentation says: "the dialog will pop up after the minimumDuration time or as soon as any progress is set". Though the behavior is different with the documentation, I think the current behavior is acceptable.
QProgressDialog* dlg = new QProgressDialog("Test", "cancel", 0, 10);
dlg->setMinimumDuration(8000);
dlg->setValue(0);
3.Test with the following code. The dialog is never displayed. But the documentation says: "the dialog will pop up after the minimumDuration time or as soon as any progress is set".
QProgressDialog* dlg = new QProgressDialog("Test", "cancel", 0, 10);
dlg->setMinimumDuration(8000);
dlg->setValue(1);
4.Test with the following code. The behavior is same as item 2.
QProgressDialog* dlg = new QProgressDialog("Test", "cancel", 0, 10);
dlg->setMinimumDuration(8000);
dlg->setValue(0);
dlg->setValue(1);
5.Test with the following code. The dialog is displayed as soon as set the progress value to 1. Why does Sleep() function affect the behavior here?
QProgressDialog* dlg = new QProgressDialog("Test", "cancel", 0, 10);
dlg->setMinimumDuration(8000);
dlg->setValue(0);
::Sleep(static_cast<DWORD>(1000));
dlg->setValue(1);
6.Test with the code below. The dialog is displayed immediately, but I set MinimumDuration to 5. Is it a problem?
QProgressDialog* dialog = new QProgressDialog("Message", "Close", 1, 10);
dialog->setMinimumDuration(5000);
dialog->setValue(0);
dialog->setValue(1);
I test on Windoes 7. What are the issues? What are the correct behaviors?

Indeed the information is scattered around so it seems to make no sense. But there is a precious hint in the doc :
QProgressDialog ... estimates the time the operation will take
(based on time for steps), and only shows itself if that
estimate is beyond minimumDuration() (4 seconds by default).
The dialog seems to use the value property to approximate the time required for steps. And it is seems that value property by default is not set
value property :
For the progress dialog to work as expected, you should initially set
this property to 0 and finally set it to QProgressDialog::maximum();
Indeed, dialog->value() returns -1 in my machine after construction.
To wrap up :
Not setting value is an issue. You have to set value sometimes to make it work.
The dialog is shown as soon as it interpolates that the total amount of work is going to take more than minimumDuration
Setting a value to anything lower than QProgressDialog::minimum(), which is the case by default, causes the progressbar to stay hidden.
Your second cases set the value to 0 = minimum. After 8 seconds, you still havent updated that value. which means the processing of a single item take more than 8 seconds. Should show.
You should modify the value from 0 -> minimum -> maximum for proper behavior. Your third case, fail to do this because value goes from -1 to 1, without being set to 0 = minimum. Unspecified, in this version doesnt show.
Your 4th case means "the first processing took 0 second, the second is not done yet". So the minimumDuration behavior kicks in. Should show.
Now that there is one second duration for the first task (case 5), the dialog approximates that 10 tasks are going to take 10s, which is bigger than 8s, so the dialog is shown as soon as dlg->setValue(1); is executed.

I tested this on OS X, with Qt 5 and get the same results
Looking closer at the documentation for setValue, it states: -
For the progress dialog to work as expected, you should initially set this property to QProgressDialog::minimum() and finally set it to QProgressDialog::maximum(); you can call setValue() any number of times in-between.
With this in mind, it works as expected, as can be seen when you first set the value to zero, then another value.
QProgressDialog* dlg = new QProgressDialog("Test", "cancel", 0, 10);
dlg->setMinimumDuration(8000);
dlg->setValue(0);
dlg->setValue(1);
So, I think the documentation for setMinimumDuration should probably link to this too, but the behaviour is correct according to the documentation, when taking setValue into account.

Related

Setting a breakpoint in pycharm changes the behavior of the code

That's really a Pycharm - IDE question - the python/wx bug is fixed.
A seemingly innocuous change broke the raceText.SetLabel() call (which simply sets the text in the static text control) below. So I set a breakpoint in item = self.items[itemDex]:
def __init__(self):
# ...
self.raceText = staticText(self,u'') # see below for staticText definition
def EvtListBox(self,event):
"""Responds to listbox selection."""
itemDex = event.GetSelection()
item = self.items[itemDex] # breakpoint here
face = self.data[item]
self.nameText.SetLabel(face.pcName)
self.raceText.SetLabel(face.getRaceName()) # this
Lo and behold self.raceText.SetLabel(face.getRaceName()) now succeeded.
So how is this possible ? What does setting a breakpoint trigger ?
EDIT: some more data:
What originally broke the SetLabel() call was this commit:
-def staticText(parent,label=u'',pos=defPos,size=defSize,style=0,name=u"staticText",id=defId,):
+def staticText(parent, label=u'', pos=defPos, size=defSize, style=0,
+ noAutoResize=True, name=u"staticText"):
"""Static text element."""
- return wx.StaticText(parent,id,label,pos,size,style,name)
+ if noAutoResize: style |= wx.ST_NO_AUTORESIZE
+ return wx.StaticText(parent, defId, label, pos, size, style, name)
Flipping noAutoResize default value to False squashed the bug - the text was set but wx.ST_NO_AUTORESIZE would prevent the control to adjust its size from u'' - so no text was displayed. So this was a plain old bug.
The question remains why on earth when setting a breakpoint in the debugger self.raceText.SetLabel() shows the text ?
EDIT: do read the answer - sanity check
IIUC, I think it's possible that running in the debugger is simply hiding or confusing the real issue.
When wx.ST_NO_AUTORESIZE is not set then changes to the label will cause the widget's size to be adjusted, which will typically cause the widget to be repainted soon via an automatic paint event from the system. When wx.ST_NO_AUTORESIZE is set, then the resize doesn't happen and so that particular paint event doesn't happen and the widget may not get repainted until the next time the parent is refreshed or something else happens that triggers a paint event.
So a likely fix would be to add a call to self.fooText.Refresh() after each SetLabel. The display will still not be updated while stopped in the debugger because the paint event normally won't happen until return to the active event loop, but in a normal run it will happen soon enough that the user will not notice unless there is some long-running thing blocking the return to the event loop.

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.

Update MFC's CSliderCtrl

I am working on a C++ MFC project and bumping in the following. I have a CSliderCtrl on my form which I call MFC_scKINECTANGLE. To make it the way I want it the next piece of code is used:
MFC_scKINECTANGLE = (CSliderCtrl * ) GetDlgItem(SC_kinectAngle);
MFC_scKINECTANGLE->SetRangeMax(27);
MFC_scKINECTANGLE->SetRangeMin(-27);
MFC_scKINECTANGLE->SetPos(0);
The problem is that the slider at the start of the program is at the top of the bar whereas it should be in the middle, and when you try to grab it, it suddenly jumps to the correct position and works fine after that. How can I make sure the slider is in the middle of the bar at the start of my program?
According to MSDN CSliderCtrl::SetRangeMax (CSliderCtrl::SetRangeMin is similar):
void SetRangeMax(
int nMax,
BOOL bRedraw = FALSE
);
You need to set bRedraw parameter to TRUE to update slider.
Another (and probably better) variant - force redraw the slider after setup.
But due to bug (or feature?) in MS trackbar implementation you cannot just call CWnd::Invalidate (for deferred redraw) or even CWnd::RedrawWindow (for immediate redraw). This will have no effect.
Fortunately there are several events that force trackbar to repaint, e.g. enabling/disabling the window:
const BOOL isEnabled = MFC_scKINECTANGLE->IsWindowEnabled();
MFC_scKINECTANGLE->EnableWindow(!isEnabled);
MFC_scKINECTANGLE->EnableWindow(isEnabled);
See this discussion for details.
I was setting the range (0 - 100) and position (50) in the dialog's constructor. The slider kept showing up initially at position 0 instead. If I called GetPos() right after SetPos() it was returning 0 instead of 50.
What made it work for me was overriding OnInitDialog() and setting the range & position there instead of in the constructor.
BOOL CVolumeDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
m_VSliderBarNormal.SetRange(0, 100, TRUE);
m_VSliderBarNormal.SetPos(50);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}

Validation in QSpinBox

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.

"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.