Qt5 C++: Custom Spinbox accepting two values - c++

I'm not really into qt but i would like to have spinbox accepting two values
i.e:
It should work like this: select value to change with mouse f.e second value click arrow button and change this value.
Is there any possibility to do that, not creating a new own custom widget?

Short answer is: no
Long answer is:
You should subclass QAbstractSpinBox and implement these two virtual methods:
virtual void stepBy(int steps) override;
virtual StepEnabled stepEnabled() const override;
Keep in mind that you will need to provide your own data store and data manipulation!
The first function determines what happens when the step in either direction is requested. The negative number for steps (that tells how many steps to go in either direction) means go down and positive means up (i.e. clicking on the arrows of the SpinBox). QSpinBox adds the value in steps to its value (so when negative it gets subtracted) for example. Here you can also catch what part of the SpiBox's string the user selected and increment that appropriately with use of
lineEdit()->selectionStart();
lineEdit()->selectedText();
and when you are done you set the correct text back with:
lineEdit()->setText(myModifedValueText); //note that your internally stored value does not need to be QString, you just need to create it from your value in this method to set it to the internal QLineEdit so it can be displayed
The second method is called when the SpinBox needs to know if it can go up or down. So basically here you check the boundaries (if there are any) and return the appropriate flags (QAbstractSpinBox::StepUpEnabled or QAbstractSpinBox::StepDownEnabled or both).
Optinally in the constructor of your SpinBox you can apply QValidator to its internal QLineEdit to accept only certain format of values when the user inputs them by hand, e.g.:
QRegExpValidator *validator = new QRegExpValidator(this);
validator->setRegExp(...); //create a RegExp for your value, you may use any Online regexp validator/creator for this to get the right one
lineEdit()->setValidator(validator);
Finally you can fine-tune your SpinBox to show a text when there is an invalid value or you can fix it yourself using QAbstractSpinBox::fixup and validate the input with the namesake QAbstractSpinBox::validate.
For really pimped out SpinBox you could also re-implement its context menu and actions where you first get the standard menu from QLineEdit:
QMenu *menu = lineEdit()->createStandardContextMenu();
in the QWidget::contextMenuEvent and add/modify it as you need before you show it with menu->exec(event->globalPos()) and then delete menu;.
However QAbstractSpinBox does most of the ground job for you so you really should be fine with implementing just the above two virtual methods.

Related

Finding a wxMenu's Selected Radio Item

