// Author: Bill Seddon // Company: Lyquidity Solutions Limited // // This work builds on code posted to SourceForge by // Jon Rista (jrista@hotmail.com) // // ContainerListView provides a listview type control // that allows controls to be contained in sub item // cells. The control is also fully compatible with // Windows XP visual styles. // // This version fixes up drawing, mouse and keyboard // event handling issues. // // This code is provided "as is" and no warranty about // it fitness for any specific task is expressed or // implied. If you choose to use this code, you do so // at your own risk. // //////////////////////////////////////////////////////// using System; using System.ComponentModel; using System.ComponentModel.Design; using System.ComponentModel.Design.Serialization; using System.Collections; using System.Collections.Specialized; using System.Diagnostics; using System.Drawing; using System.Drawing.Design; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Globalization; using System.Reflection; using System.Threading; using System.Windows.Forms; using System.Windows.Forms.Design; namespace Lyquidity.Controls.ExtendedListViews { #region Enumerations public enum WinMsg { WM_GETDLGCODE = 0x0087, WM_SETREDRAW = 0x000B, WM_CANCELMODE = 0x001F, WM_NOTIFY = 0x4e, WM_KEYDOWN = 0x100, WM_KEYUP = 0x101, WM_CHAR = 0x0102, WM_SYSKEYDOWN = 0x104, WM_SYSKEYUP = 0x105, WM_COMMAND = 0x111, WM_MENUCHAR = 0x120, WM_MOUSEMOVE = 0x200, WM_LBUTTONDOWN = 0x201, WM_MOUSELAST = 0x20a, WM_USER = 0x0400, WM_REFLECT = WM_USER + 0x1c00 } public enum DialogCodes { DLGC_WANTARROWS = 0x0001, /* Control wants arrow keys */ DLGC_WANTTAB = 0x0002, /* Control wants tab keys */ DLGC_WANTALLKEYS = 0x0004, /* Control wants all keys */ DLGC_WANTMESSAGE = 0x0004, /* Pass message to control */ DLGC_HASSETSEL = 0x0008, /* Understands EM_SETSEL message */ DLGC_DEFPUSHBUTTON = 0x0010, /* Default pushbutton */ DLGC_UNDEFPUSHBUTTON = 0x0020, /* Non-default pushbutton */ DLGC_RADIOBUTTON = 0x0040, /* Radio button */ DLGC_WANTCHARS = 0x0080, /* Want WM_CHAR messages */ DLGC_STATIC = 0x0100, /* Static item: don't include */ DLGC_BUTTON = 0x2000, /* Button item: can be checked */ } public enum ColumnScaleStyle { Slide, Spring } public enum MultiSelectMode { Single, Range, Selective } #endregion #region Event Handlers and Arguments public class ItemsChangedEventArgs: EventArgs { public int IndexChanged = -1; public ItemsChangedEventArgs(int indexChanged) { IndexChanged = indexChanged; } } public delegate void ItemsChangedEventHandler(object sender, ItemsChangedEventArgs e); #endregion #region ContainerListViewItem classes [DesignTimeVisible(false), TypeConverter("Lyquidity.Controls.ExtendedListViews.ListViewItemConverter")] public class ContainerListViewItem: ICloneable { public event MouseEventHandler MouseDown; #region Variables private Color backcolor; // private Rectangle bounds; private bool ischecked; private bool focused; private Font font; private Color forecolor; private int imageindex; private int stateimageindex; private int index; private ContainerListView listview; private bool selected; private ContainerSubListViewItemCollection subitems; private object tag; private string text; private bool styleall; private bool hovered; #endregion #region Constructors public ContainerListViewItem() { subitems = new ContainerSubListViewItemCollection(); subitems.ItemsChanged += new ItemsChangedEventHandler(OnSubItemsChanged); } public ContainerListViewItem(ContainerListView Listview, int Index) { index = Index; listview = Listview; } #endregion private void OnSubItemsChanged(object sender, ItemsChangedEventArgs e) { subitems[e.IndexChanged].MouseDown += new MouseEventHandler(OnSubItemMouseDown); } private void OnSubItemMouseDown(object sender, MouseEventArgs e) { if (MouseDown != null) MouseDown(this, e); } #region Properties public Color BackColor { get { return backcolor; } set { backcolor = value; } } /* public Rectangle Bounds { get { return bounds; } } */ public bool Checked { get { return ischecked; } set { ischecked = value; } } public bool Focused { get { return focused; } set { focused = value; } } public Font Font { get { return font; } set { font = value; } } public Color ForeColor { get { return forecolor; } set { forecolor = value; } } public int ImageIndex { get { return imageindex; } set { imageindex = value; } } public int Index { get { return index; } set { index = value; } } public ContainerListView ListView { get { return listview; } } public bool Selected { get { return selected; } set { selected = value; } } [ Category("Behavior"), Description("The items collection of sub controls."), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Editor(typeof(CollectionEditor), typeof(UITypeEditor)) ] public ContainerSubListViewItemCollection SubItems { get { return subitems; } } public int StateImageIndex { get { return stateimageindex; } set { stateimageindex = value; } } public object Tag { get { return tag; } set { tag = value; } } public string Text { get { return text; } set { text = value; } } public bool UseItemStyleForSubItems { get { return styleall; } set { styleall = value; } } [Browsable(false)] public bool Hovered { get { return hovered; } set { hovered = value; } } #endregion #region Methods public object Clone() { ContainerListViewItem lvi = new ContainerListViewItem(); lvi.BackColor = backcolor; lvi.Focused = focused; lvi.Font = font; lvi.ForeColor = forecolor; lvi.ImageIndex = imageindex; lvi.Selected = selected; lvi.Tag = tag; lvi.Text = text; lvi.UseItemStyleForSubItems = styleall; return lvi; } #endregion } public class ContainerListViewItemCollection: CollectionBase { public event MouseEventHandler MouseDown; private void OnMouseDown(object sender, MouseEventArgs e) { if (MouseDown != null) MouseDown(sender, e); } #region Interface Implementations public ContainerListViewItem this[int index] { get { return List[index] as ContainerListViewItem; } set { List[index] = value; ((ContainerListViewItem)List[index]).MouseDown += new MouseEventHandler(OnMouseDown); } } public int this[ContainerListViewItem item] { get { return List.IndexOf(item); } } public int Add(ContainerListViewItem item) { item.MouseDown += new MouseEventHandler(OnMouseDown); return item.Index = List.Add(item); } public void AddRange(ContainerListViewItem[] items) { lock(List.SyncRoot) { for (int i=0; i /// Provides a listview control in detail mode that /// provides containers for each cell in a row/column. /// The container can hold almost any object that derives /// directly or indirectly from Control. /// [DefaultProperty("Items")] [ToolboxItem(true)] [ToolboxBitmap(typeof(ContainerListView), "Resources.listview.bmp")] public class ContainerListView: Control { #region Events public event LabelEditEventHandler AfterLabelEdit; public event LabelEditEventHandler BeforeLabelEdit; public event ColumnClickEventHandler ColumnClick; public event EventHandler ItemActivate; public event EventHandler SelectedIndexChanged; protected void OnAfterLabelEdit(LabelEditEventArgs e) { if (AfterLabelEdit != null) try { AfterLabelEdit(this, e); /* "this" should be the label? BMS 2003/05/22 */ } catch {} } protected void OnBeforeLabelEdit(LabelEditEventArgs e) { if (BeforeLabelEdit != null) try { BeforeLabelEdit(this, e); /* "this" should be the label? BMS 2003/05/22 */ } catch {} } protected void OnColumnClick(ColumnClickEventArgs e) { if (ColumnClick != null) try { ColumnClick(this, e); } catch {} } protected void OnItemActivate(EventArgs e) { if (ItemActivate != null) try { ItemActivate(this, e); } catch {} } protected void OnSelectedIndexChanged(EventArgs e) { if (SelectedIndexChanged != null) try { SelectedIndexChanged(this, e); } catch {} } // This handler links any click events on an items subcontrols // to this control. Clicks will activate or deactivate the // row containing the subcontrol. protected virtual void OnSubControlMouseDown(object sender, MouseEventArgs e) { //Debug.WriteLine("Subcontrol clicked."); ContainerListViewItem item = ((ContainerListViewItem)sender); if (multiSelectMode == MultiSelectMode.Single) { selectedIndices.Clear(); selectedItems.Clear(); for (int i=0; i 0) m_cUpdateTransactions.Pop(); if (!this.InUpdateTransaction) this.Invalidate(); // Force repaint when update ended } #endregion #region Properties [ Category("Behavior"), Description("Specifies wether the selected item is always visible"), DefaultValue(true) ] public bool EnsureVisible { get { return ensureVisible; } set { ensureVisible = value; } } [ Category("Behavior"), Description("Specifies wether the control will capture the click used to focus the control and adjust the selection accordingly, or not."), DefaultValue(false) ] public bool CaptureFocusClick { get { return captureFocusClick; } set { captureFocusClick = value; } } [ Category("Behavior"), Description("The context menu displayed when the header is right-clicked.") ] public ContextMenu HeaderMenu { get { return headerMenu; } set { headerMenu = value; } } [ Category("Behavior"), Description("The context menu displayed when an item is right-clicked.") ] public ContextMenu ItemMenu { get { return itemMenu; } set { itemMenu = value; } } [ Category("Behavior"), Description("The context menu displayed when the control is right-clicked.") ] public override ContextMenu ContextMenu { get { return contextMenu; } set { contextMenu = value; } } [ Category("Behavior"), Description("The lists column headers."), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Editor(typeof(CollectionEditor), typeof(UITypeEditor)) ] public Lyquidity.Controls.ExtendedListViews.ColumnHeaderCollection Columns { get { return columns; } } [ Category("Data"), Description("The items contained in the list."), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Editor(typeof(CollectionEditor), typeof(UITypeEditor)) ] public virtual ContainerListViewItemCollection Items { get { return items; } } [ Category("Behavior"), Description("Specifies what action activates and item."), DefaultValue(ItemActivation.Standard) ] public ItemActivation Activation { get { return activation; } set { activation = value; } } [ Category("Behavior"), Description("Specifies whether column headers may be reordered."), DefaultValue(false) ] public bool AllowColumnReorder { get { return allowColumnReorder; } set { allowColumnReorder = value; } } [ Category("Appearance"), Description("Specifies what style border the control has."), DefaultValue(BorderStyle.Fixed3D) ] public BorderStyle BorderStyle { get { return borderstyle; } set { borderstyle = value; if (borderstyle == BorderStyle.Fixed3D) { borderWid = 2; } else { borderWid = 1; } Invalidate(this.ClientRectangle); } } [ Category("Appearance"), Description("Specifies wether to show column headers, and wether they respond to mouse clicks."), DefaultValue(ColumnHeaderStyle.Nonclickable) ] public ColumnHeaderStyle HeaderStyle { get { return headerStyle; } set { headerStyle = value; Invalidate(this.ClientRectangle); if (headerStyle == ColumnHeaderStyle.None) headerBuffer = 0; else headerBuffer = 20; } } [ Category("Behavior"), Description("Enables column tracking, highlighting the column gray when the mouse hovers over its header."), DefaultValue(false) ] public bool ColumnTracking { get { return doColTracking; } set { doColTracking = value; } } [ Category("Appearance"), Description("Specifies the color used for column hot-tracking."), DefaultValue(typeof(Color), "Color.WhiteSmoke") ] public Color ColumnTrackColor { get { return colTrackColor; } set { colTrackColor = value; } } [ Category("Appearance"), Description("Specifies the color used for the currently selected sorting column."), DefaultValue(typeof(Color), "Color.Gainsboro") ] public Color ColumnSortColor { get { return colSortColor; } set { colSortColor = value; } } [ Category("Behavior"), Description("Enables row tracking, highlighting the row gray when the mouse hovers over it."), DefaultValue(true) ] public bool RowTracking { get { return doRowTracking; } set { doRowTracking = value; } } [ Category("Appearance"), Description("Specifies the color used for row hot-tracking."), DefaultValue(typeof(Color), "Color.WhiteSmoke") ] public Color RowTrackColor { get { return rowTrackColor; } set { rowTrackColor = value; } } [ Category("Appearance"), Description("Specifies the color used for selected rows."), DefaultValue(typeof(Color), "SystemColors.Highlight") ] public Color RowSelectColor { get { return rowSelectColor; } set { rowSelectColor = value; } } [ Category("Behavior"), Description("Determines wether to highlight the full row or just the label of selected items."), DefaultValue(true) ] public bool FullRowSelect { get { return fullRowSelect; } set { fullRowSelect = value; } } [ Category("Appearance"), Description("Specifies the color used for grid lines."), DefaultValue(typeof(Color), "Color.WhiteSmoke") ] public Color GridLineColor { get { return gridLineColor; } set { gridLineColor = value; } } [ Category("Behavior"), Description("Specifies wether to show grid lines."), DefaultValue(false) ] public bool GridLines { get { return gridLines; } set { gridLines = value; Invalidate(this.ClientRectangle); } } [ Category("Behavior"), Description("The lists small image list (16x16).") ] public ImageList SmallImageList { get { return smallImageList; } set { smallImageList = value; Invalidate(this.ClientRectangle); } } [ Category("Behavior"), Description("The lists custom state image list (16x16).") ] public ImageList StateImageList { get { return stateImageList; } set { stateImageList = value; } } [ Category("Behavior"), Description("Determins wether primary name of each item is editable or not.") ] public bool LabelEdit { get { return labelEdit; } set { labelEdit = value; } } [ Category("Behavior"), Description("Determines wether to hide the selected rows when the control looses focus."), DefaultValue(true) ] public bool HideSelection { get { return hideSelection; } set { hideSelection = value; } } [ Category("Behavior"), Description("Determines wether to automatically select a row when the mouse is hovered over it for a short time."), DefaultValue(false) ] public bool HoverSelection { get { return hoverSelection; } set { hoverSelection = value; } } [ Category("Behavior"), Description("Determins wether the control will allow multiple selections."), DefaultValue(false) ] public bool MultiSelect { get { return multiSelect; } set { multiSelect = value; } } [ Category("Appearance"), Description("Specifies wether to use WindowsXP visual styles on this control."), DefaultValue(false) ] public bool VisualStyles { get { bool val; try { val = visualStyles && Lyquidity.Controls.ExtendedListViews.uxTheme.Wrapper.IsAppThemed(); } catch { val = visualStyles; } return val; } set { visualStyles = value; } } [Browsable(false)] public ArrayList SelectedIndices { get { return selectedIndices; } } [Browsable(false)] // public ContainerListViewItemCollection SelectedItems public SelectedContainerListViewItemCollection SelectedItems { get { return selectedItems; } } protected bool InUpdateTransaction { get { return this.m_cUpdateTransactions.Count > 0; } } #endregion #region Overrides protected const int WM_KEYDOWN = 0x0100; protected const int VK_LEFT = 0x0025; protected const int VK_UP = 0x0026; protected const int VK_RIGHT = 0x0027; protected const int VK_DOWN = 0x0028; /* protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } */ protected override void OnPaint(PaintEventArgs e) { Rectangle r = ClientRectangle; Graphics g = e.Graphics; DrawBackground(g, r); DrawRows(g, r); DrawHeaders(g, r); DrawExtra(g, r); DrawBorder(g, r); } protected override void OnResize(EventArgs e) { base.OnResize(e); GenerateHeaderRect(); GenerateRowsRect(); AdjustScrollbars(); // invalidate subitem controls for (int i=0; i= 0 && allowColumnReorder) { if (Math.Abs(e.X-lastClickedPoint.X) > 3) { // set rect for drag pos } } else if (colScaleMode) { lastColHovered = -1; Cursor.Current = Cursors.VSplit; colScalePos = e.X-lastClickedPoint.X; if (colScalePos+colScaleWid <= 0) columns[scaledCol].Width = 1; else columns[scaledCol].Width = colScalePos+colScaleWid; Invalidate(); } else { if (columns.Count > 0) { // check region mouse is in // header if (headerStyle != ColumnHeaderStyle.None) { //GenerateHeaderRect(); Cursor.Current = Cursors.Default; if (MouseInRect(e, headerRect)) { if (columnRects.Length < columns.Count) GenerateColumnRects(); for (i=0; i 10) { mouseMoveTicks = 0; Thread.Sleep(1); } return; } } } if (lastColHovered >= 0) { columns[lastColHovered].Hovered = false; lastColHovered = -1; Invalidate(); } if (items.Count > 0) { // rows GenerateRowsRect(); if (MouseInRect(e, rowsRect)) { GenerateRowRects(); for (i=0; i 10) { mouseMoveTicks = 0; Thread.Sleep(1); } } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (!isFocused) { this.Focus(); if (!captureFocusClick) return; } lastClickedPoint = new Point(e.X, e.Y); #region Columns Headers // determine if a header was pressed if (headerStyle != ColumnHeaderStyle.None) { if (MouseInRect(e, headerRect) && e.Button == MouseButtons.Left) { for (int i=0; i 0) { if (columns[i].ScaleStyle == ColumnScaleStyle.Slide) { // autosize column if (e.Clicks == 2 && e.Button == MouseButtons.Left && items.Count > 0) { int mwid = 0; int twid = 0; for (int j=0; j 0 && items[j].SubItems.Count > 0) twid = GetStringWidth(items[j].SubItems[i-1].Text) + 10; else if (i == 0) twid = GetStringWidth(items[j].Text) + 10; twid += 5; if (twid > mwid) mwid = twid; } twid = GetStringWidth(columns[i].Text); if (twid > mwid) mwid = twid; mwid += 5; columns[i].Width = mwid; } // scale column else { colScaleMode = true; colScaleWid = columnRects[i].Width; scaledCol = i; } } } else if (MouseInRect(e, columnRects[i]) && !MouseInRect(e, columnSizeRects[i])) { columns[i].Pressed = true; lastColPressed = i; } } Invalidate(); return; } } #endregion // determine if a row was pressed if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right) { if (MouseInRect(e, rowsRect) && items.Count>0) { #region Rows GenerateRowRects(); for (int i=0; i= 0) { columns[lastColPressed].Pressed = false; if (MouseInRect(e, columnRects[lastColPressed]) && !MouseInRect(e, columnSizeRects[lastColPressed]) && e.Button == MouseButtons.Left) { // invoke column click event OnColumnClick(new ColumnClickEventArgs(lastColPressed)); // change currently selected column if (lastColSelected >= 0) columns[lastColSelected].Selected = false; columns[lastColPressed].Selected = true; lastColSelected = lastColPressed; } } lastColPressed = -1; // Check for context click if (e.Button == MouseButtons.Right) { if (MouseInRect(e, headerRect)) OnHeaderMenuEvent(e); else if (MouseInRect(e, rowsRect)) { for (i=0; i=items.Count) OnContextMenuEvent(e); } else OnContextMenuEvent(e); } } protected override void OnMouseWheel(MouseEventArgs e) { if (e.Delta > 0) { if (vscrollBar.Visible) vscrollBar.Value = (vscrollBar.Value-vscrollBar.SmallChange*(e.Delta/100) < 0 ? 0 : vscrollBar.Value-vscrollBar.SmallChange*(e.Delta/100)); else if (hscrollBar.Visible) hscrollBar.Value = (hscrollBar.Value-hscrollBar.SmallChange*(e.Delta/100) < 0 ? 0 : hscrollBar.Value-hscrollBar.SmallChange*(e.Delta/100)); } else if (e.Delta < 0) { if (vscrollBar.Visible) vscrollBar.Value = (vscrollBar.Value-vscrollBar.SmallChange*(e.Delta/100) > vscrollBar.Maximum-vscrollBar.LargeChange ? vscrollBar.Maximum-vscrollBar.LargeChange : vscrollBar.Value-vscrollBar.SmallChange*(e.Delta/100)); else if (hscrollBar.Visible) hscrollBar.Value = (hscrollBar.Value-hscrollBar.SmallChange*(e.Delta/100) > hscrollBar.Maximum-hscrollBar.LargeChange ? hscrollBar.Maximum-hscrollBar.LargeChange : hscrollBar.Value-hscrollBar.SmallChange*(e.Delta/100)); } } protected override void OnKeyDown(KeyEventArgs e) { OnCheckShiftState(e); switch (e.KeyCode) { case Keys.Home: OnPageKeys(e); break; case Keys.End: OnPageKeys(e); break; case Keys.PageUp: OnPageKeys(e); break; case Keys.PageDown: OnPageKeys(e); break; case Keys.Up: OnUpDownKeys(e); break; case Keys.Down: OnUpDownKeys(e); break; default: base.OnKeyDown(e); break; } Invalidate(ClientRectangle); } protected virtual void OnPageKeys(KeyEventArgs e) { switch (e.KeyCode) { case Keys.Home: if (vscrollBar.Visible) vscrollBar.Value = 0; else { MoveToIndex(0); } if (hscrollBar.Visible) hscrollBar.Value = 0; e.Handled = true; break; case Keys.End: if (vscrollBar.Visible) vscrollBar.Value = vscrollBar.Maximum-vscrollBar.LargeChange; else { MoveToIndex(this.items.Count-1); } if (hscrollBar.Visible) hscrollBar.Value = hscrollBar.Maximum-hscrollBar.LargeChange; e.Handled = true; break; case Keys.PageUp: if (vscrollBar.Visible) vscrollBar.Value = (vscrollBar.LargeChange > vscrollBar.Value ? 0 : vscrollBar.Value-vscrollBar.LargeChange); else { MoveToIndex(0); } e.Handled = true; break; case Keys.PageDown: if (vscrollBar.Visible) vscrollBar.Value = (vscrollBar.Value+vscrollBar.LargeChange > vscrollBar.Maximum-vscrollBar.LargeChange ? vscrollBar.Maximum-vscrollBar.LargeChange : vscrollBar.Value+vscrollBar.LargeChange); else { MoveToIndex(this.items.Count-1); } e.Handled = true; break; } } protected virtual void OnUpDownKeys(KeyEventArgs e) { if (focusedItem != null && items.Count > 0) { int iIndex = focusedIndex; switch (e.KeyCode) { case Keys.Down: iIndex++; break; case Keys.Up: iIndex--; break; } MoveToIndex(iIndex); e.Handled = true; } } protected virtual void OnCheckShiftState(KeyEventArgs e) { if (multiSelect) { if (e.KeyCode == Keys.ControlKey) { multiSelectMode = MultiSelectMode.Selective; } else if (e.KeyCode == Keys.ShiftKey) { multiSelectMode = MultiSelectMode.Range; } } if (!multiSelect && e.KeyCode == Keys.Return) { OnItemActivate(new EventArgs()); } } protected override void OnKeyUp(KeyEventArgs e) { base.OnKeyUp(e); if (!e.Shift) { multiSelectMode = MultiSelectMode.Single; } } protected override void OnGotFocus(EventArgs e) { base.OnGotFocus(e); isFocused = true; Invalidate(this.ClientRectangle); } protected override void OnLostFocus(EventArgs e) { base.OnLostFocus(e); isFocused = false; Invalidate(this.ClientRectangle); } #endregion #region Helper Functions // wireing of child control events protected virtual void Attach() { items.MouseDown += new MouseEventHandler(OnSubControlMouseDown); columns.WidthResized += new EventHandler(OnColumnWidthResize); hscrollBar.ValueChanged += new EventHandler(OnScroll); vscrollBar.ValueChanged += new EventHandler(OnScroll); } protected virtual void Detach() { items.MouseDown -= new MouseEventHandler(OnSubControlMouseDown); columns.WidthResized -= new EventHandler(OnColumnWidthResize); hscrollBar.ValueChanged -= new EventHandler(OnScroll); vscrollBar.ValueChanged -= new EventHandler(OnScroll); } // Rectangle and region generation functions protected void GenerateColumnRects() { columnRects = new Rectangle[columns.Count]; columnSizeRects = new Rectangle[columns.Count]; int lwidth = 2-hscrollBar.Value; int colwid = 0; allColsWidth = 0; CalcSpringWids(ClientRectangle); for (int i=0; i 0 || columns.Count > 0 && !colScaleMode) { allColsWidth = 0; for (int i=0; i 0 ? this.ClientRectangle.Width - vsize - 4 : 0); if (allColsWidth > this.ClientRectangle.Width-4-vsize) hscrollBar.Show(); else { hscrollBar.Hide(); hsize = 0; hscrollBar.Value = 0; } vscrollBar.Left = this.ClientRectangle.Left+this.ClientRectangle.Width-vscrollBar.Width-2; vscrollBar.Top = this.ClientRectangle.Top+headerBuffer+2; vscrollBar.Height = this.ClientRectangle.Height-hsize-headerBuffer-4; vscrollBar.Maximum = allRowsHeight; vscrollBar.LargeChange = (this.ClientRectangle.Height-headerBuffer-hsize-4 > 0 ? this.ClientRectangle.Height-headerBuffer-hsize-4 : 0); if (allRowsHeight > this.ClientRectangle.Height-headerBuffer-4-hsize) vscrollBar.Show(); else { vscrollBar.Hide(); vsize = 0; vscrollBar.Value = 0; } hscrollBar.Width = this.ClientRectangle.Width-vsize-4; hscrollBar.Top = this.ClientRectangle.Top+this.ClientRectangle.Height-hscrollBar.Height-2; hscrollBar.LargeChange = (this.ClientRectangle.Width - vsize - 4 > 0 ? this.ClientRectangle.Width - vsize - 4 : 0); if (allColsWidth > this.ClientRectangle.Width-4-vsize) hscrollBar.Show(); else { hscrollBar.Hide(); hsize = 0; hscrollBar.Value = 0; } } } // mouse movement/click helpers private void UnselectAll() { for (int i=0; i= rect.Left && me.X <= rect.Left+rect.Width) && (me.Y >= rect.Top && me.Y <= rect.Top + rect.Height)) { return true; } return false; } protected void MakeSelectedVisible() { if (focusedIndex > -1 && ensureVisible) { ContainerListViewItem item = items[focusedIndex]; if (item != null && item.Focused && item.Selected) { Rectangle r = ClientRectangle; int i = items.IndexOf(item); int pos = r.Top+(rowHeight*i)+headerBuffer+2-vscrollBar.Value; try { if (pos+rowHeight+rowHeight > r.Top+r.Height) { vscrollBar.Value += Math.Abs((r.Top+r.Height)-(pos+rowHeight+rowHeight)); } else if (pos < r.Top+headerBuffer) { vscrollBar.Value -= Math.Abs(r.Top+headerBuffer-pos); } } catch (ArgumentException) { if (vscrollBar.Value > vscrollBar.Maximum) vscrollBar.Value = vscrollBar.Maximum; else if (vscrollBar.Value < vscrollBar.Minimum) vscrollBar.Value = vscrollBar.Minimum; } } } } protected int GetStringWidth(string s) { Graphics g = Graphics.FromImage(new Bitmap(32, 32)); SizeF strSize = g.MeasureString(s, this.Font); return (int)strSize.Width; } protected string TruncatedString(string s, int width, int offset, Graphics g) { string sprint = ""; int swid; int i; SizeF strSize; try { strSize = g.MeasureString(s, this.Font); swid = ((int)strSize.Width); i=s.Length; for (i=s.Length; i>0 && swid > width-offset; i--) { strSize = g.MeasureString(s.Substring(0, i), this.Font); swid = ((int)strSize.Width); } if (i < s.Length) if (i-3 <= 0) sprint = s.Substring(0, 1) + "..."; else sprint = s.Substring(0, i-3) + "..."; else sprint = s.Substring(0, i); } catch { } return sprint; } private void ShowSelectedItems() { if (firstSelected == focusedIndex) { ShowSelectedItem(firstSelected); } else if (firstSelected > focusedIndex) { for(int iCtr=firstSelected; iCtr >= focusedIndex; iCtr--) { items[iCtr].Selected = true; selectedIndices.Add(iCtr); selectedItems.Add(items[iCtr]); } } else if (firstSelected < focusedIndex) { for(int iCtr=firstSelected; iCtr <= focusedIndex; iCtr++) { items[iCtr].Selected = true; selectedIndices.Add(iCtr); selectedItems.Add(items[iCtr]); } } } private void ShowSelectedItem(int iCtr) { items[iCtr].Selected = true; selectedIndices.Add(iCtr); selectedItems.Add(items[iCtr]); } private void MoveToIndex(int iIndex) { if ((iIndex < -1) | (iIndex >= this.items.Count)) return; if (iIndex == focusedIndex) return; /// Might occur on End on first item or /// on Home on the first item or /// if the user clicks on the current node UnselectAll(); this.selectedItems.Clear(); this.selectedIndices.Clear(); if (focusedItem != null) { focusedItem.Selected = false; focusedItem.Focused = false; } focusedIndex = iIndex; if ((this.multiSelectMode == MultiSelectMode.Single) | (firstSelected == -1)) { firstSelected = focusedIndex; items[focusedIndex].Focused = true; focusedItem = items[focusedIndex]; } ShowSelectedItems(); MakeSelectedVisible(); OnSelectedIndexChanged(new EventArgs()); Invalidate(this.ClientRectangle); } private void SelectiveSelection(int iIndex) { // This is a special case and will be used when the user clicks on an item // while the control key is pressed or presses the space bar button while an // item has the focus. // unfocus the previously focused item if (focusedIndex >= 0 && focusedIndex < items.Count) items[focusedIndex].Focused = false; ContainerListViewItem item = items[iIndex]; if (items[iIndex].Selected) // Already selected? De-select. { items[iIndex].Focused = false; items[iIndex].Selected = false; int i = selectedItems[item]; selectedIndices.Remove(i); selectedItems.Remove(item); if (this.focusedItem == item) focusedItem = null; MakeSelectedVisible(); OnSelectedIndexChanged(new EventArgs()); } else { item.Focused = true; item.Selected = true; selectedIndices.Add(iIndex); selectedItems.Add(item); this.focusedItem = item; MakeSelectedVisible(); OnSelectedIndexChanged(new EventArgs()); } Invalidate(); } // rendering helpers protected int springWid = 0; protected int springCnt = 0; protected void CalcSpringWids(Rectangle r) { springCnt = 0; springWid = (r.Width-borderWid*2); for (int i=0; i 0 && springWid > 0) springWid = springWid/springCnt; } protected virtual void DrawBorder(Graphics g, Rectangle r) { // if running in XP with styles if (VisualStyles) { DrawBorderStyled(g, r); return; } Rectangle rect = this.ClientRectangle; if (borderstyle == BorderStyle.FixedSingle) { g.DrawRectangle(SystemPens.ControlDarkDark, r.Left, r.Top, r.Width, r.Height); } else if (borderstyle == BorderStyle.Fixed3D) { ControlPaint.DrawBorder3D(g, r.Left, r.Top, r.Width, r.Height, Border3DStyle.Sunken); } else if (borderstyle == BorderStyle.None) { // do not render any border } } protected virtual void DrawBackground(Graphics g, Rectangle r) { int i; int lwidth = 2, lheight=1; g.FillRectangle(new SolidBrush(BackColor), r); // Selected Column if (headerStyle == ColumnHeaderStyle.Clickable) { for (i=0; i= 0 && lastColHovered < columns.Count)) { g.FillRectangle(new SolidBrush(colTrackColor), columnRects[lastColHovered].Left, 22, columnRects[lastColHovered].Width, r.Height-22); } // hot-tracked row if (doRowTracking && (lastRowHovered >= 0 && lastRowHovered < items.Count)) { g.FillRectangle(new SolidBrush(rowTrackColor), r.Left+2, rowRects[lastRowHovered].Top, r.Left+r.Width-4, rowHeight); } // gridlines if (gridLines) { Pen p = new Pen(new SolidBrush(gridLineColor), 1.0f); lwidth = lheight = 1; // vertical for (i=0; i= r.Left+r.Width-2) break; g.DrawLine(p, r.Left+lwidth+columns[i].Width-hscrollBar.Value, r.Top+2+headerBuffer, r.Left+lwidth+columns[i].Width-hscrollBar.Value, r.Top+r.Height-2); lwidth += columns[i].Width; } // horizontal for (i=0; i r.Left+2) && (lp_scr+last < r.Left+r.Width-2)) { if (headerStyle == ColumnHeaderStyle.Clickable && columns[i].Pressed) System.Windows.Forms.ControlPaint.DrawButton(g, lp_scr+last, r.Top+2, columns[i].Width, r.Top+headerBuffer, ButtonState.Flat); else System.Windows.Forms.ControlPaint.DrawButton(g, lp_scr+last, r.Top+2, columns[i].Width, r.Top+headerBuffer, ButtonState.Normal); if (columns[i].Image != null) { g.DrawImage(columns[i].Image, new Rectangle(lp_scr+last+4, r.Top+3, 16, 16)); g.DrawString(TruncatedString(columns[i].Text, columns[i].Width, 25, g), this.Font, SystemBrushes.ControlText, (float)(lp_scr+last+22), (float)(r.Top+5)); } else { string sp = ""; if (columns[i].TextAlign == HorizontalAlignment.Left) g.DrawString(TruncatedString(columns[i].Text, columns[i].Width, 0, g), this.Font, SystemBrushes.ControlText, (float)(lp_scr+last+4), (float)(r.Top+5)); else if (columns[i].TextAlign == HorizontalAlignment.Right) { sp = TruncatedString(columns[i].Text, columns[i].Width, 0, g); g.DrawString(sp, this.Font, SystemBrushes.ControlText, (float)(lp_scr+last+columns[i].Width-Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)-4), (float)(r.Top+5)); } else { sp = TruncatedString(columns[i].Text, columns[i].Width, 0, g); g.DrawString(sp, this.Font, SystemBrushes.ControlText, (float)(lp_scr+last+(columns[i].Width/2)-(Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)/2)), (float)(r.Top+5)); } } } last += columns[i].Width; } // only render trailing column header if the end of the // last column ends before the boundary of the listview if (!(lp_scr+last+5 > r.Left+r.Width)) { g.Clip = new Region(new Rectangle(r.Left+2, r.Top+2, r.Width-5, r.Top+headerBuffer)); System.Windows.Forms.ControlPaint.DrawButton(g, lp_scr+last, r.Top+2, r.Width-(r.Left+last)-3+hscrollBar.Value , r.Top+headerBuffer, ButtonState.Normal); } } } protected virtual void DrawRows(Graphics g, Rectangle r) { // Don't paint if in transaction if (this.InUpdateTransaction) return; CalcSpringWids(r); if (columns.Count > 0) { // render listview item rows int last; int j, i; // set up some commonly used values // to cut down on cpu cycles and boost // the lists performance int lp_scr = r.Left+2-hscrollBar.Value; // left viewport position less scrollbar position int lp = r.Left+2; // left viewport position int tp_scr = r.Top+2+headerBuffer-vscrollBar.Value; // top viewport position less scrollbar position int tp = r.Top+2+headerBuffer; // top viewport position for (i=0; i r.Top+2) && (tp_scr+(rowHeight*i)+2 < r.Top+r.Height-2-hsize)) { g.Clip = new Region(new Rectangle(r.Left+2, r.Top+headerBuffer+2, r.Width-vsize-5, r.Height-hsize-5)); int rowSelWidth = (allColsWidth < (r.Width-5) || hscrollBar.Visible ? allColsWidth : r.Width-5); if (!fullRowSelect) rowSelWidth = iColWidth /* BMS 2003-05-24 */ -2; // render selected item highlights if (items[i].Selected && isFocused) { g.FillRectangle(new SolidBrush(rowSelectColor), lp, tp_scr+(rowHeight*i), rowSelWidth, rowHeight); } else if (items[i].Selected && !isFocused && hideSelection) { ControlPaint.DrawFocusRectangle(g, new Rectangle(lp_scr, tp_scr+(rowHeight*i), rowSelWidth, rowHeight)); } else if (items[i].Selected && !isFocused && !hideSelection) { g.FillRectangle(SystemBrushes.Control, lp, tp_scr+(rowHeight*i), rowSelWidth, rowHeight); } if (items[i].Focused && multiSelect && isFocused) { ControlPaint.DrawFocusRectangle(g, new Rectangle(lp_scr, tp_scr+(rowHeight*i), rowSelWidth, rowHeight)); } // render item if ((lp_scr+2+iColWidth /* BMS 2003-05-24 */ > r.Left+4)) { g.Clip = new Region(new Rectangle(lp+2, tp, (iColWidth /* BMS 2003-05-24 */ > r.Width ? r.Width-6 : iColWidth /* BMS 2003-05-24 */ -2), r.Height-hsize-5)); if (smallImageList != null && (items[i].ImageIndex >= 0 && items[i].ImageIndex < smallImageList.Images.Count)) { smallImageList.Draw(g, lp_scr+4, tp_scr+(rowHeight*i)+1, 16, 16, items[i].ImageIndex); g.DrawString(TruncatedString(items[i].Text, iColWidth /* BMS 2003-05-24 */ , 18, g), this.Font, (items[i].Selected && isFocused ? SystemBrushes.HighlightText : new SolidBrush(ForeColor)), (float)(lp_scr+22), (float)(tp_scr+(rowHeight*i)+2)); } else { g.DrawString(TruncatedString(items[i].Text, iColWidth /* BMS 2003-05-24 */ , 0, g), this.Font, (items[i].Selected && isFocused ? SystemBrushes.HighlightText : new SolidBrush(ForeColor)), (float)(lp_scr+4), (float)(tp_scr+(rowHeight*i)+2)); } } } // render sub items if (columns.Count > 0) { for (j=0; j r.Left+4) && (lp_scr+last < r.Left+r.Width-4) && (tp_scr+(rowHeight*i)+2 >= tp) /* BMS 2003-05-25 */ && (tp_scr+(rowHeight*i)+2 < r.Top+r.Height-2-hsize)) { g.Clip = new Region(new Rectangle(lp_scr+last+4, tp, (last+iColWidthPlus1 /* BMS 2003-05-24 */ > r.Width-6 ? r.Width-6 : iColWidthPlus1-6), r.Height-hsize-5)); Control c = items[i].SubItems[j].ItemControl; if (c != null) { c.Visible = true; // (tp_scr+(rowHeight*i)+2) > tp; // Can only be visible when the c.Location = new Point(lp_scr+last+2, tp_scr+(rowHeight*i)+2); c.ClientSize = new Size(iColWidthPlus1 /* BMS 2003-05-24 */ -6, rowHeight-4); c.Parent = this; } else { string sp = ""; if (columns[j+1].TextAlign == HorizontalAlignment.Left) { g.DrawString(TruncatedString(items[i].SubItems[j].Text, iColWidthPlus1 /* BMS 2003-05-24 */, 12, g), this.Font, (items[i].Selected && isFocused ? SystemBrushes.HighlightText : SystemBrushes.WindowText), (float)(lp_scr+last+4), (float)(tp_scr+(rowHeight*i)+2)); } else if (columns[j+1].TextAlign == HorizontalAlignment.Right) { sp = TruncatedString(items[i].SubItems[j].Text, iColWidthPlus1 /* BMS 2003-05-24 */, 12, g); g.DrawString(sp, this.Font, (items[i].Selected && isFocused ? SystemBrushes.HighlightText : new SolidBrush(ForeColor)), (float)(lp_scr+last+iColWidthPlus1 /* BMS 2003-05-24 */-Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)-2), (float)(tp_scr+(rowHeight*i)+2)); } else { sp = TruncatedString(items[i].SubItems[j].Text, iColWidthPlus1 /* BMS 2003-05-24 */ , 12, g); g.DrawString(sp, this.Font, (items[i].Selected && isFocused ? SystemBrushes.HighlightText : new SolidBrush(ForeColor)), (float)(lp_scr+last+(iColWidthPlus1 /* BMS 2003-05-24 */ /2)-(Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)/2)), (float)(tp_scr+(rowHeight*i)+2)); } } } else /* BMS 2003-05-25 */ { Control c = items[i].SubItems[j].ItemControl; if (c != null) { c.Visible = false; } } } } } } } protected virtual void DrawExtra(Graphics g, Rectangle r) { if (hscrollBar.Visible && vscrollBar.Visible) { g.ResetClip(); g.FillRectangle(SystemBrushes.Control, r.Width-vscrollBar.Width-borderWid, r.Height-hscrollBar.Height-borderWid, vscrollBar.Width, hscrollBar.Height); } } // visual styles rendering functions protected virtual void DrawBorderStyled(Graphics g, Rectangle r) { Region oldreg = g.Clip; g.Clip = new Region(r); g.DrawRectangle(new Pen(SystemBrushes.InactiveBorder), r.Left, r.Top, r.Width-1, r.Height-1); g.DrawRectangle(new Pen(BackColor), r.Left+1, r.Top+1, r.Width-3, r.Height-3); g.Clip = oldreg; } protected virtual void DrawHeadersStyled(Graphics g, Rectangle r) { if (headerStyle != ColumnHeaderStyle.None) { int colwid = 0; int i; int last = 2; CalcSpringWids(r); int lp_scr = r.Left-hscrollBar.Value; int lp = r.Left; int tp = r.Top+2; System.IntPtr hdc = g.GetHdc(); try { // render column headers and trailing column header for (i=0; i r.Left+r.Width)) { Lyquidity.Controls.ExtendedListViews.uxTheme.Wrapper.DrawBackground("HEADER", "HEADERITEM", "NORMAL", hdc, lp_scr+last, tp, r.Width-last-2+hscrollBar.Value, headerBuffer, r.Left, /* r.Top BMS 2003/05/22 */ tp, r.Width, headerBuffer); } } catch { } finally { g.ReleaseHdc(hdc); } last = 1; for (i=0; i r.Left+r.Width ? (r.Width - (r.Left+last))-4 : colwid-2)+hscrollBar.Value, r.Top+headerBuffer)); if (columns[i].Image != null) { g.DrawImage(columns[i].Image, new Rectangle(lp_scr+last+4, r.Top+3, 16, 16)); g.DrawString(TruncatedString(columns[i].Text, colwid, 25, g), this.Font, SystemBrushes.ControlText, (float)(r.Left+last+22-hscrollBar.Value), (float)(r.Top+5)); } else { string sp = TruncatedString(columns[i].Text, colwid, 0, g); if (columns[i].TextAlign == HorizontalAlignment.Left) { g.DrawString(TruncatedString(columns[i].Text, colwid, 0, g), this.Font, SystemBrushes.ControlText, (float)(last+4-hscrollBar.Value), (float)(r.Top+5)); } else if (columns[i].TextAlign == HorizontalAlignment.Right) { g.DrawString(sp, this.Font, SystemBrushes.ControlText, (float)(last+colwid-Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)-4-hscrollBar.Value), (float)(r.Top+5)); } else { g.DrawString(sp, this.Font, SystemBrushes.ControlText, (float)(last+(colwid/2)-(Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)/2)-hscrollBar.Value), (float)(r.Top+5)); } } last += colwid; } } } #endregion } #endregion #region Type Converters public class ToggleColumnHeaderConverter: TypeConverter { public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(InstanceDescriptor)) { return true; } return base.CanConvertTo(context, destinationType); } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(InstanceDescriptor) && value is ToggleColumnHeader) { ToggleColumnHeader lvi = (ToggleColumnHeader)value; ConstructorInfo ci = typeof(ToggleColumnHeader).GetConstructor(new Type[] {}); if (ci != null) { return new InstanceDescriptor(ci, null, false); } } return base.ConvertTo(context, culture, value, destinationType); } } public class ListViewItemConverter: TypeConverter { public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(InstanceDescriptor)) { return true; } return base.CanConvertTo(context, destinationType); } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(InstanceDescriptor) && value is ContainerListViewItem) { ContainerListViewItem lvi = (ContainerListViewItem)value; ConstructorInfo ci = typeof(ContainerListViewItem).GetConstructor(new Type[] {}); if (ci != null) { return new InstanceDescriptor(ci, null, false); } } return base.ConvertTo(context, culture, value, destinationType); } } public class SubListViewItemConverter: TypeConverter { public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(InstanceDescriptor)) { return true; } return base.CanConvertTo(context, destinationType); } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(InstanceDescriptor) && value is ContainerSubListViewItem) { ContainerSubListViewItem lvi = (ContainerSubListViewItem)value; ConstructorInfo ci = typeof(ContainerSubListViewItem).GetConstructor(new Type[] {}); if (ci != null) { return new InstanceDescriptor(ci, null, false); } } return base.ConvertTo(context, culture, value, destinationType); } } #endregion }