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

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();
}

Related

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.

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

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();
}

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

Transparent Qt-widget and Ogre

we ’ve been looking into using transparent widgets as overlays for ogre. Although Qwidgets are transparent by default, overlaying them over an Ogre widget is different and a black screen arises. On the ogre forum (http://www.ogre3d.org/forums/viewtopic.php?f=2&t=42733) I found the following:
"Ok, to get my ideas straight I’m going to summarise the various approaches I have found to combining Ogre and Qt:
Create a window (not necessarily a QGLWidget) using Qt and pass its window handle to Ogre using the ‘externalWindowHandle’ flag. Ogre then creates a context and is fully responsible for it. This is basically what I did for the first demo I made. It has the advantage that it should support both OpenGL and Direct3D, and also should work accross all platforms. The big drawback is that Qt widgets can only be overlaid (not integrated)
so you don’t get the nice transparency.
An approach similar to (1) but with the difference that the frame buffer is explicitly read back from Ogre and used as a widget background. This would again work accross all render systems and platforms, and now Qt can perform proper compositing from transparency. However, it is potenially slow doe to the need to read back every frame and copy it to Qt. But this could make a good ‘fallback’ approach. I haven’t tried implementing it yet.
This is the ‘proper’ approach which we are really considering in this thread. It involves both Ogre and Qt owning OpenGL contexts and both rendering into the same widget. This widget is set as the background of a QGraphicsView/QGraphicsScene and other widgets can be overliad with proper transpaency effects. It only works with OpenGL and is (theorectically) cross platform. There are two variations:
Allow Ogre to create it’s own OpenGL context (passing the flag ‘externalGLControl’) and then retieve the context once createRenderWindow() has finished. Under windows there are the wglGetCurrentDC() and wglGetCurrentContext() for this – other platforms should have something similar.
Have Qt create a context on Ogre’s behalf and pass that to Ogre using the ‘externalGLContext’ flag. In this case Ogre basically does nothing except render into the context it it provided with – all the control is with Qt. I haven’t quite got this to work yet as I was having crashes in both Qt and Ogre."
Approach 1 is the one we followed for a project and as said above we’ve encountered the same problem with transparency. Approach 3 is possible, but we would be limiting ourselves to openGL. The code for that is available one page 3 of the forum-link.
We’ve also found a demo video but I can’t find how it was implemented, but it looks really cool: http://youtu.be/b_3xfFuwGOQ
You can now find an implementation of the second variant of your third proposed approach in an updated QmlOgre example at https://github.com/advancingu/QmlOgre The original example had a few issues that were fixed here.
Hope this still helps a few months after your question.
I couldn't find sample implementations of options 2 and 3, so I'll share mine here.
I've tested the implementations below with Qt 5.4.0 x86 and OGRE 1.10.0 x86 on Windows 10.
Disclaimer: This is sample code. I'm not recommending you to:
implement everything in header files;
use owning raw pointers - use smart pointers, instead.
Direct OpenGL backend
A possible implementation of option 3.2 is the following:
// OgreGLWidget.h
#pragma once
#include "Scene.h"
#include "OgreSceneManager.h"
#include "OgreRenderSystem.h"
#include "OgreRenderWindow.h"
#include "OgreRoot.h"
#include <QtWidgets/QOpenGLWidget>
#include <algorithm>
#include <cstdint>
class OgreGLWidget final
: public QOpenGLWidget
{
Q_OBJECT
public:
explicit OgreGLWidget(QWidget* parent = nullptr)
:
QOpenGLWidget(parent),
Root_("plugins" OGRE_BUILD_SUFFIX ".cfg", "ogre.cfg")
{ }
~OgreGLWidget() override
{
this->Scene_.clear();
if (this->SceneManager_)
this->Root_.destroySceneManager(this->SceneManager_);
if (this->RenderWindow_)
this->RenderWindow_->destroy();
}
protected:
void initializeGL() override
{
QOpenGLWidget::initializeGL();
if (auto* glRenderSystem = this->getGlRenderSystem())
{
this->Root_.setRenderSystem(glRenderSystem);
auto* renderWindow = this->Root_.initialise(false);
assert(renderWindow == nullptr && "The render window must be created afterwards");
}
else
{
throw std::runtime_error("Cannot find GL render system");
}
Ogre::NameValuePairList params;
params["externalGLControl"] = "true";
params["hidden"] = "true";
// Pass the current OpenGL context to OGRE
// - either like this
params["currentGLContext"] = "true";
// - or like this
//if (auto const* openGLContext = QOpenGLContext::currentContext())
//{
// auto const nativeHandle = openGLContext->nativeHandle();
// if (!nativeHandle.isNull() && nativeHandle.canConvert<QWGLNativeContext>())
// {
// auto nativeContext = nativeHandle.value<QWGLNativeContext>();
// auto nativeRenderingContextHandle = nativeContext.context();
// params["externalGLContext"] = Ogre::StringConverter::toString(reinterpret_cast<unsigned long>(nativeRenderingContextHandle));
// qDebug("OpenGLContext: nativeHandle=%p, windowHandle=%p", nativeRenderingContextHandle, nativeContext.window());
// }
//}
// Setup scene
this->RenderWindow_ = this->Root_.createRenderWindow("", this->width(), this->height(), false, &params);
this->SceneManager_ = this->Root_.createSceneManager(Ogre::SceneType::ST_GENERIC);
this->SceneManager_->setAmbientLight(Ogre::ColourValue::White);
this->Scene_ = Scene(*this->SceneManager_, *this->RenderWindow_);
}
void paintGL() override
{
QOpenGLWidget::paintGL();
this->Root_.renderOneFrame();
// Queue another update to call paintGL again.
// Not ideal as it will render even if the scene hasn't changed.
this->update();
}
void resizeGL(int width, int height) override
{
QOpenGLWidget::resizeGL(width, height);
this->RenderWindow_->resize(width, height);
}
private:
Ogre::Root Root_;
Ogre::RenderWindow* RenderWindow_{ nullptr };
Ogre::SceneManager* SceneManager_{ nullptr };
Scene Scene_{};
Ogre::RenderSystem* getGlRenderSystem()
{
static Ogre::String const render_system_name{ "OpenGL Rendering Subsystem" };
auto const& renderSystems = this->Root_.getAvailableRenderers();
auto const lastRenderSystem = std::cend(renderSystems);
auto const glRenderSystemIt = std::find_if(std::cbegin(renderSystems), lastRenderSystem,
[](Ogre::RenderSystem const* renderSystem) { return renderSystem->getName() == render_system_name; });
return (glRenderSystemIt == lastRenderSystem) ? nullptr : *glRenderSystemIt;
}
};
Here you are a screenshot of the application running:
Buffered D3D9/OpenGL backend
A possible implementation of option 2 is the following:
// OgreWidget.h
#pragma once
#include "Scene.h"
#include "OgreRoot.h"
#include "OgreRenderWindow.h"
#include "OgreSceneManager.h"
#include <QtGui/qevent.h>
#include <QtGui/QPainter>
#include <QtWidgets/QWidget>
#include <cstdint>
class OgreWidget final
: public QWidget
{
Q_OBJECT
public:
explicit OgreWidget(QWidget *parent = nullptr)
:
QWidget(parent),
Root_("plugins" OGRE_BUILD_SUFFIX ".cfg", "ogre.cfg")
{
//static Ogre::String const render_system_name{ "OpenGL Rendering Subsystem" };
static Ogre::String const render_system_name{ "Direct3D9 Rendering Subsystem" };
if (auto* renderSystem = this->getRenderSystem(render_system_name))
{
this->Root_.setRenderSystem(renderSystem);
auto* renderWindow = this->Root_.initialise(false);
assert(renderWindow == nullptr && "The render window must be created afterwards");
}
else
{
throw std::runtime_error("Cannot find render system: " + render_system_name);
}
Ogre::NameValuePairList params;
params["hidden"] = "true";
this->RenderWindow_ = this->Root_.createRenderWindow("", this->width(), this->height(), false, &params);
this->SceneManager_ = this->Root_.createSceneManager(Ogre::SceneType::ST_GENERIC);
this->SceneManager_->setAmbientLight(Ogre::ColourValue::White);
this->Scene_ = Scene(*this->SceneManager_, *this->RenderWindow_);
}
~OgreWidget() override
{
this->Scene_.clear();
if (this->SceneManager_)
this->Root_.destroySceneManager(this->SceneManager_);
if (this->RenderWindow_)
this->RenderWindow_->destroy();
}
protected:
void paintEvent(QPaintEvent* evt) override
{
this->Root_.renderOneFrame();
this->RenderWindow_->update();
{
static auto const memory_category = Ogre::MemoryCategory::MEMCATEGORY_RENDERSYS;
static auto const pixel_format = Ogre::PixelFormat::PF_A8R8G8B8;
auto const width = this->RenderWindow_->getWidth();
auto const height = this->RenderWindow_->getHeight();
auto const bytesPerPixel = Ogre::PixelUtil::getNumElemBytes(pixel_format);
auto const byteCount = width * height * bytesPerPixel;
auto* data = OGRE_ALLOC_T(std::uint8_t, byteCount, memory_category);
Ogre::PixelBox pixelBox(width, height, 1, pixel_format, data);
this->RenderWindow_->copyContentsToMemory(pixelBox, pixelBox);
static auto const image_format = QImage::Format::Format_ARGB32;
QImage const background(data, width, height, image_format);
QPainter painter(this);
painter.drawImage(QPointF{ 0., 0. }, background, background.rect(), Qt::ImageConversionFlag::NoFormatConversion);
OGRE_FREE(data, memory_category);
}
QWidget::paintEvent(evt);
// Queue another update to call paintEvent again.
// Not ideal as it will render even if the scene hasn't changed.
this->update();
}
void resizeEvent(QResizeEvent* evt) override
{
auto const& size = evt->size();
auto const width = size.width();
auto const height = size.height();
this->RenderWindow_->resize(width, height);
QWidget::resizeEvent(evt);
}
private:
Ogre::Root Root_;
Ogre::RenderWindow* RenderWindow_{ nullptr };
Ogre::SceneManager* SceneManager_{ nullptr };
Scene Scene_;
Ogre::RenderSystem* getRenderSystem(Ogre::String const& renderSystemName)
{
auto const& renderSystems = this->Root_.getAvailableRenderers();
auto const lastRenderSystem = std::cend(renderSystems);
auto const renderSystemIt = std::find_if(std::cbegin(renderSystems), lastRenderSystem,
[&](Ogre::RenderSystem const* renderSystem) { return renderSystem->getName() == renderSystemName; });
return (renderSystemIt == lastRenderSystem) ? nullptr : *renderSystemIt;
}
};
Here you are a screenshot of the application running:
Performance
This approach is considerably slower than the OpenGL one.
On my machine, when the application is maximized, a frame is rendered in about:
~14ms with the (buffered) OpenGL backend;
~11ms with the (buffered) D3D9 backend;
~2 ms with the OpenGL backend;
Issues
For some reason, when using the buffered OpenGL backend, the cube has a different color:
I've not bothered to investigate it: if OpenGL is available, you most probably want to render the scene with class OgreGLWidget.
Boilerplate code
I'm embedding the OGRE scene as a child widget of centralWidget in my MainWindow instance:
// MainWindow.h
#pragma once
#include "OgreGLWidget.h"
#include "OgreWidget.h"
#include <QtWidgets/QLabel>
#include <QtWidgets/QLayout>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QPushButton>
#include "ui_MainWindow.h"
class MainWindow final
: public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr)
: QMainWindow(parent)
{
this->Ui_.setupUi(this);
auto* gridLayout = new QGridLayout(this->Ui_.centralWidget);
gridLayout->setSpacing(6);
gridLayout->setContentsMargins(0, 0, 0, 0);
// Uncomment based on whether you want to use OpenGL or D3D9 as backend.
this->OgreWidget_ = new OgreGLWidget(this->Ui_.centralWidget);
//this->OgreWidget_ = new OgreWidget(this->Ui_.centralWidget);
gridLayout->addWidget(this->OgreWidget_);
// Show an overlay widget when "Show Widget" is pressed.
auto* action = new QAction("Show Widget", this);
QObject::connect(action, &QAction::triggered, this, &MainWindow::showWidgetSlot);
this->Ui_.mainToolBar->addAction(action);
}
~MainWindow() override = default;
private slots:
void showWidgetSlot()
{
auto* w = new QWidget(this->OgreWidget_);
w->setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose);
w->setAttribute(Qt::WidgetAttribute::WA_NoMousePropagation);
w->setStyleSheet(".QWidget { background-color: rgba(255,255,255,75%); border-radius: 10px; }");
auto layout = new QVBoxLayout(w);
layout->addWidget(new QLabel(this->tr("Hello OGRE"), w));
auto* b = new QPushButton(this->tr("Ok"), w);
layout->addWidget(b);
QObject::connect(b, &QPushButton::clicked, w, &QWidget::close);
w->resize(100, 100);
w->show();
}
private:
Ui::MainWindowClass Ui_;
QWidget* OgreWidget_{ nullptr };
};
Class Scene, responsible for setting up my scene, is implemented like this:
// Scene.h
#pragma once
#include "OgreCamera.h"
#include "OgreEntity.h"
#include "OgreMaterialManager.h"
#include "OgrePass.h"
#include "OgreRenderTarget.h"
#include "OgreSceneManager.h"
#include "OgreSceneNode.h"
#include "OgreTechnique.h"
#include "OgreViewport.h"
struct Scene
{
explicit Scene() = default;
explicit Scene(Ogre::SceneManager& sceneManager_, Ogre::RenderTarget& renderTarget_)
: sceneManager(&sceneManager_)
{
auto cubeMaterial = Ogre::MaterialManager::getSingleton().create("cubeMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
if (auto* technique = cubeMaterial->getTechnique(0))
{
if (auto* pass = technique->getPass(0))
{
pass->setLightingEnabled(true);
pass->setAmbient(Ogre::ColourValue{ 0.3f, 0.1f, 0.1f, 1 });
}
}
this->entity = sceneManager_.createEntity(Ogre::SceneManager::PrefabType::PT_CUBE);
this->entity->setMaterial(cubeMaterial);
sceneManager_.getRootSceneNode()->attachObject(this->entity);
this->camera = sceneManager_.createCamera("camera");
this->camera->setPosition(-100, 100, 300);
this->camera->lookAt(0, 0, 0);
this->camera->setNearClipDistance(5);
this->light = sceneManager_.createLight("light");
this->light->setType(Ogre::Light::LightTypes::LT_DIRECTIONAL);
this->light->setDirection(0.25, -1, -0.5);
this->viewport = renderTarget_.addViewport(this->camera);
this->viewport->setBackgroundColour(Ogre::ColourValue::Blue);
}
Scene(Scene const&) = delete;
Scene(Scene&& other)
{
this->swap(other);
}
Scene& operator=(Scene const&) = delete;
Scene& operator=(Scene&& other)
{
this->swap(other);
return *this;
}
void swap(Scene& other)
{
using std::swap;
swap(this->sceneManager, other.sceneManager);
swap(this->viewport, other.viewport);
swap(this->entity, other.entity);
swap(this->camera, other.camera);
swap(this->light, other.light);
}
void clear()
{
if (this->light)
{
this->sceneManager->destroyLight(this->light);
this->light = nullptr;
}
if (this->camera)
{
this->sceneManager->destroyCamera(this->camera);
this->camera = nullptr;
}
if (this->entity)
{
this->sceneManager->destroyEntity(this->entity);
this->entity = nullptr;
}
this->sceneManager = nullptr;
}
~Scene()
{
this->clear();
}
// -- Non-owning
Ogre::SceneManager* sceneManager{ nullptr };
Ogre::Viewport* viewport{ nullptr };
// -- Owning
Ogre::Entity* entity{ nullptr };
Ogre::Camera* camera{ nullptr };
Ogre::Light* light{ nullptr };
};
*Here is the code for that video (http://youtu.be/b_3xfFuwGOQ). (Needs Qt5)
http://qt.gitorious.org/qt-labs/qmlogre
There is also Cutexture (Needs Qt4)
https://github.com/advancingu/Cutexture
http://i56.tinypic.com/b6xb83.jpg
Here is a Qt4 Ogre Widget example. (Needs Qt4)
http://zester.googlecode.com/files/QtCreatorOgre2.zip*

How to make a QWidget alpha-transparent

I need to create an alpha transparent widget, it's basically a navigation bar with a shadow and the widgets below need to be partially visible through the shadow. The widget loads a PNG then draws it on the paint event. The problem is that the shadow is all black and is not alpha-transparent.
This is the code I'm currently using:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
setAttribute(Qt::WA_NoBackground, true); // This is supposed to remove the background but there's still a (black) background
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QWidget::paintEvent(event);
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, backgroundPixmap_->width(), backgroundPixmap_->height(), *backgroundPixmap_);
x += backgroundPixmap_->width();
}
}
Does anybody know what I need to change to make sure the widget is really transparent?
You're doing too much work :-)
The setAttribute call is not necessary. By default, a widget will not draw anything on its background (assuming Qt >= 4.1). Calling QWidget::paintEvent is also unnecessary - you don't want it to do anything.
Rather than doing the pattern fill yourself, let Qt do it with a QBrush:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
// debug check here:
if (!backgroundPixmap_->hasAlphaChannel()) {
// won't work
}
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QPainter painter(this);
painter.fillRect(0, 0, width(), height(), QBrush(*backgroundPixmap));
}
Adjust the height parameter if you don't want the pattern to repeat vertically.
Are you sure your PNG file is actually transparent? The following (which is essentially what you are doing) is working for me. If this fails on your machine, perhaps include what version of Qt you are using, and what platform.
#include <QtGui>
class TransparentWidget : public QWidget {
public:
TransparentWidget()
: QWidget(),
background_pixmap_(":/semi_transparent.png") {
setFixedSize(400, 100);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, background_pixmap_);
x += background_pixmap_.width();
}
}
private:
QPixmap background_pixmap_;
};
class ParentWidget : public QWidget {
public:
ParentWidget() : QWidget() {
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(new TransparentWidget);
layout->addWidget(new QPushButton("Button"));
setLayout(layout);
setBackgroundRole(QPalette::Dark);
setAutoFillBackground(true);
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
ParentWidget w;
w.show();
return app.exec();
}

Resources