mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
Improving UI performance (#5216)
* Added fast observable collection * Updated to use 1 collection changed event per query * Moved result updating to background thread * Changed collapsed to hidden for virtualization * Moved all token cancellations inside try catch * Fixed freeze on deleting first letter * nit fixes * Moved update logic to plugin result loop * Updated doc comment for AddResults function * fix result clear on empty query
This commit is contained in:
committed by
GitHub
parent
5fb7d43aff
commit
87ae1c6a9b
@@ -2,16 +2,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace PowerLauncher.Helper
|
||||
{
|
||||
public class ResultCollection : ObservableCollection<ResultViewModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// This private variable holds the flag to
|
||||
/// turn on and off the collection changed notification.
|
||||
/// </summary>
|
||||
private bool suspendCollectionChangeNotification;
|
||||
|
||||
public void RemoveAll(Predicate<ResultViewModel> predicate)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the FastObservableCollection class.
|
||||
/// </summary>
|
||||
public ResultCollection()
|
||||
: base()
|
||||
{
|
||||
this.suspendCollectionChangeNotification = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is overriden CollectionChanged event of the observable collection.
|
||||
/// </summary>
|
||||
//public override event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Raises collection change event.
|
||||
/// </summary>
|
||||
public void NotifyChanges()
|
||||
{
|
||||
this.ResumeCollectionChangeNotification();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resumes collection changed notification.
|
||||
/// </summary>
|
||||
public void ResumeCollectionChangeNotification()
|
||||
{
|
||||
this.suspendCollectionChangeNotification = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Suspends collection changed notification.
|
||||
/// </summary>
|
||||
public void SuspendCollectionChangeNotification()
|
||||
{
|
||||
this.suspendCollectionChangeNotification = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method removes all items that match a predicate
|
||||
/// </summary>
|
||||
/// <param name="predicate">predicate</param>
|
||||
public void RemovePredicate(Predicate<ResultViewModel> predicate)
|
||||
{
|
||||
CheckReentrancy();
|
||||
|
||||
this.SuspendCollectionChangeNotification();
|
||||
for (int i = Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (predicate(this[i]))
|
||||
@@ -36,6 +89,7 @@ namespace PowerLauncher.Helper
|
||||
int oldCount = Items.Count;
|
||||
int location = newCount > oldCount ? oldCount : newCount;
|
||||
|
||||
this.SuspendCollectionChangeNotification();
|
||||
for (int i = 0; i < location; i++)
|
||||
{
|
||||
ResultViewModel oldResult = this[i];
|
||||
@@ -50,7 +104,6 @@ namespace PowerLauncher.Helper
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (newCount >= oldCount)
|
||||
{
|
||||
for (int i = oldCount; i < newCount; i++)
|
||||
@@ -66,5 +119,20 @@ namespace PowerLauncher.Helper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This collection changed event performs thread safe event raising.
|
||||
/// </summary>
|
||||
/// <param name="e">The event argument.</param>
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// Recommended is to avoid reentry
|
||||
// in collection changed event while collection
|
||||
// is getting changed on other thread.
|
||||
if(!this.suspendCollectionChangeNotification)
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,8 +315,6 @@ namespace PowerLauncher
|
||||
}
|
||||
}
|
||||
|
||||
private const int millisecondsToWait = 100;
|
||||
private static DateTime s_lastTimeOfTyping;
|
||||
private bool disposedValue = false;
|
||||
|
||||
private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
@@ -335,22 +333,7 @@ namespace PowerLauncher
|
||||
SearchBox.AutoCompleteTextBlock.Text = string.Empty;
|
||||
}
|
||||
_viewModel.QueryText = text;
|
||||
var latestTimeOfTyping = DateTime.Now;
|
||||
|
||||
Task.Run(() => DelayedCheck(latestTimeOfTyping));
|
||||
s_lastTimeOfTyping = latestTimeOfTyping;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DelayedCheck(DateTime latestTimeOfTyping)
|
||||
{
|
||||
await Task.Delay(millisecondsToWait).ConfigureAwait(false);
|
||||
if (latestTimeOfTyping.Equals(s_lastTimeOfTyping))
|
||||
{
|
||||
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
_viewModel.Query();
|
||||
}));
|
||||
_viewModel.Query();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace PowerLauncher.ViewModel
|
||||
private readonly Internationalization _translator = InternationalizationManager.Instance;
|
||||
private System.Diagnostics.Stopwatch hotkeyTimer = new System.Diagnostics.Stopwatch();
|
||||
|
||||
private readonly object _addResultsLock = new object();
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -301,13 +302,13 @@ namespace PowerLauncher.ViewModel
|
||||
_selectedResults = value;
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
ContextMenu.Visibility = Visibility.Collapsed;
|
||||
History.Visibility = Visibility.Collapsed;
|
||||
ContextMenu.Visibility = Visibility.Hidden;
|
||||
History.Visibility = Visibility.Hidden;
|
||||
ChangeQueryText(_queryTextBeforeLeaveResults);
|
||||
}
|
||||
else
|
||||
{
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
Results.Visibility = Visibility.Hidden;
|
||||
_queryTextBeforeLeaveResults = QueryText;
|
||||
|
||||
|
||||
@@ -440,54 +441,56 @@ namespace PowerLauncher.ViewModel
|
||||
var currentCancellationToken = _updateSource.Token;
|
||||
_updateToken = currentCancellationToken;
|
||||
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
|
||||
if (query != null)
|
||||
{
|
||||
// handle the exclusiveness of plugin using action keyword
|
||||
RemoveOldQueryResults(query);
|
||||
|
||||
_lastQuery = query;
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
// so looping will stop once it was cancelled
|
||||
var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken };
|
||||
Thread.Sleep(20);
|
||||
RemoveOldQueryResults(query);
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
|
||||
try
|
||||
{
|
||||
Parallel.ForEach(plugins, parallelOptions, plugin =>
|
||||
currentCancellationToken.ThrowIfCancellationRequested();
|
||||
foreach(PluginPair plugin in plugins)
|
||||
{
|
||||
if (!plugin.Metadata.Disabled)
|
||||
if (!plugin.Metadata.Disabled && !currentCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var results = PluginManager.QueryForPlugin(plugin, query);
|
||||
if (Application.Current.Dispatcher.CheckAccess())
|
||||
currentCancellationToken.ThrowIfCancellationRequested();
|
||||
lock (_addResultsLock)
|
||||
{
|
||||
UpdateResultView(results, plugin.Metadata, query);
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
UpdateResultView(results, plugin.Metadata, query);
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
currentCancellationToken.ThrowIfCancellationRequested();
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (query.RawQuery == _lastQuery.RawQuery)
|
||||
{
|
||||
Results.Results.NotifyChanges();
|
||||
}
|
||||
|
||||
if (Results.Results.Count > 0)
|
||||
{
|
||||
Results.Visibility = Visibility.Visible;
|
||||
Results.SelectedIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Results.Visibility = Visibility.Hidden;
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
|
||||
// this should happen once after all queries are done so progress bar should continue
|
||||
// until the end of all querying
|
||||
if (currentUpdateSource == _updateSource)
|
||||
{ // update to hidden if this is still the current query
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
queryTimer.Stop();
|
||||
var queryEvent = new LauncherQueryEvent()
|
||||
{
|
||||
@@ -505,8 +508,8 @@ namespace PowerLauncher.ViewModel
|
||||
_updateSource?.Cancel();
|
||||
_lastQuery = _emptyQuery;
|
||||
Results.SelectedItem = null;
|
||||
Results.Visibility = Visibility.Hidden;
|
||||
Results.Clear();
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,11 +713,6 @@ namespace PowerLauncher.ViewModel
|
||||
{
|
||||
Results.AddResults(list, metadata.ID);
|
||||
}
|
||||
|
||||
if (Results.Visibility != Visibility.Visible && list.Count > 0)
|
||||
{
|
||||
Results.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public void ColdStartFix()
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace PowerLauncher.ViewModel
|
||||
|
||||
public ResultCollection Results { get; }
|
||||
|
||||
private readonly object _addResultsLock = new object();
|
||||
private readonly object _collectionLock = new object();
|
||||
private readonly Settings _settings;
|
||||
// private int MaxResults => _settings?.MaxResultsToShow ?? 6;
|
||||
@@ -154,12 +153,12 @@ namespace PowerLauncher.ViewModel
|
||||
|
||||
public void RemoveResultsExcept(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID != metadata.ID);
|
||||
Results.RemovePredicate(r => r.Result.PluginID != metadata.ID);
|
||||
}
|
||||
|
||||
public void RemoveResultsFor(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
|
||||
Results.RemovePredicate(r => r.Result.PluginID == metadata.ID);
|
||||
}
|
||||
|
||||
public void SelectNextTabItem()
|
||||
@@ -214,28 +213,12 @@ namespace PowerLauncher.ViewModel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
/// Add new results to ResultCollection
|
||||
/// </summary>
|
||||
public void AddResults(List<Result> newRawResults, string resultId)
|
||||
{
|
||||
lock (_addResultsLock)
|
||||
{
|
||||
var newResults = NewResults(newRawResults, resultId);
|
||||
|
||||
// update UI in one run, so it can avoid UI flickering
|
||||
Results.Update(newResults);
|
||||
|
||||
if (Results.Count > 0)
|
||||
{
|
||||
Margin = new Thickness { Top = 8 };
|
||||
SelectedIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Margin = new Thickness { Top = 0 };
|
||||
Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
{
|
||||
var newResults = NewResults(newRawResults, resultId);
|
||||
Results.Update(newResults);
|
||||
}
|
||||
|
||||
private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
|
||||
|
||||
Reference in New Issue
Block a user