How to get the row/column location of a widget in a QGridLayout? - qt

I made an own GridPushButton class to store the buttons position in gridlayout. The parent is QPushButton. I have a problem with asking it's x and y coordinates in window (like x:654, y:768). I thought it will be inherited from base class, but it doesn't. Now i have two options:
Use the original QPushButton class and somehow get its position in gridlayout (like x:0, y:1 if it's in the first row and second column) or
Use my GridPushButton and somehow get the x and y coordinate in window.
class GridPushButton : public QPushButton
{
Q_OBJECT
public:
GridPushButton(int coordX, int coordY, QWidget *parent = 0);
int coordinateX() { return _coordX; }
int coordinateY() { return _coordY; }
protected:
int _coordX;
int _coordY;
};
This is my class. I tried to make a new private variable and give it the QPushButton::x(), but doesn't work. Any idea to get the x and y coordinate from parent?
Or any idea to get the QPushButtons position in GridLayout?

There are two notions of coordinates that you're mixing up. There is the position within the parent widget. That's available via QWidget::x(), QWidget::y() and QWidget::pos() methods. You don't need to implement anything here: it already works.
Then there's the notion of the row and column within the grid layout. This can be obtained without a need for any subclassing. The grid layout knows where its widgets are, you can simply ask it for the row/column location of any widget.
#include <QtWidgets>
struct Pos { int row = -1, col = -1; };
Pos gridPosition(QWidget * widget) {
if (! widget->parentWidget()) return {};
auto layout = qobject_cast<QGridLayout*>(widget->parentWidget()->layout());
if (! layout) return {};
int index = layout->indexOf(widget);
Q_ASSERT(index >= 0);
int _;
Pos pos;
layout->getItemPosition(index, &pos.row, &pos.col, &_, &_);
return pos;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QGridLayout l(&w);
QLabel gridPos;
l.addWidget(&gridPos, 0, 0, 1, 4);
for (int i = 1; i < 4; ++ i)
for (int j = 0; j < 3; ++ j) {
auto b = new QPushButton(QString("%1,%2").arg(i).arg(j));
l.addWidget(b, i, j);
QObject::connect(b, &QPushButton::clicked, [&gridPos, b]{
auto p = gridPosition(b);
gridPos.setText(QString("Grid Pos: %1,%2")
.arg(p.row).arg(p.col));
});
}
w.show();
return a.exec();
}

Related

QListView max number of items in view

I need to calculate max number of items in current view of QListView.
I wrote code like this:
void MyListView::resizeEvent(QResizeEvent *event)
{
QListView::resizeEvent(event);
QFontMetrics fm (this->font());
int fontHeight = fm.lineSpacing();
QRect cr = contentsRect();
int windowHeight = cr.bottom() - cr.top();
int maxItemsCount = windowHeight / fontHeight;
qDebug()<<"max items in view: "<< maxItemsCount;
}
but calculated max number of items is is incorrect.
E.g. in case of my window height and font height I get 32 max items in view when in fact current view has 28 items. Perhaps someone can suggest something, how to calculate it properly?
My idea is to use QListView::indexAt() (inherited from QAbstractView) to obtain the row index for
the top-left corner
the bottom-left corner
of the list view viewport and determining the number of visible items by difference of them.
To check this out, I made an MCVE testQListViewNumVisibleItems.cc:
// Qt header:
#include <QtWidgets>
class ListWidget: public QListWidget {
public:
ListWidget(QWidget *pQParent = nullptr): QListWidget(pQParent) { }
virtual ~ListWidget() = default;
ListWidget(const ListWidget&) = delete;
ListWidget& operator=(const ListWidget&) = delete;
int getNumVisibleItems() const
{
const QSize size = viewport()->size();
const QModelIndex qMIdx0 = indexAt(QPoint(0, 0));
const QModelIndex qMIdx1 = indexAt(QPoint(0, size.height() - 1));
//qDebug() << "qMIdx0:" << qMIdx0 << "qMIdx1:" << qMIdx1;
return qMIdx0.isValid()
? (qMIdx1.isValid() ? qMIdx1.row() : count()) - qMIdx0.row()
: 0;
}
};
const int MaxItems = 20;
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
ListWidget qLst;
qLst.resize(200, 200);
qLst.show();
// timer to populate list view
using namespace std::chrono_literals;
QTimer qTimer;
qTimer.setInterval(1000ms);
// install signal handlers
int n = 0;
QObject::connect(&qTimer, &QTimer::timeout,
[&]() {
qLst.addItem(QString("item %0").arg(++n));
qDebug() << "Visible items:" << qLst.getNumVisibleItems();
if (n >= MaxItems) qTimer.stop();
});
// runtime loop
qTimer.start();
return app.exec();
}
and a CMakeLists.txt:
project(QListViewNumVisibleItems)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt5Widgets CONFIG REQUIRED)
include_directories("${CMAKE_SOURCE_DIR}")
add_executable(testQListViewNumVisibleItems testQListViewNumVisibleItems.cc)
target_link_libraries(testQListViewNumVisibleItems Qt5::Widgets)
built and tested in VS2017 on Windows 10:
After having implemented what came in my mind, I googled a bit to possibly see other approaches. (I admit I should've done before.)
Thereby I found the following possible duplicate:
SO: Simple way to get all visible items in the QListView
The accepted answer doesn't contain much more than the hint for indexAt and a link to a Qt-FAQ article:
How can i get hold of all of the visible items in my QListView?
In order to get hold of the visible items in a QListView http://doc.qt.io/qt-5/qlistview.html, then you can iterate over them using indexAt() http://doc.qt.io/qt-5/qlistview.html#indexAt. You can get hold of the first visible item using indexAt(QPoint(0, 0)), then in order to get the index at the next position then use visualRect() http://doc.qt.io/qt-5/qlistview.html#visualRect to find out what your next call to itemAt() should be. This position would be:
visualRect.y() + visualRect.height() + 1 effectively.
See the following example for an illustration:
#include <QtGui>
QList <QModelIndex>myList;
class ListView : public QListView
{
Q_OBJECT
public:
ListView()
{
QStringListModel *myModel = new QStringListModel(this);
QStringList list;
list << "a" << "b" <<"c" <<"d" <<"e" <<"f" <<"g" <<"h" <<"i" <<"j" <<"k";
myModel->setStringList(list);
setModel(myModel);
QTimer::singleShot(3000, this, SLOT(test1()));
}
public slots:
void test1()
{
QModelIndex firstIndex = indexAt(QPoint(0, 0));
if (firstIndex.isValid()) {
myList << firstIndex;
} while (viewport()->rect().contains(QPoint(0, visualRect(firstIndex).y() + visualRect(firstIndex).height() + 1 ))) {
firstIndex = indexAt(QPoint(0, visualRect(firstIndex).y() + visualRect(firstIndex).height() + 1 ));
myList << firstIndex;
}
qDebug() << myList.count() << "are visible";
}
};
#include "main.moc"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
ListView window;
window.resize(100, 50);
window.show();
return app.exec();
}

