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)
Related
I need to have several shortcuts for one push button. For example Ctrl+W and Enter and Return (Enter and Return are different in Qt), any of them would cause a click on the button. How to do this? If the button was QAction, I would call setShortcuts() ( See Two shortcuts for one action which is NOT a duplicate. It is different, relates to QAction not QPushButton. ) but QPushButton has only setShortcut() (singular), which seems to not allow this. What solution or hack wold you suggest?
OK, I have got a solution which is not that hacky. I can create a QPushButton and a QAction, then set multiple shortcuts for QAction using QAction::setShortcuts() and connect this action to QPushButton::animateClick(). At first this solution did not work because I called connect(action, &QAction::triggered, button, &QPushButton::animateClick);. The problem was with invisible default arguments. QAction::triggered sends true/false indicating whether the action is checked. But QPushButton::animatedClick expects number of milliseconds to remain visually 'pressed'. Therefore it remained 'pressed' only for 0 or 1 millisecond, which is not enough to notice it (the default value of the argument is 100 milliseconds). This can be solved using lambda, hence:
// 'this' refers to a parent widget
auto action = new QAction(this);
action->setShortcuts({ tr("Ctrl+W"), tr("Return"), tr("Enter") });
this->addAction(action);
auto button = new QPushButton(tr("Hey, click me!"));
connect(action, &QAction::triggered, [button](){ button->animateClick(); });
And that's it.
There is a container Widget instance A and there are contained Widget instances C1, C2, C3, ... etc within A.
There is a slot that handles Widget A's action_triggreed() signal.
Is there a way to determine which of the target Widgets C1, C2, C3, ... was clicked?
The reason for this necessity is that there are numerous contained widgets, and it doesn't make sense to use connect() method on each, which would require 50+ lines of extra connect statements, one for each!
For example: Consider a QToolBox with lots of Buttons. How would you determine which Button is pressed by using a QToolBox action_triggered or similar signals, without using signals and slots for individual Buttons separately?
You can use the QObject::sender() function.
void A::triggeredSlot() {
QObject* obj = sender();
QButton* but = qobject_cast<QButton*>(obj);
}
Just use the correct types etc.
There are a few general approaches.
The first one is straightforward - you should connect each widget's signal to the slot and check what sender() is. Yes, lines of code for connecting.
The second one is to use a kind of mapper. Every widget is connected to the mapper and only mapper is connected to the target slot. Lines of code again, at least in the part where you connect widgets to the mapper. There is a generic QSignalMapper or you can use more specific one suited for your widgets. For example, if they are buttons then you can use QButtonGroup class. Every button is registered in the group and only one signal/slot connection is required.
QButtonGroup group;
group->addButton(buttonC1,C1_ID);
...
group->addButton(buttonC1,Cn_ID);
connect(&group,SIGNAL(buttonClicked(QAbstractButton*),this,SLOT(buttonClicked(QAbstractButton*));
The third approach is to detect mouse event only on the mother's widget A and then iterate over all its children and find which one is under mouse. Less code, you can easily add new widgets, but the cost is iterating over all widgets in runtime. Below is an example. Note, that you can add specific QObject names or properties to the widgets C1... so that you could filter them if you are interested only in a part of children widgets of the given type.
void mousePressEvent(QMouseEvent* event)
{
if (event->button()==Qt::LeftButton)
{
QList<QToolButton*> buttons=findChildren<QToolButton*>(); // you can also use specific object names on the widgets under your interest
foreach (QToolButton* button, buttons)
{
if (button->underMouse()) // you could try isDown() for button, but I'm not sure if that will work here
{
emit buttonClicked(button);
break;
}
}
}
}
Well, you could try another approach also. Detect the mouse event, get cursor position and find the child widget on that position. Not costly in runtime.
void mousePressEvent(QMouseEvent* event)
{
if (event->button()==Qt::LeftButton)
{
QPoint pt=mapFromGlobal(QCursor::pos());
QWidget* child=childAt(pt);
if (child)
{
emit childClicked(child);
}
}
}
Suppose I have a QPropertyAnimator animating (moving), say, a button - slightly to the left over the course of 10 seconds.
When the button reaches its destination on the other side of the window it should change its text to "banana" using the QLineEdit::setText() function.
If the QLineEdit::setText() function is issued directly after the animation start;
QPropertyAnimator *animator = new QPropertyAnimator(someButton, "pos");
animator->setDuration(10000);
animator->setStartValue(*current position of the button*);
animator->setEndValue(*current position of the button with x-100*);
animator->start();
QLineEdit::setText(QString("Banana"));
The text changes before it has the chance to start moving. Luckily, QPropertyAnimator emits a signal when the animation is completed - aptly titled finished().
One would like to just be able to:
connect(animator, SIGNAL(finished()), someButton, SLOT(setText("Banana")));
But since you can't pass an argument to a slot that won't work.
How do I change the text after the animation is complete without creating different "proxy" functions (slots) to do it without arguments?
As already others told, you should use lambda, in your case,
connect( animator, &QPropertyAnimator::finished, [&]()
{
m_lineEdit->setText( QString("Banana") );
} );
I have one QTableWidget with some QTableWidgetsItems on it. Some items use checkboxes. I've added the checkboxes using the follow code:
QTableWidgetsItem->setCheckState(Qt::Checked);
I would like now to call some function when this checkbox state change. Using a signal for example.
What may be the easiest way to accomplish this?
The easiest way to do this is to capture signal(s) of QTableWidget with slot(s) in the class that contains QTableWidget. While it would seem that QTableWidget::itemActivated might be our best bet, it is uncertain whether or not this is emitted when the Qt::CheckState is equal to Qt::Checked. In addition, even if this was true, the signal would not provide you the capabilities of handling item unchecking which your application may need to do.
So, here is my proposed solution. Capture the QTableWidget::itemPressed and QTableWidget::itemClicked signals with slots defined in the class that contains the QTableWidget. As itemPressed should be called BEFORE the mouse button is released, and itemClicked should be called AFTER the mouse button is released, the Qt::CheckState for that QTableWidgetItem should only be set in between these two signal emissions. Thus, you can determine exactly when a QTableWidgetItem's checkState has changed with low memory overhead.
Here is an example of what these slots could look like:
void tableItemPressed(QTableWidgetItem * item)
{
// member variable used to keep track of the check state for a
// table widget item currently being pressed
m_pressedItemState = item->checkState();
}
void tableItemClicked(QTableWidgetItem * item)
{
// if check box has been clicked
if (m_pressedItemState != item->checkState())
{
// perform check logic here
}
}
And the signals/ slots would be connected as follows:
connect(m_tableWidget,SIGNAL(itemPressed(QTableWidgetItem *)),this,SLOT(tableItemPressed(QTableWidgetItem *)));
connect(m_tableWidget,SIGNAL(itemClicked(QTableWidgetItem *)),this,SLOT(tableItemClicked(QTableWidgetItem *)));
Where m_tableWidget is the QTableWidget * you associate with your table widget.
I am populating a sytem tray icon menu (QMenu) from entries in an xml file which is read when my application starts up.
I am unsure of how to properly set up the SLOT end of the action:
QList<CMenuItem> menuItems = m_layout->getMenuItems();
QListIterator<CMenuItem> iter(menuItems);
while (iter.hasNext())
{
CMenuItem menuItem = iter.next();
QAction *action = new QAction(menuItem.qsTitle, this);
connect(action, SIGNAL(triggered()), this, SLOT(launchMenuItem()));
trayIconMenu->addAction(action);
}
How does my "launchMenuItem()" SLOT know which menu item was triggered? I can't make a SLOT for each menu item as I don't know how many items will exist until run time.
I can think of some ugly ways to do this, but I am looking for the RIGHT way.
What I usually do is to use QAction::setData(const QVariant&) to store whatever action ID I need. Then on slot side I retrieve ID with QAction::data() and behave accordingly.
Note that QVariant obviously accepts much more than basic int (which is what I use to identify actions), you can pass any QVariant-compatible info.
edit : oh! btw, this is somehow ugly because I make use of QObject::sender() to cast triggered action back. Sorry for that, but it works anyway.