[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
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
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…
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.
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)
Hello
Try to look this example elastic nodes [qt-project.org]
@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.
@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.
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 :)
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:
- #ifndef PIANOKEY_H
- #define PIANOKEY_H
- #include <QWidget>
- {
- Q_OBJECT
- public:
- private:
- bool isPressed, isBlack;
- protected:
- };
- #endif // PIANOKEY_H
- #include "pianokey.h"
- #include <QPainter>
- QColor drawColor;
- if (isBlack)
- if (isPressed)
- else
- else
- if (isPressed)
- else
- painter.fillRect(rect(), drawColor);
- }
- isPressed = 1;
- repaint();
- }
- isPressed = 0;
- repaint();
- }
Then, the piano widget:
- #ifndef WIDGET_H
- #define WIDGET_H
- #include <QWidget>
- #include <QHBoxLayout>
- #include "pianokey.h"
- {
- Q_OBJECT
- public:
- ~Widget();
- private:
- void arrangeBlackKeys();
- protected:
- };
- #endif // WIDGET_H
- #include "widget.h"
- // create white keys and add to layout
- for (int x = 0; x < 7; ++x) {
- whiteKeys << new PianoKey(0, this);
- layout->addWidget(whiteKeys[x]);
- }
- // create black keys
- for (int x = 0; x < 5; ++x)
- blackKeys << new PianoKey(1, this);
- // determine which white keys have black keys
- blackLocations << 0 << 1 << 3 << 4 << 5;
- resize(800, 600);
- //put black keys where they belong
- arrangeBlackKeys();
- }
- Widget::~Widget() {}
- void Widget::arrangeBlackKeys() {
- int keyWidth = whiteKeys[0]->width() / 1.6;
- int keyHeight = whiteKeys[0]->height() / 1.6;
- int offset = whiteKeys[0]->width() / 1.5;
- int xLocation, yLocation = whiteKeys[0]->pos().y();
- for (int x = 0; x < blackKeys.length(); ++x) {
- xLocation = blackLocations[x];
- blackKeys[x]->resize(keyWidth, keyHeight);
- blackKeys[x]->move(whiteKeys[xLocation]->pos().x() + offset, yLocation);
- }
- }
- arrangeBlackKeys();
- }
And finally, the straightforward main.cpp:
- #include <QtGui/QApplication>
- #include "widget.h"
- int main(int argc, char *argv[]) {
- Widget w;
- w.show();
- return a.exec();
- }
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.
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.
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?
You must log in to post a reply. Not a member yet? Register here!