How display multiple values in QLabel in Qcreator

I am developing calculator using Qt SDK I want to get values from the calculator buttons in to one Qlabel. I have designed GUI using design view I tried to get values using following code.
void MainWindow::on_three_button_clicked(){
ui->label->setText("3");
}
but then Only I can get one button value at a time
please help me on this...
You can have one QString variable to store current pressed value. Here I am considering your class variable defined as QString myEnteredNum. Then you can use it as below code:
void MainWindow::on_three_button_clicked(){
myEnteredNum.append("3"); // This method you can add in each number slot
ui->label->setText(myEnteredNum);
}
I hope this helps you.
There are two general approaches to this. You can append the digits directly to a number, and bind the label to the number. The m_first label below is updated that way. You can also append the digits directly to the label. The m_second label is updated so.
#include <QtWidgets>
class Window : public QWidget {
Q_OBJECT
Q_PROPERTY(double number MEMBER m_number WRITE setNumber NOTIFY numberChanged)
double m_number;
QGridLayout m_grid;
QLabel m_first, m_second;
QPushButton m_digits[10], m_clear;
public:
Window(QWidget * parent = 0) : QWidget(parent),
m_grid(this), m_clear("C") {
m_grid.addWidget(&m_first, 0, 0, 1, 3);
m_grid.addWidget(&m_second, 1, 0, 1, 3);
m_grid.addWidget(&m_clear, 5, 2);
for (int i = 0; i < 9; ++i) {
m_digits[i].setText(QString::number(i));
if (i > 0) m_grid.addWidget(m_digits + i, 2 + (i-1)/3, (i-1)%3);
else m_grid.addWidget(m_digits + i, 5, 1);
QObject::connect(m_digits + i, &QPushButton::clicked, [i, this]{
// Add the digit to the number
setNumber(m_number * 10.0 + i);
// Add the digit to the label
m_second.setText(m_second.text().append(QString::number(i)));
});
}
QObject::connect(&m_clear, &QPushButton::clicked, [this]{
setNumber(0);
m_second.setText("0");
});
QObject::connect(this, &Window::numberChanged,
&m_second, &QLabel::setNum);
emit m_clear.clicked(); // initialize everything
}
Q_SIGNAL void numberChanged(double);
void setNumber(double n) { m_number = n; emit numberChanged(n); }
};
int main(int argc, char ** argv) {
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
#include "main.moc"
Finally, a calculator is stateful and, to avoid common mistakes, you could use a QStateMachine to make this fact explicit, and to adjust the behavior of the calculator depending on which state it's in.

Qt - Declaring a QLabel array and adding it to a QTabWidget's tab

I have declared a QLabel array (which i will be using as picture boxes) and the function call which is going to fill the array in the header file of the project as:
void fillArray();
QLabel *label_array[7];
Then i called the declared function in the main(). That function is going to fill the array and add the labels to the QTabWidget tab's body. In the source file i wrote the following:
void MainWindow::fillArray()
{
for (int i = 0; i < sizeof(MainWindow::label_array) - 1; i++)
{
MainWindow::label_array[i] = new QLabel("Label " + i + 1);
ui->tabWgt->widget(0)->layout()->addWidget(MainWindow::label_array[i])
}
}
But the problem is it returns an error at the moment i build the program. I tried creating a layout and adding it to the layout and setting the tab's layout to that layout, it works, but it gets really messy and it is not understandable.
void MainWindow::fillArray()
{
QHBoxLayout *layout = new QHBoxLayout;
for (int i = 0; i < sizeof(MainWindow::label_array) - 1; i++)
{
MainWindow::label_array[i] = new QLabel("Label " + i + 1);
MainWindow::label_array[i]->move(10, 5*i); // this doesn't work...
layout->addWidget(MainWindow::label_array[i]);
}
ui->tabWgt->widget(0)->setLayout(layout);
}
Is it possible to add the labels directly to the tab's body without having to declare a layout (since i'd need to do it to every created array i declare along my program, and i have no idea how they are being located)?
How to position the label correctly?
Is it possible to add the labels directly to the tab's body without having to declare a layout (...) ?
No, because by default widget don't have a layout and you have to create it. But you can do it more clear:
void MainWindow::fillArray()
{
/* set layout for tab */
ui->tabWgt->widget(0)->setLayout(new QVBoxLayout);
/* calculate size of array */
const uint array_size = sizeof(MainWindow::label_array)/sizeof(MainWindow::label_array[0]);
/* fill tab widget */
for (uint i = 0; i < array_size; i++)
{
MainWindow::label_array[i] = new QLabel(QString("Label %1").arg(i+1));
ui->tabWgt->widget(0)->layout()->addWidget(MainWindow::label_array[i]);
}
}
How to position the label correctly?
Here is an article which describe how to use different layouts for Qt 4.8 or here for Qt5.3.
In no case will you need to allocate the labels explicitly on the heap. You should store them directly as a member array.
With C++11, you should use range-based for to enumerate the safe, zero-overhead std::array.
Otherwise, use the C-style array and the sizeof(array)/sizeof(array[0]) idiom to determine its size from within the setup method.
Having written a whole bunch of QWidget based code, I now generally consider child widgets explicitly allocated on the heap in the widget's constructor to be premature pessimizations. There's rarely any reason to write the code that way. The widgets already allocate their implementation data on the heap (the pimpl idiom). When you allocate this skinny, pointer-sized QWidget subclass on the heap, you're simply doubling the number of heap allocations for no good reason at all. Don't.
#include <QApplication>
#include <QLabel>
#include <QVBoxLayout>
#include <array>
class WidgetCpp11 : public QWidget {
QVBoxLayout m_layout;
std::array<QLabel, 3> m_labels; // No overhead compared to `QLabel m_labels[3]`
void setupLabels() {
int n = 1;
for (QLabel & label: m_labels) {
label.setText(QString("Label %1").arg(n++));
m_layout.addWidget(&label);
}
}
public:
WidgetCpp11(QWidget * parent = 0) : QWidget(parent), m_layout(this) {
setupLabels();
}
};
class WidgetCpp98 : public QWidget {
QVBoxLayout m_layout;
QLabel m_labels[3];
void setupLabels() {
for (uint i = 0; i < sizeof(m_labels)/sizeof(m_labels[0]); ++i) {
m_labels[i].setText(QString("Label %1").arg(i+1));
m_layout.addWidget(m_labels+i);
}
}
public:
WidgetCpp98(QWidget * parent = 0) : QWidget(parent), m_layout(this) {
setupLabels();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
WidgetCpp98 w;
w.show();
return a.exec();
}

navigate between widgets using arrows in QT

I'm working on UI using QT. The ui is simple, it's app based like IPhone or Android. Say there are 9 items (3 rows x 3 cols).
What I want to do is to navigate between widgets using arrow keys.
If the focus is in [row 1,col 1] and I press down arrow, I want it to go to [row 2, col 1]
another example.
If the focus is in [row 2,col 3] and I press up arrow, I want it to go to [row 1, col 3]
But the current behavior is up and right always go to next widget and down and left always go to previous widget.
Is there any way to do this in qt? or I need to create some algorithm to do this?
Thanks
UPDATE: See amazing example at the end.
Basic Widget focus navigation starts out with this:
http://qt-project.org/doc/qt-4.8/focus.html
Arrow navigation is available easily with a QTableView:
http://qt-project.org/doc/qt-4.8/qtableview.html#navigation
If you can get your widgets to work inside the structure of a QTableView, then you don't need to implement it, it comes as a part of the wrapper/view widget.
http://qt-project.org/doc/qt-4.8/qtablewidget.html#details
http://qt-project.org/doc/qt-4.8/model-view-programming.html
Model View programming does have a learning curve, but it is worth while to learn and use.
But this is by no means the only way to accomplish this.
There are event filters, key events, focus events that can be leveraged to accomplish this feat without using a QTableView or QTableWidget. But figuring out the best way to do it without making it look messy may take some time.
http://qt-project.org/doc/qt-4.8/qcoreapplication.html#notify
http://doc.qt.digia.com/qq/qq11-events.html
http://qt-project.org/doc/qt-4.8/eventsandfilters.html
http://qt-project.org/doc/qt-4.8/qkeyevent.html#details
http://qt-project.org/doc/qt-4.8/qfocusevent.html
Key events are set to the item with the focus, and if they ignore the event it propagates up to its parent. So as long as your items in your table/grid ignore the key events having to do with the arrow keys, then you could have your parent widget listen for the key events and handle them appropriately.
http://qt-project.org/doc/qt-4.8/qt.html#Key-enum
http://qt-project.org/doc/qt-4.8/qt.html#FocusReason-enum
http://qt-project.org/doc/qt-4.8/qwidget.html#setFocus
http://qt-project.org/doc/qt-4.8/qapplication.html#focusWidget
Hope that helps.
EDIT: Fully working example in QGraphicsView of what you want to do:
Qt Creator > Welcome tab > Examples > Pad Navigator Example
http://qt-project.org/doc/qt-4.8/graphicsview-padnavigator.html
Here is the relevant code from the example:
// Enable key navigation using state transitions
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < columns; ++x) {
QState *state = stateGrid[y][x];
QKeyEventTransition *rightTransition = new QKeyEventTransition(this, QEvent::KeyPress,
Qt::Key_Right, state);
QKeyEventTransition *leftTransition = new QKeyEventTransition(this, QEvent::KeyPress,
Qt::Key_Left, state);
QKeyEventTransition *downTransition = new QKeyEventTransition(this, QEvent::KeyPress,
Qt::Key_Down, state);
QKeyEventTransition *upTransition = new QKeyEventTransition(this, QEvent::KeyPress,
Qt::Key_Up, state);
rightTransition->setTargetState(stateGrid[y][(x + 1) % columns]);
leftTransition->setTargetState(stateGrid[y][((x - 1) + columns) % columns]);
downTransition->setTargetState(stateGrid[(y + 1) % rows][x]);
upTransition->setTargetState(stateGrid[((y - 1) + rows) % rows][x]);
EDIT:
Amazing example using QShortcuts and a QGridLayout and a bunch of QPushButtons:
widget.cpp
#include "widget.h"
#include <QPushButton>
#include <QApplication>
#include <QShortcut>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_grid = new QGridLayout;
for(int r = 0; r < 10; r++)
{
for(int c = 0; c < 10; c++)
{
m_grid->addWidget(new QPushButton("Row " + QString::number(r)
+ ", Col " + QString::number(c)),
r, c);
}
}
this->setLayout(m_grid);
m_grid->itemAtPosition(1, 1)->widget()->setFocus();
this->setStyleSheet("QPushButton::focus{ background: black; color: white;}");
// only works for in Qt for Embedded Linux, Symbian and Windows CE only.
// QApplication::setNavigationMode(Qt::NavigationModeKeypadDirectional);
QShortcut * shortcut;
shortcut = new QShortcut(QKeySequence(Qt::Key_Up),this,
SLOT(on_up()));
shortcut = new QShortcut(QKeySequence(Qt::Key_Down),this,
SLOT(on_down()));
shortcut = new QShortcut(QKeySequence(Qt::Key_Left),this,
SLOT(on_left()));
shortcut = new QShortcut(QKeySequence(Qt::Key_Right),this,
SLOT(on_right()));
}
void Widget::on_up()
{
moveFocus(0, -1);
}
void Widget::on_down()
{
moveFocus(0, 1);
}
void Widget::on_left()
{
moveFocus(-1, 0);
}
void Widget::on_right()
{
moveFocus(1, 0);
}
void Widget::moveFocus(int dx, int dy)
{
if(qApp->focusWidget() == 0)
return;
int idx = m_grid->indexOf(qApp->focusWidget());
if(idx == -1)
return;
int r, c, rowSpan, colSpan;
m_grid->getItemPosition(idx, &r, &c, &rowSpan, &colSpan);
QLayoutItem* layoutItem = m_grid->itemAtPosition(r + dy, c + dx);
if(layoutItem == 0)
return;
layoutItem->widget()->setFocus();
}
Widget::~Widget()
{
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QGridLayout>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
QGridLayout * m_grid;
public slots:
void on_up();
void on_down();
void on_left();
void on_right();
void moveFocus(int dx, int dy);
};
#endif // WIDGET_H
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
To enable keypad navigation, build Qt with QT_KEYPAD_NAVIGATION defined.
https://het.as.utexas.edu/HET/Software/html/qapplication.html#keypadNavigationEnabled

