Best practice to handle multiple documents in QMdiArea? - qt

I need to design and develop a kind of graphics piece of software, which can edit customized files containing graphical elements, etc.
I expect the piece of software to contain many documents thanks to the QMdiArea which is actually my central widget inside of my QMainWindow.
For each document, I will need both a QGraphicsView and a QGraphicsScene as well, since they work together.
Now, my question is, should I inherit QGraphicsView with a protected/private member to its own QGraphicsScene, or should I create a class which inherits QWidget and handles instances of QGraphicsView / QGraphicsScene ?
Or is there any solution left that I didn't think about?

First off, I don't think you need a QWidget to manage the QGraphicsScene and QGraphicsView. With that in mind, the "best practice" is typically to avoid subclassing if possible. Eventually you might have to subclass QGraphicsView (if you want to change its default functionality), but nothing in your question implies that you need to right now. Also note that there is a function QGraphicsView::scene() that returns the view's current scene, so there is no need to make the scene a member (it already is).
If you ever need to access a particular view or scene, you can do something like this:
MainWindow::onActionClearActiveWindow() // just an example
{
QMdiArea *myMdiArea = static_cast<QMdiArea*>(centralWidget());
QGraphicsView *activeView = static_cast<QGraphicsView*>(myMdiArea->widget());
QGraphicsScene *activeScene = activeView->scene();
activeScene->clear();
}
See also QMdiArea::subWindowList() which returns a list of all the sub windows.

Related

How to get the size of QGraphicsView that is in a layout?

I'm new to Qt programming and I am developing a drawing application. I have a class MyWidget which has a member QGraphicsView. MyWidget is a member of another class MainWidget (which has other widgets as well and all of them are in a layout).MainWidget is the central widget of my subclass of QMainWindow.
I have created functions to scale the view with the mouse wheel event and a function to drag the scene around.
The problem is - I want to set the Scene's size to be fixed, and to be 3 times the size of the view, but since the view is managed by a layout in order to take as much space as possible I can't get the view's size?
Any help appreciated.
the size property will give you the current size of your widget:
http://qt-project.org/doc/qt-4.8/qwidget.html#size-prop
Alternatively, you could subclass QGraphicsView and re-implement the resizeEvent:
http://qt-project.org/doc/qt-4.8/qwidget.html#resizeEvent
For a full example, have a look into:
http://qt-project.org/doc/qt-4.8/widgets-scribble.html
You can definitely get the view's size. There are two ways to go about it:
Attach an event filter object to your view: myView->installEventFilter(filterObject). The filterObject's eventFilter method will be invoked for all events reaching your view, including resize events.
Use a custom view class and reimplement resizeEvent. This method gets called each time the widget is resized. Do note that designer allows you to add custom classes without having to write plugins.

Events and signals in Qt's QGraphicsItem: How is this *supposed* to work?

Like other primitives in Qt, QGraphicsItems can handle mouse events and the like. Sweet! Now say I need for an event on one QGraphicsItem to be propagated to some other QGraphicsItems in the same scene. I can think of two ways that one might approach this:
(A) The Naive Approach - Signaling
Concept : Connect sibling QGraphicsItems together with signals. Event handlers on QGraphicsItem call emit()s that evoke coordinated responses on other QGraphicItems. This follows the general design pattern established throughout the Qt framework.
Implementation : For reasons that I do not fully grasp, QGraphicsItems cannot emit() signals. It has been suggested that derived classes that also inherit from QGraphicsObject may be able to work around this. It seems to me, though, that the exclusion of emit() on QGraphicsItems was probably an intentional design decision on the part of the Qt devs and, therefore, multiple inheritance is probably not the Right Solution.
(B) Container-Level Event Handling
Concept : QGraphicsItems always exist in the context of a container of type QGraphicsScene. Events that in (A) were handled at the level of the QGraphicsItem are instead handled by an object inheriting from QGraphicsScene. This object also implements the logic for coordinating responses between sibling QGraphicsItems.
Implementation : QGraphicsScene definitely has the ability to handle events that would otherwise make their way down to QGraphicsItems. QGraphicsScene also provides the itemsAt() method for determining which of the things in it are affected by positional events, like mouse clicks. Still, building up considerable logic within a container class for coordinated action among containees feels like a failure to encapsulate properly. Bad practice? Maybe, but this seems to be the way it's done in at least one official example.
Questions
What's the Right Solution here? If not A or B, then is it something else that I haven't thought of?
Why did the Qt devs allow QGraphicsItems to receive events but not send signals? This seems like a major exception to the design pattern used throughout the framework.
An extension of this problem is communication between QGraphicsItems and higher-order container classes, like the main application. How is that meant to be addressed?
Signaling is not part of QGraphicItem because they do not inherit from QObjects. This was a design decision for performance reasons, to allow very large and fast scenes. If you decide you really need special cases for signals, QGraphicsWidget was created to fill this gap. It does inherit from QObject and lets you have a mixture of QWidget and QGraphicsItem functionality. Though it is recommended that you avoid this if your scenes are even moderately sizable.
Another option which might be relevant to your situation is to make use of the sceneEventFilter method. You can set one item to receive events for another and decide if they should be propagated or not:
http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qgraphicsitem.html#sceneEventFilter
One item can be set as the filter for multiple objects. And it can identify each individual item and event to respond to.
Generally though you should make use of the scene for coordination across its objects. That is already the pattern being used for events (the scene coordinating delivery of all events to the items).
Also, it seems that your option A is not possible because QGraphicsItem does not even have an emit method. You would need to compose a QObject instance inside it as a member and use that to emit signals. Something along the lines of myItem.qobject.emit(). Otherwise, you would have to inherit your own completely custom one from QGraphicsObject
Update 1: Addressing your main comment update
Your specific situation is a rectangle with "hot corners". I would see this being a custom QGraphicsItem. You would probably subclass QGraphicsRectItem, and then compose the child hot corner items inside as children items (setParentItem()). Now your rectangle item knows about its children and can act on them directly. You could set the rectangle item to be the sceneEventFilter for the children and handle their events directly. No need to go back up to the scene. Let all this logic live in the class.
Update 2: Addressing your added question #3
Propagating communications up beyond the scene to QWidget's has a couple approaches I can think of:
This is a situation where you can consider if you want to use a QGraphicsObject subclass as your root item, and then compose the rest of your objects as children (the rect, then the hot corners as children of the rect). This would allow the object to emit signals. For clarity they would probably still be connected to the scene, and then the higher order container of the scene would connect to the scene. You would have to choose this approach on a case by case, depending on the complexity of your scene and whether the QGraphicsObject has any performance impact on it. You should probably avoid this if you will have a great number of these instances.
You could define a callback for your rect class, for which the scene can set. Either something like: graphicsRect.resizedCallback as an attribute, or a setter graphicsRect.setResizedCallback(cbk). In your rect class, you would just call that when appropriate. If the callback it set, it can be used to call something on your scene directly. The rect class still has no knowledge of that logic. It just calls a callback.
Those are just some suggestions. I'm sure there are other ways.
I suggest B unless you have relatively few QGraphicsItems. I believe QGraphicsItems are not QObjects because there is a certain amount of overhead associated with QObjects. The QGraphicsView framework was designed to allow fast insertion and deletion of many (e.g., thousands) QGraphicsItems into a scene, so a lighter-weight approach was preferred.
I would look to the concept of parenting in QGraphicsItems. QGraphicsItems can have parents and children, and this has several effects similar to parenting amongst QObjects. For example, if you move a parent QGraphicsItem, its children will move with it, and if you delete a parent, its children will be deleted. You can access a QGraphicsItem's parent using QGraphicsItem::parentItem(), and children using QGraphicsItem::childItems(). So, you can easily access sibling items like this:
QList<QGraphicsItem *> mySiblings = this->parentItem()->childItems();
Note that mySiblings includes this.