Let's say that I have a group of radio items in a wxMenu. I know that exactly one of these will be checked at any given time.
Does the wxMenu or some other construct hold onto the index of the checked item, or do I need to call the isChecked on each radio item till I find the checked element to find it's index?
I've asked this question about how to do that, but I'd much prefer wxWidgets saved me from doing that everywhere.
No, saving the index of the last selected item (as shown in ravenspoint's answer) or using wxMenuBarBase::IsChecked() until you find the selected radio button is the only way to do it.
For wxWidgets to provide access to the currently selected button it would need not only to store it (which means not forgetting to update not only when the selected changes, but also when items are inserted into/deleted from the menu, so it's already not completely trivial), but to somehow provide access to the radio items group you're interested in, which would require being able to identify it and currently there is no way to do it and adding it isn't going to be particularly simple.
What could be done easily, however, is writing a reusable function int GetIndexOfSelectedRadioItem(int firstItem) that would start at the given item and call IsChecked() on subsequent items until it returns true and return the offset of the item. You should be able to do it in your own code, but if you'd like to include such function in wxWidgets itself (as a static wxMenuBar method, probably), please don't hesitate to send patches/pull requests doing it!
It is easy enough to roll your own.
Bind an event handler to wxEVT_COMMAND_RADIOBUTTON_SELECTED for every button. In the handler, extract the ID of the selected radio button and store it somewhere.
Like this:
ResolMenu = new wxMenu();
ResolMenu->AppendRadioItem(idRcvLoRez,"Low Resolution");
ResolMenu->AppendRadioItem(idRcvMeRez,"Medium Resolution");
ResolMenu->AppendRadioItem(idRcvHiRez,"High Resolution");
ResolMenu->Check( idRcvLoRez, true );
Bind(wxEVT_MENU,&cFrame::onRcvRez,this,idRcvLoRez);
Bind(wxEVT_MENU,&cFrame::onRcvRez,this,idRcvMeRez);
Bind(wxEVT_MENU,&cFrame::onRcvRez,this,idRcvHiRez);
void onRcvRez( wxCommandEvent& event )
{
myRezID = event.GetId();

Undo for individual cells of QTableWidget in Qt?

I have a QTableWidget in my Main Window class.
I am unable to find a functionality which will undo the text change for the specified cell.
What I want to do is:
void myCellUndoFunc(int row, int col)
{
table->item(row, col)->undo(); //table is my QTableWidget
}
The problem is that there is no such undo().
So my question is, can there be a workaround for this problem using maybe some foo-doo combination of SIGNAL's & SLOT's?
Thanks!
PS: Please do not suggest to use Model/View framework because I have used QTableWidget extensively in my application. Sorry for the same.
Maybe you should use the
void QTableWidgetItem::setData ( int role, const QVariant & value ) [virtual]
using the Qt::UserRole you are able to specify the last value. In your method u can access the previously set value with the data()-Method. The only thing you have to do is always keep the old value up-to-date.
Before you set the new value of the QTableWidgetItem
tw->setData(Qt::UserRole, tw->text())
and on undo u could than retrieve the data with
tw->setText(tw->data(Qt::UserRole).toString())
where "tw" is the current QTableWidgetItem using the contextmenu-event, the clicked-event or whatever u want. You could also subclass the QTableWidgetItem and handle this whole thing internally in your class, creating an undo()-method, storing the old value, etc.

Replace QWidget with a new QWidget

This questions to me reeks of maybe a lack of understanding of C++, as the possibilities I've considered for my problem all seem to make no sense on why this could be occuring. Feedback appreciated.
I'm using the form designer to create a form class with a table in it. I'm trying to replace the table with another table generated in a helper class. I'm only doing this so I can (hopefully) maintain the nice grid layout I've designed, and through pointer manipulation, get the replacement I desire. Here's some code snippets from the table form constructor and relevant calls :
//tableData is defined in the header file as a QTableWidget*
tableData = this->findChild<QTableWidget *>("tableData");
....
setup();
void setup(){
tableData = Utilities::createTable(this->file, tableDelim);
//createTable returns QTableWidget*
... other assignments, and label text updates, which seem to all work
}
My understanding is that tableData is a pointer, and if printed, will give the address of the QTableWidget from the layout. So then if I create a QTableWidget* and then assign tableData to that, tableData should now point to the new widget. Instead, I see only a blank screen.
I tried checking what the tableData pointer is before I assign it to the new QTableWidget*, and after. The second pointer shown is what is generated by createTable() :
QTableWidget(0x101272d40, name = "tableData") QTableWidget(0x10127b3b0, name = "test_sample2.nuc.stats")
QTableWidget(0x10127b3b0, name = "test_sample2.nuc.stats") QTableWidget(0x10127b3b0, name = "test_sample2.nuc.stats")
It seems the pointer is being reassigned, but the table drawn isn't the right one.
What gives?
My understanding is that you want to design the table layout in designer but fill in the data from an external source.
I would suggest, to just use the QTableWidget that is created in setupUi() and modify Utilities::createTable() such that it becomes Utilities::populateTable(QTableWidget & table, <all the other parameters you need>). (Or use QTableWidget * if you prefer - however I like putting the non-zero assertion responsibility on the caller...)
Apart from that, I agree with Sebastian Lange.
You are right with your assumption. You do set a variable to be a pointer to a object and next you set the variable to be a pointer to another object. You never change any objects, just your variable which is not used to display anything.
You would need to do something like:
//tableData is defined in the header file as a QTableWidget*
tableData = this->findChild<QTableWidget *>("tableData");
parentLayout = tableData->parent()->layout(); //Get the parent widget to add another table.
parentLayout->removeWidget(tableData);
delete tableData;
parentLayout->addWidget(createTable());
You need to use pTheContainerOfTheOriginalTableWidget->addWidget(tableData); See here: http://qt-project.org/forums/viewthread/16547
Be sure you remove the original tableWidget so you don't have two (I assume you don't want two).
If I understand you correctly we have such situation.
call of setupUi (which generated by qt tootls),
there there is something like this(pseudo code):
oldTablePtr = new QTableWidget(parent);
someLayout->addWidget(oldTablePtr);
So parent and layout hold value of oldTablePtr.
And if you set variable oldTablePtr nothing changed.
parent send QPaintEvent to oldTablePtr.
So you need call delete oldTablePtr, that remove this widget from list of childs of parent, and move newTablePtr to the same layout.
There's no need to replace it in code, you can do it in Qt Designer. Just place QTableWidget on form, then rightclick it and choose Promote widget in menu, then you will need just enter your classname.
Currently I don't have Qt Designer near me, so edits will be appreciated.

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.

valueChanged of doubleSpinBox not working

//in my .h file i have:
void on_doubleSpinBox_test_valueChanged(double t);
//in my .cpp(mainwindow):
void MainWindow::on_doubleSpinBox_test_valueChanged(double t)
{
ui->lineEdit_test->setText(QString::number((double) t/2));
}
My problem is that when i set the value for lineEdit in SpinBox everything works, however in doubleSpinBox there is no message sent (changing value doesn't work).
What am i doing wrong? How to make this method to work?
The valueFromText and textFromValue methods might do what you want. They allow displaying the value of the spin box in a customized format, such as always displaying half the value of the spin box as in your code above.
http://doc.trolltech.com/4.7/qdoublespinbox.html#valueFromText
http://doc.trolltech.com/4.7/qdoublespinbox.html#textFromValue
A sample of how to use the methods is in the class QSpinBox and they are used the same in QDoubleSpinBox.
http://doc.trolltech.com/4.7/qspinbox.html#subclassing-qspinbox