use a created vector as range for QDoubleSpinBox and QSlider

I've created a vector v with 128 elements from -3000 to 3000 and I want to associate it to a QDoubleSpinBox and a QSlider, because dividing 6000 to 128 and setting the QDoubleSpinBox we have always the round problem. So can we set the range and the stepsize both to a QDoubleSpinBox and QSlider with a vector like this?
std::vector<double> v(128);
for (int i = 0; i < 128; ++i)
{
v[i] = -3000.0 + 6000.0 * (i) / 127.0;
}
QSlider only operates with int steps, so you'd just need to do the calculation yourself:
#include <QtGui>
#include <cmath>
class MyWidget : public QWidget {
Q_OBJECT
public:
MyWidget() : QWidget() {
slider_ = new QSlider;
slider_->setRange(0, 127);
connect(slider_, SIGNAL(valueChanged(int)), SLOT(ChangeSpinBox(int)));
box_ = new QDoubleSpinBox;
box_->setRange(-3000.0, 3000.0);
connect(box_, SIGNAL(valueChanged(double)), SLOT(ChangeSlider(double)));
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(slider_);
layout->addWidget(box_);
setLayout(layout);
}
private slots:
void ChangeSpinBox(int sliderValue) {
if (convertSpinBoxValueToSlider(box_->value()) != sliderValue) {
box_->setValue((6000.0 * sliderValue / 127.0) - 3000.0);
}
}
void ChangeSlider(double spinBoxValue) {
slider_->setValue(convertSpinBoxValueToSlider(spinBoxValue));
}
private:
QSlider *slider_;
QDoubleSpinBox *box_;
static int convertSpinBoxValueToSlider(double value) {
return qRound((value + 3000.0) * 127.0 / 6000.0);
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
MyWidget w;
w.show();
return app.exec();
}
#include "main.moc"
Or am I not understanding your question?
First of all, you have a bug in your vector computation. If you want to divide a range to X parts, then your vector must be of size X+1 not X. Example: you want to split range 0-10 to 5 parts. How many items you need? Your code suggests 5. But the answer is [0,2,4,6,8,10] = 6. So have vector 129 and do the division by 128.
So you would like to have a step size of 6000/128 = 48.875 exactly. You can do that with QDoubleSpinBox
QDoubleSpinBox::setRange(-3000.0,3000.0);
QDoubleSpinBox::setSingleStep(48.875);
but not with QSlider which takes only integer. You can avoid the rounding error by multiplying your range by 1000.
QSlider::setRange(-3000000,3000000);
QSlider::setSingleStep(48875);
For setting the selected result to the spinbox, you need to divide by 1000 again of course.

Resources