// Copyright (c) Ivan Bondarev, Stanislav Mihalkovich (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Drawing; using System.ComponentModel; using System.Drawing.Drawing2D; using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows.Forms; using ICSharpCode.TextEditor.Document; using Debugger; namespace VisualPascalABC { public static class ScreenScale { private static double scale = -1; public static double Calc() { if (scale > 0) return scale; var dpiXProperty = typeof(System.Windows.SystemParameters).GetProperty("DpiX", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); var dpiX = (int)dpiXProperty.GetValue(null, null); scale = (double)dpiX / 96; return scale; } } public class DynamicTreeDebuggerRow : DynamicTreeRow { // Columns: // 0 = plus sign // 1 = icon // 2 = text // 3 = value ListItem listItem; Image image; bool populated = false; bool visible = true; public ListItem ListItem { get { return listItem; } } public DynamicTreeDebuggerRow(NamedValue val) : this(new ValueItem(val,null)) { } public DynamicTreeDebuggerRow(ListItem listItem) { if (listItem == null) throw new ArgumentNullException("listItem"); this.listItem = listItem; this.listItem.Changed += delegate { Update(true); }; this.Shown += delegate { visible = true; WorkbenchServiceFactory.DebuggerManager.DoInPausedState(delegate { Update(true); }); }; this.Hidden += delegate { visible = false; }; DebuggerGridControl.AddColumns(this.ChildColumns); this[1].Paint += OnIconPaint; this[3].FinishLabelEdit += OnLabelEdited; this[3].MouseDown += OnMouseDown; Update(true); } void Update(bool show_val) { if (!visible) return; this[1].Text = ""; // Icon if (listItem.SpecialName != null) this[2].Text = listItem.SpecialName; else this[2].Text = listItem.Name; if (!show_val) return; this.image = listItem.Image; if (this[2].Text == "" || this[2].Text == null) if (!(listItem is BaseTypeItem && listItem.ImageIndex != -1)) this[2].Text = "base"; else this[2].Text = listItem.Type; if (listItem.Name == "" || this.ListItem == null) this[3].Text = "{" + listItem.Type + "}"; else this[3].Text = listItem.Text; this[3].AllowLabelEdit = listItem.CanEditText; this.ShowPlus = listItem.HasSubItems; this.ShowMinusWhileExpanded = true; } void OnIconPaint(object sender, ItemPaintEventArgs e) { if (image != null) { // SSM 21.01.19 High dpi e.Graphics.DrawImage/*Unscaled*/(image, e.ClipRectangle); } } void OnLabelEdited(object sender, DynamicListEventArgs e) { try { listItem.SetText(((DynamicListItem)sender).Text); } catch { } } void OnMouseDown(object sender, DynamicListMouseEventArgs e) { if (e.Button == MouseButtons.Right) { ContextMenuStrip menu = listItem.GetContextMenu(); if (menu != null) { menu.Show(e.List, e.Location); } } } /// /// Called when plus is pressed in debugger tooltip. /// Sets the data to be show in the next level. /// protected override void OnExpanding(DynamicListEventArgs e) { if (!populated) { WorkbenchServiceFactory.DebuggerManager.DoInPausedState(delegate { Populate(); }); } } void Populate() { this.ChildRows.Clear(); foreach (ListItem subItem in listItem.SubItems) { this.ChildRows.Add(new DynamicTreeDebuggerRow(subItem)); } populated = true; } } public class ToolTipInfo { object toolTipObject; /// /// Gets the tool tip text to be displayed. /// May be null. /// public string ToolTipText { get { return this.toolTipObject as string; } } /// /// Gets the DebuggerGridControl to be shown as tooltip. /// May be null. /// public Control ToolTipControl { get { return this.toolTipObject as Control; } } /// /// Initializes a new instance of the class. /// /// The tooltip text to be displayed. public ToolTipInfo(string toolTipText) { this.toolTipObject = toolTipText; } /// /// Initializes a new instance of the class. /// /// The DebuggerGridControl to be shown as tooltip. public ToolTipInfo(Control toolTipControl) { this.toolTipObject = toolTipControl; } } public class DebuggerGridControl : DynamicList { // Columns: // 0 = plus sign // 1 = icon // 2 = text // 3 = value DynamicTreeRow row; public static void AddColumns(IList columns) { columns.Add(new DynamicListColumn()); columns.Add(new DynamicListColumn()); columns.Add(new DynamicListColumn()); columns.Add(new DynamicListColumn()); columns[0].BackgroundBrush = Brushes.White; columns[0].BackgroundBrushInactive = Brushes.White; columns[0].RowHighlightBrush = null; // default is allowgrow = true and autosize = false columns[0].AllowGrow = false; columns[1].AllowGrow = false; // SSM 21.01.19 High DPI columns[1].Width = 16 + Convert.ToInt32(16 * (ScreenScale.Calc()-1)); columns[1].ColumnSeperatorColor = Color.Transparent; columns[1].ColumnSeperatorColorInactive = Color.Transparent; columns[2].AutoSize = true; columns[2].MinimumWidth = 75; columns[2].ColumnSeperatorColor = Color.White; columns[2].ColumnSeperatorColorInactive = Color.FromArgb(172, 168, 153); columns[3].AutoSize = true; columns[3].MinimumWidth = 75; } public DebuggerGridControl(DynamicTreeRow row) { this.row = row; BeginUpdate(); AddColumns(Columns); Rows.Add(row); row.Expanded += delegate { isExpanded = true; }; row.Collapsed += delegate { isExpanded = false; }; CreateControl(); using (Graphics g = CreateGraphics()) { this.Width = GetRequiredWidth(g); } this.Height = row.Height; EndUpdate(); } DynamicTreeRow.ChildForm frm; public void ShowForm(ICSharpCode.TextEditor.TextArea textArea, /*Point*/ICSharpCode.TextEditor.TextLocation logicTextPos) { frm = new DynamicTreeRow.ChildForm(); frm.AllowResizing = false; frm.Owner = textArea.FindForm(); int ypos = (textArea.Document.GetVisibleLine(logicTextPos.Y) + 1) * textArea.TextView.FontHeight - textArea.VirtualTop.Y; Point p = new Point(0, ypos); p = textArea.PointToScreen(p); p.X = Control.MousePosition.X - 16; p.Y -= 1; frm.StartPosition = FormStartPosition.Manual; frm.ShowInTaskbar = false; frm.Location = p; frm.ClientSize = new Size(Width + 2, row.Height + 2); Dock = DockStyle.Fill; frm.Controls.Add(this); frm.ShowWindowWithoutActivation = true; frm.Show(); textArea.Click += OnTextAreaClick; textArea.KeyDown += OnTextAreaClick; frm.ClientSize = new Size(frm.ClientSize.Width, row.Height + 2); } public bool IsMouseOver { get { if (frm != null && !frm.IsDisposed) { return frm.ClientRectangle.Contains(frm.PointToClient(Control.MousePosition)); } return false; } } void OnTextAreaClick(object sender, EventArgs e) { ((ICSharpCode.TextEditor.TextArea)sender).KeyDown -= OnTextAreaClick; ((ICSharpCode.TextEditor.TextArea)sender).Click -= OnTextAreaClick; frm.Close(); } bool isExpanded; public bool AllowClose { get { return !isExpanded; } } } public class DynamicTreeRow : DynamicListRow { CollectionWithEvents childColumns = new CollectionWithEvents(); CollectionWithEvents childRows = new CollectionWithEvents(); DynamicListItem plus; public DynamicTreeRow() { plus = this[0]; ShowPlus = true; } #region Plus painting bool showPlus; public bool ShowPlus { get { return showPlus; } set { if (showPlus == value) return; showPlus = value; plus.HighlightBrush = showPlus ? Brushes.AliceBlue : null; //plus.Cursor = showPlus ? Cursors.Hand : null; if (showPlus) { plus.Click += OnPlusClick; plus.Paint += OnPlusPaint; //plus.MouseHover += OnPlusMouseHover; } else { plus.Click -= OnPlusClick; plus.Paint -= OnPlusPaint; //plus.MouseHover -= OnPlusMouseHover; } OnShowPlusChanged(EventArgs.Empty); } } static readonly Color PlusBorder = Color.FromArgb(120, 152, 181); static readonly Color LightPlusBorder = Color.FromArgb(176, 194, 221); public static readonly Color DefaultExpandedRowColor = Color.FromArgb(235, 229, 209); public static void DrawPlusSign(Graphics graphics, Rectangle r, bool drawMinus) { using (Brush b = new LinearGradientBrush(r, Color.White, DynamicListColumn.DefaultRowHighlightBackColor, 66f)) { graphics.FillRectangle(b, r); } using (Pen p = new Pen(PlusBorder)) { graphics.DrawRectangle(p, r); } using (Brush b = new SolidBrush(LightPlusBorder)) { graphics.FillRectangle(b, new Rectangle(r.X, r.Y, 1, 1)); graphics.FillRectangle(b, new Rectangle(r.Right, r.Y, 1, 1)); graphics.FillRectangle(b, new Rectangle(r.X, r.Bottom, 1, 1)); graphics.FillRectangle(b, new Rectangle(r.Right, r.Bottom, 1, 1)); } graphics.DrawLine(Pens.Black, r.Left + 2, r.Top + r.Height / 2, r.Right - 2, r.Top + r.Height / 2); if (!drawMinus) { graphics.DrawLine(Pens.Black, r.Left + r.Width / 2, r.Top + 2, r.Left + r.Width / 2, r.Bottom - 2); } } protected virtual void OnPlusPaint(object sender, ItemPaintEventArgs e) { Rectangle r = e.ClipRectangle; var sc = ScreenScale.Calc(); var infl = Convert.ToInt32(4 * sc); r.Inflate(-infl, -infl); DrawPlusSign(e.Graphics, r, expandedIn != null && expandedIn.Contains(e.List)); } protected override DynamicListItem CreateItem() { DynamicListItem item = base.CreateItem(); item.Paint += delegate(object sender, ItemPaintEventArgs e) { if (e.Item != plus && expandedIn != null && !expandedRowColor.IsEmpty && expandedIn.Contains(e.List)) { using (Brush b = new SolidBrush(expandedRowColor)) { e.Graphics.FillRectangle(b, e.FillRectangle); } } }; return item; } List expandedIn; Color expandedRowColor = DefaultExpandedRowColor; public bool ShowMinusWhileExpanded { get { return expandedIn != null; } set { if (this.ShowMinusWhileExpanded == value) return; expandedIn = value ? new List() : null; } } /// /// Gets/Sets the row color used when the row is expanded. Only works together with ShowMinusWhileExpanded. /// public Color ExpandedRowColor { get { return expandedRowColor; } set { expandedRowColor = value; } } #endregion #region Events public event EventHandler Expanding; public event EventHandler Expanded; public event EventHandler Collapsed; public event EventHandler ShowPlusChanged; protected virtual void OnExpanding(DynamicListEventArgs e) { if (Expanding != null) { Expanding(this, e); } } protected virtual void OnExpanded(DynamicListEventArgs e) { if (Expanded != null) { Expanded(this, e); } } protected virtual void OnCollapsed(DynamicListEventArgs e) { if (Collapsed != null) { Collapsed(this, e); } } public virtual void OnShowPlusChanged(EventArgs e) { if (ShowPlusChanged != null) { ShowPlusChanged(this, e); } } #endregion #region Properties public CollectionWithEvents ChildColumns { get { return childColumns; } set { if (value == null) throw new ArgumentNullException("value"); childColumns = value; } } public CollectionWithEvents ChildRows { get { return childRows; } set { if (value == null) throw new ArgumentNullException("value"); childRows = value; } } //public static readonly Color DefaultBorderColor = Color.FromArgb(195, 192, 175); public static readonly Color DefaultBorderColor = Color.LightGray; Color childBorderColor = DefaultBorderColor; public Color ChildBorderColor { get { return childBorderColor; } set { childBorderColor = value; } } #endregion #region Child form static bool isOpeningChild; /// /// Block the next click event - used to ensure that a click on the "-" sign /// does not cause the submenu to open again when the "-" sign becomes a "+" sign /// before the click event is handled. /// bool blockClickEvent; protected virtual void OnPlusMouseHover(object sender, DynamicListEventArgs e) { OnPlusClick(sender, e); } protected virtual void OnPlusClick(object sender, DynamicListEventArgs e) { if (blockClickEvent) { blockClickEvent = false; return; } OnExpanding(e); ChildForm frm = new ChildForm(); frm.Closed += delegate { blockClickEvent = true; if (expandedIn != null) expandedIn.Remove(e.List); OnCollapsed(e); plus.RaiseItemChanged(); Timer timer = new Timer(); timer.Interval = 200; timer.Tick += delegate(object sender2, EventArgs e2) { ((Timer)sender2).Stop(); ((Timer)sender2).Dispose(); blockClickEvent = false; }; timer.Start(); }; Point p = e.List.PointToScreen(e.List.GetPositionFromRow(this)); p.Offset(e.List.Columns[0].Width, Height); frm.StartPosition = FormStartPosition.Manual; frm.BackColor = childBorderColor; frm.Location = p; frm.ShowInTaskbar = false; frm.Owner = e.List.FindForm(); VerticalScrollContainer scrollContainer = new VerticalScrollContainer(); scrollContainer.Dock = DockStyle.Fill; DynamicList childList = new DynamicList(childColumns, childRows); childList.Dock = DockStyle.Fill; childList.KeyDown += delegate(object sender2, KeyEventArgs e2) { if (e2.KeyData == Keys.Escape) { frm.Close(); // workaround focus problem: sometimes the mainform gets focus after this e.List.FindForm().Focus(); } }; scrollContainer.Controls.Add(childList); frm.Controls.Add(scrollContainer); int screenHeight = Screen.FromPoint(p).WorkingArea.Bottom - p.Y; screenHeight -= frm.Size.Height - frm.ClientSize.Height; int requiredHeight = childList.TotalRowHeight + 4; int formHeight = Math.Min(requiredHeight, Math.Min(300,screenHeight)); if (formHeight < requiredHeight && formHeight < 100) { int missingHeight = Math.Min(200, requiredHeight - formHeight); formHeight += missingHeight; frm.Top -= missingHeight; } // Autosize child window int formWidth; using (Graphics g = childList.CreateGraphics()) { formWidth = 8 + childList.GetRequiredWidth(g); } int screenWidth = Screen.FromPoint(p).WorkingArea.Right - p.X; if (formWidth > screenWidth) { int missingWidth = Math.Min(100, formWidth - screenWidth); formWidth = screenWidth + missingWidth; frm.Left -= missingWidth; } frm.ClientSize = new Size(formWidth, formHeight); frm.MinimumSize = new Size(100, Math.Min(50, formHeight)); isOpeningChild = true; frm.Show(); isOpeningChild = false; childList.Focus(); if (expandedIn != null) expandedIn.Add(e.List); OnExpanded(e); plus.RaiseItemChanged(); } public class ChildForm : Form, IActivatable { bool isActivated = true; public bool IsActivated { get { return isActivated; } } bool allowResizing = true; public bool AllowResizing { get { return allowResizing; } set { if (allowResizing == value) return; allowResizing = value; this.DockPadding.All = value ? 2 : 1; } } public ChildForm() { this.FormBorderStyle = FormBorderStyle.None; this.DockPadding.All = 1; this.BackColor = DefaultBorderColor; } bool showWindowWithoutActivation; /// /// Gets/Sets whether the window will receive focus when it is shown. /// public bool ShowWindowWithoutActivation { get { return showWindowWithoutActivation; } set { showWindowWithoutActivation = value; } } protected override bool ShowWithoutActivation { get { return showWindowWithoutActivation; } } protected override CreateParams CreateParams { get { CreateParams p = base.CreateParams; DesignHelper.AddShadowToWindow(p); return p; } } #region Resizing the form private enum MousePositionCodes { HTERROR = (-2), HTTRANSPARENT = (-1), HTNOWHERE = 0, HTCLIENT = 1, HTCAPTION = 2, HTSYSMENU = 3, HTGROWBOX = 4, HTSIZE = HTGROWBOX, HTMENU = 5, HTHSCROLL = 6, HTVSCROLL = 7, HTMINBUTTON = 8, HTMAXBUTTON = 9, HTLEFT = 10, HTRIGHT = 11, HTTOP = 12, HTTOPLEFT = 13, HTTOPRIGHT = 14, HTBOTTOM = 15, HTBOTTOMLEFT = 16, HTBOTTOMRIGHT = 17, HTBORDER = 18, HTREDUCE = HTMINBUTTON, HTZOOM = HTMAXBUTTON, HTSIZEFIRST = HTLEFT, HTSIZELAST = HTBOTTOMRIGHT, HTOBJECT = 19, HTCLOSE = 20, HTHELP = 21 } protected override void WndProc(ref Message m) { base.WndProc(ref m); if (m.Msg == 0x0084) // WM_NCHITTEST HitTest(ref m); } void HitTest(ref Message m) { if (!allowResizing) return; int mousePos = m.LParam.ToInt32(); int mouseX = mousePos & 0xffff; int mouseY = mousePos >> 16; //System.Diagnostics.Debug.WriteLine(mouseX + " / " + mouseY); Rectangle bounds = Bounds; bool isLeft = mouseX == bounds.Left || mouseX + 1 == bounds.Left; bool isTop = mouseY == bounds.Top || mouseY + 1 == bounds.Top; bool isRight = mouseX == bounds.Right - 1 || mouseX == bounds.Right - 2; bool isBottom = mouseY == bounds.Bottom - 1 || mouseY == bounds.Bottom - 2; if (isLeft) { if (isTop) m.Result = new IntPtr((int)MousePositionCodes.HTTOPLEFT); else if (isBottom) m.Result = new IntPtr((int)MousePositionCodes.HTBOTTOMLEFT); else m.Result = new IntPtr((int)MousePositionCodes.HTLEFT); } else if (isRight) { if (isTop) m.Result = new IntPtr((int)MousePositionCodes.HTTOPRIGHT); else if (isBottom) m.Result = new IntPtr((int)MousePositionCodes.HTBOTTOMRIGHT); else m.Result = new IntPtr((int)MousePositionCodes.HTRIGHT); } else if (isTop) { m.Result = new IntPtr((int)MousePositionCodes.HTTOP); } else if (isBottom) { m.Result = new IntPtr((int)MousePositionCodes.HTBOTTOM); } } #endregion protected override void OnActivated(EventArgs e) { isActivated = true; base.OnActivated(e); Refresh(); } protected override void OnDeactivate(EventArgs e) { isActivated = false; base.OnDeactivate(e); if (isOpeningChild) { Refresh(); return; } BeginInvoke(new MethodInvoker(CloseOnDeactivate)); } void CloseOnDeactivate() { ChildForm owner = Owner as ChildForm; if (owner != null) { if (owner.isActivated) Close(); else owner.CloseOnDeactivate(); } else { Close(); } } } #endregion } public static class DesignHelper { static int shadowStatus; /// /// Adds a shadow to the create params if it is supported by the operating system. /// public static void AddShadowToWindow(CreateParams createParams) { if (shadowStatus == 0) { // Test OS version shadowStatus = -1; // shadow not supported if (Environment.OSVersion.Platform == PlatformID.Win32NT) { Version ver = Environment.OSVersion.Version; if (ver.Major > 5 || ver.Major == 5 && ver.Minor >= 1) { shadowStatus = 1; } } } if (shadowStatus == 1) { createParams.ClassStyle |= 0x00020000; // set CS_DROPSHADOW } } } public class DynamicListRow { int height = Convert.ToInt32(16 * ScreenScale.Calc()); public int Height { get { return height; } set { if (value < 2) throw new ArgumentOutOfRangeException("value", value, "value must be at least 2"); if (height != value) { height = value; OnHeightChanged(EventArgs.Empty); } } } public event EventHandler HeightChanged; protected virtual void OnHeightChanged(EventArgs e) { if (HeightChanged != null) { HeightChanged(this, e); } } public event EventHandler Shown; public event EventHandler Hidden; protected virtual void OnShown(DynamicListEventArgs e) { if (Shown != null) { Shown(this, e); } } protected virtual void OnHidden(DynamicListEventArgs e) { if (Hidden != null) { Hidden(this, e); } } internal void NotifyListVisibilityChange(DynamicList list, bool visible) { if (visible) OnShown(new DynamicListEventArgs(list)); else OnHidden(new DynamicListEventArgs(list)); } /// /// Fired when any item has changed. /// public event EventHandler ItemChanged; protected virtual void OnItemChanged(EventArgs e) { if (ItemChanged != null) { ItemChanged(this, e); } } internal void RaiseItemChanged(DynamicListItem item) { OnItemChanged(EventArgs.Empty); } DynamicListItem[] items = new DynamicListItem[10]; public DynamicListItem this[int columnIndex] { [DebuggerStepThrough] get { if (columnIndex < 0) throw new ArgumentOutOfRangeException("columnIndex", columnIndex, "columnIndex must be >= 0"); if (columnIndex > DynamicList.MaxColumnCount) throw new ArgumentOutOfRangeException("columnIndex", columnIndex, "columnIndex must be <= " + DynamicList.MaxColumnCount); if (columnIndex >= items.Length) { Array.Resize(ref items, columnIndex * 2 + 1); } DynamicListItem item = items[columnIndex]; if (item == null) { items[columnIndex] = item = CreateItem(); } return item; } } protected virtual DynamicListItem CreateItem() { return new DynamicListItem(this); } } public class DynamicList : UserControl, VerticalScrollContainer.IScrollable { public const int MaxColumnCount = 1000; public IDocument doc; public DynamicList() : this(new CollectionWithEvents(), new CollectionWithEvents()) { } public DynamicList(CollectionWithEvents columns, CollectionWithEvents rows) { inUpdate = true; if (columns == null) throw new ArgumentNullException("columns"); if (rows == null) throw new ArgumentNullException("rows"); this.columns = columns; this.rows = rows; // we have to register our events on the existing items foreach (DynamicListColumn column in columns) { OnColumnAdded(null, new CollectionItemEventArgs(column)); } foreach (DynamicListRow row in rows) { OnRowAdded(null, new CollectionItemEventArgs(row)); } columns.Added += OnColumnAdded; columns.Removed += OnColumnRemoved; rows.Added += OnRowAdded; rows.Removed += OnRowRemoved; this.BackColor = DefaultBackColor; this.SetStyle(ControlStyles.UserPaint, true); this.SetStyle(ControlStyles.Selectable, true); this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); inUpdate = false; RecalculateColumnWidths(); } public new static readonly Color DefaultBackColor = Color.White; protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { columns.Added -= OnColumnAdded; columns.Removed -= OnColumnRemoved; rows.Added -= OnRowAdded; rows.Removed -= OnRowRemoved; foreach (DynamicListColumn column in columns) { OnColumnRemoved(null, new CollectionItemEventArgs(column)); } foreach (DynamicListRow row in rows) { OnRowRemoved(null, new CollectionItemEventArgs(row)); } WorkbenchServiceFactory.DebuggerManager.RemoveMarker(doc); } } void OnColumnAdded(object sender, CollectionItemEventArgs e) { e.Item.MinimumWidthChanged += ColumnMinimumWidthChanged; e.Item.WidthChanged += ColumnWidthChanged; RecalculateColumnWidths(); } void OnColumnRemoved(object sender, CollectionItemEventArgs e) { e.Item.MinimumWidthChanged -= ColumnMinimumWidthChanged; e.Item.WidthChanged -= ColumnWidthChanged; RecalculateColumnWidths(); } void OnRowAdded(object sender, CollectionItemEventArgs e) { e.Item.HeightChanged += RowHeightChanged; e.Item.ItemChanged += RowItemChanged; if (Visible && Parent != null) e.Item.NotifyListVisibilityChange(this, true); } void OnRowRemoved(object sender, CollectionItemEventArgs e) { e.Item.HeightChanged -= RowHeightChanged; e.Item.ItemChanged -= RowItemChanged; if (Visible) e.Item.NotifyListVisibilityChange(this, false); } bool oldVisible = false; protected override void OnVisibleChanged(EventArgs e) { base.OnVisibleChanged(e); bool visible = Visible && Parent != null; if (visible == oldVisible) return; oldVisible = visible; foreach (DynamicListRow row in Rows) { row.NotifyListVisibilityChange(this, visible); } } void ColumnMinimumWidthChanged(object sender, EventArgs e) { RecalculateColumnWidths(); } void RowHeightChanged(object sender, EventArgs e) { Redraw(); } void RowItemChanged(object sender, EventArgs e) { Redraw(); } bool inRecalculateColumnWidths; bool inRecalculateNeedsRedraw; void RecalculateColumnWidths() { if (inUpdate) return; if (inRecalculateColumnWidths) return; inRecalculateColumnWidths = true; inRecalculateNeedsRedraw = false; try { int availableWidth = ClientSize.Width; int minRequiredWidth = 0; foreach (DynamicListColumn c in columns) { if (c.AllowGrow) minRequiredWidth += c.MinimumWidth; else availableWidth -= c.Width; availableWidth -= 1; } // everyone gets c.MinimumWidth * availableWidth / minRequiredWidth foreach (DynamicListColumn c in columns) { if (c.AllowGrow) c.Width = Math.Max(2, c.MinimumWidth * availableWidth / minRequiredWidth); } } finally { inRecalculateColumnWidths = false; } if (inRecalculateNeedsRedraw) { Redraw(); } } void ColumnWidthChanged(object sender, EventArgs e) { if (inRecalculateColumnWidths) { inRecalculateNeedsRedraw = true; return; } Redraw(); } bool inUpdate; public void BeginUpdate() { inUpdate = true; } public void EndUpdate() { inUpdate = false; RecalculateColumnWidths(); } void Redraw() { if (inUpdate) return; Invalidate(); } List allowedControls = new List(); List removedControls = new List(); int scrollOffset = 0; public int ScrollOffset { get { return scrollOffset; } set { if (scrollOffset != value) { scrollOffset = value; Redraw(); } } } int VerticalScrollContainer.IScrollable.ScrollOffsetY { get { return this.ScrollOffset; } set { this.ScrollOffset = value; } } int VerticalScrollContainer.IScrollable.ScrollHeightY { get { return this.TotalRowHeight; } } public int GetRequiredWidth(Graphics graphics) { int width = 0; for (int i = 0; i < columns.Count; i++) { if (columns[i].AutoSize) { int minimumWidth = DynamicListColumn.DefaultWidth; foreach (DynamicListRow row in Rows) { DynamicListItem item = row[i]; item.MeasureMinimumWidth(graphics, ref minimumWidth); } width += minimumWidth; } else { width += columns[i].Width; } width += 1; } return width; } int lineMarginY = 0; public int LineMarginY { get { return lineMarginY; } set { if (lineMarginY == value) return; lineMarginY = value; Redraw(); } } protected override void OnPaint(PaintEventArgs e) { //Debug.WriteLine("OnPaint"); Graphics g = e.Graphics; allowedControls.Clear(); int columnIndex = -1; foreach (DynamicListColumn col in Columns) { columnIndex += 1; if (!col.AutoSize) continue; int minimumWidth = DynamicListColumn.DefaultWidth; foreach (DynamicListRow row in Rows) { DynamicListItem item = row[columnIndex]; item.MeasureMinimumWidth(e.Graphics, ref minimumWidth); } col.MinimumWidth = minimumWidth; } int controlIndex = 0; int xPos; int yPos = -scrollOffset; Size clientSize = ClientSize; foreach (DynamicListRow row in Rows) { if (yPos + row.Height > 0 && yPos < clientSize.Height) { xPos = 0; for (columnIndex = 0; columnIndex < columns.Count; columnIndex++) { DynamicListColumn col = columns[columnIndex]; Rectangle rect = new Rectangle(xPos, yPos, col.Width, row.Height); if (columnIndex == columns.Count - 1) rect.Width = clientSize.Width - 1 - rect.Left; DynamicListItem item = row[columnIndex]; Control ctl = item.Control; if (ctl != null) { allowedControls.Add(ctl); if (rect != ctl.Bounds) ctl.Bounds = rect; if (!this.Controls.Contains(ctl)) { this.Controls.Add(ctl); this.Controls.SetChildIndex(ctl, controlIndex); } controlIndex += 1; } else { item.PaintTo(e.Graphics, rect, this, col, item == itemAtMousePosition); } xPos += col.Width + 1; } } yPos += row.Height + lineMarginY; } xPos = 0; Form containerForm = FindForm(); bool isFocused; if (containerForm is IActivatable) isFocused = (containerForm as IActivatable).IsActivated; else isFocused = this.Focused; for (columnIndex = 0; columnIndex < columns.Count - 1; columnIndex++) { DynamicListColumn col = columns[columnIndex]; xPos += col.Width + 1; Color separatorColor =Color.Empty; //if (isFocused) if (columnIndex == columns.Count-2) { separatorColor = Color.LightGray;//col.ColumnSeperatorColor; if (separatorColor.IsEmpty) separatorColor = col.ColumnSeperatorColorInactive; } /*else { separatorColor = col.ColumnSeperatorColorInactive; if (separatorColor.IsEmpty) separatorColor = col.ColumnSeperatorColor; }*/ if (separatorColor.IsEmpty) separatorColor = BackColor; using (Pen separatorPen = new Pen(separatorColor)) { e.Graphics.DrawLine(separatorPen, xPos - 1, 1, xPos - 1, Math.Min(clientSize.Height, yPos) - 2); } } removedControls.Clear(); foreach (Control ctl in Controls) { if (!allowedControls.Contains(ctl)) removedControls.Add(ctl); } foreach (Control ctl in removedControls) { Debug.WriteLine("Removing control"); Controls.Remove(ctl); Debug.WriteLine("Control removed"); } allowedControls.Clear(); removedControls.Clear(); base.OnPaint(e); } /// /// Gets if the parent form of this list is the active window. /// public bool IsActivated { get { Form containerForm = FindForm(); if (containerForm is IActivatable) return (containerForm as IActivatable).IsActivated; else return this.Focused; } } protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); RecalculateColumnWidths(); } public DynamicListRow GetRowFromPoint(int yPos) { int y = -scrollOffset; foreach (DynamicListRow row in Rows) { if (yPos < y) break; if (yPos <= y + row.Height) return row; y += row.Height + lineMarginY; } return null; } /// /// Gets the upper left corner of the specified row. /// public Point GetPositionFromRow(DynamicListRow row) { int y = -scrollOffset; foreach (DynamicListRow r in Rows) { if (r == row) return new Point(0, y); y += r.Height + lineMarginY; } throw new ArgumentException("The row in not in this list!"); } /// /// Gets the height of all rows. /// public int TotalRowHeight { get { int y = 0; foreach (DynamicListRow r in Rows) { y += r.Height + lineMarginY; } return y; } } public int GetColumnIndexFromPoint(int xPos) { int columnIndex = 0; int x = 0; foreach (DynamicListColumn col in Columns) { if (xPos < x) break; if (xPos <= x + col.Width) return columnIndex; x += col.Width + 1; columnIndex += 1; } return -1; } public DynamicListItem GetItemFromPoint(Point position) { DynamicListRow row = GetRowFromPoint(position.Y); if (row == null) return null; int columnIndex = GetColumnIndexFromPoint(position.X); if (columnIndex < 0) return null; return row[columnIndex]; } protected override void OnClick(EventArgs e) { base.OnClick(e); DynamicListItem item = GetItemFromPoint(PointToClient(Control.MousePosition)); if (item != null) item.PerformClick(this); } protected override void OnDoubleClick(EventArgs e) { base.OnDoubleClick(e); DynamicListItem item = GetItemFromPoint(PointToClient(Control.MousePosition)); if (item != null) item.PerformDoubleClick(this); } protected override void OnMouseHover(EventArgs e) { base.OnMouseHover(e); DynamicListItem item = GetItemFromPoint(PointToClient(Control.MousePosition)); if (item != null) item.OnMouseHover(this); } DynamicListItem itemAtMousePosition; DynamicListRow rowAtMousePosition; public DynamicListItem ItemAtMousePosition { get { return itemAtMousePosition; } } public DynamicListRow RowAtMousePosition { get { return rowAtMousePosition; } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); DynamicListRow row = GetRowFromPoint(e.Y); if (rowAtMousePosition != row) { rowAtMousePosition = row; Invalidate(); } if (row == null) return; int columnIndex = GetColumnIndexFromPoint(e.X); if (columnIndex < 0) return; DynamicListItem item = row[columnIndex]; if (itemAtMousePosition != item) { if (itemAtMousePosition != null) { OnLeaveItem(itemAtMousePosition); } ResetMouseEventArgs(); // raise hover again itemAtMousePosition = item; if (item != null) { if (item.Cursor != null) this.Cursor = item.Cursor; item.OnMouseEnter(this); } } if (item != null) { item.OnMouseMove(new DynamicListMouseEventArgs(this, e)); } } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); DynamicListItem item = GetItemFromPoint(e.Location); if (item != null) item.OnMouseDown(new DynamicListMouseEventArgs(this, e)); } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); DynamicListItem item = GetItemFromPoint(e.Location); if (item != null) item.OnMouseUp(new DynamicListMouseEventArgs(this, e)); } protected override void OnMouseLeave(EventArgs e) { rowAtMousePosition = null; if (itemAtMousePosition != null) { OnLeaveItem(itemAtMousePosition); itemAtMousePosition = null; } base.OnMouseLeave(e); } protected virtual void OnLeaveItem(DynamicListItem item) { itemAtMousePosition.OnMouseLeave(this); this.Cursor = Cursors.Default; } readonly CollectionWithEvents columns; readonly CollectionWithEvents rows; public CollectionWithEvents Columns { [DebuggerStepThrough] get { return columns; } } [Browsable(false)] public CollectionWithEvents Rows { [DebuggerStepThrough] get { return rows; } } } public interface IActivatable { bool IsActivated { get; } event EventHandler Activated; event EventHandler Deactivate; } public sealed class DynamicListItem { DynamicListRow row; internal DynamicListItem(DynamicListRow row) { this.row = row; } public void RaiseItemChanged() { row.RaiseItemChanged(this); } Cursor cursor; public DynamicListRow Row { get { return row; } } public Cursor Cursor { get { return cursor; } set { cursor = value; } } #region BackgroundBrush / Control Brush backgroundBrush; public Brush BackgroundBrush { get { return backgroundBrush; } set { if (backgroundBrush != value) { backgroundBrush = value; RaiseItemChanged(); } } } Brush backgroundBrushInactive; public Brush BackgroundBrushInactive { get { return backgroundBrushInactive; } set { if (backgroundBrushInactive != value) { backgroundBrushInactive = value; RaiseItemChanged(); } } } Brush highlightBrush; public Brush HighlightBrush { get { return highlightBrush; } set { if (highlightBrush != value) { highlightBrush = value; RaiseItemChanged(); } } } Control control; public Control Control { get { return control; } set { if (control != value) { control = value; RaiseItemChanged(); } } } #endregion #region MeasureWidth / Paint public event EventHandler MeasureWidth; internal void MeasureMinimumWidth(Graphics graphics, ref int minimumWidth) { if (MeasureWidth != null) { MeasureWidthEventArgs e = new MeasureWidthEventArgs(graphics); MeasureWidth(this, e); minimumWidth = Math.Max(minimumWidth, e.ItemWidth); } if (text.Length > 0) { // Prevent GDI exception (forum-12284) when text is very long if (text.Length > short.MaxValue) { text = text.Substring(0, short.MaxValue - 1); } int width = 2 + (int)graphics.MeasureString(text, font, new PointF(0, 0), textFormat).Width; minimumWidth = Math.Max(minimumWidth, width); } } public event EventHandler Paint; internal void PaintTo(Graphics g, Rectangle rectangle, DynamicList list, DynamicListColumn column, bool isMouseEntered) { Rectangle fillRectangle = rectangle; fillRectangle.Width += 1; if (highlightBrush != null && isMouseEntered) { g.FillRectangle(highlightBrush, fillRectangle); } else { bool isActivated = list.IsActivated; Brush bgBrush = GetBrush(isActivated, backgroundBrush, backgroundBrushInactive); if (bgBrush == null) { bgBrush = GetBrush(isActivated, column.BackgroundBrush, column.BackgroundBrushInactive); if (isActivated && list.RowAtMousePosition == row && column.RowHighlightBrush != null) bgBrush = column.RowHighlightBrush; } g.FillRectangle(bgBrush, fillRectangle); } if (Paint != null) { Paint(this, new ItemPaintEventArgs(g, rectangle, fillRectangle, list, column, this, isMouseEntered)); } if (text.Length > 0) { // SSM 21.01.19 High DPI textFormat.LineAlignment = StringAlignment.Center; g.DrawString(text, font, textBrush, rectangle, textFormat); } } Brush GetBrush(bool isActive, Brush activeBrush, Brush inactiveBrush) { return isActive ? (activeBrush ?? inactiveBrush) : (inactiveBrush ?? activeBrush); } #endregion #region Text drawing string text = string.Empty; public string Text { get { return text; } set { if (value == null) throw new ArgumentNullException("value", "Use string.Empty instead of null!"); if (text != value) { text = value; RaiseItemChanged(); } } } Font font = Control.DefaultFont; public Font Font { get { return font; } set { if (value == null) throw new ArgumentNullException("value"); if (font != value) { font = value; RaiseItemChanged(); } } } Brush textBrush = SystemBrushes.ControlText; public Brush TextBrush { get { return textBrush; } set { if (value == null) throw new ArgumentNullException("value"); if (textBrush != value) { textBrush = value; RaiseItemChanged(); } } } static StringFormat defaultTextFormat; public static StringFormat DefaultTextFormat { get { if (defaultTextFormat == null) { defaultTextFormat = (StringFormat)StringFormat.GenericDefault.Clone(); defaultTextFormat.FormatFlags |= StringFormatFlags.NoWrap; } return defaultTextFormat; } } StringFormat textFormat = DefaultTextFormat; public StringFormat TextFormat { get { return textFormat; } set { if (value == null) throw new ArgumentNullException("value"); if (textFormat != value) { textFormat = value; RaiseItemChanged(); } } } #endregion #region Mouse Events public event EventHandler Click; public void PerformClick(DynamicList list) { if (Click != null) Click(this, new DynamicListEventArgs(list)); HandleLabelEditClick(list); } public event EventHandler DoubleClick; public void PerformDoubleClick(DynamicList list) { if (DoubleClick != null) DoubleClick(this, new DynamicListEventArgs(list)); } public event EventHandler MouseHover; internal void OnMouseHover(DynamicList list) { if (MouseHover != null) { MouseHover(this, new DynamicListEventArgs(list)); } } public event EventHandler MouseLeave; internal void OnMouseLeave(DynamicList list) { if (MouseLeave != null) { MouseLeave(this, new DynamicListEventArgs(list)); } if (highlightBrush != null) RaiseItemChanged(); } public event EventHandler MouseEnter; internal void OnMouseEnter(DynamicList list) { if (MouseEnter != null) { MouseEnter(this, new DynamicListEventArgs(list)); } if (highlightBrush != null) RaiseItemChanged(); } public event EventHandler MouseMove; internal void OnMouseMove(DynamicListMouseEventArgs e) { if (MouseMove != null) { MouseMove(this, e); } } public event EventHandler MouseDown; internal void OnMouseDown(DynamicListMouseEventArgs e) { if (MouseDown != null) { MouseDown(this, e); } } public event EventHandler MouseUp; internal void OnMouseUp(DynamicListMouseEventArgs e) { if (MouseUp != null) { MouseUp(this, e); } } #endregion #region Label editing bool allowLabelEdit; public bool AllowLabelEdit { get { return allowLabelEdit; } set { allowLabelEdit = value; } } public event EventHandler BeginLabelEdit; public event EventHandler FinishLabelEdit; public event EventHandler CanceledLabelEdit; void HandleLabelEditClick(DynamicList list) { if (!allowLabelEdit) return; TextBox txt = new TextBox(); txt.Text = this.Text; AssignControlUntilFocusChange(txt); if (BeginLabelEdit != null) BeginLabelEdit(this, new DynamicListEventArgs(list)); bool escape = false; txt.KeyDown += delegate(object sender2, KeyEventArgs e2) { if (e2.KeyData == Keys.Enter || e2.KeyData == Keys.Escape) { e2.Handled = true; if (e2.KeyData == Keys.Escape) { if (CanceledLabelEdit != null) CanceledLabelEdit(this, new DynamicListEventArgs(list)); escape = true; } this.Control = null; txt.Dispose(); } }; txt.LostFocus += delegate { if (!escape) { this.Text = txt.Text; if (FinishLabelEdit != null) FinishLabelEdit(this, new DynamicListEventArgs(list)); } }; } /// /// Display the control for this item. Automatically assign focus to the control /// and removes+disposes the control when it looses focus. /// public void AssignControlUntilFocusChange(Control control) { MethodInvoker method = delegate { if (!control.Focus()) { control.Focus(); } control.LostFocus += delegate { this.Control = null; control.Dispose(); }; }; control.HandleCreated += delegate { control.BeginInvoke(method); }; this.Control = control; } #endregion } public class DynamicListEventArgs : EventArgs { DynamicList list; public DynamicList List { [DebuggerStepThrough] get { return list; } } public DynamicListEventArgs(DynamicList list) { if (list == null) throw new ArgumentNullException("list"); this.list = list; } } public class DynamicListMouseEventArgs : MouseEventArgs { DynamicList list; public DynamicList List { [DebuggerStepThrough] get { return list; } } public DynamicListMouseEventArgs(DynamicList list, MouseEventArgs me) : base(me.Button, me.Clicks, me.X, me.Y, me.Delta) { if (list == null) throw new ArgumentNullException("list"); this.list = list; } } public class MeasureWidthEventArgs : EventArgs { Graphics graphics; public Graphics Graphics { get { return graphics; } } int itemWidth; public int ItemWidth { get { return itemWidth; } set { itemWidth = value; } } public MeasureWidthEventArgs(Graphics graphics) { if (graphics == null) throw new ArgumentNullException("graphics"); this.graphics = graphics; } } public class ItemPaintEventArgs : PaintEventArgs { DynamicList list; DynamicListColumn column; DynamicListItem item; bool isMouseEntered; Rectangle fillRectangle; public Rectangle FillRectangle { get { return fillRectangle; } } public DynamicList List { get { return list; } } public DynamicListColumn Column { get { return column; } } public DynamicListRow Row { get { return item.Row; } } public DynamicListItem Item { get { return item; } } public bool IsMouseEntered { get { return isMouseEntered; } } public ItemPaintEventArgs(Graphics graphics, Rectangle rectangle, Rectangle fillRectangle, DynamicList list, DynamicListColumn column, DynamicListItem item, bool isMouseEntered) : base(graphics, rectangle) { this.fillRectangle = fillRectangle; this.list = list; this.column = column; this.item = item; this.isMouseEntered = isMouseEntered; } } public class DynamicListColumn : ICloneable { // SSM 21.01.19 High DPI public static int DefaultWidth = Convert.ToInt32(16 * ScreenScale.Calc()); int width = DefaultWidth; int minimumWidth = DefaultWidth; bool allowGrow = true; bool autoSize = false; //public static readonly Color DefaultBackColor = Color.FromArgb(247, 245, 233); public static readonly Color DefaultBackColor = SystemColors.Window; public static readonly Brush DefaultBackBrush = new SolidBrush(DefaultBackColor); public static readonly Color DefaultRowHighlightBackColor = Color.FromArgb(221, 218, 203); //public static readonly Color DefaultRowHighlightBackColor = SystemColors.Highlight; public static readonly Brush DefaultRowHighlightBrush = new SolidBrush(DefaultRowHighlightBackColor); //public static readonly Color DefaultInactiveBackColor = Color.FromArgb(242, 240, 228); public static readonly Color DefaultInactiveBackColor = SystemColors.Window; public static readonly Brush DefaultInactiveBackBrush = new SolidBrush(DefaultInactiveBackColor); Brush backgroundBrush = DefaultBackBrush; Brush backgroundBrushInactive = DefaultInactiveBackBrush; Brush rowHighlightBrush = DefaultRowHighlightBrush; public virtual DynamicListColumn Clone() { return (DynamicListColumn)base.MemberwiseClone(); } object ICloneable.Clone() { return this.Clone(); } #region Properties public int MinimumWidth { get { return minimumWidth; } set { if (value < 2) throw new ArgumentOutOfRangeException("value", value, "MinimumWidth must be at least 2"); if (minimumWidth != value) { minimumWidth = value; if (MinimumWidthChanged != null) { MinimumWidthChanged(this, EventArgs.Empty); } } } } public event EventHandler MinimumWidthChanged; public int Width { get { return width; } set { if (value < 2) throw new ArgumentOutOfRangeException("value", value, "Width must be at least 2"); if (width != value) { width = value; if (WidthChanged != null) { WidthChanged(this, EventArgs.Empty); } } } } public event EventHandler WidthChanged; public bool AllowGrow { get { return allowGrow; } set { allowGrow = value; } } public bool AutoSize { get { return autoSize; } set { autoSize = value; } } public Brush BackgroundBrush { get { return backgroundBrush; } set { if (value == null) throw new ArgumentNullException("value"); backgroundBrush = value; } } public Brush BackgroundBrushInactive { get { return backgroundBrushInactive; } set { if (value == null) throw new ArgumentNullException("value"); backgroundBrushInactive = value; } } public Brush RowHighlightBrush { get { return rowHighlightBrush; } set { rowHighlightBrush = value; } } Color columnSeperatorColor = Color.Empty; /// /// Sets the color that is used to the right of this column as separator color. /// public Color ColumnSeperatorColor { get { return columnSeperatorColor; } set { columnSeperatorColor = value; } } Color columnSeperatorColorInactive = Color.Empty; /// /// Sets the color that is used to the right of this column as separator color. /// public Color ColumnSeperatorColorInactive { get { return columnSeperatorColorInactive; } set { columnSeperatorColorInactive = value; } } #endregion } public sealed class CollectionWithEvents : IList { List list = new List(); public event EventHandler> Added; public event EventHandler> Removed; void OnAdded(T item) { if (Added != null) Added(this, new CollectionItemEventArgs(item)); } void OnRemoved(T item) { if (Removed != null) Removed(this, new CollectionItemEventArgs(item)); } public T this[int index] { get { return list[index]; } set { T oldValue = list[index]; if (!object.Equals(oldValue, value)) { list[index] = value; OnRemoved(oldValue); OnAdded(value); } } } public int Count { [DebuggerStepThrough] get { return list.Count; } } public bool IsReadOnly { get { return false; } } public int IndexOf(T item) { return list.IndexOf(item); } public void Insert(int index, T item) { list.Insert(index, item); OnAdded(item); } public void RemoveAt(int index) { T item = list[index]; list.RemoveAt(index); OnRemoved(item); } public void Add(T item) { list.Add(item); OnAdded(item); } public void AddRange(IEnumerable range) { foreach (T t in range) { Add(t); } } public void Clear() { List oldList = list; list = new List(); foreach (T item in oldList) { OnRemoved(item); } } public bool Contains(T item) { return list.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { list.CopyTo(array, arrayIndex); } public bool Remove(T item) { if (list.Remove(item)) { OnRemoved(item); return true; } return false; } [DebuggerStepThrough] public IEnumerator GetEnumerator() { return list.GetEnumerator(); } [DebuggerStepThrough] System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return list.GetEnumerator(); } } public class CollectionItemEventArgs : EventArgs { T item; public T Item { get { return item; } } public CollectionItemEventArgs(T item) { this.item = item; } } public class VerticalScrollContainer : Control { public interface IScrollable { int ScrollOffsetY { get; set; } int ScrollHeightY { get; } int Height { get; } event MouseEventHandler MouseWheel; } IScrollable scrollable; bool showButtonsOnlyIfRequired = true; ScrollButtonControl down = new ScrollButtonControl(); ScrollButtonControl up = new ScrollButtonControl(); Timer timer = new Timer(); int scrollSpeed = 5; public int ScrollSpeed { get { return scrollSpeed; } set { scrollSpeed = value; } } public VerticalScrollContainer() { up.Arrow = ScrollButton.Up; down.Arrow = ScrollButton.Down; up.Dock = DockStyle.Top; down.Dock = DockStyle.Bottom; this.TabStop = false; this.SetStyle(ControlStyles.Selectable, false); Controls.Add(up); Controls.Add(down); UpdateEnabled(); timer.Interval = 50; timer.Tick += delegate { ScrollBy((int)timer.Tag); }; up.MouseDown += delegate { timer.Tag = -scrollSpeed; ScrollBy(-scrollSpeed); timer.Start(); }; down.MouseDown += delegate { timer.Tag = scrollSpeed; ScrollBy(scrollSpeed); timer.Start(); }; up.MouseUp += StopTimer; down.MouseUp += StopTimer; } void StopTimer(object sender, MouseEventArgs e) { timer.Stop(); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { timer.Dispose(); } } void UpdateEnabled() { if (scrollable == null) { up.Visible = down.Visible = true; up.Enabled = down.Enabled = false; } else { int scrollHeightY = scrollable.ScrollHeightY; if (showButtonsOnlyIfRequired) { if (scrollHeightY <= this.Height) { scrollable.ScrollOffsetY = 0; up.Visible = down.Visible = false; return; } up.Visible = down.Visible = true; } else { up.Visible = down.Visible = true; if (scrollable.ScrollHeightY <= scrollable.Height) { scrollable.ScrollOffsetY = 0; up.Enabled = down.Enabled = false; return; } } // set enabled up.Enabled = scrollable.ScrollOffsetY > 0; down.Enabled = scrollable.ScrollOffsetY < scrollHeightY - scrollable.Height; } } void ScrollBy(int amount) { scrollable.ScrollOffsetY = Math.Max(0, Math.Min(scrollable.ScrollOffsetY + amount, scrollable.ScrollHeightY - scrollable.Height)); UpdateEnabled(); } public bool ShowButtonsOnlyIfRequired { get { return showButtonsOnlyIfRequired; } set { if (showButtonsOnlyIfRequired != value) { showButtonsOnlyIfRequired = value; UpdateEnabled(); } } } void ScrollableWheel(object sender, MouseEventArgs e) { ScrollBy(-e.Delta / 3); } protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); UpdateEnabled(); } protected override void OnControlAdded(ControlEventArgs e) { base.OnControlAdded(e); if (scrollable == null && !DesignMode) { scrollable = e.Control as IScrollable; if (scrollable != null) { scrollable.MouseWheel += ScrollableWheel; Controls.SetChildIndex(e.Control, 0); UpdateEnabled(); } } } protected override void OnControlRemoved(ControlEventArgs e) { base.OnControlRemoved(e); if (scrollable == e.Control) { scrollable.MouseWheel -= ScrollableWheel; scrollable = null; UpdateEnabled(); } } protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); // HACK: Windows.Forms bug workaround BeginInvoke(new MethodInvoker(PerformLayout)); } } public class ScrollButtonControl : Control { public ScrollButtonControl() { this.BackColor = DynamicListColumn.DefaultBackColor; this.TabStop = false; this.SetStyle(ControlStyles.Selectable, false); this.SetStyle(ControlStyles.ResizeRedraw, true); } protected override Size DefaultSize { get { return new Size(14, 14); } } ScrollButton arrow = ScrollButton.Down; public ScrollButton Arrow { get { return arrow; } set { arrow = value; Invalidate(); } } bool drawSeparatorLine = true; public bool DrawSeparatorLine { get { return drawSeparatorLine; } set { drawSeparatorLine = value; Invalidate(); } } Color highlightColor = SystemColors.Highlight; public Color HighlightColor { get { return highlightColor; } set { highlightColor = value; Invalidate(); } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); const int margin = 2; int height = this.ClientSize.Height; int size = height - 2 * margin; int width = this.ClientSize.Width; int left = (width - size) / 2; int right = (width + size) / 2; Point[] triangle; switch (arrow) { case ScrollButton.Down: triangle = new Point[] { new Point(left, margin), new Point(right, margin), new Point(width / 2, margin + size) }; if (drawSeparatorLine) e.Graphics.DrawLine(SystemPens.GrayText, 0, 0, width, 0); break; case ScrollButton.Up: triangle = new Point[] { new Point(left, margin + size), new Point(right, margin + size), new Point(width / 2, margin) }; if (drawSeparatorLine) e.Graphics.DrawLine(SystemPens.GrayText, 0, height - 1, width, height - 1); break; default: return; } Color color; if (Enabled) color = cursorEntered ? HighlightColor : ForeColor; else color = SystemColors.GrayText; using (Brush b = new SolidBrush(color)) { e.Graphics.FillPolygon(b, triangle); } } bool cursorEntered = false; protected override void OnMouseEnter(EventArgs e) { base.OnMouseEnter(e); cursorEntered = true; Invalidate(); } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); cursorEntered = false; Invalidate(); } } }