QWidget stays within parent QWidget

i'm quite new to Qt and i've a question.
I've got an application with multiple windows/QFrame. I'd like them to only exist within the mainwindow (it's also the parent gadget). When I move them, I want the to stay within the parent gadget.
Is is possible ?
If yes, how ?
I've been through the Qt doc and i've found nothing. I though maybe a simple option can do that. Or do I have to create a new Widget with customs mouse Event methods ?
Thx
If you want a Multiple Document Interface (MDI) GUI you can use the QMdiArea and QMdiSubWindow classes to implement this. Have a look at the detailed description section of QMdiArea for using it with a QMainWindow example, but it also works on any other widget as well.

Transparent QLabel in QGraphicsView a good idea?

I'm trying to write a piece of software that allows one to click on a video frame and mark the x,y coordinates of a location in the frame. To design this, I've been wanting to use a QGraphicsView subclass and, on the mouse click event, instantiate a QLabel with a PNG image "target" on where the click occurred.
So far I've gotten everything to work except getting the QLabel to be transparent. All of the info I've found online doesn't seem to work with the latest Qt. Should I totally rethink my design and utilize some sort of integration with painting in Qt? Or is there a way to salvage the QLabel PNG implementation and indeed make the label transparent?
Thanks,
--Dany.
QLabel inside QGraphicsView is not a good idea indeed. QGraphicsView was designed to host QGraphicsItems, to display an image you should use QGraphicsPixmapItem.
Embedding QWidget into QGraphicsView has some overhead and was really designed for complex widgets that can't be easily reimplemented in terms of QGraphicItems.
I think a QGraphicsItem with ItemIgnoresTransformations flag is just what you want.

How to subclass a widget to add more elements to it?

I'm trying to make a subclass of QTableView that has an embedded QLineEdit at the top for filtering the results as-you-type. I need my table to have the same API as a normal QTableView, so I want to subclass it rather than subclassing QWidget and adding a QLineEdit and QTableView to it.
I thought I could just re-implement paintEvent(QPaintEvent*), alter the QPaintEvent's rect() to start a bit lower (the height of a QLineEdit, so it draws under it) and then pass it thru to QTableView::paintEvent(), but the QPaintEvent argument only dictates what region needs to be repainted, not the region where the widget should be painted.
Anything you do in this regard is going to be hacky and result in just as much work (probably more work) as manually mapping all of the signals and slots to a child widget. You'll need to do a lot more than just change the paint events, you'd also have to adjust all of the mouse events, adjust any update rectangles, etc.
Alternatively you could just take the QTableView class from the Qt source and modify it directly (though that will probably break the LGPL and require you to publish your source if you don't have a commercial license.) But the easiest clean method is going to be implementing a container widget with the QTableView as a child.
I would try overriding the paintEvent, changing the widget::pos to be abit lower than it is and call QTableView::paintEvent()
I have to agree with Daniel: I don't think this is the right approach. You will likely want to create a custom widget with a line edit for performing the filtering. Otherwise, you will be entering into the challenging world of Qt hacking.
If you really need to provide access to the QTableView interface then simply add a public get method that returns a reference to the table.
This is somewhat similar to how Qt provides a QTabWidget class that inherits QWidget but has a private QTabBar that it uses internally. One significant difference is that it provides a protected tabBar() accessor rather than a public one.

Resources