PDA

View Full Version : Can't seem to get QGraphicsView to refresh



smahnken
25th August 2008, 22:21
I'm working on a puzzle game played on a hexagonal board. The main window is designed using QT Designer, and contains a QGraphicsView widget named viewFrame.

The board on which the game is played is a class which inherits QGraphicsScene:
class Xorbz_Board : public QGraphicsScene All game pieces ("markers") are added to the scene as they are constructed.

The scene is associated with the view during code initialization:

viewFrame->setScene( (QGraphicsScene*)myBoard );


When the mouse moves over a marker, it should highlight it and available moves with a circle:

void Xorbz_Marker::hoverEnterEvent( QGraphicsSceneHoverEvent* event )
{
QGraphicsEllipseItem* e;
...

// On hoverEnter, highlight available moves
highlights = new QGraphicsItemGroup( this, this->scene() );
...

// Highlight our current node
e = new QGraphicsEllipseItem( s*n->locX(), s*n->locY(), s, s, highlights );
e->setPen( QCOLOR_GREEN );
e->setVisible( true );
scene()->addItem( e );

// Highlight available moves
...

scene()->update();
}


The highlights are subsequently deleted by the hoverLeave event.

The problem: The view doesn't refresh to show the highlights. My animated graphics don't refresh either (unless I'm resizing the window), so I'm guessing I'm doing something incorrect in an obvious fashion, but I can't see it.

I've set breakpoints in the hoverEnterEvent routine, and they do get hit when I move the mouse over the markers, so I know that the code is getting hit.

So here's the question: After I add things to my scene, I invoke the scene's update() function, and that should be it, right? Or do I need to do something with the view too? I've read in places about reimplementing paint(), but is that requisite when using primitives exclusively?

Thanks in advance!

pherthyl
25th August 2008, 22:30
I'm working on a puzzle game played on a hexagonal board. The main window is designed using QT Designer, and contains a QGraphicsView widget named viewFrame.

The board on which the game is played is a class which inherits QGraphicsScene:
class Xorbz_Board : public QGraphicsScene All game pieces ("markers") are added to the scene as they are constructed.

The scene is associated with the view during code initialization:

viewFrame->setScene( (QGraphicsScene*)myBoard );


When the mouse moves over a marker, it should highlight it and available moves with a circle:

void Xorbz_Marker::hoverEnterEvent( QGraphicsSceneHoverEvent* event )
{
QGraphicsEllipseItem* e;
...

// On hoverEnter, highlight available moves
highlights = new QGraphicsItemGroup( this, this->scene() );
...

// Highlight our current node
e = new QGraphicsEllipseItem( s*n->locX(), s*n->locY(), s, s, highlights );
e->setPen( QCOLOR_GREEN );
e->setVisible( true );
scene()->addItem( e );

// Highlight available moves
...

scene()->update();
}


The highlights are subsequently deleted by the hoverLeave event.

I assume Xorbz_Marker derives from QGraphicsItem or one of its subclasses?
I can't see anything blatantly wrong with your code, but you are doing highlighting in a very strange way. I would just set a flag on hover, then call update() on the marker item and in the paint() funciton you can then draw the highlight on the item. Creating a new highlight item is not particularly efficient.

You don't have to update the scene when adding an item, it should just work.. Might need to post some more code to see where the problem is.

smahnken
26th August 2008, 00:22
Xorbz_Marker derives from QGraphicsPixmapItem.

I'll try your suggestion; it sure seems simpler. I just don't know the painter stuff very well.

ram136682
26th August 2008, 04:20
Hi I find that you update only the graphics scene ... but the graphic view is not updated ...

Try updating the graphics view as soon as you make any change to the graphic scene ...

It should work .... :)

Regards
-Ram

salmanmanekia
26th August 2008, 07:23
.. but the graphic view is not updated .....
Regards
-Ram
I think this is not a good idea to update the whole view if there are many widgets/items in a scene then you shouldnt be updating the whole view..just update what you want ...:)

ram136682
26th August 2008, 15:38
Hi ,

Update doesn't mean that you should update the whole view.

You should try a combination of the signal
void QGraphicsScene::changed ( const QList<QRectF> & region ) and the overloaded update function ( these update functions updates only the regions
that are changed)

