March 16, 2012

planarian planarian
Lab Rat
26 posts

[SOLVED] making Qpainter graphics clickable

Page  
1

Hi,

I’m new to Qt and have spent a fair amount of time perusing Blanchette/Summerfield as well as this site’s instructional materials, but still feel like I’m missing the forest for the trees.

Currently, I’m trying to figure out how to a associate a Qpainter-drawn shape with the instance of a class, such that if I click the shape, an associated method executes. More concretely, I’m trying to put together a piano-keyboard widget, with a separate graphic/instance associated with each key. What is the “correct” way to think about this?

If a similar question has already been answered on this board, I apologize in advance.

Jay

20 replies

March 16, 2012

p-himik p-himik
Lab Rat
235 posts

I think that the easiest way to do it is to use QGraphicsScene and QGraphicsWidget instead of simple painting.

March 16, 2012

timoph timoph
Lab Rat
17 posts

Quessing you’ve have subclassed QWidget and doing the painting inside paintEvent. To get mouse clicks working you also need to provide an event handler for it. see http://doc.qt.nokia.com/4.7-snapshot/eventsandfilters.html#event-handlers

March 16, 2012

Deleted Member # 269f Deleted Member # 269f
Lab Rat
224 posts

A regular QWidget provides you both with the ability to use QPainter to draw on it directly as well as mouse click event handlers, so you can implement your class by subclassing QWidget, adding the extra functionality and overload the paint and mouse click events and you are good to go. You don’t really need the QGraphicsScene/View/Item stack unless you plan view and item transformations such as zoom, rotation and so on…

March 16, 2012

Andre Andre
Robot Herder
6422 posts

When working with the QWidget approach however, you should be aware that there is no connection whatsoever between what you paint, and how you react to mouse input. Any connection that you want to create, you will have to create yourself. So, you cannot make a QPainter (or what you paint with it) clickable, but you can respond to clicks in your widget in such as way that it seems to the end user that he does click on a shape.

Using the QGraphics* stack helps you in the sense that it is quite easy to make a mapping between the two, as the framework already handles clicks. Instantiating a QGraphicsItem for each key is a valid approach, IMHO. You don’t need to use rotations or other transformations for the framework to be useful.

March 16, 2012

Asperamanca Asperamanca
Robot Herder
722 posts

I think GraphicsView is more suitable for what you want to do.

One thing that might help you, and one that would work in both the QWidget and GraphicsView world:

If you describe your keys as QPainterPath objects, you can use them both for painting and hit testing.

See QPainterPath::contains(QPointF)

March 16, 2012

Wilk Wilk
Lab Rat
119 posts

Hello
Try to look this example elastic nodes [qt-project.org]

March 16, 2012

Deleted Member # 269f Deleted Member # 269f
Lab Rat
224 posts

@Andre – his specific application is a piano simulator – which is pretty much rectangle shape for every key and the click is intercepted by the widget, there is no need to involve the actual shape into event handling, it is purely cosmetic, nor the overhead of the QGraphics* stack.

All he needs is to implement the pressed/released piano key paint event and repaint the widget upon events. In his scenario I don’t think he really needs to have multiple shapes in the widget and be able to distinguish between clicking them, he only needs one QWidged derived key class and to instance it for every key in a layout, which will help him get his keys laid out correctly as the program window size changes – something he won’t get with QGrpahicsView. That is why I recommended subclassing QWidget – it suffices, is simpler and he will be able to accomplish his assignment in an easier fashion.

Edit: Naturally, he will be able to use layout only for the white keys, the black keys he will have to bind to the position of the white keys, since the black keys must be on top of the white and be sort of floating but still positioned by the white keys which are positioned by the layout, it should work well.

March 16, 2012

Asperamanca Asperamanca
Robot Herder
722 posts

@ddriver

