|
|
@@ -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
|