Please check the following link

http://doc.trolltech.com/4.4/qgraphicsscene.html#changed

Regards
-Ram

pherthyl
26th August 2008, 18:26
Hi ,

Update doesn't mean that you should update the whole view.

You should try a combination of the signal
void QGraphicsScene::changed ( const QList<QRectF> & region ) and the overloaded update function ( these update functions updates only the regions
that are changed)

You don't need to do any of that. If the view is not updating, the problem lies somewhere else. If you add an item to the scene, the framework will update the views properly. If it isn't, there is something wrong, and hacking around with manually calling update and listening for change events is not the right way to do it.

If we can see more code (compilable example is best) then we could diagnose further...

smahnken
27th August 2008, 17:00
Perhaps someone could clarify something for me. I read in the docs that update() schedules a refresh to run when control returns to the event loop. So one possibility that could exist is that I'm not returning to the event loop... However, if that was the case, I wouldn't be getting the hoverEnter and hoverLeave events, right? Hitting those is evidence that the event loop is running...?

Regarding posting more code, the only question is what to post. The code base spans better than a couple dozen source files in addition to ui and such. The source .zip file is 2.4 MB. I had it functional using QT3, and I've been in the process of migrating it to QT4 native (no QT3 compatibility classes). Add to that the fact that I've been focusing on the *cough* Windows *cough* build, and I'm not sure how much interest anybody else will have in digging through the entire code base. Still, I'd appreciate the help if anyone wants to get their hands dirty.

smahnken
28th August 2008, 18:35
More clues!

So I think I've made a discovery that ought to be an "a-ha!" moment, but I'm missing something. Hopefully someone else will know the answer.

So originally I was creating my QGraphicsView by including it in the UI designed using QT Designer, and then setting the scene during code initialization. On a whim, I decide to rip it out of the GUI design and create it dynamically during code initialization instead.

I found that if I parent the QGraphicsView to my main window, I don't get scene refreshes. If, however, I construct it with a NULL parent (so it pops up in a separate window) I DO get screen refreshes, and everything works as intended.

SO... There must be something I'm not configuring properly in the main window or its connection to the view. I tried ensuring that the MainWindow had updates enabled by calling setUpdatesEnabled( true );, and I tried connecting the scene's changed() signal to the MainWindow's update() slot. Neither seem to help.

Anybody got any other ideas?

wysota
28th August 2008, 18:56
Could we see any code related to the problem? How do you insert items to the scene? How do you initialize the view and the scene?

smahnken
28th August 2008, 19:26
Sure. It's all open-source, so I'll post anything you'd like. I just didn't want to get flamed for making any horrendously long posts... :)

The main window inherits from the GUI done in QT Designer:

class Xorbz : public QMainWindow, private Ui::XorbzGui
{
Q_OBJECT

public:
Xorbz( QWidget* parent = 0, Qt::WFlags fl = Qt::Window );
~Xorbz();

bool event( QEvent* e );

public slots:
virtual void gameNew(); ///< Create a new game (solo or network)
virtual void gameJoin(); ///< Join a network game
virtual void gameExit(); ///< Quit playing XorbZ
virtual void helpIndex(); ///< Open the help index
virtual void helpContents(); ///< Open the help contents
virtual void helpAbout(); ///< Display a dialog with XorbZ info
virtual void helpAboutQt(); ///< Display information about Qt
virtual void toolsExportLevel(); ///< Save a XorbZ level to text format
virtual void toolsImportTextLevel(); ///< Generate a XorbZ level from a text file

void playSound( SoundType s ) { if( sounds[s]) sounds[s]->play(); };

protected:
class Xorbz_Game* currentGame; ///< The current XorbZ game
void sendToServer( QString message );

private:
void updateScores();
void initGui(); ///< Initialize the GUI
Xorbz_Sound* sounds[ NumSounds ]; ///< An array of sounds for various events
Xorbz_GameInfo* myGameInfo; ///< A pointer to the game information struct
Xorbz_Board* myBoard; ///< The graphics scene
Xorbz_Level* myLevel; ///< A pointer to the XorbZ level being played
QGraphicsView* viewFrame;

QTcpSocket* tcpSocket; ///< The connection to the game server
void networkConnect( QHostAddress server );

void showStatus( QString msg, int t=0 ) { QMainWindow::statusBar()->showMessage( msg, t ); };

private slots:
void networkRead();
void networkError( QAbstractSocket::SocketError e );

};