a) GraphicsView also has layouts. Laying out the keys would be just as simple in GraphicsView as with widgets
b) Talking overhead: Creating a QWidget is an expensive operation. By comparison, QGraphicsWidget is cheap as dirt.
c) GraphicsView is no more complicated than working with widgets. It only depends on what you are used to work with. For someone who has a lot more widget experience than GraphicsView experience, going the QWidget way is probably going to produce results sooner, true.

Interesting that no one mentioned QML so far.

March 16, 2012

Deleted Member # 269f Deleted Member # 269f
Lab Rat
224 posts

Yes, QML should be the easiest and fastest way, but the OP said he needs QPainter, which could very well mean custom painting in which case he will have to still do his own QML component in C++. But for piano keys, rounded rectangles are good enough IMO, so QML is probably the best solution, plus it supports sound playback too :)

March 16, 2012

Deleted Member # 269f Deleted Member # 269f
Lab Rat
224 posts

Since it is Friday afternoon and I had nothing better to do, here is my pick on a fairly primitive, half-baked QWidget based 1 octave piano keyboard:

First, the PianoKey class:

  1. #ifndef PIANOKEY_H
  2. #define PIANOKEY_H
  3.  
  4. #include <QWidget>
  5.  
  6. class PianoKey : public QWidget
  7. {
  8.     Q_OBJECT
  9. public:
  10.     explicit PianoKey(bool blackKey, QWidget *parent = 0);
  11.  
  12. private:
  13.     bool isPressed, isBlack;
  14.  
  15. protected:
  16.     void paintEvent(QPaintEvent *);
  17.     void mousePressEvent(QMouseEvent *);
  18.     void mouseReleaseEvent(QMouseEvent *);
  19. };
  20.  
  21. #endif // PIANOKEY_H

  1. #include "pianokey.h"
  2. #include <QPainter>
  3.  
  4. PianoKey::PianoKey(bool blackKey, QWidget *parent) : QWidget(parent),isPressed(0), isBlack(blackKey) {}
  5.  
  6. void PianoKey::paintEvent(QPaintEvent *) {
  7.     QPainter painter(this);
  8.     QColor drawColor;
  9.  
  10.     if (isBlack)
  11.         if (isPressed)
  12.             drawColor = Qt::gray;
  13.         else
  14.             drawColor = Qt::black;
  15.     else
  16.         if (isPressed)
  17.             drawColor = Qt::lightGray;
  18.         else
  19.             drawColor = Qt::white;
  20.  
  21.     painter.fillRect(rect(), drawColor);
  22. }
  23.  
  24. void PianoKey::mousePressEvent(QMouseEvent *) {
  25.     isPressed = 1;
  26.     repaint();
  27. }
  28.  
  29. void PianoKey::mouseReleaseEvent(QMouseEvent *) {
  30.     isPressed = 0;
  31.     repaint();
  32. }

Then, the piano widget:

  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3.  
  4. #include <QWidget>
  5. #include <QHBoxLayout>
  6. #include "pianokey.h"
  7.  
  8. class Widget : public QWidget
  9. {
  10.     Q_OBJECT
  11.    
  12. public:
  13.     explicit Widget(QWidget *parent = 0);
  14.     ~Widget();
  15.    
  16. private:
  17.     QHBoxLayout *layout;
  18.     void arrangeBlackKeys();
  19.  
  20.     QList<PianoKey *> whiteKeys;
  21.     QList<PianoKey *> blackKeys;
  22.     QList<int> blackLocations;
  23.  
  24. protected:
  25.     void resizeEvent(QResizeEvent *);
  26. };
  27.  
  28. #endif // WIDGET_H

  1. #include "widget.h"
  2.  
  3. Widget::Widget(QWidget *parent) : QWidget(parent) {
  4.     layout = new QHBoxLayout(this);
  5.  
  6.     // create white keys and add to layout
  7.     for (int x = 0; x < 7; ++x) {
  8.         whiteKeys << new PianoKey(0, this);
  9.         layout->addWidget(whiteKeys[x]);
  10.     }
  11.  
  12.     // create black keys
  13.     for (int x = 0; x < 5; ++x)
  14.         blackKeys << new PianoKey(1, this);
  15.  
  16.     // determine which white keys have black keys
  17.     blackLocations << 0 << 1 << 3 << 4 << 5;
  18.  
  19.     resize(800, 600);
  20.  
  21.     //put black keys where they belong
  22.     arrangeBlackKeys();
  23. }
  24.  
  25. Widget::~Widget() {}
  26.  
  27. void Widget::arrangeBlackKeys() {
  28.     int keyWidth = whiteKeys[0]->width() / 1.6;
  29.     int keyHeight = whiteKeys[0]->height() / 1.6;
  30.     int offset = whiteKeys[0]->width() / 1.5;
  31.     int xLocation, yLocation = whiteKeys[0]->pos().y();
  32.  
  33.     for (int x = 0; x < blackKeys.length(); ++x) {
  34.         xLocation = blackLocations[x];
  35.         blackKeys[x]->resize(keyWidth, keyHeight);
  36.         blackKeys[x]->move(whiteKeys[xLocation]->pos().x() + offset, yLocation);
  37.     }
  38. }
  39.  
  40. void Widget::resizeEvent(QResizeEvent *) {
  41.     arrangeBlackKeys();
  42. }

