
Introduction
Every time I use a ListView, I think that I should provide a context menu to support the hiding of columns, which is very much required if the number of columns are more and you need to scroll to see the last column. I often think why the ListView doesn't come with this support built-in? So I decided to implement a ListView with built-in support for hiding columns, using a context menu. Using this extended listview which I named as ListViewEx, I get a context menu built-in which can hide columns. The best part is I don't have to create the menu items. Every time I add a new column, the menu for hiding it is ready by default :-).
Background
A ListView control allows you to display a list of items with an item text and, optionally, an icon to identify the type of item. When the View property of the control is set to View.Details, the items can be displayed in different columns. A ColumnHeader class represents a single column in the ListView control. A ListView contains a ColumnHeaderCollection class which stores the column headers that are displayed in the ListView control when the View property is set to View.Details. The ListView.ColumnHeaderCollection stores ColumnHeader objects that define the text to display for a column as well as how the column header is displayed in the ListView control when displaying columns. When a ListView displays columns, the items and their subitems are displayed in their own columns.
Implementation
I have implemented the ListViewEx control by extending the ListView, ColumnHeaderCollection, and ColumnHeader classes.
ColumnHeaderEx Class
This class extends the ColumnHeader class and provides the following properties and an event to notify that the visibility of the column has changed.
public bool Visible {
get{return columnVisible;}
set{ShowColumn(value);}
}
public MenuItem ColumnMenuItem {
get{return menuItem;}
}
public event EventHandler VisibleChanged;
When the visibility of the column is changed, the subscriber for this event (ColumnHeaderCollectionEx) is notified and the menuitem is checked/unchecked depending on weather the column is hidden or displayed. The visibility of the column can be changed by changing the Visible property or by clicking the context menu, which is handled here alone. The following code does the described activity:
private void ShowColumn(bool visible) {
if(columnVisible != visible) {
columnVisible = visible;
menuItem.Checked = visible;
if(VisibleChanged != null) {
VisibleChanged(this, EventArgs.Empty);
}
}
}
private void MenuItemClick(Object sender, System.EventArgs e) {
MenuItem menuItem = (MenuItem)sender;
ShowColumn(!menuItem.Checked);
}
ColumnHeaderCollectionEx Class
This class extends the ColumnHeaderCollection and contains a list of ColumnHeadersExs. Columns can be added to the collection using the Add or AddRange methods which are listed as below. Whenever a column is added to the collection, we subscribe to the VisibleChanged changed event of the column headers and keep a reference to the column in a list for further use.
public override ColumnHeader Add(string str,
int width, HorizontalAlignment textAlign) {
ColumnHeaderEx column = new ColumnHeaderEx(str,
width, textAlign);
this.Add (column);
return column;
}
public override int Add(ColumnHeader column) {
return this.Add (new ColumnHeaderEx(column));
}
public override void AddRange(ColumnHeader[] values) {
for(int index = 0; index < values.Length; index++) {
this.Add (new ColumnHeaderEx(values[index]));
}
}
public int Add(ColumnHeaderEx column) {
int retValue = base.Add (column);
columnList.Add(column.ColumnID, column);
ContextMenu.MenuItems.Add(column.ColumnMenuItem);
column.VisibleChanged += new EventHandler(ColumnVisibleChanged);
return retValue;
}
When a column is hidden, the VisibleChanged event is fired which is handled in this class. Here, we remove the column from the base but we still have it in our list. When a column is shown, we find out the index where the column has to be displayed and insert it back. This is listed below:
private void ColumnVisibleChanged(object sender, EventArgs e) {
ColumnHeaderEx column = (ColumnHeaderEx)sender;
if(column.Visible == true) {
ColumnHeaderEx prevHeader = FindPreviousVisibleColumn(column);
if(prevHeader == null) {
base.Insert(0, column);
}
else {
base.Insert(prevHeader.Index + 1, column);
}
}
else {
base.Remove(column);
}
}
private ColumnHeaderEx FindPreviousVisibleColumn(ColumnHeaderEx column) {
int index = columnList.IndexOfKey(column.ColumnID);
if(index > 0) {
ColumnHeaderEx prevColumn =
(ColumnHeaderEx)columnList.GetByIndex(index - 1);
if((prevColumn != null) && (prevColumn.Visible == false)) {
prevColumn = FindPreviousVisibleColumn(prevColumn);
}
return prevColumn;
}
return null;
}
ListViewEx Class
This class extends the ListView class. Here we don't do much, we ensure that ColumnHeaderCollectionEx is used instead of ColumnHeaderCollection, that's all. This is done by overriding the Columns property.
public new ColumnHeaderCollectionEx Columns {
get{return columnHeadersEx;}
}
How to use the code
This ListControlEx can be used exactly the way ListControl is used with respect to adding columns, removing columns, etc. But if you want to hide a column programmatically, you could do the following:
listViewEx.Columns[4].Visible = false;
listViewEx.Columns[5].Visible = false;
Or if you don't want a menu for a particular column, try this:
listViewEx.Columns[0].ColumnMenuItem.Visible = false;
Conclusion
Most of the functionality have been described here. If you find mistakes, you can correct them. Or you can send me mails explaining the bugs or mistakes you found.