The main window initializes like so. Note that viewFrame is the name of the QGraphicsView:

Xorbz::Xorbz( QWidget* parent, Qt::WFlags fl)
: QMainWindow( parent, fl)
{
// Create a splash screen
QSplashScreen* s = new QSplashScreen( QPixmap::QPixmap( SplashScreenPixmap ) );
s->show();
s->showMessage( "Xorbz is loading...", Qt::AlignLeft | Qt::AlignBottom, QCOLOR_WHITE );
currentGame = NULL;
myGameInfo = NULL;

// Initialize the GUI
initGui();
setUpdatesEnabled( true );

// Load the graphics
qDebug( "Loading graphics..." );
s->showMessage("Loading graphics...", Qt::AlignLeft | Qt::AlignBottom, QCOLOR_WHITE );
myBoard = new Xorbz_Board( /*this*/ );
viewFrame->setScene( (QGraphicsScene*)myBoard );

// Initialize the sound system
...

// Other initialization
s->showMessage("Finishing up initialization...", Qt::AlignLeft | Qt::AlignBottom, QCOLOR_WHITE );
// Seed random numbers
srand( QTime::QTime().secsTo(QTime::currentTime()) );
tcpSocket = new QTcpSocket(this);
myLevel = new Xorbz_Level();

// Close the splash screen
s->finish( this );
delete s;
}


The initGui() function referenced is here:

void Xorbz::initGui( void )
{
// First start the UI from the designer
setupUi( this );

// Now add all the components not present
setWindowTitle( "XorbZ" );

// Connect actions to slots
...

}

The scene is subclassed into Xorbz_Board:

class Xorbz_Board : public QGraphicsScene
{
public:
Xorbz_Board( QObject* parent = NULL );
~Xorbz_Board( void );

void setLevel( Xorbz_Level* newLevel = NULL );
void setInfo( Xorbz_GameInfo* gameInfo );
Xorbz_Marker* addMarker( Xorbz_Node* node );
Xorbz_Marker* moveMarker( Xorbz_Node* from, Xorbz_Node* to );

private:
QTimer* timer; ///< The animation timer
Xorbz_Graphics* graphics; ///< The graphics used to draw the board
Xorbz_Level* level; ///< A pointer to the current level
QList<Xorbz_Marker*> markers; ///< Markers on the board
QList<Xorbz_PlayerDisplay*> players; ///< Player info displays

};

And the board gets initialized like so:

Xorbz_Board::Xorbz_Board( QObject* parent )
: QGraphicsScene( 0, 0, 640, 480, parent )
{
// Create the graphics
graphics = new Xorbz_Graphics();

// Set the default background
setBackgroundBrush( QCOLOR_BACKGROUND );

// Setup an animation timer
timer = new QTimer( this );
connect(timer, SIGNAL(timeout()), this, SLOT(advance()));
timer->start( SceneUpdatePeriod );

}

Most of the scene items are added by the setLevel function:


void Xorbz_Board::setLevel( Xorbz_Level* newLevel )
{
// Clear the scene of all components
qDeleteAll( items() );

// Store a pointer to the new level
level = newLevel;

// Now reconstruct the board with new level items
if( level )
{
quint8 scale = level->scale;
quint8 width = level->width;
Xorbz_GNode* gn;
int i, x, y;

// Set the graphics, including background
setBackgroundBrush( *level->background );
graphics->scale( scale );

// Create GNodes and Markers
for(i=0; i<(int)(width*level->height); i++)
{
Xorbz_Node* n = level->node(i);

if( n->type() != Xorbz_Node::NodeTypeNone )
{
// Calculate the location for this node
y = level->gridOriginY + scale*(i / width) - scale/2;
x = level->gridOriginX + scale*(i % width) - ( i/width & 1 ? 0 : scale/2 );

// Create the GNode
gn = new Xorbz_GNode( n, graphics );
gn->setPos( x, y );
gn->setAcceptedMouseButtons(0);
addItem( gn );

// If the node is occupied...
if( n->type() >= Xorbz_Node::NodeTypePlayer1 )
{
// ...Create a marker
addMarker( n );
}
}
}

// Create a title
QGraphicsTextItem* title = addText( level->title, QFONT_LEVEL_TITLE );
title->setPos( LevelTitleX, LevelTitleY );
title->setZValue( LevelTitleZ );
title->setDefaultTextColor( QCOLOR_LEVEL_TITLE );
title->setVisible( true );

}

// Update the scene
update();
}

