I got a library to display pictures, lets call it PictureGLWidget, with:
class PictureGLWidget: public QGLWidget {
so PictureGLWidget extends QGLWidget. In PictureGlWidget the
void PictureGlWidget::mouseReleaseEvent(QMouseEvent* releaseEvent);
is already implemented.
I started an own project, lets say class MyMainWindow, where I just use a PictureGlWidget as a Pointerobject:
PictureGlWidget * myPictureGLWidget = new PictureGlWidget(...);
//..
layout->addWidget(myPictureGLWidget , 0, 1);
Here at this point, I already can see the PictureGlWidget and the corresponding picture in my MainwindowWidget. When I click in that PictureGlWidget, hold the mouse, I can move the picture (like 2D-scrolling), since it is much bigge than my little MainWindow.
Further on PictureGlWidget provides a function
bool PictureGlWidget::getPictureLocation(double& xPos, double& yPos);
which just tells me the Pictures center position, where I released the current clipping of the picture. Remeber my picture is much bigger than my little MainWindowWidget and thus much much more bigger than my PictureGLWidget. Imagine the picture has 4000x4000px (0,0 upper left). The PictureGLWidget is only to display lets say 800x800px. So the getPictureLocation() sets the center cooridinates of the current displayed picture part and it would return something like (400, 400), which might be somewhere in the midldle upper left corner.
I would like to grab the current displayed pictureparts (just a little part of that big picture) center position, after scrolling in that Widget and I released the mouse. I thought I do that by overwriting the
MyMainWindow::mouseReleaseEvent(QMouseEvent *event){ qDebug() << "Mouse released!"; }
method, but did not connected it anywhere yet. Currently it is not reacting on my mouseReleases and that text is not displayed.
The virtual protected methods in QWidget that you can override to react on some events don't need to be "connected". These are not Qt slots but classical functions Qt automatically calls when necessary.
As explained in Qt Event system doc, if the implementation PictureGlWidget::mouseReleaseEvent(QMouseEvent*) accept the event, it is not propagated to the parent widget. But you can install an event filter to your PictureGLWidget and receive events before they are sent to it.
PictureGlWidget * myPictureGLWidget = new PictureGlWidget(...);
layout->addWidget(myPictureGLWidget , 0, 1);
myPictureGLWidget->installEventFilter(this);
Then implements the right method in your main window:
bool MyMainWindow::eventFilter(QObject *object, QEvent *event)
{
if (object == myPictureGLWidget && event->type() == QEvent::MouseButtonRelease) {
QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
// Do what you need here
}
// The event will be correctly sent to the widget
return false;
// If you want to stop the event propagation now:
// return true
}
You can even decide if, after doing what you have to do, you want to stop the event, or send it to the PictureQLWidget instace (the normal behavior).
Doc:
http://doc.qt.io/qt-4.8/qobject.html#installEventFilter
http://doc.qt.io/qt-4.8/qobject.html#eventFilter
Do not forget the Q_OBJECT keyword in your MyGLwidget custom class declaration
Related
Hi all is there any way to automatically expand a QToolbar if there is too many QActions in?
Using Qt version 5.4.1 C++11
Ive tried :ui->mainToolBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred)
But this only expands it horizontally. I need it to expand vertically like the Expand button does.
Always expanding a toolbar vertically is not possible as far as I know (never seen it). A solution would be to add multiple toolbars. This way, you can arrange them one under the other.
What you can try is to add a custom widget to the toolbar that grows horizontally. This was proposed here by using a QScrollArea... Not sure whether this is exactly what you want, but it may work good enough.
This is how you can make a function to expand/retract a QToolbar. Firstly using a Forloop get all the child widgets from the QToolbar. You can use a Bool to lock to only get the first Widget which is the Expanding button/Action.
bool get_first_action{true};
for(QWidget* widget : ui->myToolBar->findChildren<QWidget*>())
{
if(get_first_action)
{
get_first_action = false;
// This is the expanding action!
m_action_expand = widget;
}
}
Or you can do this which is probably a bit safer.
for(QWidget* widget : ui->myToolBar->findChildren<QWidget*>())
{
if(widget->objectName() == "qt_toolbar_ext_button")
{
// This is the expanding action!
m_action_expand = widget;
}
}
Once you have the sneaky expanding action assign it to a member varible
// Make sure to initialize this in the constructor!
// m_action_expand = new QWidget(this // parent)
QWidget* m_action_expand;
Now create a handy function with a good name;
void MainWindow::forceToolbarExpand()
{
// Grab the position of the expanding action/widget
QPointF pos(m_action_expand->pos());
// Create a fake/dummy event that replicates the mouse button press
QMouseEvent event_press(QEvent::MouseButtonPress, pos, Qt::LeftButton,0, 0);
// Create a fake/dummy event that replicates the mouse button release
QMouseEvent event_release(QEvent::MouseButtonRelease, pos, Qt::LeftButton,0, 0);
// These two events together will fire the QAction::Toggled signal.
// Make sure to send the events!
QApplication::sendEvent(m_action_expand, &event_press);
QApplication::sendEvent(m_action_expand, &event_release);
}
And there we have it your QToolbar, if it can be expanded/retracted now will when you call this function. I'm not too sure if you can directly Moc/fake the toggled event but you can try it. I know this method works so yeah.
I am creating a simple gauge in Qt 4.7.4, and everything is working wonderfully. Except for the fact that, for the life of me, I cannot get the dial shape to paint over the text labels when it passes over them. It always paints it behind the label. I am just using a simple drawpolygon() method.
I'm thinking this has something to do about paint events? I am drawing everything inside a QFrame inside a MainWindow. I am using QFrame's paintEvent.
Edit:
The QLabels are created on start up with new QLabel(this). They are only created once, and never touched again ( Similar to manually adding them on the Ui with Designer). The drawpolygon() is in the QFrame's Paint event.
"myclass.h"
class gauge : public QFrame
{
Q_OBJECT
public:
explicit gauge(QWidget *parent = 0);
~gauge();
void setValues(int req, int Limit, bool extra=false);
private:
void drawDial();
protected:
void paintEvent(QPaintEvent *e);
};
"myclass.cpp"
void gauge::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e);
drawDial();
return;
}
void gauge::drawDial()
{
QPainter Needle(this);
Needle.save();
Needle.setRenderHint(Needle.Antialiasing, true); // Needle was Staggered looking, This will make it smooth
Needle.translate(centrePt); // Center of Widget
Needle.drawEllipse(QPoint(0,0),10,10);
Needle.restore();
Needle.end();
}
If the gauge widget and the QLabels are siblings, then you can move the gauge widget to the front by calling its raise() method.
If the QLabels are children of the gauge widget, on the other hand, then they will always display in front of it. In that case you can either reorganize your widget hierarchy so that they are siblings instead, or you can get rid of the QLabels and simply call drawText() from your paintEvent() method instead (after drawDial() returns)
I use MarbleWidget with OpenStreetMap on Qt.
Wheel zoom shows blurry images on the map. Therefore, I want to synchronize the mouse wheel with ZoomIn() and ZoomOut() inorder user to get sharp images on the map.
I want to do something like this:
QObject::connect( MarbleWidget, SIGNAL(??????), this, SLOT(wheelEvent(wheelEvent)) );
void MainWindow::wheelEvent(QWheelEvent *event){
//....
}
Is there any signal or event that I can use from MarbleWidget for ??????? above line?
And, how can I disable the mouse zoom on the MarbleWidget?
You can make your own input handler and tell MarbleWidget to use it. This will allow you to intercept mouse wheel events in the way you are asking.
Create a custom input handler
MarbleWidget uses a default input handler. Inside of MarbleInputHandler.cpp there is a function eventFilter(QObject*, QEvent*) that handles (among other things) the QEvent::Wheel event. Derive from this class and override eventFilter:
class MyMarbleInputHandler : public MarbleWidgetDefaultInputHandler
{
Q_OBJECT
public:
explicit MyMarbleInputHandler(MarbleWidget* mw) :
MarbleWidgetDefaultInputHandler(mw) {}
virtual bool eventFilter(QObject *o, QEvent *e);
signals:
void wheelEvent(QWheelEvent *event);
};
Basically, you want to intercept QEvent::Wheel and emit your own signal. Anything you don't handle yourself should be passed along to the base class.
bool MyMarbleInputHandler::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Wheel)
{
emit wheelEvent(static_cast<QWheelEvent*>(e));
return true;
}
return MarbleWidgetDefaultInputHandler::eventFilter(o, e);
}
Create a custom MarbleWidget
The constructor below shows how you can set the input handler defined above. You'll also have to wire the signal/slot.
class MyMarbleWidget : public MarbleWidget
{
Q_OBJECT
public:
explicit MyMarbleWidget()
{
MyMarbleInputHandler *myMarbleInputHandler = new MyMarbleInputHandler(this);
setInputHandler(myMarbleInputHandler);
connect(myMarbleInputHandler, SIGNAL(wheelEvent(QWheelEvent*)),
this, SLOT(handleWheelEvent(QWheelEvent*)));
}
public slots:
void handleWheelEvent(QWheelEvent *event)
{
if (event->delta() > 0) zoomIn();
else zoomOut();
}
};
handleWheelEvent() provides the code to zoom in/out. Not all scroll wheels work the same, so you'll have to figure out how much movement of the mouse wheel it will take to zoom in/out by one step. In this example, it zooms in/out one step based on each event, paying attention only to the sign of delta() and ignoring its magnitude.
You might also check out MarbleDefaultInputHandler::handleWheel() to see what's going on with the default behavior. They use interpolated/stretched bitmap images between vector layers to provide a smoother animation when zooming. Note that the plus+ and minus- keys on the keyboard will allow you to zoom to non-interpolated map levels, whereas the mouse wheel zooms using animated ("blurry") interpolated layers. This behavior is documented in a bug report.
I have 2 custom qgraphicsitems on a qgraphicsScene, rendered by a qgraphicsview. Now I want to be able to drag and drop one of the 2 items to the other kind. But which events should I reimplement for this? The documentation is a bit confusing on this.
also I want the qgraphicsitem to jump back to its original position if the user drags it to another area than the qgraphicsitem it should be dropped on.
As far as i know this is not implemented in the QGraphicsScene itself.
You must derive your own class from QGraphicsView or QGraphicsScene and then overload:
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT;
protected:
virtual void mousePressEvent(QMouseEvent* event);
virtual void mouseMoveEvent(QMouseEvent* event);
virtual void mouseReleaseEvent(QMouseEvent* event);
...
private:
QGraphicsItem *currentDraggedItem;
};
QGraphicsView gives works with view/window coordinates while QGraphicsScene works with Scene coordinates.
Add code like:
void MyGraphicsView::mousePressEvent(QMouseEvent* event)
{
currentDraggedItem = itemAt(event->pos());
QGraphicsView::mousePressEvent(event);
}
void MyGraphicsView::mouseReleaseEvent(QMouseEvent* event)
{
QGraphicsItem *foundItem = itemAt(event->pos());
if(foundItem && currentDraggedItem &&
foundItem != currentDraggedItem)
{
// Handle DragDrop Here
}
QGraphicsView::mouseReleaseEvent(event);
}
This does the job for one QGaphicsScene. If you have two of them - the both have to know each other and you must translate coordinates from the one QGraphicsView to the other QGraphicsView. using mapTo...().
The key to this is checking the QGraphicsItems rect and seeing if they intersect.
So, when the mouse down is pressed on an item, store its current position. You can now move it and wait for the mouse release. On the release of the mouse button, check if the bounding rects of the items intersect with QRect::contains(const QRectF). If they do, then you've dropped one onto the other. If not, then animate the graphics item back to the previously stored position.
Just make sure that when you're checking the bounding rects for intersection that you're doing this with both of them in scene space coordinates. Either convert them, or use QGraphicsItem::sceneBoundingRect().
I am a beginner in Qt, and I want to use QPainter.
My process is like this: I receive data coordinates (x,y) from the serial port, like (1,1), (2,3), etc. I want to draw these points in a window every time I receive data.
I see the QPainter is used in events, and just paints one time. How can I use it every time I receive data? Just like a have a signal DataCome() and a slot Paint().\
By the Way ,thx a lot to the Answer.Your advise is very Useful .
In short ,updata() or repaint() is work in this case .
I have another question .
Assume ,the serial port continuous to send the coordinate points to computer,
and I want to display all the point in the window. Is there some method ,I can leave those points came early on the window,and I just need to paint the new points?Like "hold on " in matlab. Or I need a container to store the coordinates ,and paint all of them very time.
I've set a quick example that will hopefully help you understand the mechanisms you need to utilize to accomplish your task.
It consists of a Listener class which listens for data and sends it to the Widget for drawing. In my example I've set it it up so that the data is randomly generated and sent on regular intervals using a timer, but in your case that will be your serial port data.
Since I assume what you want to do is a plot, you cannot use the paintEvent to draw single points, because each time it will show only one point and the points data will not accumulate, so you need to draw to a pixmap, which you just display in the paintEvent.
Here are the Widget and Listener classes:
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget *parent = 0) : QWidget(parent) {
resize(200, 200);
p = new QPixmap(200, 200);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.drawPixmap(0, 0, 200, 200, *p);
}
public slots:
void receiveData(int x, int y) {
QPainter painter(p);
painter.setBrush(Qt::black);
QPoint point(x, y);
painter.drawPoint(point);
data.append(point);
repaint();
}
private:
QPixmap *p;
QVector<QPoint> data;
};
class Listener : public QObject {
Q_OBJECT
public:
Listener(QObject *p = 0) : QObject(p) {
QTimer * t = new QTimer(this);
t->setInterval(200);
connect(t, SIGNAL(timeout()), this, SLOT(sendData()));
t->start();
}
signals:
void dataAvaiable(int, int);
public slots:
void sendData() {
emit dataAvaiable(qrand() % 200, qrand() % 200);
}
};
... and main:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
Listener l;
QObject::connect(&l, SIGNAL(dataAvaiable(int,int)), &w, SLOT(receiveData(int,int)));
w.show();
return a.exec();
}
So what happens is a random data will be generated every 200 msec, sent to the Widget, where it is added to the pixmap and the Widget is updated to show the new entry.
EDIT: Considering how small a point (pixel) is, you may want to draw small circles instead. You can also color the point based on its data values, so you can get a gradient, for example low values might be green, but the higher it gets it can turn yellow and finally red...
You also might want to add the received data to a QVector<QPoint> if you will need it later, this can be done in the receiveData slot.
Another thing that might be worth mentioning - in the example everything is in range 0-200, the data, the plot window - very convenient. In reality this won't be the case, so you will need to map the data to the plot size, which may be changing depending on the widget size.
Here is a template I commonly use to normalize values in some range. You may want to simplify it a bit depending on your requirements.
template <typename Source, typename Target>
Target normalize(Source s, Source max, Source min, Target floor, Target ceiling) {
return ((ceiling - floor) * (s - min) / (max - min) + floor);
}
Edit2: Added the data vector to store all the received points in numerical form.
QPainter can operate on any object that inherits from QPaintDevice.
One such object is QWidget. When one wants QWidget to re-render, you call repaint or update with the rectangular region that requires re-rendering.
repaint immediately causes the paintEvent to happen, whilst update posts a paintEvent on the event queue. Both these are slots, so it should be safe to hook them up to a signal from another thread.
Then you have to override the virtual method "paintEvent" and create a painter with the widget:
void MyWidget::paintEvent( QPaintEvent * evt )
{
QPainter painter( this );
//... do painting using painter.
}
You can look at the AnalogClock example that is distributed with Qt Help as example.
You use QPainter only in the paintEvent of a QWidget. You can do it like this:
Keep a list of received points as a member and in the paintEvent, traverse this list and paint the required points. When a new point is received, add it to the list and call widget->update(). This tells the widget to refresh itself, and the widget will call paintEvent when the time is right.
Create a QPixmap instance, then draw on that like this:
QPixmap pixmap(100, 100);
QPainter p(&pixmap);
// do some drawing
You can then do with the pixmap whatever you want: paint it in the paint event, write it to disk...