From 231710ca68be3974ad227e0a5254ff341f8b7448 Mon Sep 17 00:00:00 2001 From: F K <54195004+fredjk-gh@users.noreply.github.com> Date: Tue, 30 Jan 2024 20:49:53 -0500 Subject: [PATCH] [Core + Framework] Performance improvements for re-drawing the grid (#141) This proposes a *new method* on IObservatoryCore: `SetGridItems(worker, items)` which does 2 things: * Clears the grid * Adds the given items. Effectively replaces the entire content of the grid -- which is something a handful of plugins now do. Why add this when you could just call Core's Clear + AddGridItems methods?? So it can all be done within the same rendering suppression "scope" to reduce flickering and increase rendering speed. Speaking of rendering suppression, I have implemented such rendering suppression "scope" which uses Listview's built-in Begin/EndUpdate() in combination with temporary removal of the sort comparer (as is done for read-alls). This was also applied to the existing AddGridItems(worker, items) method as well, addressing a TODO. --- .../PluginManagement/PluginCore.cs | 54 ++++++++++++++++++- ObservatoryCore/UI/PluginHelper.cs | 1 + ObservatoryCore/UI/PluginListView.cs | 26 +++++++-- ObservatoryFramework/Interfaces.cs | 7 +++ ObservatoryFramework/ObservatoryFramework.xml | 7 +++ 5 files changed, 89 insertions(+), 6 deletions(-) diff --git a/ObservatoryCore/PluginManagement/PluginCore.cs b/ObservatoryCore/PluginManagement/PluginCore.cs index 5e919f0..d351d07 100644 --- a/ObservatoryCore/PluginManagement/PluginCore.cs +++ b/ObservatoryCore/PluginManagement/PluginCore.cs @@ -2,6 +2,7 @@ using Observatory.Framework.Files; using Observatory.Framework.Interfaces; using Observatory.NativeNotification; +using Observatory.UI; using Observatory.Utils; using System; using System.Collections.ObjectModel; @@ -114,11 +115,27 @@ namespace Observatory.PluginManagement public void AddGridItems(IObservatoryWorker worker, IEnumerable<object> items) { - //TODO: Use better bulk handling here. + BeginBulkUpdate(worker); + foreach (var item in items) { worker.PluginUI.DataGrid.Add(item); } + + EndBulkUpdate(worker); + } + + public void SetGridItems(IObservatoryWorker worker, IEnumerable<object> items) + { + BeginBulkUpdate(worker); + + worker.PluginUI.DataGrid.Clear(); + foreach (var item in items) + { + worker.PluginUI.DataGrid.Add(item); + } + + EndBulkUpdate(worker); } public void ClearGrid(IObservatoryWorker worker, object templateItem) @@ -186,5 +203,40 @@ namespace Observatory.PluginManagement { NativePopup.CloseAll(); } + + private void BeginBulkUpdate(IObservatoryWorker worker) + { + PluginListView? listView = FindPluginListView(worker); + if (listView == null) return; + + ExecuteOnUIThread(() => { listView.SuspendDrawing(); }); + } + + private void EndBulkUpdate(IObservatoryWorker worker) + { + PluginListView? listView = FindPluginListView(worker); + if (listView == null) return; + + ExecuteOnUIThread(() => { listView.ResumeDrawing(); }); + } + + private PluginListView? FindPluginListView(IObservatoryWorker worker) + { + if (worker.PluginUI.PluginUIType != PluginUI.UIType.Basic + || !(worker.PluginUI.UI is Panel)) return null; + + PluginListView? listView = null; + Panel panel = worker.PluginUI.UI as Panel; + + foreach (var control in panel.Controls) + { + if (control?.GetType() == typeof(PluginListView)) + { + listView = (PluginListView)control; + return listView; + } + } + return null; + } } } diff --git a/ObservatoryCore/UI/PluginHelper.cs b/ObservatoryCore/UI/PluginHelper.cs index d9fb8fa..899dcd6 100644 --- a/ObservatoryCore/UI/PluginHelper.cs +++ b/ObservatoryCore/UI/PluginHelper.cs @@ -61,6 +61,7 @@ namespace Observatory.UI { Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom | AnchorStyles.Top }; + plugin.PluginUI.UI = panel; IObservatoryComparer columnSorter; if (plugin.ColumnSorter != null) diff --git a/ObservatoryCore/UI/PluginListView.cs b/ObservatoryCore/UI/PluginListView.cs index 3897123..469978e 100644 --- a/ObservatoryCore/UI/PluginListView.cs +++ b/ObservatoryCore/UI/PluginListView.cs @@ -1,20 +1,17 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; using System.Threading.Tasks; +using System.Windows.Forms; namespace Observatory.UI { internal class PluginListView : ListView { - [DllImport("user32.dll")] - private static extern int SendMessage(IntPtr hWnd, int wMsg, bool wParam, int lParam); - - private const int WM_SETREDRAW = 11; - public PluginListView() { OwnerDraw = true; @@ -28,6 +25,25 @@ namespace Observatory.UI base.GridLines = false;//We should prevent the default drawing of gridlines. } + // Stash for performance when doing large UI updates. + private IComparer? comparer = null; + + public void SuspendDrawing() + { + BeginUpdate(); + comparer = ListViewItemSorter; + } + + public void ResumeDrawing() + { + if (comparer != null) + { + ListViewItemSorter = comparer; + comparer = null; + } + EndUpdate(); + } + private static void DrawBorder(Graphics graphics, Pen pen, Rectangle bounds, bool header = false) { diff --git a/ObservatoryFramework/Interfaces.cs b/ObservatoryFramework/Interfaces.cs index e595679..7fd6541 100644 --- a/ObservatoryFramework/Interfaces.cs +++ b/ObservatoryFramework/Interfaces.cs @@ -187,6 +187,13 @@ namespace Observatory.Framework.Interfaces /// <param name="items">Grid items to be added. Object types should match original template item used to create the grid.</param> public void AddGridItems(IObservatoryWorker worker, IEnumerable<object> items); + /// <summary> + /// Replace the contents of the grid with the provided items. + /// </summary> + /// <param name="worker">Reference to the calling plugin's worker interface.</param> + /// <param name="items">Grid items to be added. Object types should match original template item used to create the grid.</param> + public void SetGridItems(IObservatoryWorker worker, IEnumerable<object> items); + /// <summary> /// Clears basic UI grid, removing all items. /// </summary> diff --git a/ObservatoryFramework/ObservatoryFramework.xml b/ObservatoryFramework/ObservatoryFramework.xml index d6172cd..bd14ee8 100644 --- a/ObservatoryFramework/ObservatoryFramework.xml +++ b/ObservatoryFramework/ObservatoryFramework.xml @@ -1605,6 +1605,13 @@ <param name="worker">Reference to the calling plugin's worker interface.</param> <param name="items">Grid items to be added. Object types should match original template item used to create the grid.</param> </member> + <member name="M:Observatory.Framework.Interfaces.IObservatoryCore.SetGridItems(Observatory.Framework.Interfaces.IObservatoryWorker,System.Collections.Generic.IEnumerable{System.Object})"> + <summary> + Replace the contents of the grid with the provided items. + </summary> + <param name="worker">Reference to the calling plugin's worker interface.</param> + <param name="items">Grid items to be added. Object types should match original template item used to create the grid.</param> + </member> <member name="M:Observatory.Framework.Interfaces.IObservatoryCore.ClearGrid(Observatory.Framework.Interfaces.IObservatoryWorker,System.Object)"> <summary> Clears basic UI grid, removing all items.