I think the core of the problem is in there someplace. I can provide the code for the other classes as well (eg Xorbz_GNode, et al) if you think the issue may be there.

Thanks!

smahnken
28th August 2008, 19:31
Here's the GUI for QT Designer...

wysota
28th August 2008, 19:34
Sure. It's all open-source, so I'll post anything you'd like. I just didn't want to get flamed for making any horrendously long posts... :)

Well... you could have limited yourself to the relevant part of the code...

Where do you accept hover events?

smahnken
28th August 2008, 19:51
The Xorbz_GNode constructor:



Xorbz_GNode::Xorbz_GNode( Xorbz_Node* node, Xorbz_Graphics* graphics )
:QGraphicsPixmapItem( graphics->marker( Xorbz_Node::NodeTypeEmpty ) ),
myNode( node ),
myGraphics( graphics )
{
currentWarpFrame = 0;
highlight = QColor::QColor("Transparent");
setZValue( LayerBackground );
setAcceptHoverEvents( true );
setVisible( true );
}

The class declaration is pretty simple:

class Xorbz_GNode : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT

public:
Xorbz_GNode( Xorbz_Node* node, Xorbz_Graphics* graphics );

inline int type() const { return Xorbz_Graphics::TypeXorbzMarker; };
inline Xorbz_Node* node() const { return myNode; };

// Override members
void advance ( int phase );
void hoverEnterEvent( QGraphicsSceneHoverEvent* event );
void hoverLeaveEvent( QGraphicsSceneHoverEvent* event );
void paint ( QPainter * p, const QStyleOptionGraphicsItem* opt, QWidget * w = 0 );

private:
QColor highlight;
Xorbz_Node* myNode;
Xorbz_Graphics* myGraphics;
int currentWarpFrame;
};

I did incorporate pherthyl's idea of just drawing the highlight in the overridden paint() function, so the hover event handler just sets the flag (a color, actually) and calls update():

void Xorbz_GNode::hoverEnterEvent( QGraphicsSceneHoverEvent* event )
{
// Highlight our current node
highlight = QCOLOR_GREEN;

update();
}

The hoverLeave event just sets the color back to "Transparent" and calls update().

wysota
28th August 2008, 20:03
And you don't see any changes when you hover over some item, right? Does the hover event handler get called at all? Could you verify that? Why do you make your item inherit QObject, by the way?

smahnken
28th August 2008, 20:09
And you don't see any changes when you hover over some item, right?
Correct.


Does the hover event handler get called at all? Could you verify that?
Yes, it does. I've confirmed it putting a breakpoint on the hoverEnter event handler; it gets hit as soon as I mouse over the item.


Why do you make your item inherit QObject, by the way?
Umm... Probably legacy code I haven't cleaned up yet. :o

wysota
28th August 2008, 21:01
What is the implementation of paint() and boundingRect() of your item?

smahnken
28th August 2008, 21:16
void Xorbz_GNode::paint ( QPainter * p, const QStyleOptionGraphicsItem* opt, QWidget * w )
{
int s = (int)boundingRect().width();

// First paint the pixmap
QGraphicsPixmapItem::paint( p, opt, w );

// Highlight appropriately
p->setPen( highlight );
p->drawEllipse( -1, -1, s, s );

// If the node is part of a warp group, draw the warp graphics
if( myNode->warps() )
{
p->drawPixmap( 0, 0, myGraphics->warp()[ currentWarpFrame ] );
}
}

There is no implementation of boundingRect(). The QGraphicsPixmapItem version should do the job... Do I need to override it?

wysota
29th August 2008, 08:32
Try surrounding the call to the base class implementation with QPainter::save() and QPainter::restore() and see if it helps. On the other hand you might be drawing the ellipse incorrectly. Try changing the code to
p->drawRect(boundingRect()); and see if it helps.