Click here to Skip to main content
Click here to Skip to main content

XPTable: .NET ListView Update

, 4 Jul 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
An update to the excellent XPTable control

Screenshot - XPTableUpdate.png

Introduction

XPTable is a customizable ListView written by Matthew Hall. Go here to see the excellent article on XPTable and to download the original source code and demo application. After using it myself and being jolly impressed with it, I thought there were still some features that would be great to have. So, I have added some extras onto the original. This article explains what features have been added and how to use them.

PS: I don't know if anyone else has made any significant additions to XPTable. If they have, it might be worth sharing them. There is an XPTable project on SourceForge. Matthew Hall seems not to be actively involved with this control anymore.

After a brief description of the new features, there is a section for each feature that describes in more detail what it allows you to do, how you code it, and a summary of the changes I have made to XPTable to get it working. I have tried to keep these additions in the same style and following the same architecture as the original version. So hopefully if you are familiar with XPTable these will seem pretty intuitive.

Disclaimer

I realise that XPTable has now been around for a while and I am obviously not aware of all the various ways people will have used it. I may have introduced changes that break your existing usage of XPTable. If so, let me know; there may be a way around it.

Summary of new features

There are four new features now supported. Here they are:

  • Column spanning: just like Colspan in HTML
  • Word wrapping: the row height is increased so that all the text in a given cell can be wrapped and shown
  • Grouping of rows: this allows you to always keep rows together, like if you have Autopreview switched on for a mailbox in Outlook
  • Multiple sort indices

Column spanning

If you know HTML, you may well have used the <td colspan=2> attribute to allow the text in one column to flow over into the next column(s). Cells in XPTable can now similarly be allowed to span over a number of following cells.

Screenshot - XPTableUpdateColspan.png

Using the code

Quite simply, a cell now has the property ColSpan, which behaves just the same way as the colspan attribute in HTML. It has a default value of 1 and setting it to something greater than 1 means that the contents of that cell will be allowed to 'spill over' into the next ColSpan - 1 cells if there are that many. This property only affects a single cell. All the other cells in other rows will behave as normal, unless you set their ColSpan too.

Cell cell = new Cell("This is text that will go over to the next column");
cell.ColSpan = 2;
    // The contents of this cell will 'spill over' into the next cell.

Note that you do not add cell objects for the cells that are "covered over" by a colspanning cell. So if you have a table with 4 columns and you want to add a row where the second column spans over 2 columns, you only need to add 3 cells to the row as follows:

Row row = new Row();
row.Cells.Add(new Cell("column 1"));     // First cell is for column 1
Cell cell = new Cell("columns 2 and 3"); // Second cell is for column 2 and 3
cell.ColSpan = 2;
row.Cells.Add(cell)
// We don't actually add a cell for column 3 on its own
row.Cells.Add(new Cell("column 4"));
    // The third cell we add is actually for column 4

Implementation

Whenever a row is rendered, each cell checks to see if has ColSpan > 1. If so, it extends the size it renders over to include the following cells as appropriate. For the purposes of allowing selection and focus, the conversion from screen coordinatess (X, Y pixels) to grid coordinates (row, column) has to take this overlap into account.

Word wrapping

Cells can have word wrapping enabled, so that the text in a cell wraps and the height of the row is increased such that all of the text is visible.

Screenshot - XPTableUpdateWordWrap.png

Using the code

Enabling word wrapping means that each row may have a different height. This possibility introduces many more calculations when rending the table. It requires many calls to Graphics.MeasureString, so if it is not required it is best to switch it off globally. It is switched off by default, so if you want to use this, you need to enable it using Table.EnableWordWrap. With that enabled, simply set the property Cell.WordWrap to true for any cell you wish to word wrap.

private void AddRowsToTable()
{
    Table table = this.table;
        // The Table control on a form - already initialised
    table.EnableWordWrap = true;
        // If false, then Cell.WordWrap is ignored

    Row row1 = new Row();
    Cell cell1 = new Cell("This is a cell with quite long text");
    cell1.WordWrap = true;
        // The row height will be increased so we can see all the text
    row1.Cells.Add(cell1);

    Row row2 = new Row();
    Cell cell2 = new Cell("This is long text that will just be truncated");
    cell2.WordWrap = false;         // Not needed - it is false by default
    row2.Cells.Add(cell2);
}

