From 77c6a2890b3f9f6180d6cd5220638f8de3f9c3ca Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 May 2022 02:54:09 -0600 Subject: [PATCH] Finalized VirtualFlowControl --- .../VirtualFlowControl.Designer.cs | 7 +- .../ProcessQueue/VirtualFlowControl.cs | 170 +++++++++++++----- 2 files changed, 133 insertions(+), 44 deletions(-) diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs index 3b215429..1220cadc 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs @@ -36,17 +36,18 @@ this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.panel1.BackColor = System.Drawing.Color.PaleGreen; + this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel1.Location = new System.Drawing.Point(0, 0); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(359, 507); + this.panel1.Size = new System.Drawing.Size(377, 505); this.panel1.TabIndex = 0; // // VirtualFlowControl // + this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.Controls.Add(this.panel1); this.Name = "VirtualFlowControl"; - this.Size = new System.Drawing.Size(379, 507); + this.Size = new System.Drawing.Size(377, 505); this.ResumeLayout(false); } diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index c6a1966c..c5c76554 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -1,20 +1,33 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; namespace LibationWinForms.ProcessQueue { - internal delegate void RequestDataDelegate(int firstIndex, int numVisible, ProcessBookControl[] panelsToFill); + internal delegate void RequestDataDelegate(int firstIndex, int numVisible, IReadOnlyList panelsToFill); + internal delegate void ControlButtonClickedDelegate(int itemIndex, string buttonName, ProcessBookControl panelClicked); internal partial class VirtualFlowControl : UserControl { + /// + /// Triggered when the needs to update the displayed s + /// public event RequestDataDelegate RequestData; + /// + /// Triggered when one of the 's buttons has been clicked + /// + public event ControlButtonClickedDelegate ButtonClicked; + + /// + /// The number of virtual s in the + /// public int VirtualControlCount { get => _virtualControlCount; set { - if (_virtualControlCount == 0) + if (_virtualControlCount == 0) vScrollBar1.Value = 0; _virtualControlCount = value; @@ -24,19 +37,41 @@ namespace LibationWinForms.ProcessQueue } private int _virtualControlCount; - private int VirtualHeight => _virtualControlCount * CONTROL_HEIGHT - vScrollBar1.Height; - private readonly int PROCESSBOOKCONTROL_MARGIN; - private readonly int CONTROL_HEIGHT; + //https://stackoverflow.com/a/2882878/3335599 + int ScrollValue => Math.Max(Math.Min(VirtualHeight, vScrollBar1.Value), 0); + + /// + /// The virtual height of all virtual controls within this + /// + private int VirtualHeight => _virtualControlCount * VirtualControlHeight - vScrollBar1.Height + 2 * TopMargin; + + /// + /// Item index of the first virtual + /// + private int FirstVisibleVirtualIndex => ScrollValue / VirtualControlHeight; + + /// + /// The display height of this + /// + private int DisplayHeight => DisplayRectangle.Height; + + /// + /// The total height, inclusing margins, of the repeated + /// + private readonly int VirtualControlHeight; //90 + private readonly int TopMargin; private const int WM_MOUSEWHEEL = 522; - private const int NUM_ACTUAL_CONTROLS = 20; + /// + /// Total number of actual controls added to the panel. 23 is sufficient up to a 4k monitor height. + /// + private const int NUM_ACTUAL_CONTROLS = 23; private const int SCROLL_SMALL_CHANGE = 120; private const int SCROLL_LARGE_CHANGE = 3 * SCROLL_SMALL_CHANGE; - private readonly VScrollBar vScrollBar1; - private readonly ProcessBookControl[] BookControls = new ProcessBookControl[NUM_ACTUAL_CONTROLS]; + private readonly List BookControls = new(); public VirtualFlowControl() { @@ -51,39 +86,76 @@ namespace LibationWinForms.ProcessQueue LargeChange = SCROLL_LARGE_CHANGE, Dock = DockStyle.Right }; + panel1.Width -= vScrollBar1.Width; Controls.Add(vScrollBar1); - panel1.Resize += (_, _) => AdjustScrollBar(); + panel1.Resize += (_, _) => + { + AdjustScrollBar(); + DoVirtualScroll(); + }; - if (this.DesignMode) + vScrollBar1.Scroll += (_, s) => SetScrollPosition(s.NewValue); + + var control = InitControl(0); + BookControls.Add(control); + panel1.Controls.Add(control); + VirtualControlHeight = control.Height + control.Margin.Top + control.Margin.Bottom; + TopMargin = control.Margin.Top; + + if (DesignMode) return; - vScrollBar1.Scroll += (_, _) => DoVirtualScroll(); - - for (int i = 0; i < NUM_ACTUAL_CONTROLS; i++) + for (int i = 1; i < NUM_ACTUAL_CONTROLS; i++) { - BookControls[i] = new ProcessBookControl(); - - if (i == 0) - { - PROCESSBOOKCONTROL_MARGIN = BookControls[i].Margin.Left + BookControls[i].Margin.Right; - CONTROL_HEIGHT = BookControls[i].Height + BookControls[i].Margin.Top + BookControls[i].Margin.Bottom; - } - - BookControls[i].Location = new Point(2, CONTROL_HEIGHT * i); - BookControls[i].Width = panel1.ClientRectangle.Width - PROCESSBOOKCONTROL_MARGIN; - BookControls[i].Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top; - panel1.Controls.Add(BookControls[i]); + control = InitControl(VirtualControlHeight * i); + BookControls.Add(control); + panel1.Controls.Add(control); } - panel1.Height += SCROLL_SMALL_CHANGE; + vScrollBar1.SmallChange = VirtualControlHeight; + vScrollBar1.LargeChange = 3 * VirtualControlHeight; + + panel1.Height += 2*VirtualControlHeight; } + private ProcessBookControl InitControl(int locationY) + { + var control = new ProcessBookControl(); + control.Location = new Point(control.Margin.Left, locationY + control.Margin.Top); + control.Width = panel1.ClientRectangle.Width - control.Margin.Left - control.Margin.Right; + control.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top; + control.cancelBtn.Click += ControlButton_Click; + control.moveFirstBtn.Click += ControlButton_Click; + control.moveUpBtn.Click += ControlButton_Click; + control.moveDownBtn.Click += ControlButton_Click; + control.moveLastBtn.Click += ControlButton_Click; + return control; + } + + /// + /// Handles all button clicks from all , detects which one sent the click, and fires + /// + private void ControlButton_Click(object sender, EventArgs e) + { + Control button = sender as Control; + Control form = button.Parent; + while (form is not ProcessBookControl) + form = form.Parent; + + int clickedIndex = BookControls.IndexOf((ProcessBookControl)form); + + ButtonClicked?.Invoke(FirstVisibleVirtualIndex + clickedIndex, button.Name, BookControls[clickedIndex]); + } + + /// + /// Adjusts the max width and enabled status based on the and the + /// private void AdjustScrollBar() { - int maxFullVisible = DisplayRectangle.Height / CONTROL_HEIGHT; + int maxFullVisible = DisplayHeight / VirtualControlHeight; if (VirtualControlCount <= maxFullVisible) { @@ -100,44 +172,60 @@ namespace LibationWinForms.ProcessQueue } } + /// + /// Calculated the virtual controls that are in view at the currrent scroll position and windows size, then fires for the visible controls + /// private void DoVirtualScroll() { - //https://stackoverflow.com/a/2882878/3335599 - int scrollValue = Math.Max(Math.Min(VirtualHeight, vScrollBar1.Value), 0); + int firstVisible = FirstVisibleVirtualIndex; - int position = scrollValue % CONTROL_HEIGHT; + int position = ScrollValue % VirtualControlHeight; panel1.Location = new Point(0, -position); - int firstVisible = scrollValue / CONTROL_HEIGHT; + int visibleHeight = ScrollValue - firstVisible * VirtualControlHeight + DisplayHeight; - int window = DisplayRectangle.Height; + int count = visibleHeight / VirtualControlHeight; - int count = window / CONTROL_HEIGHT; - - if (window % CONTROL_HEIGHT != 0) + if (visibleHeight % VirtualControlHeight != 0) count++; + count = Math.Min(count, VirtualControlCount); RequestData?.Invoke(firstVisible, count, BookControls); - for (int i = 0; i < BookControls.Length; i++) - BookControls[i].Visible = i <= count && VirtualControlCount > 0; + for (int i = 0; i < BookControls.Count; i++) + BookControls[i].Visible = i < count; } + /// + /// Set scroll value to an integral multiple of VirtualControlHeight + /// + private void SetScrollPosition(int value) + { + int newPos = (int)Math.Round((double)value / VirtualControlHeight) * VirtualControlHeight; + if (vScrollBar1.Value != newPos) + { + vScrollBar1.Value = Math.Min(newPos, vScrollBar1.Maximum); + DoVirtualScroll(); + } + } protected override void WndProc(ref Message m) { + //Capture mouse wheel movement and interpret it as a scroll event if (m.Msg == WM_MOUSEWHEEL) { //https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel - int wheelDelta = -(short)(((ulong)m.WParam) >> 16 & 0xffff); + int wheelDelta = -(short)(((ulong)m.WParam) >> 16 & ushort.MaxValue); + + int newScrollPosition; if (wheelDelta > 0) - vScrollBar1.Value = Math.Min(vScrollBar1.Value + wheelDelta, vScrollBar1.Maximum); + newScrollPosition = Math.Min(vScrollBar1.Value + wheelDelta, vScrollBar1.Maximum); else - vScrollBar1.Value = Math.Max(vScrollBar1.Value + wheelDelta, vScrollBar1.Minimum); + newScrollPosition = Math.Max(vScrollBar1.Value + wheelDelta, vScrollBar1.Minimum); - DoVirtualScroll(); + SetScrollPosition(newScrollPosition); } base.WndProc(ref m);