In my project, I have a QMenu with a submenu item. The submenu has a lot of items so it's height is relatively large.
I want to vertically center the submenu relative to the item that executed the submenu.
I have already subclassed the submenu I want to reposition and tried changing the geometry on "aboutToShow" just to test things, but this has no effect:
class MySubMenu : public QMenu
{
Q_OBJECT
public:
QuickMod();
~QuickMod();
private slots:
void centerMenu();
};
MySubMenu::MySubMenu()
{
connect(this, SIGNAL(aboutToShow()), this, SLOT(centerMenu()));
}
MySubMenu::~MySubMenu()
{
}
void MySubMenu::centerMenu()
{
qDebug() << x() << y() << width() << height();
setGeometry(x(), y()-(height()/2), width(), height());
}
Here is an image I quickly MS Painted that I hope visually explains what I'm trying to achieve: (Before and After)
Thanks for your time!
aboutToShow is emited before the geometry is updated so the changes are overwritten later. The solution is to change the position an instant after they are displayed, for this we can use a QTimer with a small time.
Example:
#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
#include <QTimer>
class CenterMenu: public QMenu{
Q_OBJECT
public:
CenterMenu(QWidget *parent = Q_NULLPTR):QMenu{parent}{
connect(this, &CenterMenu::aboutToShow, this, &CenterMenu::centerMenu);
}
CenterMenu(const QString &title, QWidget *parent = Q_NULLPTR): QMenu{title, parent}{
connect(this, &CenterMenu::aboutToShow, this, &CenterMenu::centerMenu);
}
private slots:
void centerMenu(){
QTimer::singleShot(0, [this](){
move(pos() + QPoint(0, -height()/2));
});
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow w;
auto fileMenu = new QMenu("Menu1");
w.menuBar()->addMenu(fileMenu);
fileMenu->addAction("action1");
fileMenu->addAction("action2");
auto children_menu = new CenterMenu("children menu");
children_menu->addAction("action1");
children_menu->addAction("action2");
children_menu->addAction("action3");
children_menu->addAction("action4");
children_menu->addAction("action5");
children_menu->addAction("action6");
fileMenu->addMenu(children_menu);
w.show();
return a.exec();
}
#include "main.moc"
Related
I am learning Qt for fun. And I got a question:
How could I drag and drop the QLabel in Qt among two different windows?
Here is what I have so far:
As you can tell from the .gif(which does not want to become downloaded and visible here for some reasons, but if you click on the link to it, you can clearly see it) provided above right now there are two main problems:
I can not move the QLabel outside of the window (and hence am not able to register the drag and drop event).
The label is flashing for some reasons when I am moving it.
Here is the relevant part of the implementation from the .gif:
#ifndef DRAGGERP_H
#define DRAGGERP_H
#include <QLabel>
#include <QApplication>
#include <QMouseEvent>
#include <QPoint>
class DraggerP : public QLabel
{
QPoint offset;
QPoint startingPosition;
public:
DraggerP(QWidget* parent = nullptr) : QLabel(parent){ }
protected:
void enterEvent(QEvent* event) override
{
QApplication::setOverrideCursor(Qt::PointingHandCursor);
}
void leaveEvent(QEvent* event) override
{
QApplication::restoreOverrideCursor();
}
void mousePressEvent(QMouseEvent* event) override
{
startingPosition = pos();
offset = QPoint(
event->pos().x() - pos().x() + 0.5*width(),
event->pos().y() - pos().y() + 0.5*height()
);
}
void mouseMoveEvent(QMouseEvent* event) override
{
move(event->pos() + offset);
}
void mouseReleaseEvent(QMouseEvent* event) override
{
move(startingPosition);
}
};
#endif // DRAGGERP_H
This is the extension of the QLabel I am using to create the drag and drop effect.
I do not need the whole solution, at least an idea of how to accomplish this and what am I doing wrong here.
Here is a pretty good example and I used it as a starting point.
That strange movement that the QLabel suffers is because the position of the QLabel now depends on the layout, the job of the layout is to establish the position of the widgets depending on the policies you establish.
The solution is not to implement those actions in the QLabel but in the MainWindow as I show below:
#include <QApplication>
#include <QLabel>
#include <QMainWindow>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QTime>
#include <QDrag>
#include <QMimeData>
#include <QMouseEvent>
class MainWindow: public QMainWindow {
QScrollArea scrollArea;
QWidget contentWidget;
QVBoxLayout lay;
public:
MainWindow(QWidget* parent=nullptr): QMainWindow(parent){
qsrand((uint) QTime::currentTime().msec());
setCentralWidget(&scrollArea);
scrollArea.setWidget(&contentWidget);
contentWidget.setLayout(&lay);
scrollArea.setWidgetResizable(true);
for(int i=0; i< 20; i++){
QLabel *label = new QLabel(QString("label %1").arg(i));
QPalette pal = label->palette();
pal.setColor(QPalette::Background, QColor(10 +qrand() % 240, 10 +qrand() % 240, 10 +qrand() % 240));
label->setAutoFillBackground(true);
label->setPalette(pal);
lay.addWidget(label);
}
setAcceptDrops(true);
}
protected:
void mousePressEvent(QMouseEvent *event){
QMainWindow::mousePressEvent(event);
QWidget *child = childAt(event->pos());
if(qobject_cast<QLabel *>(child))
createDrag(event->pos(), child);
}
void dropEvent(QDropEvent *event){
QByteArray byteArray = event->mimeData()->data("Label");
QWidget * widget = *reinterpret_cast<QWidget**>(byteArray.data());
QLabel * new_label = qobject_cast<QLabel *>(widget);
QWidget *current_children = childAt(event->pos());
QLabel * current_label = qobject_cast<QLabel*>(current_children);
int index = 0;
if(new_label){
if(current_label)
index = lay.indexOf(current_label);
else{
index = 0;
QLayoutItem *item = lay.itemAt(index);
while(item->widget()->pos().y() < event->pos().y() && item)
item = lay.itemAt(index++);
}
lay.insertWidget(index, new_label);
}
}
private:
void createDrag(const QPoint &pos, QWidget *widget){
if(widget == Q_NULLPTR)
return;
QByteArray byteArray(reinterpret_cast<char*>(&widget),sizeof(QWidget*));
QDrag *drag = new QDrag(this);
QMimeData * mimeData = new QMimeData;
mimeData->setData("Label",byteArray);
drag->setMimeData(mimeData);
QPoint globalPos = mapToGlobal(pos);
QPoint p = widget->mapFromGlobal(globalPos);
drag->setHotSpot(p);
drag->setPixmap(widget->grab());
drag->exec(Qt::CopyAction | Qt::MoveAction);
}
protected:
void dragEnterEvent(QDragEnterEvent *event){
if(event->mimeData()->hasFormat("Label"))
event->acceptProposedAction();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w1;
MainWindow w2;
w1.show();
w2.show();
return a.exec();
}
I have a display window like this:
Above display widgets are QGraphicsView widgets (they are in a QGridLayout) and what I want to achieve is that:
when user click in MainWindow, I want to seize that clicked position and decide which QGraphicsView widget contains that position and set the border of that selected QGraphicsView widget to green color. And only one QGraphicView widget can be selected at a time.
Can anyone give me some ideas?
Thanks
You can use installEventFilter for your QGraphicsViews and detect mouse press events on them. So, you can define current view and make border for it as you want. Small example:
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsView>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public:
bool eventFilter(QObject* watched, QEvent* event) override;
private:
Ui::MainWindow *ui;
QGraphicsView* view1_;
QGraphicsView* view2_;
QGraphicsView* selectedView_;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QGridLayout>
#include <QMessageBox>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
view1_(nullptr),
view2_(nullptr),
selectedView_(nullptr)
{
ui->setupUi(this);
QGridLayout* grid = new QGridLayout(this->centralWidget());
view1_ = new QGraphicsView(this);
view2_ = new QGraphicsView(this);
grid->addWidget(view1_, 0, 0);
grid->addWidget(view2_, 0, 1);
view1_->viewport()->installEventFilter(this);
view2_->viewport()->installEventFilter(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
bool MainWindow::eventFilter(QObject* watched, QEvent* event)
{
qDebug() << event->type();
if (event->type() == QEvent::MouseButtonPress)
{
if (watched == view1_->viewport()){
selectedView_ = view1_;
QMessageBox::information(this, "!", "First");
return false;
}
else if (watched == view2_->viewport()){
selectedView_ = view2_;
QMessageBox::information(this, "!", "Second");
return false;
}
}
return QMainWindow::eventFilter(watched, event);
}
If you only want to change the border color on mouse hover, you wouldn't need such complicated programming. Qt supports style sheets, just like CSS.
In this case, it's enough to attach the following stylesheet to your MainWindow.
QGraphicsView:hover {
border-style: solid;
border-width: 2px;
border-color: green;
}
There's two ways to get this done:
Using the Designer: First select the MainWindow and then in its properties panel click on the styleSheet and copy and paste the style sheet.
Using code: Use setStyleSheet(...) method of QMainWindow and pass the style sheet as a string.
What it should do: When the QComboBox is on the first item (index 0) it should hide the QStackedWidget. Which causes the QComboBox to extend as much as possible. As soon as you change the item in the QComboBox to anything, the QComboxBox should shrink and the QStackedWidget should display the correct page.
What I have and what it does instead: I test what the current index item is of the QComboBox and depending on that I change the size policies and visibility of widgets in order to obtain what I want. But it doesn't work. I tried to do workarounds but I cant seem to figure this out.
I also used qDebug() to see what currentIndexItem returns, and it seems to be stuck at 0, no matter to what index I change the QComboBox to. Do I have to update the currentItemIndex?
Note: I have a signal connected in the designer from QComboBox to QStackedWidget: currentIndexChanged(int) -> setCurrentIndex(int)
Here is my code.
My code:
interfacewindow.h
#ifndef INTERFACEWINDOW_H
#define INTERFACEWINDOW_H
#include <QMainWindow>
#include <QPainter>
#include <QComboBox>
namespace Ui
{
class InterfaceWindow;
}
class InterfaceWindow : public QMainWindow
{
Q_OBJECT
public:
explicit InterfaceWindow(QWidget *parent = 0);
~InterfaceWindow();
private:
Ui::InterfaceWindow *ui;
private slots:
void on_actionClose_triggered();
};
#endif // INTERFACEWINDOW_H
interfacewindow.cpp
#include "interfacewindow.h"
#include "ui_interfacewindow.h"
#include <QDebug>
InterfaceWindow::InterfaceWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::InterfaceWindow)
{
ui->setupUi(this);
if(ui->comboBox->currentIndex() == 0)
{
ui->comboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
ui->stackedWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
ui->stackedWidget->setVisible(false);
}
else
{
ui->comboBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
ui->stackedWidget->setVisible(true);
}
}
InterfaceWindow::~InterfaceWindow()
{
delete ui;
}
void InterfaceWindow::on_actionClose_triggered()
{
this->close();
}
main.cpp
#include <QApplication>
#include "interfacewindow.h"
int main(int argc, char **argv)
{
QApplication a(argc, argv);
InterfaceWindow w;
w.show();
return a.exec();
}
I am putting a QProgressBar inside a QSplashScreen by subclassing QSplashScreen. It overrides the drawContents() method.
I thought I had set the geometry correctly, but it renders at both the top and bottom of the screen. I don't know why. Perhaps there's another way to align it. The numbers are correct, as the image is 380x284, so a 19 height progress bar should be 265 pixels down.
Sorry for crappy picture, splash screen wasn't showing up with print screen button. It's just a 1 color white splash screen at the moment, but as you can see, progress bar at top and bottom (they're both the same colors, its the lighting from the camera).
http://i.imgur.com/p1LoJ.jpg
Another issue will be the showMessage() method of QSplashScreen. I want the message to appear above the progress bar, right-aligned... if anyone has any ideas how to do that.
splashscreen.cpp
#include "splashscreen.h"
SplashScreen::SplashScreen(QApplication *app, QWidget *parent) :
QSplashScreen(parent)
{
this->app = app;
this->setPixmap(QPixmap(":/images/splashscreen.png"));
this->setCursor(Qt::BusyCursor);
// if I dont make it a child, it *only* renders at the top
progress = new QProgressBar(this);
progress->setGeometry(0, 265, 380, 19); // puts it at bottom
progress->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
progress->setValue(0);
progress->setMaximum(100);
progress->setEnabled(true);
this->showMessage("Hello", Qt::AlignBottom);
connect(progress, SIGNAL(valueChanged(int)), this, SLOT(progressBarUpdated(int)));
}
void SplashScreen::drawContents(QPainter *painter)
{
QSplashScreen::drawContents(painter);
this->progress->render(painter);
}
void SplashScreen::progressBarUpdated(int value)
{
this->repaint();
this->app->processEvents();
}
splashscreen.h
#ifndef SPLASHSCREEN_H
#define SPLASHSCREEN_H
#include <QSplashScreen>
#include <QProgressBar>
#include <QApplication>
class SplashScreen : public QSplashScreen
{
Q_OBJECT
public:
explicit SplashScreen(QApplication *app, QWidget *parent = 0);
QProgressBar *progress;
QWidget *spacer;
QApplication *app;
public slots:
void progressBarUpdated(int value);
protected:
void drawContents(QPainter *painter);
};
#endif // SPLASHSCREEN_H
main.cpp
#include <QtGui/QApplication>
#include <time.h>
#include "splashscreen.h"
#include "mainwindow.h"
int main(int argc, char *argv[])
{
srand(time(0));
QApplication a(argc, argv);
SplashScreen *splash = new SplashScreen(&a);
splash->show();
// snip.. loading a ton of stuff into memory at startup
// if you're testing this you might have to sleep/timer here iono
MainWindow w;
splash->finish(&w);
w.show();
return app.exec();
}
You can paint progress directly, without creating QProgressBar. For example:
sp.h:
#ifndef SPLASHSCREEN_H
#define SPLASHSCREEN_H
#include <QSplashScreen>
#include <QApplication>
class SplashScreen : public QSplashScreen
{
Q_OBJECT
public:
explicit SplashScreen(QApplication *app, QWidget *parent = 0);
int m_progress;
QApplication *app;
public slots:
void setProgress(int value)
{
m_progress = value;
if (m_progress > 100)
m_progress = 100;
if (m_progress < 0)
m_progress = 0;
update();
}
protected:
void drawContents(QPainter *painter);
};
#endif // SPLASHSCREEN_H
sp.cpp
#include "sp.h"
SplashScreen::SplashScreen(QApplication *aApp, QWidget *parent) :
QSplashScreen(parent), app(aApp), m_progress(0)
{
this->setPixmap(QPixmap(":/images/splashscreen.png"));
this->setCursor(Qt::BusyCursor);
this->showMessage("Hello", Qt::AlignBottom);
}
void SplashScreen::drawContents(QPainter *painter)
{
QSplashScreen::drawContents(painter);
// Set style for progressbar...
QStyleOptionProgressBarV2 pbstyle;
pbstyle.initFrom(this);
pbstyle.state = QStyle::State_Enabled;
pbstyle.textVisible = false;
pbstyle.minimum = 0;
pbstyle.maximum = 100;
pbstyle.progress = m_progress;
pbstyle.invertedAppearance = false;
pbstyle.rect = QRect(0, 265, 380, 19); // Where is it.
// Draw it...
style()->drawControl(QStyle::CE_ProgressBar, &pbstyle, painter, this);
}
May be this helps you.
I am attempting to create a custom widget. My Widget renders itself unless it is inside a scroll area. The code below works. If I change the if(0) to an if(1) inside the MainWindow constructor, it will not render the "Hello World" string. I assume that I must (re)implement some additional methods, but so far I have not been able to find the correct ones with trial and error.
// hellowidget.h
#ifndef HELLOWIDGET_H
#define HELLOWIDGET_H
#include <QtGui>
class HelloWidget : public QWidget
{
Q_OBJECT
public:
HelloWidget(QWidget *parent = 0);
void paintEvent(QPaintEvent *event);
};
#endif // HELLOWIDGET_H
// hellowidget.cpp
#include "hellowidget.h"
HelloWidget::HelloWidget(QWidget *parent)
: QWidget(parent)
{
}
void HelloWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawText(rect(), Qt::AlignCenter, "Hello World");
}
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtGui>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include "hellowidget.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
HelloWidget *hello = new HelloWidget;
QWidget *central = hello;
if( 0 )
{
QScrollArea *scroll = new QScrollArea ;
scroll->setWidget(hello);
central = scroll;
}
setCentralWidget( central );
}
MainWindow::~MainWindow()
{
}
// main.cpp
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
You just have to give your HelloWidget a size and place.
Add this line to your code.
hello->setGeometry(QRect(110, 80, 120, 80));
Or if you want to fill the scroll area with your widget:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QScrollArea *const scroll(new QScrollArea);
QHBoxLayout *const layout(new QHBoxLayout(scroll));
HelloWidget *const hello(new HelloWidget);
hello->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
layout->addWidget(hello);
setCentralWidget( scroll );
}
Per Qt docs, "When using a scroll area to display the contents of a custom widget, it is important to ensure that the size hint of the child widget is set to a suitable value. If a standard QWidget is used for the child widget, it may be necessary to call QWidget::setMinimumSize() to ensure that the contents of the widget are shown correctly within the scroll area."
Does it work right if you follow these instructions?
I was pulling my hair out over this also, but eventually found QScrollArea's setWidgetResizable, which made the QScrollArea allow my widget to expand to take up the available space.