PDA

View Full Version : QListView and column moving



gadnio
5th January 2006, 14:57
Hi there,
I don't know how to import the thread from your old site(qtforum.org), so I'm asking here:
Is there a way to move columns of a list view programatically?
Basically, here's what I do:
I change the list view header's columns with


list_view->header()->moveColumn( column_no, to_idx );

but that does not change the contents.
Having looked through the QT sources, the ListView connects to the moved() signal emitted from the QHeader and in the slot that handles the signal triggerUpdate() is called.
I have tried this myself, e.g.


list_view->header()->moveColumn( column_no, to_idx );
list_view->triggerUpdate();

No success, though. At some reason, the list view does not want to repaint its contents.
So, how the hell can i swap/move columns without iterating through all the items and swapping text as a mad one (that is stupid and slow enough, i want the operation to be faster).
Pleeeeeeease help!

wysota
5th January 2006, 15:26
Try emitting indexChange(...) from QHeader.


void QHeader::indexChange ( int section, int fromIndex, int toIndex ) [signal]
This signal is emitted when the user moves section section from index position fromIndex, to index position toIndex.

gadnio
5th January 2006, 15:32
Ok, HOW to do that? How to emit a signal from another class?
(Sorry for the might-be silly question, though, but the QListView has its own header and I do not know how to make it emit the wanted signal)

jacek
5th January 2006, 15:38
So, how the hell can i swap/move columns without iterating through all the items and swapping text as a mad one (that is stupid and slow enough, i want the operation to be faster).
First of all, try disabling updates (using QWidget::setUpdatesEnabled()) while you switch columns. This should reduce the flicker.

Another way you could try requires subclassing QListViewItem. You will also need a mapping that maps logical columns (those which you use in your program) to physical ones (those that are visible in the view). This map should be shared among all items.

Now YourViewItem::paintCell() should be somthing like this:


void YourViewItem::paintCell ( QPainter * p, const QColorGroup& cg, int column, int width, int align )
{
QListViewItem::paintCell( p, cg, _map[ column ], width, align );
}

Probably you will have to change other methods too (AFAIR similar solution was discussed at QtForum).

Now if you want to switch columns, all you have to do is to update the map and redraw QListView contents.