And finally, the straightforward main.cpp:

  1. #include <QtGui/QApplication>
  2. #include "widget.h"
  3.  
  4. int main(int argc, char *argv[]) {
  5.     QApplication a(argc, argv);
  6.     Widget w;
  7.     w.show();
  8.    
  9.     return a.exec();
  10. }

March 16, 2012

planarian planarian
Lab Rat
26 posts

I want to thank everyone who responded. Clearly, there are a lot of options for getting this done, which is great if you know your way around, but otherwise rather befuddling.

@ddriver: Your 130+ lines of code is well beyond any help I could have asked for. I am VERY grateful.

It’s going to take me some time to digest all of this. If necessary I’ll follow up with additional questions later.

Thanks again.

March 16, 2012

Deleted Member # 269f Deleted Member # 269f
Lab Rat
224 posts

That’s the “beauty” of programming, often you can do one thing in numerous ways :)

Be advised, this is a rather” barebone” implementation, it lacks the signals needed to make the keys actually do something besides drawing themselves, also you can’t press and slide the mouse accords the keyboard, something you need to sync the keys with their parent widget to accomplish, the keys work like regular push buttons. You will need to add a few signals and slots to communicate between the parent widget and the keys in order to make this a practically useful solution, but it is not that hard. Also you can easily modify it to draw as many keys as you need, I did only one octave just as an example.

March 27, 2012

planarian planarian
Lab Rat
26 posts

you can’t press and slide the mouse accords the keyboard, something you need to sync the keys with their parent widget to accomplish[…]You will need to add a few signals and slots to communicate between the parent widget and the keys

I’ve been working at this, but haven’t yet succeeded. My idea was to move the mouse press/release events up to the parent widget and place a new event handler, mouseMoveEvent, in class PianoKey. As I understand it, any PianoKey instance triggered by mouseMoveEvent needs to signal the remaining PianoKey instances to become inactive, so I need to have PianoKey signal itself. The problem is that when I try to setup a connection in the parent class (Widget), I can’t figure out how to direct the signal to every instance of PianoKey, since they’re embedded in QLists. Am I on the right track?

March 27, 2012

Asperamanca Asperamanca
Robot Herder
722 posts

What if you just handled the mouseMoveEvent in every key, and checked whether the button is currently pressed? In addition, using the leaveEvent, you could release a key when sliding over it.

March 27, 2012

planarian planarian
Lab Rat
26 posts

That sounds like a great idea, but I don’t understand how I would handle mouseMoveEvent “in every key.” Doesn’t the event only get triggered in the key that is touched?

Page  
1

  ‹‹ Finding the center of QGraphicsPixmapItem      Problems with HP printers ››

You must log in to post a reply. Not a member yet? Register here!