#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Nuclex.Support.Collections;
namespace Nuclex.Windows.Forms {
/// ListView allowing for other controls to be embedded in its cells
///
///
/// There basically were two possible design choices: Provide a specialized
/// ListViewSubItem that carries a Control instead of a string or manage the
/// embedded controls seperate of the ListView's items.
///
///
/// The first option requires a complete rewrite of the ListViewItem class
/// and its related support classes, all of which are surprisingly large and
/// complex. Thus, I chose the less clean but more doable latter option.
///
///
/// This control is useful for simple item lists where you want to provide
/// a combobox, checkbox or other control to the user for a certain column.
/// It will not perform well for lists with hundreds of items since it
/// requires a control to be created per row and management of the embedded
/// controls is designed for limited usage.
///
///
public partial class ContainerListView : System.Windows.Forms.ListView {
/// Message sent to a control to let it paint itself
private const int WM_PAINT = 0x000F;
/// Initializes a new ContainerListView
public ContainerListView() {
this.embeddedControlClickedDelegate = new EventHandler(embeddedControlClicked);
this.embeddedControls = new ObservableList();
this.embeddedControls.ItemAdded +=
new EventHandler>(embeddedControlAdded);
this.embeddedControls.ItemRemoved +=
new EventHandler>(embeddedControlRemoved);
this.embeddedControls.Clearing += new EventHandler(embeddedControlsClearing);
InitializeComponent();
// Eliminate flickering
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
base.View = View.Details;
this.columnHeaderHeight = Font.Height;
}
/// Controls being embedded in the ListView
public ICollection EmbeddedControls {
get { return this.embeddedControls; }
}
/// Updates the controls embeded into the list view
public void UpdateEmbeddedControls() {
if(View != View.Details) {
for(int index = 0; index < this.embeddedControls.Count; ++index) {
this.embeddedControls[index].Control.Visible = false;
}
} else {
for(int index = 0; index < this.embeddedControls.Count; ++index) {
ListViewEmbeddedControl embeddedControl = this.embeddedControls[index];
Rectangle cellBounds = this.GetSubItemBounds(
Items[embeddedControl.Row], embeddedControl.Column
);
bool intersectsColumnHeader =
(base.HeaderStyle != ColumnHeaderStyle.None) &&
(cellBounds.Top < base.Font.Height);
embeddedControl.Control.Visible = !intersectsColumnHeader;
embeddedControl.Control.Bounds = cellBounds;
}
}
}
/// Calculates the boundaries of a cell in the list view
/// Item in the list view from which to calculate the cell
/// Index der cell whose boundaries to calculate
/// The boundaries of the specified list view cell
///
/// When the specified sub item index is not in the range of valid sub items
///
protected Rectangle GetSubItemBounds(ListViewItem item, int subItem) {
int[] order = GetColumnOrder();
if(order == null) { // No Columns
return Rectangle.Empty;
}
if(subItem >= order.Length) {
throw new IndexOutOfRangeException("SubItem " + subItem + " out of range");
}
// Determine the border of the entire ListViewItem, including all sub items
Rectangle itemBounds = item.GetBounds(ItemBoundsPortion.Entire);
int subItemX = itemBounds.Left;
// Find the horizontal position of the sub item. Because the column order can vary,
// we need to use Columns[order[i]] instead of simply doing Columns[i] here!
ColumnHeader columnHeader;
int i;
for(i = 0; i < order.Length; ++i) {
columnHeader = this.Columns[order[i]];
if(columnHeader.Index == subItem) {
break;
}
subItemX += columnHeader.Width;
}
return new Rectangle(
subItemX, itemBounds.Top, this.Columns[order[i]].Width, itemBounds.Height
);
}
/// Responds to window messages sent by the operating system
/// Window message that will be processed
protected override void WndProc(ref Message message) {
switch(message.Msg) {
case WM_PAINT: {
UpdateEmbeddedControls();
break;
}
}
base.WndProc(ref message);
}
/// Called when the list of embedded controls has been cleared
/// Collection that has been cleared of its controls
/// Not used
private void embeddedControlsClearing(object sender, EventArgs arguments) {
this.BeginUpdate();
try {
foreach(ListViewEmbeddedControl embeddedControl in this.embeddedControls) {
embeddedControl.Control.Click -= this.embeddedControlClickedDelegate;
this.Controls.Remove(embeddedControl.Control);
}
}
finally {
this.EndUpdate();
}
}
/// Called when a control gets removed from the embedded controls list
/// List from which the control has been removed
///
/// Event arguments providing a reference to the removed control
///
private void embeddedControlAdded(
object sender, ItemEventArgs arguments
) {
arguments.Item.Control.Click += this.embeddedControlClickedDelegate;
this.Controls.Add(arguments.Item.Control);
}
/// Called when a control gets added to the embedded controls list
/// List to which the control has been added
///
/// Event arguments providing a reference to the added control
///
private void embeddedControlRemoved(
object sender, ItemEventArgs arguments
) {
if(this.Controls.Contains(arguments.Item.Control)) {
arguments.Item.Control.Click -= this.embeddedControlClickedDelegate;
this.Controls.Remove(arguments.Item.Control);
}
}
/// Called when an embedded control has been clicked on
/// Embedded control that has been clicked
/// Not used
private void embeddedControlClicked(object sender, EventArgs arguments) {
this.BeginUpdate();
try {
SelectedItems.Clear();
foreach(ListViewEmbeddedControl embeddedControl in this.embeddedControls) {
if(ReferenceEquals(embeddedControl.Control, sender)) {
if((embeddedControl.Row > 0) && (embeddedControl.Row < Items.Count)) {
Items[embeddedControl.Row].Selected = true;
}
}
}
}
finally {
this.EndUpdate();
}
}
/// Obtains the current column order of the list
/// An array indicating the order of the list's columns
private int[] GetColumnOrder() {
int[] order = new int[this.Columns.Count];
for(int index = 0; index < this.Columns.Count; ++index) {
order[this.Columns[index].DisplayIndex] = index;
}
return order;
}
/// Height of the list view's column header
private int columnHeaderHeight;
/// Event handler for when embedded controls are clicked on
private EventHandler embeddedControlClickedDelegate;
/// Controls being embedded in this ListView
private ObservableList embeddedControls;
}
} // namespace Nuclex.Windows.Forms