Archive for January, 2007

GtkTreeView column resizing

Monday, January 15th, 2007

The infamous column resizing bug, #316087, has a good amount of duplicates. While it might look like a programming bug which needs to be fixed, there is actually much more involved: what you see happening is actually "correct", but it looks really strange. I've been looking at some other toolkits to see whether they support a likewise expand flag and how they solve this issue. More details under the cut, since it's a pretty lengthy piece. The columns in GtkTreeView support live resizing, in fact non-live resizing isn't even supported. In general this works very well. However, GtkTreeViewColumn also has an "expand" property. All extra horizontal space left after allocating size to the columns is equally divided between all columns with the expand flag set to true. This is really useful in cases where you want to give the most important column in your view the extra space, when it is not the last (rightmost) column (by default this rightmost column gets the extra space). For example: if you are writing a media player, you would probably set the expand flag on the column holding the track name / title. What's the big deal here? Create a tree view with two expandable columns, make all columns resizable, look what happens when resizing. Let me elaborate. Suppose we have the following columns:

   [column0][expandable1    ][column2][expandable3    ][column4] 

If you try to resize the column “expandable1″ (using the handle between “expandable1″ and “column2″) you will notice that as soon as you press the mouse button the column separator will jump to the right. What's actually happening here? Well, the column is set to use-resized-width mode, and the resized-width has just been set to the actual width of the column PLUS the extra size from expand (that's where the mouse cursor was when you pressed the button). Of course, since the column is still expandable, it receives extra space on top of the resized-width. What happens if you try to resize column2? Column2 grows, while both expandable1 and expandable3 shrink. Because column2 is growing, there is less extra space to divide between expandable1 and expandable3. It looks really weird, but the behavior is 100% correct. A possible bug fix which came to mind: as soon as a user resizes a column, turn expand off on all columns, and for all columns set the width to their requested width plus the extra space allocated to them. This should actually work if you apply the patch from comment #26 in #316087. Automatically turning off the expand flags is not the nicest solution of course. After a suggestion from Nickolay, I started to look how other toolkits handle this. I've only had a quick look at some toolkits; if there are errors in my quick analysis or if there's a toolkit with a really nice resizing mode missing out, please let me know. From a quick look I figured that Windows does not support live resizing, so it falls out of the scope of this discussion. Java's Swing has a pretty thought out approach to resizing (I've looked at JTable here). The actual resizing happens in the doLayout() method. If the table itself resizes, the change of width will be distributed to all columns in the table. If one of the columns changes width, JTable will do resizing according to the resize mode set by the programmer:

  • resize-off: column widths won't be adjusted, a horizontal scroll bar is used when the sum of the column widths is bigger than the width allocated to the table.
  • resize-next: use the column after the resizing column to absorb the change in width. With this mode the column separator between adjacent columns can be changed without affected the other columns.
  • resize-subsequent: use all columns after the resizing column to absorb the changes.
  • resize-last-column: adjusts the size of the last column only.
  • resize-all-columns: with this mode all columns are adjusted, also the one which is being resized. It does look a little awkward.

The minimum and maximum column widths are respected if they are set. You can find some nice graphics which might make everything more clear here: http://java.sun.com/products/jlf/ed1/dg/higp.htm#999756. JTable does not seem to have an expand flag for columns. Cocoa's NSTableView has likewise resizing modes (although a little different here and there) since Mac OS X 10.4. It does not support an expand flag on columns. ETree/ETable does not support an expand flag like GtkTreeView does. Instead you set an expansion value and columns all get a share of the extra space based on their expansion value. Interesting is that it seems that only columns marked as resizable are allocated extra space. When live resizing, only columns to the right of the column being resized actually change width. So all in all it looks pretty similar to the (default) “resize subsequent” mode in JTable, however in ETable only resizable columns are modified. QListView in Qt3 has a resize mode property too, however it only supports none, all or the last column (deciding which column will be resized to fit the list view). Qt4 does not seem to have support for this; when the layout is updated columns are sizes based on a per-column size hint. Both do not support an expand flag. Being able to set a resize mode like in JTable in GTK+ might be nice, but the only thing it can solve is nicely resizing columns without increasing the width of the list view (ie. avoiding a horizontal scroll bar). Introducing this won't help our expand flag problems, since also with a resize mode resizing will look weird. (Unless somebody comes up with a brilliant special resize mode with takes expandable columns into account). Another interesting observation in the bug report by John Bryant says that the expand flag is actually only really used to get nice sizes for the column “on start up”. After that the expandable flag is of course still active, but the changes most probably aren't that huge. In summary, after writing this piece, I actually think that disabling the expand flags after the first column resize by the user is a fair way to solve this problem. We should keep in memory that adding resizes modes might be nice in the future. Suggestions, comments are of course welcome. If nobody has better ideas I will probably end up committing something like the patch in comment 26 mentioned above.