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 ColumnHeadersEx
s. 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.