Implementation

In Table.OnPaintRows(), if the global switch is enabled, then each row is checked to see if it contains a "word wrap" cell. If it does, then the renderer for that cell is obtained. Using GetCellHeight -- a new member of ICellRender -- the minimum height required to display the whole cell content is determined. This is then used as the new height for the row.

Rows now remember the height they were when they were last rendered, which is used in loads of places where anything to do with y-coordinates is required. Only TextCellRenderer actually returns anything at the moment. All other kinds of cell just go with the table default for row height. I'm not sure how useful or possible it is to wrap any other cell types.

Row grouping

If you use Outlook, you may have noticed that in the "email listview" you can switch on Auto Preview. This adds a row underneath each email showing a bit of the email content. However, if you sort the list you will also notice that the Auto Preview row ignores the sorting process. It just sticks with its "parent" row, which does obey the sorting. This is what I was after here. Yes, I know Outlook doesn't really do it with a listview, but I was after a similar effect.

You can now attach child rows to a parent row, so that the child rows just kind of "stick" to the parent when sorted. They remain under the parent row and are kept in the order they are added to the parent. This may not be what you were thinking of when you saw "XPTable does Grouping" and if so, sorry for the disappointment: this isn't the type of grouping that allows groups to be collapsed and expanded. These "Grouped" rows look like normal rows. They just behave differently when being sorted.

Screenshot - XPTableUpdate.png

Using the code

A Row now has a SubRows property, which is a RowCollection just like the Table.Rows property. To add a sub-row, the Row is just added to the SubRows collection and not to Table.Rows. The rest is as normal:

private void AddEmailRows(TableModel table, bool read, string from,
    string sent, string subject, string preview)
{
    Row row = new Row();            // This is the parent row
    row.Cells.Add(new Cell(
        "", read ? Resources.EmailRead : Resources.EmailUnRead));
    row.Cells.Add(new Cell(from));
    row.Cells.Add(new Cell(DateTime.Parse(sent)));
    table.Rows.Add(row);

    // Add a sub-row that shows just the
    // email subject in grey (single line only)
    Row subrow = new Row();         // The subject line is a sub-row
    subrow.Cells.Add(new Cell());   // Add cells to the subrow as normal
    Cell cell = new Cell(subject);
    cell.ForeColor = Color.Gray;
    cell.ColSpan = 2;
    subrow.Cells.Add(cell);
    row.SubRows.Add(subrow);        // Add this subrow to the parent row

    // The subrow is not added directly to the
    // main table - just the parent row

    // Add a sub-row that shows just a preview of the
    // email body in blue, and wraps too
    subrow = new Row();             // The preview line is the second sub-row
    subrow.Cells.Add(new Cell());   // Add cells to the subrow as normal
    cell = new Cell(preview);
    cell.ForeColor = Color.Blue;
    cell.ColSpan = 2;
    cell.WordWrap = true;
    subrow.Cells.Add(cell);
    row.SubRows.Add(subrow);        // Add this subrow to the parent row
}

Implementation

When a sub-row is added to the SubRows collection, it is in fact added to the main table's collection of Rows behind the scenes. However, it remains in the SubRows collection too. All the main behaviour of the row -- i.e. being rendered, clickable, everything other than sorting -- is done as for normal rows because it is in the main Rows collection.

The only time the SubRows collection comes into play is when sorting takes place. The sub-rows stick to the parent row when sorting occurs by adopting the parent values when being compared to other rows. That is, unless the row is being compared to another child of the same parent or the parent itself. In that case, the index in the SubRows collection is used in the comparison. In this way, the sub-rows always appear underneath the parent row in the order they were added to the SubRows collection.

Multiple sort indices

The original version allowed the table to be sorted using a single column as the sort key, activated by clicking on the column header. This primary sort column still behaves exactly as sorting did before, but you can now specify programatically a number of columns to be used as sort keys, along with the direction to sort in for each column. These take effect when two or more rows end up having the same value in the column that is being used as the primary sort key.

