I need to implement a QFrame that has a title (see image). However, after reading QFrame's documentation and trying to re-implement the paintEvent(QPaintEvent*) method, I failed to find any solution.
I was wondering if any of you could provide a small exemple demonstrating how I can achieve somthing like this:
Thank you!
As an alternative to creating your own compound widget you might be able to use the contents margins to fake a title bar...
#include <QFont>
#include <QFrame>
#include <QPainter>
class titled_frame: public QFrame {
using super = QFrame;
public:
explicit titled_frame (const QString &title = "A Title Here", QWidget *parent = nullptr)
: super(parent)
, m_title(title)
{
/*
* Set the top margin based on the font height.
*/
setContentsMargins(0, 2 * fontInfo().pixelSize(), 0, 0);
}
protected:
virtual void paintEvent (QPaintEvent *event) override
{
/*
* Draw the title centred in the top margin.
*/
QPainter painter(this);
QRect title_rect(QPoint(0, 0), QSize(width(), contentsMargins().top()));
painter.fillRect(title_rect, Qt::blue);
painter.setPen(Qt::black);
painter.drawText(title_rect, Qt::AlignCenter, m_title);
/*
* Defer to the base class implementation to update everything else.
*/
super::paintEvent(event);
}
private:
QString m_title;
};
Then use as...
titled_frame tf("A Title Here");
auto *layout = new QVBoxLayout(&tf);
layout->addWidget(new QLabel("Any QLayout or QWidget here..."));
tf.show();
Related
I am trying to solve a graphics problem using the latest version of Qt. The image below shows what I managed to get so far and I will use it to explain the expected result.
I'm using a main vertical layout and within the biggest widget of the layout there is a horizontal layout with only one child: the square widget. The expected behavior would be of course to have the square widget centered horizontally and taking up the biggest space available. It is not required to use the same layout configuration, but the look of the interface should be the same.
The image above has been obtained by setting a QSizePolicy of minimumExpanding for both vertical and horizontal to the square widget and by forcing it to be square with the following code:
void SquareWidget::resizeEvent(QResizeEvent *event) {
//This is an override to the QWidget method
QSize s = size();
if (s.height()<s.width()) {
resize(s.height(), s.height());
} else {
resize(s.width(), s.width());
}
return;
}
While trying to solve this problem I went through the documentation some of the answers on this website and I couldn't find a clear answer about how to do two tasks.
First problem: how to make the widget square and keep its aspect ratio?
In this question
it is said that the method heightForWidth () doesn't work in newer versions of qt, and after a test it doesn't work for me either. The above override of resizeEvent, on the other hand, causes recursion because there are calls to resize() (and as far as I understand the layout should handle the resizing).
Second problem: how to center the square?
I tried using the layout alignment properties (center horizontally and vertically) but they cause the widget size to be immutable.
Maybe I am not understanding something about how Qt handles the widget placement. Any suggestion or clarification will be greatly appreciated.
You can do it with a QGridLayout.
Please see the attached code.
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MyWidget final : public QWidget
{
Q_OBJECT
protected:
virtual void resizeEvent(QResizeEvent * event) override;
};
class MainWindow final : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow() = default;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QGridLayout>
#include <QLabel>
#include <QResizeEvent>
#include <QSpacerItem>
void MyWidget::resizeEvent(QResizeEvent * event)
{
event->accept();
const QSize current_size = size();
const int min = std::min(current_size.width(), current_size.height());
resize(min, min);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto main_widget = new QWidget;
auto header = new QLabel("Hello World");
auto center_widget = new MyWidget;
auto footer = new QLabel("Good bye World");
auto spacer_left = new QSpacerItem(10, 10, QSizePolicy::Expanding);
auto spacer_right = new QSpacerItem(10, 10, QSizePolicy::Expanding);
auto grid_layout = new QGridLayout(main_widget);
auto center_palette = center_widget->palette();
center_palette.setColor(QPalette::Background, Qt::blue);
center_widget->setAutoFillBackground(true);
center_widget->setPalette(center_palette);
grid_layout->addWidget(header, 0, 1);
grid_layout->addItem(spacer_left, 1, 0);
grid_layout->addWidget(center_widget, 1, 1);
grid_layout->addItem(spacer_right, 1, 2);
grid_layout->addWidget(footer, 2, 1);
header->setAlignment(Qt::AlignCenter);
footer->setAlignment(Qt::AlignCenter);
setCentralWidget(main_widget);
}
Please see the result here
Rather than trying to get the widget to keep itself square and centred it might be simpler to reparent it and put the required logic in the parent widget type.
So, something like...
class keep_child_square_and_centred: public QWidget {
using super = QWidget;
public:
explicit keep_child_square_and_centred (QWidget *parent = nullptr)
: super(parent)
, m_widget(nullptr)
{}
virtual void set_widget (QWidget *widget)
{
if ((m_widget = widget))
m_widget->setParent(this);
}
virtual QSize sizeHint () const override
{
return(m_widget ? m_widget->sizeHint() : super::sizeHint());
}
protected:
virtual void resizeEvent (QResizeEvent *event) override
{
super::resizeEvent(event);
fixup();
}
private:
void fixup ()
{
if (m_widget) {
QRect r(QPoint(), QSize(height(), height()));
r.moveCenter(rect().center());
m_widget->setGeometry(r);
}
}
QWidget *m_widget;
};
Then use as...
keep_child_square_and_centred w;
SquareWidget sq;
w.set_widget(&sq);
You may still need to play around with a few settings if the parent is in a layout though.
This is my code:
#include "mainwindow.h"
#include <QDebug>
#include <QCameraInfo>
#include <QHBoxLayout>
#include <fstream>
#include <assert.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
m_QPushButton_calibration = new QPushButton("Calibrate", this);
connect(m_QPushButton_calibration, SIGNAL (released()),this, SLOT (handleButton()));
QList<QCameraInfo> l_QListQCameraInfo_available_cameras = QCameraInfo::availableCameras();
m_QWidget_viewfinder_holder = new QWidget;
m_QWidget_viewfinder_holder->setStyleSheet ("background-color: black");
m_QCameraViewfinder_viewfinder = new QCameraViewfinder(m_QWidget_viewfinder_holder);
if (l_QListQCameraInfo_available_cameras.length() >= 2)
{
m_QCamera_required_camera = new QCamera (l_QListQCameraInfo_available_cameras[1]);
m_QCamera_required_camera->setViewfinder(m_QCameraViewfinder_viewfinder);
m_QCamera_required_camera->start ();
}
m_QWidget_central = new QWidget;
m_QGridLayout_central = new QGridLayout;
m_QWidget_central->setLayout (m_QGridLayout_central);
m_QGridLayout_central->addWidget (m_QPushButton_calibration, 0, 0, 1, 1);
m_QGridLayout_central->addWidget (m_QWidget_viewfinder_holder, 1, 0, 1, 1);
this->setCentralWidget (m_QWidget_central);
m_QCameraViewfinder_viewfinder->show();
}
void MainWindow::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 30));
painter.drawText(rect(), Qt::AlignCenter, "Qt");
}
MainWindow::~MainWindow()
{
delete m_QPushButton_calibration;
delete m_QCameraViewfinder_viewfinder;
delete m_QCamera_required_camera;
delete m_QGridLayout_central;
delete m_QWidget_central;
}
void MainWindow::handleButton()
{
qDebug() << "handleButton";
}
I actually wish to draw a line on m_QWidget_viewfinder_holder widget.
How will that QPaintEvent function know where do I want it to draw line?
Can I use QPaintEvent as a member function in a class inherited from QMainWindow?
How to draw with QPainter on a specific widget from a group of widgets in QMainWindow?
You cannot draw on a widget from another widget. Each widget is responsible for drawing its own surface in the paintEvent() function.
How will that QPaintEvent function know where do I want it to draw line?
First, note that QPaintEvent is a class, not a function.
Now you probably want to talk about the paintEvent() function.
The function "knows" where to draw because it is part of a widget and the widget has a geometry.
For instance if I want to create a Rectangle widget that draws a rectangle with a 5px margin, I would write something like:
void Rectangle::paintEvent(QPaintEvent * e)
{
QRect rectangle(5, 5, width() - 5, height() - 5);
QPainter painter(this);
painter.drawRect(rectangle);
}
Can I use QPaintEvent as a member function in a class inherited from QMainWindow?
You can reimplement the paintEvent() member function in any class that inherits QWidget. If you inherits from a class that already draws something you need to call your parent class function.
void MainWindow::paintEvent(QPaintEvent *event)
{
QMainWindow::paintEvent(event); // Let QMainWindow draw itself
QPainter painter(this);
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 30));
painter.drawText(rect(), Qt::AlignCenter, "Qt");
}
However, please note that you are not likely willing to reimplement the painteEvent() of a MainWindow. What you generally want to do is to add a child widget to the MainWindow.
I actually wish to draw a line on m_QWidget_viewfinder_holder widget.
Create a ViewFinderHolder class like so:
class ViewFinderHolder: public QWidget {
Q_OBJECT
public:
explicit ViewFinder(QWidget *parent = 0)
...
}
Reimplement the paintEvent() function:
class ViewFinderHolder: public QWidget {
Q_OBJECT
public:
explicit ViewFinderHolder(QWidget *parent = 0)
...
protected:
void paintEvent(QPaintEvent *e);
}
void ViewFinderHolder::paintEvent(QPaintEvent *event)
{
QLineF line(10.0, 80.0, 90.0, 20.0);
QPainter(this);
painter.drawLine(line);
}
Finally in the MainWindow constructor replace:
m_QWidget_viewfinder_holder = new QWidget;
by:
m_QWidget_viewfinder_holder = new ViewFinder();
However, as m_QCameraViewfinder_viewfinder is a child of m_QWidget_viewfinder_holder, it will be drawn over it and may hide the drawing you did in ViewFinderHolder::paintEvent().
On a side note, you can remove the delete statements in the destructor of MainWindow. Deleting an instance of MainWidow will delete its child widgets.
Is it possible to show minimum, maximum and current selected value of QSlider? Of course I can use labels to display this, but I think there must be such possibility in QSlider
You have two options..
1) as being mentioned in comments - sub - class
2) add as many QLabel's as you like with QSlider as a parent, install eventHandler() on QSlider to catch resize event to proper position them, and obviously handle scroll events, so you can update them... So labels will just float on top of QSlider
Here is my quick implementation of a fancy slider which subclass qslider to displays the current value just below the slider handle into a tooltip.
Header
#ifndef FANCYSLIDER_H
#define FANCYSLIDER_H
#include <QSlider>
class FancySlider : public QSlider
{
Q_OBJECT
public:
explicit FancySlider(QWidget *parent = 0);
explicit FancySlider(Qt::Orientation orientation, QWidget *parent = 0);
protected:
virtual void sliderChange(SliderChange change);
};
#endif // FANCYSLIDER_H
Cpp
#include "FancySlider.h"
#include <QStyleOptionSlider>
#include <QToolTip>
FancySlider::FancySlider(QWidget * parent)
: QSlider(parent)
{
}
FancySlider::FancySlider(Qt::Orientation orientation, QWidget * parent)
: QSlider(orientation, parent)
{
}
void FancySlider::sliderChange(QAbstractSlider::SliderChange change)
{
QSlider::sliderChange(change);
if (change == QAbstractSlider::SliderValueChange )
{
QStyleOptionSlider opt;
initStyleOption(&opt);
QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
QPoint bottomRightCorner = sr.bottomLeft();
QToolTip::showText(mapToGlobal( QPoint( bottomRightCorner.x(), bottomRightCorner.y() ) ), QString::number(value()), this);
}
}
Here is my implementation, without subclassing. Instanciate a slider with a label at right, showing the current value of slider :
QWidget *tmpW1 = new QWidget(this);
QHBoxLayout *tmpH1 = new QHBoxLayout(this);
QLabel *tmpLabel = new QLabel("0");
QSlider *tmpSlider = new QSlider(Qt::Horizontal, this);
tmpSlider->setMinimum(0);
tmpSlider->setMaximum(10);
tmpSlider->setValue(5);
tmpH1->addWidget(tmpSlider);
tmpH1->addWidget(tmpLabel);
tmpW1->setLayout(tmpH1);
QObject::connect(tmpSlider, &QSlider::valueChanged, this, [=] () {
tmpLabel->setText(QString::number(tmpSlider->value()));
});
I have this little test-case which is supposed to show two widgets, with one overlapping the other completely. The one is translucent so the other widget should shine through it.
For that purpose, I set a style sheet on the one widget using a type-selector Menu (which is its class name). But instead of making the widget opaque by a factor of 200/255, it makes it completely translucent as if the type-selector doesn't apply at all to the menu object, so that I see no shine of blue anymore.
If I instead use the * selector, it works as expected. I tested the value of metaObject()->className(), which correctly reports Menu. Can anyone hint me to the error I have made please? This is a reduced testcase of a real program which shows a much more weird behavior, and I first want to make this reduced testcase work.
#include <QtGui/QApplication>
#include <QtGui/QWidget>
#include <QtGui/QLayout>
#include <QtGui/QVBoxLayout>
#include <QtGui/QLabel>
#include <QtGui/QResizeEvent>
class Menu: public QWidget {
Q_OBJECT
public:
Menu(bool translucent, QWidget *p):QWidget(p) {
if(translucent) {
setStyleSheet("Menu { background-color: rgba(0, 0, 150, 200) }");
}
QLabel *label = new QLabel(
translucent ? "\n\nHello I'm translucent" : "I'm not translucent");
label->setStyleSheet("color: white; font-size: 20pt");
QLayout *mylayout = new QVBoxLayout;
setLayout(mylayout);
mylayout->addWidget(label);
}
};
class MyWindow : public QWidget {
public:
MyWindow() {
Menu *m1 = new Menu(false, this);
Menu *m2 = new Menu(true, this);
m1->lower();
m2->raise();
}
protected:
void resizeEvent(QResizeEvent *event) {
foreach(QWidget *w, findChildren<QWidget*>()) {
w->setGeometry(0, 0, width(), height());
}
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
MyWindow w;
w.show();
app.exec();
}
When using stylesheets with QWidget subclasses, you are supposed to override paintEvent this way:
void Menu::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
See the stylesheet reference from Qt documentation.
I've been created a frameless window in Qt that have widgets and background. but i have a problem in that form, when i resize form all widgets resize good but background not
See this pic for demonstration
When no resize occured:
http://0000.2.img98.net/out.php/i20624_no-resize.jpg
when resize occured:
http://0000.2.img98.net/out.php/i20625_with-resize.jpg
and here is my code for creating Form:
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QPushButton>
#include <QLabel>
#include <QComboBox>
#include <QPixmap>
#include <QVBoxLayout>
#include <QPainter>
#include <QMouseEvent>
#include <QtGui>
#include <QSizeGrip>
class MyWidget : public QWidget {
Q_OBJECT
private:
QPushButton* button;
QLabel* label;
QComboBox* combobox;
QPixmap pixmap;
public:
explicit MyWidget(QWidget *parent = 0) : QWidget(parent, Qt::FramelessWindowHint)
{
// Create some controls
button = new QPushButton();
label = new QLabel();
combobox = new QComboBox();
QVBoxLayout* l = new QVBoxLayout();
l->addWidget(button);
l->addWidget(label);
l->addWidget(combobox);
QSizeGrip *grip = new QSizeGrip(parent);
l->addWidget(grip, 0, Qt::AlignBottom | Qt::AlignRight);
setLayout(l);
resize (400, 500);
setAttribute(Qt::WA_TranslucentBackground); // enable translucent background
pixmap = QPixmap("./1.png");
}
protected:
virtual void paintEvent (QPaintEvent* event) {
QPainter painter(this);
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 0, 0, 0));
QRect rec = pixmap.rect();
painter.drawRect(this->rect());
painter.drawPixmap(this->rect(), pixmap, rec);
}
private:
bool pressed;
QPoint mousePressPoint;
protected:
virtual void mousePressEvent ( QMouseEvent * event ) {
QWidget::mousePressEvent(event);
if (!pressed) {
pressed = true;
mousePressPoint = event->pos();
}
}
#endif // MYWIDGET_H
Since your controls are centered in the window but don't look like they are, it might indicate that there is a transparent border around the non-transparent part of the image you are using as background.
You can remove the transparency from the brush in paintEvent to confirm that, with, for example:
painter.setBrush(QColor(0, 0, 0, 255));
To be more clear, the problem is not in your code, but in the image: open the image with an editor, select only the non-transparent part, keep only that part by using the "cropping tool", and finally save the image.