diff --git a/src/modules/launcher/PowerLauncher/Helper/ResultCollection.cs b/src/modules/launcher/PowerLauncher/Helper/ResultCollection.cs index 735c19e8c9..fae6738b9b 100644 --- a/src/modules/launcher/PowerLauncher/Helper/ResultCollection.cs +++ b/src/modules/launcher/PowerLauncher/Helper/ResultCollection.cs @@ -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 { + /// + /// This private variable holds the flag to + /// turn on and off the collection changed notification. + /// + private bool suspendCollectionChangeNotification; - public void RemoveAll(Predicate predicate) + /// + /// Initializes a new instance of the FastObservableCollection class. + /// + public ResultCollection() + : base() + { + this.suspendCollectionChangeNotification = false; + } + + /// + /// This event is overriden CollectionChanged event of the observable collection. + /// + //public override event NotifyCollectionChangedEventHandler CollectionChanged; + + /// + /// Raises collection change event. + /// + public void NotifyChanges() + { + this.ResumeCollectionChangeNotification(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + /// + /// Resumes collection changed notification. + /// + public void ResumeCollectionChangeNotification() + { + this.suspendCollectionChangeNotification = false; + } + + /// + /// Suspends collection changed notification. + /// + public void SuspendCollectionChangeNotification() + { + this.suspendCollectionChangeNotification = true; + } + + /// + /// This method removes all items that match a predicate + /// + /// predicate + public void RemovePredicate(Predicate 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 } } } + + /// + /// This collection changed event performs thread safe event raising. + /// + /// The event argument. + 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); + } + } } -} +} \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs index 5fcdeeefa8..d7d12295dc 100644 --- a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs +++ b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs @@ -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(); } } diff --git a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs index a44e0a4623..fc5304a260 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs @@ -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() diff --git a/src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs index 58364bb517..ba947d1b36 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs @@ -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 } /// - /// To avoid deadlock, this method should not called from main thread + /// Add new results to ResultCollection /// public void AddResults(List 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 NewResults(List newRawResults, string resultId)