So, if you have 3 columns -- i.e. Firstname, Surname and Height -- you can now specify the sort order as, in pseudo SQL: ORDER BY Surname, Firstname, Height DESC. Now if the user clicks on Surname, the list is sorted first by Surname and then by Firstname. If two people have exactly the same name, then the tallest is at the top. I don't even know what the original XPTable would do in this scenario; just output them in the order they were added to the table?

In the example below, two tables have been created. The only difference between them is that the second has multiple sort columns defined: Surname, Firstname, Height DESC. The Surname column has been clicked on both tables and the resulting sorted table is shown in the images. Without multi-column sorting, the 3 Hobbs rows end up with the Marks above Dave, and then the shortest Mark at the top. This would appear to just be the order in which they were added to the table.

When multiple sorting columns are defined, the Hobbs rows have Dave first -- correctly sorted to be above the Marks -- and the Marks are correctly sorted so that the taller one is above the shorter. Of course, when the value in the primary sorting column is unique, the extra sorting columns have no effect.

Without multi-column sorting:

Screenshot - XPTableUpdateWithOutMulti.png

With multi-column sorting:

Screenshot - XPTableUpdateWithMulti.png

Using the code

Create a SortColumnCollection and add SortColumns to it as required. Then set this collection as the ColumnModel.SecondarySortOrders property.

// Order will be Surname, Name, Height DESC
SortColumnCollection sort = new SortColumnCollection();
sort.Add(new SortColumn(3, SortOrder.Ascending));   // Surname
sort.Add(new SortColumn(2, SortOrder.Ascending));   // Name
sort.Add(new SortColumn(1, SortOrder.Descending));  // Height
table.ColumnModel.SecondarySortOrders = sort;

Implementation

The sorting classes have been refactored into Row comparison operations and Cell comparisons. The SorterBase class manages the row comparisons and will make as many Cell comparison operations as required in order to find out the correct order.

If ever a comparison operation comes back as 0 -- i.e. the rows are the same -- then the SorterBase now trundles off through its SecondarySortOrder collection. It makes the appropriate IComparer for the next secondary sorting column -- NumericComparer, etc. -- and gets the appropriate Cells from both rows being compared. Then it sees what this IComparer makes of it, taking sort order into account. This is repeated until either it runs out of secondary sorting columns or a greater-than or less-than result is returned from the comparison. Each inheritor of ComparerBase now only performs Cell-based comparisons.

History

I decided to continue from where the original left off, but jump to Version 1.1. I've included a couple of bug fixes that were mentioned on the forum.

  • 17th June, 2007 - Initial release. (1.1.0)
    • Bug Fix: CellCheckBoxEventArgs (Paul Sprague)
    • Bug Fix: Cursor bug? (Peter Stuer)
  • 2nd July, 2007 - Bug fix release. (1.1.1)
    • Bug Fix: Removed hardcoded Top alignment
    • Bug Fix: Fixed erratic painting of selection (jover)
    • Bug Fix: Fixed error if no rows added to table (jover)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

adambl

