#region CPL License /* Nuclex Framework Copyright (C) 2002-2019 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion 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