You could also experiment with drag & drop mechanism (something tells me that you can use it to switch columns, but I'm not sure if it works).

gadnio
5th January 2006, 16:20
Okay, the best thing to do here is to swap the text of the columns as a mad man...
WHY the hell is it so hard? I have examined most of the QHeader's source and the QListView's source and didn't find a reason why


list_view->moveSection( section, pos );
list_view->triggerUpdate();

does not work! No matter what I do, I cannot change this behaviour. Any ideas? (The map example does not work, though, it gets very messy when the user starts to move the sections, etc. and the QHeader does not handle drag and drop, but change the columns by doing some strange things in responce to mouse events). Although reviewed most of that things, didn't find a real reason why the above code does not work.
Any help will be appreciated.

wysota
5th January 2006, 16:27
Ok, HOW to do that? How to emit a signal from another class?


Subclass QHeader. Of course you'll have to attach the new header class to the list view for it to work, probably also by subclassing. QListView should be able to drag&drop columns. You could look at the code which does that to see how it is done internally. Or see what is conencted to that indexChange() signal and call that method directly.

gadnio
5th January 2006, 16:39
I can subclass QHeader and make moveSection to emit the signal when done, but setting it to the list view -- that's the problem. I cannot access the QListViewPrivate class which holds the header (the QListView itself has some private members attatched to the QHeader's signals. And, some code from the QListView itself:


//this is from QHeader::mouseReleaseEvent
void QHeader::mouseReleaseEvent( QMouseEvent *e )
{
if ( e->button() != LeftButton )
return;
int oldOldHandleIdx = oldHandleIdx;
State oldState = state;
state = Idle;
switch ( oldState ) {
case Pressed: {
int section = d->i2s[handleIdx];
emit released( section );
if ( sRect( handleIdx ).contains( e->pos() ) ) {
oldHandleIdx = handleIdx;
emit sectionClicked( handleIdx );
emit clicked( section );
} else {
handleIdx = oldHandleIdx;
}
repaint(sRect( handleIdx ), FALSE);
if ( oldOldHandleIdx != handleIdx )
repaint(sRect(oldOldHandleIdx ), FALSE );
} break;
case Sliding: {
int c = orient == Horizontal ? e->pos().x() : e->pos().y();
c += offset();
if ( reverse() )
c = d->lastPos - c;
handleColumnResize( handleIdx, c - d->pressDelta, TRUE );
} break;
case Moving: {
#ifndef QT_NO_CURSOR
unsetCursor();
#endif
int section = d->i2s[handleIdx];
if ( handleIdx != moveToIdx && moveToIdx != -1 ) {
moveSection( section, moveToIdx );
handleIdx = oldHandleIdx;
//---------------------------------------------------------------------
// looks like all we do here is call moveSection and then emit signals and everyone is happy
//---------------------------------------------------------------------
emit moved( handleIdx, moveToIdx );
emit indexChange( section, handleIdx, moveToIdx );
emit released( section );
repaint(); // a bit overkill, but removes the handle as well
} else {
if ( sRect( handleIdx).contains( e->pos() ) ) {
oldHandleIdx = handleIdx;
emit released( section );
emit sectionClicked( handleIdx );
emit clicked( section );
} else {
handleIdx = oldHandleIdx;
}
repaint(sRect( handleIdx ), FALSE );
if(oldOldHandleIdx != handleIdx)
repaint(sRect(oldOldHandleIdx ), FALSE );
}
break;
}
case Blocked:
//nothing
break;
default:
// empty, probably. Idle, at any rate.
break;
}
}
//-----------------------------------------------------------------------------
//this is inside the QListView's constructor
//-----------------------------------------------------------------------------

connect( d->h, SIGNAL(sizeChange(int,int,int)),
this, SLOT(handleSizeChange(int,int,int)) );
connect( d->h, SIGNAL(indexChange(int,int,int)),
this, SLOT(handleIndexChange()) );
connect( d->h, SIGNAL(sectionClicked(int)),
this, SLOT(changeSortColumn(int)) );
connect( d->h, SIGNAL(sectionHandleDoubleClicked(int)),
this, SLOT(adjustColumn(int)) );
connect( horizontalScrollBar(), SIGNAL(sliderMoved(int)),
d->h, SLOT(setOffset(int)) );
connect( horizontalScrollBar(), SIGNAL(valueChanged(int)),
d->h, SLOT(setOffset(int)) );

//-----------------------------------------------------------------------------
//this is the handleIndexChange() method
//-----------------------------------------------------------------------------
/*!
\internal
Handles renaming when sections are being swapped by the user.
*/

void QListView::handleIndexChange()
{
if ( isRenaming() ) {
if ( d->defRenameAction == QListView::Reject ) {
currentItem()->cancelRename( currentItem()->renameCol );
} else {
currentItem()->okRename( currentItem()->renameCol );
}
}
triggerUpdate();
}

Apparently, all it does is some checks that I know in my case are all true (they PASS when checked by hand) and calls triggerUpdate(). When I call triggerUpdate after the column has moved, nothing happens.

wysota
5th January 2006, 17:05
You could just copy this column moving code to your own (public?) method and attach the header to the list view.

How to do it (three ways):

Subclass QListView and create a copy of your own header instead of QHeader.
Use QObject::child() to grab a pointer to the header and modify it
<hackish>Use header() method of QListView and cast it to (QHeader*) to remove the const constraint. I don't know if it'll work, but you might try it.</hackish>


Whichever way you use, make sure to connect all the proper signals and slots.

gadnio
5th January 2006, 17:09
Can you help me get working the following sollution to the problem?
Can it be done this way?


//-----------------------------------------------------------------------------
void nListView::move_column( int column_index, int position )
{
QHeader * h = header();
QPoint start_pos( h->sectionRect( column_index ).top() + 1, h->sectionRect( column_index ).left() + 1 );
QPoint end_pos( h->sectionRect( h->sectionAt( position ) ).top() + 1, h->sectionRect( h->sectionAt( position ) ).left() + 1 );
start_pos = header()->mapFromGlobal( mapToGlobal( start_pos ) );
end_pos = header()->mapFromGlobal( mapToGlobal( end_pos ) );
QMouseEvent * mouse_event = new QMouseEvent( QEvent::MouseButtonPress, start_pos, LeftButton, NoButton );
qApp->postEvent( h, mouse_event );
mouse_event = new QMouseEvent( QEvent::MouseMove, end_pos, LeftButton, LeftButton );
qApp->postEvent( h, mouse_event );
mouse_event = new QMouseEvent( QEvent::MouseButtonRelease, end_pos, NoButton, LeftButton );
qApp->postEvent( h, mouse_event );
// int old_pos = header()->mapToIndex( column_index );
// header()->moveSection( column_index, position );
// column_moved( column_index, old_pos, position );
// triggerUpdate();
}

wysota
5th January 2006, 17:40
The funny thing is that calling triggerUpdate() should be sufficient.

jacek
5th January 2006, 18:13
The solution is far more easier than I thought (tested under Qt 3.3.5 on PLD Linux):


#include <qapplication.h>
#include <qheader.h>
#include <qlistview.h>
#include <qpushbutton.h>
#include <qvbox.h>

class Test : public QVBox
{
Q_OBJECT
public:
Test() : idx( 2 )
{
_lv = new QListView( this );
_lv->addColumn( "A" );
_lv->addColumn( "B" );
_lv->addColumn( "C" );

new QListViewItem( _lv, "a1", "b1", "c1" );
new QListViewItem( _lv, "a2", "b2", "c2" );
new QListViewItem( _lv, "a3", "b3", "c3" );

QPushButton *pb = new QPushButton( "Switch", this );
connect( pb, SIGNAL( clicked() ), this, SLOT( switchColumns() ) );
}

private slots:
void switchColumns()
{
_lv->header()->moveSection( 0, idx );
_lv->triggerUpdate();
_lv->header()->update();

idx = 2 - idx;
}

private:
QListView *_lv;
int idx;
};

int main( int argc, char **argv )
{
QApplication app( argc, argv );

Test mw;

app.setMainWidget( &mw );
mw.show();

return app.exec();
}

#include "main.moc"