I am adding a compass to the top right corner of a QGraphicsView like so:
ui.compass = new QLabel(viewport());
ui.compass->setPixmap(pm);
ui.compass->setContentsMargins(viewport()->width() - pm.width() - 2, 2, 0, 0);
When the QGraphicsView is resized, I simply reset the left margin (the same code as the last line above) to keep it on the right.
The problem happens when I resize the QGraphicsView to any width wider than when the application started, in which case, the compass is clipped (as seen in the screenshot below) and eventually disappears completely, as if there is an invisible curtain is slides behind. When resizing smaller than the initial size, all works as expected. After resizing smaller, it can be resized larger also without issue until reaching that initial width again.
Edit: the application saves the geometry on close and restores next start up. If I resize the window to be larger, such that compass is not visible, close the application and start it again, the graphics view starts as the larger size and the compass is visible, until I try to resize wider again.
Edit 2: added minimum working example. Just resize the application wider than start up and watch the compass image disappear.
#include <QApplication>
#include <QMainWindow>
#include <QGraphicsView>
#include <QLabel>
#include <QPixmap>
class MyGraphicsView : public QGraphicsView
{
public:
MyGraphicsView()
: pm(":/images/Compass.svg")
, compass(new QLabel(viewport()))
{
compass->setPixmap(pm);
}
private:
void resizeEvent(QResizeEvent* event) override
{
QGraphicsView::resizeEvent(event);
QMargins margins = compass->contentsMargins();
margins.setLeft(viewport()->width() - pm.width() - 2);
compass->setContentsMargins(margins);
}
QLabel* compass;
QPixmap pm;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow win;
win.setCentralWidget(new MyGraphicsView());
win.show();
return app.exec();
}
Related
I am trying to have a window (in the form of a QWidget) consisting of both a menu on the right and a graphics area on the left.
Despite the numerous websites explaining the many ways to use QGraphicsScene and QGraphicsView, I just couldn't figure out how to do it.
Here's main.cpp modified to work on its own :
#include <QApplication>
#include <QRectF>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QPushButton>
#include <QVBoxLayout>
#include <QGraphicsRectItem>
#include <QPalette>
int main (int argc, char * argv []) {
QApplication app (argc, argv) ;
// Main window
QWidget frame ;
frame.setFixedSize(750, 550) ;
frame.show() ;
// Right side, works ok
QWidget menu (&frame) ;
menu.setFixedSize(200, 500) ;
menu.move(550, 10) ;
QPalette pal = menu.palette() ;
pal.setColor(QPalette::Background, Qt::red) ;
menu.setPalette(pal) ; // I expected this to color the whole area
// to show the extent of the zone devoted to the menu,
// but it only outlines the button
menu.show() ;
QPushButton button ("Menu", &menu) ;
button.show() ; // I didn't think this was necessary,
// but the button didn't appear without this line
// Left side, nothing displayed
QVBoxLayout layout ;
QGraphicsScene * scene = new QGraphicsScene () ;
QGraphicsView view (scene) ;
layout.addWidget(&view) ;
QWidget canvas (&frame) ;
canvas.setLayout(&layout) ;
// I found this trick to include a QGraphicsScene inside a QWidget,
// I haven't had the opportunity to see whether it really works.
scene->addItem(new QGraphicsRectItem(10, 10, 20, 20)) ;
// The above line has no visible effect
view.show() ;
return app.exec() ;
}
I would expect this to create a window, put a bunch of buttons on the right side (or in the case of the rediced code I provided, just a single button), and draw a rectangle on the left, but it leaves the whole left area blank.
Does the problem come from how I put the QGraphicsView inside the QWidget ? Or is it failing to draw because of something else ? Do I have to update the QGraphicsView to reflect the change ? Is it just out of visible range ?
Finally, is the failure to draw in any way related to the fact that the whole application crashes on line QWidget canvas (&frame) ; when closed ?
I just repaired you program a bit, so that it illustrates, what you can do with Qt and how you should likely use the framework.
I just moved the QPushButton to a QAction residing in a QMenuBar. The QMenuBar can be added to a QMainWindow, which is reasonable for a normal app.
The central widget of the QMainWindow contains the QGraphicsView. Now, you just forgot to connect the QGraphicsScene with the QGraphicsView. That was the reasons for not seeeing anything in your view.
QGraphicsView and QGraphicsScene are just a typical example for a MVC pattern. You can also add another QGraphicsView and connect it to the same QGraphicsScene.
You should also create all you objects with new, as Qt automatically disposes all its children of a QObject, if it is either deleted or leaves scope.
If you are realyl interesting into seriously learning Qt I suggest, that you are creating plenty small example programs like these. It really helped me a lot.
#include <QApplication>
#include <QMenuBar>
#include <QGraphicsView>
#include <QVBoxLayout>
#include <QGraphicsRectItem>
#include <QMainWindow>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
auto mainWindow = new QMainWindow;
auto menuBar = new QMenuBar;
auto menu = new QMenu("Menu");
auto action = new QAction("Action");
menu->addAction(action);
menuBar->addMenu(menu);
mainWindow->setMenuBar(menuBar);
auto frame = new QFrame;
frame->setLayout(new QVBoxLayout);
mainWindow->setCentralWidget(frame);
auto scene = new QGraphicsScene();
auto view=new QGraphicsView(scene);
view->setScene(scene); // That connects the view with the scene
frame->layout()->addWidget(view);
QObject::connect(action, &QAction::triggered, [&]() {
scene->addItem(new QGraphicsRectItem(10, 10, 20, 20));
});
mainWindow->show();
return app.exec();
}
I'm making a side view drag racing game in QT c++. I want to move my view around my scene from left to right. I have the scene set to 3600x800, but i want the view the be at the far left of my scene not at the center at the start. When i press W on my keyboard I want the view to move to the left for 1px. How do I do that? I can't find anything online
scene=new QGraphicsScene(this);
view = new QGraphicsView;
scene->setSceneRect(0,0,3600,800);
view->setScene(scene);
You will never find something so particular on the internet, you should look for each part separately:
If you want it to appear on the left side then you must use horizontalScrollBar() of the GraphicsView when it is displayed, we can do that with the showEvent method.
if you want to do an action when you press any key you could overwrite the keyPressEvent method.
To move the sceneRect() you must make a copy, move it and set it again.
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QKeyEvent>
#include <QScrollBar>
class GraphicsView: public QGraphicsView{
public:
using QGraphicsView::QGraphicsView;
protected:
void keyPressEvent(QKeyEvent *event){
if(event->key() == Qt::Key_W){
if(scene()){
QRectF rect = scene()->sceneRect();
rect.translate(1, 0);
scene()->setSceneRect(rect);
}
}
}
void showEvent(QShowEvent *event){
QGraphicsView::showEvent(event);
if(isVisible()){
horizontalScrollBar()->setValue(0);
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
GraphicsView view;
scene.setSceneRect(0,0,3600,800);
view.setScene(&scene);
scene.addRect(0, 200, 400, 400, Qt::NoPen, Qt::red);
view.show();
return a.exec();
}
I would like to create a QGraphicsRectItem and display its name with a QGraphicsSimpleTextItem. I want the size of the text to be unaffected by the zoom. I also want the position of the text to be centered on the QGraphicsRectItem.
Here is my attempt so far :
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QPen>
#include <QWheelEvent>
#include <cmath>
#include <QDebug>
class MainView : public QGraphicsView {
public:
MainView(QGraphicsScene *scene) : QGraphicsView(scene) { setBackgroundBrush(QBrush(QColor(255, 255, 255)));}
protected:
void wheelEvent(QWheelEvent *event) {
double scaleFactor = pow(2.0, event->delta() / 240.0);
scale(scaleFactor, scaleFactor);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 800, 800);
QGraphicsRectItem* rectItem = new QGraphicsRectItem(QRectF(0, 0, 400, 200));
rectItem->setPos(200, 200);
rectItem->setBrush(QColor(255, 0, 0));
scene.addItem(rectItem);
QGraphicsSimpleTextItem *nameItem = new QGraphicsSimpleTextItem("name", rectItem);
QFont f = nameItem->font();
f.setPointSize(12);
nameItem->setFont(f);
nameItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
nameItem->setPos(rectItem->rect().center());
MainView view(&scene);
view.show();
return a.exec();
}
Unfortunately you can see : on the capture that when I unzoom ( on the right ), the text doesn't stay inside the rectangle.
How to keep the text inside the rectangle and centered ?
Thank you.
I also want the position of the text to be centered on the QGraphicsRectItem
What you're seeing is correct, since you're scaling about the top left of the QGraphicsView and the text item is placed in the centre of the rectangle.
If you scaled about the centre of your QGraphicsRectItem, you'd see the text would maintain its position in the centre of the rect.
Another way of looking at this is to position the text in the top left corner of the rectangle. You'll note that when you scale here, the text will appear correct, up until it can no longer fit in the rectangle.
Continue scaling and you'll see that the top left of the text is still in the centre, but since the text does not obey the transform, it is pushed outside
It may appear that the top left of the text is below the rectangle, but the bounding rect of the text takes accents into consideration (e.g รจ).
So, by having the text positioned in the centre of the rect, rather than the top left, the appearance of the text being outside the rect is exacerbated.
Once you've zoomed out so far, the transform of the rect is dealing in fractions of a point size, but the non transformed text is unaffected and so the difference between, for example, 0.6 and 0.9 of a pixel is irrelevant and will be positioned at the same pixel.
You need to consider what you're trying to achieve. Is it really necessary to zoom to that extent or can you restrict it beyond a certain point, where you won't notice this issue?
My Qt application consists of several screens added on a QStackedLayout(). Now after some useraction I would like a little popup window that confirms the action and disappears after a few seconds. What I would like is a gray rectangle with black border and some text in it. No buttons, no titlebar.
I tried to do it with QMessage Box (see code below) but in general it doesnt seem to be possible to adjust border styles for QMessageBox(). Also the size can't be adjusted.
QMessageBox* tempbox = new QMessageBox;
tempbox->setWindowFlags(Qt::FramelessWindowHint); //removes titlebar
tempbox->setStandardButtons(0); //removes button
tempbox->setText("Some text");
tempbox->setFixedSize(800,300); //has no effect
tempbox->show();
QTimer::singleShot(2000, tempbox, SLOT(close())); //closes box after 2 seconds
So, how can I program a custom popup Window in Qt?
First of all, I'd like to recommend the Windows Flags Example of the Qt docs. It provides a nice sample to play around with this topic. In this sample, a QWidget is derived to show the effect of the distinct flags. This brought me to the idea that probably any QWidget can be used for this if the appropriate Qt::WindowFlags are set. I choosed QLabel because
it can display text
it inherits from QFrame and, thus, can have a frame.
Source code testQPopup.cc:
// standard C++ header:
#include <iostream>
#include <string>
// Qt header:
#include <QApplication>
#include <QLabel>
#include <QMainWindow>
#include <QTimer>
using namespace std;
int main(int argc, char **argv)
{
cout << QT_VERSION_STR << endl;
// main application
#undef qApp // undef macro qApp out of the way
QApplication qApp(argc, argv);
// setup GUI
QMainWindow qWin;
qWin.setFixedSize(640, 400);
qWin.show();
// setup popup
QLabel qPopup(QString::fromLatin1("Some text"),
&qWin,
Qt::SplashScreen | Qt::WindowStaysOnTopHint);
QPalette qPalette = qPopup.palette();
qPalette.setBrush(QPalette::Background, QColor(0xff, 0xe0, 0xc0));
qPopup.setPalette(qPalette);
qPopup.setFrameStyle(QLabel::Raised | QLabel::Panel);
qPopup.setAlignment(Qt::AlignCenter);
qPopup.setFixedSize(320, 200);
qPopup.show();
// setup timer
QTimer::singleShot(1000,
[&qPopup]() {
qPopup.setText(QString::fromLatin1("Closing in 3 s"));
});
QTimer::singleShot(2000,
[&qPopup]() {
qPopup.setText(QString::fromLatin1("Closing in 2 s"));
});
QTimer::singleShot(3000,
[&qPopup]() {
qPopup.setText(QString::fromLatin1("Closing in 1 s"));
});
QTimer::singleShot(4000, &qPopup, &QLabel::hide);
// run application
return qApp.exec();
}
I compiled with VS2013 and Qt 5.6 on Windows 10 (64 bit). The image below shows a snaphot:
To make the popup better visible (and because I liked it), I changed the background color of the QLabel for popup. And, I couldn't resist to add a little countdown.
Is there a way to programmatically invoke the Aera maximize effect using C or C++ for a specific window/window ID?
For example:
or
(source: thebuzzmedia.com)
I am using a border-less Qt window and Qt has an API for getting the window ID. I want to programmatically trigger the windows effects without the known triggers.
I don't want to talk about every single detail involved in achieving this effect, not only there's a lot that goes on but you also mentioned you understand the logic to place the windows at their specific locations. In this answer I'll address what I believe are the 2 main challenges:
How to receive and handle a maximize event?
How to create an approximation of the aero snap effect?
In order to answer the first question, we must analyze which event handlers are triggered when the window is maximized:
void resizeEvent(QResizeEvent* evt); // Invoked first,
void paintEvent(QPaintEvent* event); // then second,
void changeEvent(QEvent* evt); // and at last.
A Qt application is first notified of a resizeEvent(), which is followed by a paintEvent() to draw the window (or widget), and only after everything has been displayed, changeEvent() is invoked to let you know the widget was maximized (maybe it's a little bit late to receive such notification, I don't know).
Of all these, the only one we care about is resizeEvent(). This event handler informs the new window/widget size that can be used for comparison with the desktop size, thus allowing us to know if the event was actually a maximize request. Once we identify a maximize request, we can figure out whether the application should be maximized (and anchored) to right, left or to the center of the screen.
This would be the time to create the aero snap widget and place it on the screen as a visual clue to the user.
To answer the second question, I don't think is possible to call the native Windows API and ask it politely to perform this effect on your window. The only other logical choice is to write a code that approximates this effect ourselves.
The visual appearance can be replicated by drawing a transparent window with a shadow-ish border. The approach demonstrated in the source code below, creates and customizes a QWidget to make it behave and look like a aero snap window:
It's not the most beautiful thing in the world, I know. This demo creates a regular window for the user to interact with, and once it's maximized, it places itself to the left of the screen. To the right size of the screen it displays something that resembles an aero snap window (shown above).
The idea behind the aero snap widget is very simple: a QWidget with transparent background and a custom painting procedure. In other words, it's a transparent window which draws a rounded rectangle with a shadow and that's it.
To make it a bit more realistic, you should add some animation to resize the widget little by little. A for loop might do the trick, but if you need something fancy you'll end up using timers. If you take a look here, you can see the quickest & dirtiest method to perform animation with Qt in action, and better ways to deal with animation. However, for simple tasks like this, stick with frame-based animation.
main.cpp:
#include "window.h"
#include <QApplication>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
window.h:
#pragma once
#include "snapwindow.h"
#include <QMainWindow>
#include <QEvent>
class Window : public QMainWindow
{
public:
Window();
void resizeEvent(QResizeEvent* evt);
//void paintEvent(QPaintEvent* event);
void changeEvent(QEvent* evt);
private:
SnapWindow* _sw;
};
window.cpp:
#include "window.h"
#include "snapwindow.h"
#include <QDebug>
#include <QWindowStateChangeEvent>
#include <QApplication>
#include <QDesktopWidget>
Window::Window()
{
setWindowTitle("AeroSnap");
resize(300, 300);
_sw = new SnapWindow(this);
_sw->hide();
}
void Window::changeEvent(QEvent* evt)
{
if (evt->type() == QEvent::WindowStateChange)
{
QWindowStateChangeEvent* event = static_cast<QWindowStateChangeEvent*>(evt);
if (event->oldState() == Qt::WindowNoState &&
windowState() == Qt::WindowMaximized)
{
qDebug() << "changeEvent: window is now maximized!";
}
}
}
// resizeEvent is triggered before window_maximized event
void Window::resizeEvent(QResizeEvent* evt)
{
qDebug() << "resizeEvent: request to resize window to: " << evt->size();
QSize desktop_sz = QApplication::desktop()->size();
//qDebug() << "resizeEvent: desktop sz " << desktop_sz.width() << "x" << desktop_sz.height();
// Apparently, the maximum size a window can have in my system (1920x1080)
// is actually 1920x990. I suspect this happens because the taskbar has 90px of height:
desktop_sz.setHeight(desktop_sz.height() - 90);
// If this not a request to maximize the window, don't do anything crazy.
if (desktop_sz.width() != evt->size().width() ||
desktop_sz.height() != evt->size().height())
return;
// Alright, now we known it's a maximize request:
qDebug() << "resizeEvent: maximize this window to the left";
// so we update the window geometry (i.e. size and position)
// to what we think it's appropriate: half width to the left
int new_width = evt->size().width();
int new_height = evt->size().height();
int x_offset = 10;
setGeometry(x_offset, 45, new_width/2, new_height-45); // y 45 and height -45 are due to the 90px problem
/* Draw aero snap widget */
_sw->setGeometry(new_width/2-x_offset, 0, new_width/2, new_height);
_sw->show();
// paintEvent() will be called automatically after this method ends,
// and will draw this window with the appropriate geometry.
}
snapwindow.h:
#pragma once
#include <QWidget>
class SnapWindow : public QWidget
{
public:
SnapWindow(QWidget* parent = 0);
void paintEvent(QPaintEvent *event);
};
snapwindow.cpp:
#include "snapwindow.h"
#include <QPainter>
#include <QGraphicsDropShadowEffect>
SnapWindow::SnapWindow(QWidget* parent)
: QWidget(parent)
{
// Set this widget as top-level (i.e. owned by user)
setParent(0);
/* Behold: the magic of creating transparent windows */
setWindowFlags(Qt::Widget | Qt::FramelessWindowHint);
setStyleSheet("background:transparent;");
setAttribute(Qt::WA_NoSystemBackground, true); // speed up drawing by removing unnecessary background initialization
setAttribute(Qt::WA_TranslucentBackground);
//setAutoFillBackground(true);
/* Use Qt tricks to paint stuff with shadows */
QGraphicsDropShadowEffect* effect = new QGraphicsDropShadowEffect();
effect->setBlurRadius(12);
effect->setOffset(0);
effect->setColor(QColor(0, 0, 0, 255));
setGraphicsEffect(effect);
}
void SnapWindow::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
/* Lazy way of painting a shadow */
QPainter painter(this);
QPen pen(QColor(180, 180, 180, 200));
pen.setWidth(3);
painter.setPen(pen);
// Offset 6 and 9 pixels so the shadow shows up properly
painter.drawRoundedRect(QRect(6, 6, (width()-1)-9, (height()-1)-9), 18, 18);
}
This is just a quick demo to point you to the right direction. It is by no means a complete implementation of the effect you are looking for.
Maybe it is not what you need, but this effect is just resizing and moving window then try use Qt methods to do this.
bool left = false;
QSize size = QApplication::desktop()->size();//resolution of current screen
if(left)
{//left side
this->setGeometry(0, 0, size.width()/2, size.height());//(maybe need do some changes)
}
else
{//right side
this->setGeometry(size.width()/2, 0, size.width()/2, size.height());
}
With QApplication::desktop() it will work properly on screen with different resolutions.
In web I found something similar in winapi, but it didn't work properly:
HWND act = GetForegroundWindow();
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
The best way
Combine this approaches. For example:
HWND act = GetForegroundWindow();
bool left = false;
QSize size = QApplication::desktop()->size();
if(left)
{
this->move(0,0);
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
this->resize(size.width()/2,QApplication::desktop()->height());
}
else
{
this->move(size.width()/2,0);
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
this->resize(size.width()/2,QApplication::desktop()->height());
}
Why? Because move() regulate left and right sides, but PostMessage (winapi) set window's height properly on every screen (window will not locate lower then taskbar, as in your example)
EDIT
I changed code a little and now it is better. Yes, it is resizing again, but now it hasn't winapi code (PostMessage etc), so Photoshop doesn't catch it, there is one interesting method in Qt which called availableGeometry. It return normal height of screen which we need, with this method borderless windows perfectly simulates Aero Snap effects in different directions. It is works, maybe don't so good, but as I can see, there isn't API for Aero effects. Maybe this approach will be normal for yoo.
There is Aero Peek in Qt : http://qt-project.org/doc/qt-5/qtwinextras-overview.html , but it is can't solve this problem too.
Code:
bool left = true;
bool upper = true;
if(upper)
{
QRect rect = QApplication::desktop()->availableGeometry(-1);
this->setGeometry(rect);
}
else if(left)
{
QRect rect = QApplication::desktop()->availableGeometry(-1);
rect.setWidth(rect.width()/2);
this->setGeometry(rect);
}
else
{
QRect rect = QApplication::desktop()->availableGeometry(-1);
int half = rect.width()/2;
rect.setX(half);
rect.setWidth(half);
this->setGeometry(rect);
}
Try it with frameless window! You should choose one direction or let user choose it.