Browse Source

Added async population feature

sdoroff 7 years ago
parent
commit
242b9251df

+ 6 - 2
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@@ -8,7 +8,6 @@
               HorizontalAlignment="Center"
               Gap="8">
       <StackPanel Orientation="Vertical">
-        
         <TextBlock Text="MinimumPrefixLength: 1"/>
         <AutoCompleteBox Width="200"
                          Margin="0,0,0,8"
@@ -35,6 +34,7 @@
       
 
       <StackPanel Orientation="Vertical">
+        
         <TextBlock Text="ValueMemeberSelector"/>
         <AutoCompleteBox Width="200"
                          Margin="0,0,0,8"
@@ -47,8 +47,12 @@
         <AutoCompleteBox Name="MultiBindingBox"
                          Width="200"
                          Margin="0,0,0,8"
-                         ValueMemberBinding="{Binding Capital}"
                          FilterMode="Contains"/>
+        <TextBlock Text="Async Populate"/>
+        <AutoCompleteBox Name="AsyncBox"
+                         Width="200"
+                         Margin="0,0,0,8"
+                         FilterMode="None"/>
       </StackPanel>
     </StackPanel>
   </StackPanel>

+ 18 - 0
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@@ -6,6 +6,8 @@ using Avalonia.Markup.Xaml.Data;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace ControlCatalog.Pages
 {
@@ -109,6 +111,9 @@ namespace ControlCatalog.Pages
 
             var multibindingBox = this.FindControl<AutoCompleteBox>("MultiBindingBox");
             multibindingBox.ValueMemberBinding = binding;
+
+            var asyncBox = this.FindControl<AutoCompleteBox>("AsyncBox");
+            asyncBox.AsyncPopulator = PopulateAsync;
         }
         private IEnumerable<AutoCompleteBox> GetAllAutoCompleteBox()
         {
@@ -117,6 +122,19 @@ namespace ControlCatalog.Pages
                     .OfType<AutoCompleteBox>();
         }
 
+        private bool StringContains(string str, string query)
+        {
+            return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
+        }
+        private async Task<IEnumerable<object>> PopulateAsync(string searchText, CancellationToken cancellationToken)
+        {
+            await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken);
+
+            return
+                States.Where(data => StringContains(data.Name, searchText) || StringContains(data.Capital, searchText))
+                      .ToList();
+        }
+
         private void InitializeComponent()
         {
             AvaloniaXamlLoader.Load(this);

+ 70 - 0
src/Avalonia.Controls/AutoCompleteBox.cs

@@ -23,6 +23,8 @@ using Avalonia.Utilities;
 using System.Globalization;
 using System.Collections.Specialized;
 using System.Reactive.Disposables;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace Avalonia.Controls
 {
@@ -360,6 +362,8 @@ namespace Avalonia.Controls
         private IDisposable _collectionChangeSubscription;
 
         private IMemberSelector _valueMemberSelector;
+        private Func<string, CancellationToken, Task<IEnumerable<object>>> _asyncPopulator;
+        private CancellationTokenSource _populationCancellationTokenSource;
 
         private bool _itemTemplateIsFromValueMemeberBinding = true;
         private bool _settingItemTemplateFromValueMemeberBinding;
@@ -559,6 +563,12 @@ namespace Avalonia.Controls
                 o => o.ValueMemberSelector,
                 (o, v) => o.ValueMemberSelector = v);
 
+        public static readonly DirectProperty<AutoCompleteBox, Func<string, CancellationToken, Task<IEnumerable<object>>>> AsyncPopulatorProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, Func<string, CancellationToken, Task<IEnumerable<object>>>>(
+                nameof(AsyncPopulator),
+                o => o.AsyncPopulator,
+                (o, v) => o.AsyncPopulator = v);
+
         private static int ValidateMinimumPrefixLength(AutoCompleteBox control, int value)
         {
             Contract.Requires<ArgumentOutOfRangeException>(value >= -1);
@@ -1107,6 +1117,12 @@ namespace Avalonia.Controls
             set { SetAndRaise(TextFilterProperty, ref _textFilter, value); }
         }
 
+        public Func<string, CancellationToken, Task<IEnumerable<object>>> AsyncPopulator
+        {
+            get { return _asyncPopulator; }
+            set { SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); }
+        }
+
         /// <summary>
         /// Gets or sets a collection that is used to generate the items for the
         /// drop-down portion of the
@@ -1702,6 +1718,11 @@ namespace Avalonia.Controls
             // Update the prefix/search text.
             SearchText = Text;
 
+            if(TryPopulateAsync(SearchText))
+            {
+                return;
+            }
+
             // The Populated event enables advanced, custom filtering. The 
             // client needs to directly update the ItemsSource collection or
             // call the Populate method on the control to continue the 
@@ -1713,6 +1734,55 @@ namespace Avalonia.Controls
                 PopulateComplete();
             }
         }
+        private bool TryPopulateAsync(string searchText)
+        {
+            _populationCancellationTokenSource?.Cancel(false);
+            _populationCancellationTokenSource?.Dispose();
+            _populationCancellationTokenSource = null;
+
+            if(_asyncPopulator == null)
+            {
+                return false;
+            }
+
+            _populationCancellationTokenSource = new CancellationTokenSource();
+            var task = PopulateAsync(searchText, _populationCancellationTokenSource.Token);
+            if (task.Status == TaskStatus.Created)
+                task.Start();
+
+            return true;
+        }
+        private async Task PopulateAsync(string searchText, CancellationToken cancellationToken)
+        {
+
+            try
+            {
+                IEnumerable<object> result = await _asyncPopulator.Invoke(searchText, cancellationToken);
+                var resultList = result.ToList();
+
+                if (cancellationToken.IsCancellationRequested)
+                {
+                    return;
+                }
+
+                await Dispatcher.UIThread.InvokeAsync(() =>
+                {
+                    if (!cancellationToken.IsCancellationRequested)
+                    {
+                        Items = resultList;
+                        PopulateComplete();
+                    }
+                });
+            }
+            catch (TaskCanceledException)
+            { }
+            finally
+            {
+                _populationCancellationTokenSource?.Dispose();
+                _populationCancellationTokenSource = null;
+            }
+
+        }
 
         /// <summary>
         /// Private method that directly opens the popup, checks the expander