United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
QuestionDataSourceColumnBinder and Column.Visible [modified] PinmemberMember 1101384415-Aug-14 12:25 
AnswerRe: DataSourceColumnBinder and Column.Visible Pinmemberadambl18-Aug-14 2:14 
GeneralRe: DataSourceColumnBinder and Column.Visible PinmemberMember 1101384418-Aug-14 3:37 
QuestionStrange AutoResizeColumnWidths + FullRowSelect PinmemberMember 1101384415-Aug-14 3:43 
AnswerRe: Strange AutoResizeColumnWidths + FullRowSelect Pinmemberadambl15-Aug-14 3:49 
GeneralRe: Strange AutoResizeColumnWidths + FullRowSelect PinmemberMember 1101384415-Aug-14 5:22 
GeneralRe: Strange AutoResizeColumnWidths + FullRowSelect Pinmemberadambl18-Aug-14 1:43 
GeneralRe: Strange AutoResizeColumnWidths + FullRowSelect PinmemberMember 1101384418-Aug-14 2:38 
QuestionXPtable / GTK# PinmemberMember 918842522-Mar-14 23:10 
QuestionIs that update available for 64bit ? Error in 64 bit system [modified] PinmemberMember 1020747118-Nov-13 19:48 
QuestionWordWrapping problem PinmemberMember 850712912-Mar-13 23:38 
QuestionXPTable: Word Wrapping Issue PinmemberMember 850712912-Mar-13 23:33 
AnswerRe: XPTable: Word Wrapping Issue Pinmemberadambl13-Mar-13 7:00 
GeneralRe: XPTable: Word Wrapping Issue PinmemberMember 850712919-Mar-13 23:33 
Bugrowcollection bug PinmemberPaul Hildebrandt9-Jan-13 10:22 
GeneralRe: rowcollection bug Pinmemberadambl28-Jan-13 12:31 
Questiondatabinding overrides settings? PinmemberPaul Hildebrandt5-Jan-13 11:24 
AnswerRe: databinding overrides settings? Pinmemberadambl28-Jan-13 12:48 
BugIssue in showing scrollbars PinmemberCptHook17-Oct-12 4:45 
GeneralRe: Issue in showing scrollbars Pinmemberadambl19-Oct-12 13:43 
GeneralRe: Issue in showing scrollbars PinmemberCptHook31-Oct-12 0:23 
GeneralRe: Issue in showing scrollbars Pinmemberadambl28-Jan-13 12:21 
GeneralRe: Issue in showing scrollbars PinmemberCptHook29-Jan-13 0:40 
GeneralRe: Issue in showing scrollbars Pinmemberadambl29-Jan-13 13:10 
GeneralRe: Issue in showing scrollbars Pinmemberadambl29-Jan-13 23:07 
GeneralRe: Issue in showing scrollbars PinmemberCptHook5-Feb-13 6:56 
GeneralRe: Issue in showing scrollbars PinmemberCptHook13-Feb-13 2:20 
GeneralRe: Issue in showing scrollbars Pinmemberadambl13-Feb-13 5:11 
QuestionHey Adam, varying cell renderer per row, possible? PinmemberNWGaEagle27-Jun-12 13:25 
AnswerRe: Hey Adam, varying cell renderer per row, possible? Pinmemberadambl28-Jun-12 0:25 
GeneralRe: Hey Adam, varying cell renderer per row, possible? PinmemberNWGaEagle28-Jun-12 3:22 
QuestionAutoResizeColumnWidths() doesn't exist? PinmemberMember 771704026-Jun-12 9:41 
AnswerRe: AutoResizeColumnWidths() doesn't exist? Pinmemberadambl26-Jun-12 13:03 
Questionword wraping problem for more that one cell in a row PinmemberStart at 4021-Mar-12 2:19 
AnswerRe: word wraping problem for more that one cell in a row PinmemberAbris14-May-12 3:43 
GeneralRe: word wraping problem for more that one cell in a row Pinmemberadambl28-Jan-13 12:05 
QuestionVersion 1.2.2 released Pinmemberadambl8-Nov-11 3:01 
AnswerRe: Version 1.2.2 released Pinmemberpophelix2-Dec-11 2:01 
BugScroll in Combobox - Arithmetic operator overloading [modified] Pinmemberjensymueller3-Nov-11 4:22 
GeneralRe: Scroll in Combobox - Arithmetic operator overloading Pinmemberadambl8-Nov-11 3:00 
BugRe: Scroll in Combobox - Arithmetic operator overloading Pinmemberthefox_hh18-Sep-12 2:11 
GeneralRe: Scroll in Combobox - Arithmetic operator overloading Pinmemberadambl31-Jan-13 9:55 
QuestionChange Other Cell Value After Cell Edit PinmemberCoding_Junkies6-Oct-11 21:41 
AnswerRe: Change Other Cell Value After Cell Edit Pinmemberadambl8-Oct-11 10:10 
GeneralRe: Change Other Cell Value After Cell Edit Pinmemberadambl8-Oct-11 10:11 
GeneralRe: Change Other Cell Value After Cell Edit PinmemberCoding_Junkies8-Oct-11 12:55 
QuestionPicture over more than one row Pinmemberjulianw9228-Sep-11 4:40 
AnswerRe: Picture over more than one row Pinmemberadambl30-Sep-11 5:41 
GeneralRe: Picture over more than one row PinmemberJulian-w16-Oct-11 2:11 
GeneralRe: Picture over more than one row Pinmemberadambl16-Oct-11 12:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 4 Jul 2007
Article Copyright 2007 by adambl
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid