| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726 |
- // (c) Copyright Microsoft Corporation.
- // This source is subject to the Microsoft Public License (Ms-PL).
- // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
- // All other rights reserved.
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Collections.Specialized;
- using System.ComponentModel;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using Avalonia.Collections;
- using Avalonia.Controls.Primitives;
- using Avalonia.Controls.Templates;
- using Avalonia.Controls.Utils;
- using Avalonia.Data;
- using Avalonia.Input;
- using Avalonia.Interactivity;
- using Avalonia.Threading;
- using Avalonia.VisualTree;
- namespace Avalonia.Controls
- {
- /// <summary>
- /// Provides data for the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
- /// event.
- /// </summary>
- public class PopulatedEventArgs : EventArgs
- {
- /// <summary>
- /// Gets the list of possible matches added to the drop-down portion of
- /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
- /// control.
- /// </summary>
- /// <value>The list of possible matches added to the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
- public IEnumerable Data { get; private set; }
- /// <summary>
- /// Initializes a new instance of the
- /// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />.
- /// </summary>
- /// <param name="data">The list of possible matches added to the
- /// drop-down portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
- public PopulatedEventArgs(IEnumerable data)
- {
- Data = data;
- }
- }
- /// <summary>
- /// Provides data for the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
- /// event.
- /// </summary>
- /// <QualityBand>Stable</QualityBand>
- public class PopulatingEventArgs : CancelEventArgs
- {
- /// <summary>
- /// Gets the text that is used to determine which items to display in
- /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
- /// control.
- /// </summary>
- /// <value>The text that is used to determine which items to display in
- /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
- public string Parameter { get; private set; }
- /// <summary>
- /// Initializes a new instance of the
- /// <see cref="T:Avalonia.Controls.PopulatingEventArgs" />.
- /// </summary>
- /// <param name="parameter">The value of the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
- /// property, which is used to filter items for the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
- public PopulatingEventArgs(string parameter)
- {
- Parameter = parameter;
- }
- }
- /// <summary>
- /// Represents the filter used by the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to
- /// determine whether an item is a possible match for the specified text.
- /// </summary>
- /// <returns>true to indicate <paramref name="item" /> is a possible match
- /// for <paramref name="search" />; otherwise false.</returns>
- /// <param name="search">The string used as the basis for filtering.</param>
- /// <param name="item">The item that is compared with the
- /// <paramref name="search" /> parameter.</param>
- /// <typeparam name="T">The type used for filtering the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" />. This type can
- /// be either a string or an object.</typeparam>
- /// <QualityBand>Stable</QualityBand>
- public delegate bool AutoCompleteFilterPredicate<T>(string search, T item);
- /// <summary>
- /// Specifies how text in the text box portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control is used
- /// to filter items specified by the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// property for display in the drop-down.
- /// </summary>
- /// <QualityBand>Stable</QualityBand>
- public enum AutoCompleteFilterMode
- {
- /// <summary>
- /// Specifies that no filter is used. All items are returned.
- /// </summary>
- None = 0,
- /// <summary>
- /// Specifies a culture-sensitive, case-insensitive filter where the
- /// returned items start with the specified text. The filter uses the
- /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
- /// method, specifying
- /// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
- /// the string comparison criteria.
- /// </summary>
- StartsWith = 1,
- /// <summary>
- /// Specifies a culture-sensitive, case-sensitive filter where the
- /// returned items start with the specified text. The filter uses the
- /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
- /// method, specifying
- /// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
- /// comparison criteria.
- /// </summary>
- StartsWithCaseSensitive = 2,
- /// <summary>
- /// Specifies an ordinal, case-insensitive filter where the returned
- /// items start with the specified text. The filter uses the
- /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
- /// method, specifying
- /// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
- /// string comparison criteria.
- /// </summary>
- StartsWithOrdinal = 3,
- /// <summary>
- /// Specifies an ordinal, case-sensitive filter where the returned items
- /// start with the specified text. The filter uses the
- /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
- /// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
- /// the string comparison criteria.
- /// </summary>
- StartsWithOrdinalCaseSensitive = 4,
- /// <summary>
- /// Specifies a culture-sensitive, case-insensitive filter where the
- /// returned items contain the specified text.
- /// </summary>
- Contains = 5,
- /// <summary>
- /// Specifies a culture-sensitive, case-sensitive filter where the
- /// returned items contain the specified text.
- /// </summary>
- ContainsCaseSensitive = 6,
- /// <summary>
- /// Specifies an ordinal, case-insensitive filter where the returned
- /// items contain the specified text.
- /// </summary>
- ContainsOrdinal = 7,
- /// <summary>
- /// Specifies an ordinal, case-sensitive filter where the returned items
- /// contain the specified text.
- /// </summary>
- ContainsOrdinalCaseSensitive = 8,
- /// <summary>
- /// Specifies a culture-sensitive, case-insensitive filter where the
- /// returned items equal the specified text. The filter uses the
- /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
- /// method, specifying
- /// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
- /// the search comparison criteria.
- /// </summary>
- Equals = 9,
- /// <summary>
- /// Specifies a culture-sensitive, case-sensitive filter where the
- /// returned items equal the specified text. The filter uses the
- /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
- /// method, specifying
- /// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
- /// comparison criteria.
- /// </summary>
- EqualsCaseSensitive = 10,
- /// <summary>
- /// Specifies an ordinal, case-insensitive filter where the returned
- /// items equal the specified text. The filter uses the
- /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
- /// method, specifying
- /// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
- /// string comparison criteria.
- /// </summary>
- EqualsOrdinal = 11,
- /// <summary>
- /// Specifies an ordinal, case-sensitive filter where the returned items
- /// equal the specified text. The filter uses the
- /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
- /// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
- /// the string comparison criteria.
- /// </summary>
- EqualsOrdinalCaseSensitive = 12,
- /// <summary>
- /// Specifies that a custom filter is used. This mode is used when the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
- /// or
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
- /// properties are set.
- /// </summary>
- Custom = 13,
- }
- /// <summary>
- /// Represents a control that provides a text box for user input and a
- /// drop-down that contains possible matches based on the input in the text
- /// box.
- /// </summary>
- public class AutoCompleteBox : TemplatedControl
- {
- /// <summary>
- /// Specifies the name of the selection adapter TemplatePart.
- /// </summary>
- private const string ElementSelectionAdapter = "PART_SelectionAdapter";
- /// <summary>
- /// Specifies the name of the Selector TemplatePart.
- /// </summary>
- private const string ElementSelector = "PART_SelectingItemsControl";
- /// <summary>
- /// Specifies the name of the Popup TemplatePart.
- /// </summary>
- private const string ElementPopup = "PART_Popup";
- /// <summary>
- /// The name for the text box part.
- /// </summary>
- private const string ElementTextBox = "PART_TextBox";
- private IEnumerable _itemsEnumerable;
- /// <summary>
- /// Gets or sets a local cached copy of the items data.
- /// </summary>
- private List<object> _items;
- /// <summary>
- /// Gets or sets the observable collection that contains references to
- /// all of the items in the generated view of data that is provided to
- /// the selection-style control adapter.
- /// </summary>
- private AvaloniaList<object> _view;
- /// <summary>
- /// Gets or sets a value to ignore a number of pending change handlers.
- /// The value is decremented after each use. This is used to reset the
- /// value of properties without performing any of the actions in their
- /// change handlers.
- /// </summary>
- /// <remarks>The int is important as a value because the TextBox
- /// TextChanged event does not immediately fire, and this will allow for
- /// nested property changes to be ignored.</remarks>
- private int _ignoreTextPropertyChange;
- /// <summary>
- /// Gets or sets a value indicating whether to ignore calling a pending
- /// change handlers.
- /// </summary>
- private bool _ignorePropertyChange;
- /// <summary>
- /// Gets or sets a value indicating whether to ignore the selection
- /// changed event.
- /// </summary>
- private bool _ignoreTextSelectionChange;
- /// <summary>
- /// Gets or sets a value indicating whether to skip the text update
- /// processing when the selected item is updated.
- /// </summary>
- private bool _skipSelectedItemTextUpdate;
- /// <summary>
- /// Gets or sets the last observed text box selection start location.
- /// </summary>
- private int _textSelectionStart;
- /// <summary>
- /// Gets or sets a value indicating whether the user initiated the
- /// current populate call.
- /// </summary>
- private bool _userCalledPopulate;
- /// <summary>
- /// A value indicating whether the popup has been opened at least once.
- /// </summary>
- private bool _popupHasOpened;
- /// <summary>
- /// Gets or sets the DispatcherTimer used for the MinimumPopulateDelay
- /// condition for auto completion.
- /// </summary>
- private DispatcherTimer _delayTimer;
- /// <summary>
- /// Gets or sets a value indicating whether a read-only dependency
- /// property change handler should allow the value to be set. This is
- /// used to ensure that read-only properties cannot be changed via
- /// SetValue, etc.
- /// </summary>
- private bool _allowWrite;
- /// <summary>
- /// The TextBox template part.
- /// </summary>
- private TextBox _textBox;
- private IDisposable _textBoxSubscriptions;
- /// <summary>
- /// The SelectionAdapter.
- /// </summary>
- private ISelectionAdapter _adapter;
- /// <summary>
- /// A control that can provide updated string values from a binding.
- /// </summary>
- private BindingEvaluator<string> _valueBindingEvaluator;
- /// <summary>
- /// A weak subscription for the collection changed event.
- /// </summary>
- private IDisposable _collectionChangeSubscription;
- private IMemberSelector _valueMemberSelector;
- private Func<string, CancellationToken, Task<IEnumerable<object>>> _asyncPopulator;
- private CancellationTokenSource _populationCancellationTokenSource;
- private bool _itemTemplateIsFromValueMemeberBinding = true;
- private bool _settingItemTemplateFromValueMemeberBinding;
- private object _selectedItem;
- private bool _isDropDownOpen;
- private bool _isFocused = false;
- private string _text = string.Empty;
- private string _searchText = string.Empty;
- private AutoCompleteFilterPredicate<object> _itemFilter;
- private AutoCompleteFilterPredicate<string> _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith);
- public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
- RoutedEvent.Register<SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox));
- public static readonly StyledProperty<string> WatermarkProperty =
- TextBox.WatermarkProperty.AddOwner<AutoCompleteBox>();
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
- /// dependency property.</value>
- public static readonly StyledProperty<int> MinimumPrefixLengthProperty =
- AvaloniaProperty.Register<AutoCompleteBox, int>(
- nameof(MinimumPrefixLength), 1,
- validate: ValidateMinimumPrefixLength);
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
- /// dependency property.</value>
- public static readonly StyledProperty<TimeSpan> MinimumPopulateDelayProperty =
- AvaloniaProperty.Register<AutoCompleteBox, TimeSpan>(
- nameof(MinimumPopulateDelay),
- TimeSpan.Zero,
- validate: ValidateMinimumPopulateDelay);
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" />
- /// dependency property.</value>
- public static readonly StyledProperty<double> MaxDropDownHeightProperty =
- AvaloniaProperty.Register<AutoCompleteBox, double>(
- nameof(MaxDropDownHeight),
- double.PositiveInfinity,
- validate: ValidateMaxDropDownHeight);
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
- /// dependency property.</value>
- public static readonly StyledProperty<bool> IsTextCompletionEnabledProperty =
- AvaloniaProperty.Register<AutoCompleteBox, bool>(nameof(IsTextCompletionEnabled));
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" />
- /// dependency property.</value>
- public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
- AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(nameof(ItemTemplate));
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
- /// dependency property.</value>
- public static readonly DirectProperty<AutoCompleteBox, bool> IsDropDownOpenProperty =
- AvaloniaProperty.RegisterDirect<AutoCompleteBox, bool>(
- nameof(IsDropDownOpen),
- o => o.IsDropDownOpen,
- (o, v) => o.IsDropDownOpen = v);
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
- /// dependency property.</value>
- public static readonly DirectProperty<AutoCompleteBox, object> SelectedItemProperty =
- AvaloniaProperty.RegisterDirect<AutoCompleteBox, object>(
- nameof(SelectedItem),
- o => o.SelectedItem,
- (o, v) => o.SelectedItem = v);
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
- /// dependency property.</value>
- public static readonly DirectProperty<AutoCompleteBox, string> TextProperty =
- AvaloniaProperty.RegisterDirect<AutoCompleteBox, string>(
- nameof(Text),
- o => o.Text,
- (o, v) => o.Text = v);
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
- /// dependency property.</value>
- public static readonly DirectProperty<AutoCompleteBox, string> SearchTextProperty =
- AvaloniaProperty.RegisterDirect<AutoCompleteBox, string>(
- nameof(SearchText),
- o => o.SearchText,
- unsetValue: string.Empty);
- /// <summary>
- /// Gets the identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.FilterMode" />
- /// dependency property.
- /// </summary>
- public static readonly StyledProperty<AutoCompleteFilterMode> FilterModeProperty =
- AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterMode>(
- nameof(FilterMode),
- defaultValue: AutoCompleteFilterMode.StartsWith,
- validate: ValidateFilterMode);
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
- /// dependency property.</value>
- public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<object>> ItemFilterProperty =
- AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<object>>(
- nameof(ItemFilter),
- o => o.ItemFilter,
- (o, v) => o.ItemFilter = v);
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
- /// dependency property.</value>
- public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<string>> TextFilterProperty =
- AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<string>>(
- nameof(TextFilter),
- o => o.TextFilter,
- (o, v) => o.TextFilter = v,
- unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
- /// <summary>
- /// Identifies the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// dependency property.
- /// </summary>
- /// <value>The identifier for the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// dependency property.</value>
- public static readonly DirectProperty<AutoCompleteBox, IEnumerable> ItemsProperty =
- AvaloniaProperty.RegisterDirect<AutoCompleteBox, IEnumerable>(
- nameof(Items),
- o => o.Items,
- (o, v) => o.Items = v);
- public static readonly DirectProperty<AutoCompleteBox, IMemberSelector> ValueMemberSelectorProperty =
- AvaloniaProperty.RegisterDirect<AutoCompleteBox, IMemberSelector>(
- nameof(ValueMemberSelector),
- 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);
- return value;
- }
- private static TimeSpan ValidateMinimumPopulateDelay(AutoCompleteBox control, TimeSpan value)
- {
- Contract.Requires<ArgumentOutOfRangeException>(value.TotalMilliseconds >= 0.0);
- return value;
- }
- private static double ValidateMaxDropDownHeight(AutoCompleteBox control, double value)
- {
- Contract.Requires<ArgumentOutOfRangeException>(value >= 0.0);
- return value;
- }
- private static bool IsValidFilterMode(AutoCompleteFilterMode mode)
- {
- switch (mode)
- {
- case AutoCompleteFilterMode.None:
- case AutoCompleteFilterMode.StartsWith:
- case AutoCompleteFilterMode.StartsWithCaseSensitive:
- case AutoCompleteFilterMode.StartsWithOrdinal:
- case AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive:
- case AutoCompleteFilterMode.Contains:
- case AutoCompleteFilterMode.ContainsCaseSensitive:
- case AutoCompleteFilterMode.ContainsOrdinal:
- case AutoCompleteFilterMode.ContainsOrdinalCaseSensitive:
- case AutoCompleteFilterMode.Equals:
- case AutoCompleteFilterMode.EqualsCaseSensitive:
- case AutoCompleteFilterMode.EqualsOrdinal:
- case AutoCompleteFilterMode.EqualsOrdinalCaseSensitive:
- case AutoCompleteFilterMode.Custom:
- return true;
- default:
- return false;
- }
- }
- private static AutoCompleteFilterMode ValidateFilterMode(AutoCompleteBox control, AutoCompleteFilterMode value)
- {
- Contract.Requires<ArgumentException>(IsValidFilterMode(value));
- return value;
- }
- /// <summary>
- /// Handle the change of the IsEnabled property.
- /// </summary>
- /// <param name="e">The event data.</param>
- private void OnControlIsEnabledChanged(AvaloniaPropertyChangedEventArgs e)
- {
- bool isEnabled = (bool)e.NewValue;
- if (!isEnabled)
- {
- IsDropDownOpen = false;
- }
- }
- /// <summary>
- /// MinimumPopulateDelayProperty property changed handler. Any current
- /// dispatcher timer will be stopped. The timer will not be restarted
- /// until the next TextUpdate call by the user.
- /// </summary>
- /// <param name="e">Event arguments.</param>
- private void OnMinimumPopulateDelayChanged(AvaloniaPropertyChangedEventArgs e)
- {
- var newValue = (TimeSpan)e.NewValue;
- // Stop any existing timer
- if (_delayTimer != null)
- {
- _delayTimer.Stop();
- if (newValue == TimeSpan.Zero)
- {
- _delayTimer = null;
- }
- }
- if (newValue > TimeSpan.Zero)
- {
- // Create or clear a dispatcher timer instance
- if (_delayTimer == null)
- {
- _delayTimer = new DispatcherTimer();
- _delayTimer.Tick += PopulateDropDown;
- }
- // Set the new tick interval
- _delayTimer.Interval = newValue;
- }
- }
- /// <summary>
- /// IsDropDownOpenProperty property changed handler.
- /// </summary>
- /// <param name="e">Event arguments.</param>
- private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e)
- {
- // Ignore the change if requested
- if (_ignorePropertyChange)
- {
- _ignorePropertyChange = false;
- return;
- }
- bool oldValue = (bool)e.OldValue;
- bool newValue = (bool)e.NewValue;
- if (newValue)
- {
- TextUpdated(Text, true);
- }
- else
- {
- ClosingDropDown(oldValue);
- }
- UpdatePseudoClasses();
- }
- private void OnSelectedItemPropertyChanged(AvaloniaPropertyChangedEventArgs e)
- {
- if (_ignorePropertyChange)
- {
- _ignorePropertyChange = false;
- return;
- }
- // Update the text display
- if (_skipSelectedItemTextUpdate)
- {
- _skipSelectedItemTextUpdate = false;
- }
- else
- {
- OnSelectedItemChanged(e.NewValue);
- }
- // Fire the SelectionChanged event
- List<object> removed = new List<object>();
- if (e.OldValue != null)
- {
- removed.Add(e.OldValue);
- }
- List<object> added = new List<object>();
- if (e.NewValue != null)
- {
- added.Add(e.NewValue);
- }
- OnSelectionChanged(new SelectionChangedEventArgs(SelectionChangedEvent, removed, added));
- }
- /// <summary>
- /// TextProperty property changed handler.
- /// </summary>
- /// <param name="e">Event arguments.</param>
- private void OnTextPropertyChanged(AvaloniaPropertyChangedEventArgs e)
- {
- TextUpdated((string)e.NewValue, false);
- }
- private void OnSearchTextPropertyChanged(AvaloniaPropertyChangedEventArgs e)
- {
- if (_ignorePropertyChange)
- {
- _ignorePropertyChange = false;
- return;
- }
- // Ensure the property is only written when expected
- if (!_allowWrite)
- {
- // Reset the old value before it was incorrectly written
- _ignorePropertyChange = true;
- SetValue(e.Property, e.OldValue);
- throw new InvalidOperationException("Cannot set read-only property SearchText.");
- }
- }
- /// <summary>
- /// FilterModeProperty property changed handler.
- /// </summary>
- /// <param name="e">Event arguments.</param>
- private void OnFilterModePropertyChanged(AvaloniaPropertyChangedEventArgs e)
- {
- AutoCompleteFilterMode mode = (AutoCompleteFilterMode)e.NewValue;
- // Sets the filter predicate for the new value
- TextFilter = AutoCompleteSearch.GetFilter(mode);
- }
- /// <summary>
- /// ItemFilterProperty property changed handler.
- /// </summary>
- /// <param name="e">Event arguments.</param>
- private void OnItemFilterPropertyChanged(AvaloniaPropertyChangedEventArgs e)
- {
- AutoCompleteFilterPredicate<object> value = e.NewValue as AutoCompleteFilterPredicate<object>;
- // If null, revert to the "None" predicate
- if (value == null)
- {
- FilterMode = AutoCompleteFilterMode.None;
- }
- else
- {
- FilterMode = AutoCompleteFilterMode.Custom;
- TextFilter = null;
- }
- }
- /// <summary>
- /// ItemsSourceProperty property changed handler.
- /// </summary>
- /// <param name="e">Event arguments.</param>
- private void OnItemsPropertyChanged(AvaloniaPropertyChangedEventArgs e)
- {
- OnItemsChanged((IEnumerable)e.NewValue);
- }
- private void OnItemTemplatePropertyChanged(AvaloniaPropertyChangedEventArgs e)
- {
- if (!_settingItemTemplateFromValueMemeberBinding)
- _itemTemplateIsFromValueMemeberBinding = false;
- }
- private void OnValueMemberBindingChanged(IBinding value)
- {
- if(_itemTemplateIsFromValueMemeberBinding)
- {
- var template =
- new FuncDataTemplate(
- typeof(object),
- o =>
- {
- var control = new ContentControl();
- control.Bind(ContentControl.ContentProperty, value);
- return control;
- });
- _settingItemTemplateFromValueMemeberBinding = true;
- ItemTemplate = template;
- _settingItemTemplateFromValueMemeberBinding = false;
- }
- }
- static AutoCompleteBox()
- {
- FocusableProperty.OverrideDefaultValue<AutoCompleteBox>(true);
- MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnMinimumPopulateDelayChanged);
- IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnIsDropDownOpenChanged);
- SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSelectedItemPropertyChanged);
- TextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnTextPropertyChanged);
- SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSearchTextPropertyChanged);
- FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnFilterModePropertyChanged);
- ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemFilterPropertyChanged);
- ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemsPropertyChanged);
- IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnControlIsEnabledChanged);
- }
- /// <summary>
- /// Initializes a new instance of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> class.
- /// </summary>
- public AutoCompleteBox()
- {
- ClearView();
- }
- /// <summary>
- /// Gets or sets the minimum number of characters required to be entered
- /// in the text box before the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> displays
- /// possible matches.
- /// matches.
- /// </summary>
- /// <value>
- /// The minimum number of characters to be entered in the text box
- /// before the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
- /// displays possible matches. The default is 1.
- /// </value>
- /// <remarks>
- /// If you set MinimumPrefixLength to -1, the AutoCompleteBox will
- /// not provide possible matches. There is no maximum value, but
- /// setting MinimumPrefixLength to value that is too large will
- /// prevent the AutoCompleteBox from providing possible matches as well.
- /// </remarks>
- public int MinimumPrefixLength
- {
- get { return GetValue(MinimumPrefixLengthProperty); }
- set { SetValue(MinimumPrefixLengthProperty, value); }
- }
- /// <summary>
- /// Gets or sets a value indicating whether the first possible match
- /// found during the filtering process will be displayed automatically
- /// in the text box.
- /// </summary>
- /// <value>
- /// True if the first possible match found will be displayed
- /// automatically in the text box; otherwise, false. The default is
- /// false.
- /// </value>
- public bool IsTextCompletionEnabled
- {
- get { return GetValue(IsTextCompletionEnabledProperty); }
- set { SetValue(IsTextCompletionEnabledProperty, value); }
- }
- /// <summary>
- /// Gets or sets the <see cref="T:Avalonia.DataTemplate" /> used
- /// to display each item in the drop-down portion of the control.
- /// </summary>
- /// <value>The <see cref="T:Avalonia.DataTemplate" /> used to
- /// display each item in the drop-down. The default is null.</value>
- /// <remarks>
- /// You use the ItemTemplate property to specify the visualization
- /// of the data objects in the drop-down portion of the AutoCompleteBox
- /// control. If your AutoCompleteBox is bound to a collection and you
- /// do not provide specific display instructions by using a
- /// DataTemplate, the resulting UI of each item is a string
- /// representation of each object in the underlying collection.
- /// </remarks>
- public IDataTemplate ItemTemplate
- {
- get { return GetValue(ItemTemplateProperty); }
- set { SetValue(ItemTemplateProperty, value); }
- }
- /// <summary>
- /// Gets or sets the minimum delay, after text is typed
- /// in the text box before the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
- /// populates the list of possible matches in the drop-down.
- /// </summary>
- /// <value>The minimum delay, after text is typed in
- /// the text box, but before the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> populates
- /// the list of possible matches in the drop-down. The default is 0.</value>
- public TimeSpan MinimumPopulateDelay
- {
- get { return GetValue(MinimumPopulateDelayProperty); }
- set { SetValue(MinimumPopulateDelayProperty, value); }
- }
- /// <summary>
- /// Gets or sets the maximum height of the drop-down portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
- /// </summary>
- /// <value>The maximum height of the drop-down portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
- /// The default is <see cref="F:System.Double.PositiveInfinity" />.</value>
- /// <exception cref="T:System.ArgumentException">The specified value is less than 0.</exception>
- public double MaxDropDownHeight
- {
- get { return GetValue(MaxDropDownHeightProperty); }
- set { SetValue(MaxDropDownHeightProperty, value); }
- }
- /// <summary>
- /// Gets or sets a value indicating whether the drop-down portion of
- /// the control is open.
- /// </summary>
- /// <value>
- /// True if the drop-down is open; otherwise, false. The default is
- /// false.
- /// </value>
- public bool IsDropDownOpen
- {
- get { return _isDropDownOpen; }
- set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
- }
- /// <summary>
- /// Gets or sets the <see cref="T:Avalonia.Data.Binding" /> that
- /// is used to get the values for display in the text portion of
- /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
- /// control.
- /// </summary>
- /// <value>The <see cref="T:Avalonia.Data.IBinding" /> object used
- /// when binding to a collection property.</value>
- [AssignBinding]
- public IBinding ValueMemberBinding
- {
- get { return _valueBindingEvaluator?.ValueBinding; }
- set
- {
- if (ValueMemberBinding != value)
- {
- _valueBindingEvaluator = new BindingEvaluator<string>(value);
- OnValueMemberBindingChanged(value);
- }
- }
- }
- /// <summary>
- /// Gets or sets the MemberSelector that is used to get values for
- /// display in the text portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
- /// </summary>
- /// <value>The MemberSelector that is used to get values for display in
- /// the text portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
- public IMemberSelector ValueMemberSelector
- {
- get { return _valueMemberSelector; }
- set { SetAndRaise(ValueMemberSelectorProperty, ref _valueMemberSelector, value); }
- }
- /// <summary>
- /// Gets or sets the selected item in the drop-down.
- /// </summary>
- /// <value>The selected item in the drop-down.</value>
- /// <remarks>
- /// If the IsTextCompletionEnabled property is true and text typed by
- /// the user matches an item in the ItemsSource collection, which is
- /// then displayed in the text box, the SelectedItem property will be
- /// a null reference.
- /// </remarks>
- public object SelectedItem
- {
- get { return _selectedItem; }
- set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); }
- }
- /// <summary>
- /// Gets or sets the text in the text box portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
- /// </summary>
- /// <value>The text in the text box portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
- public string Text
- {
- get { return _text; }
- set { SetAndRaise(TextProperty, ref _text, value); }
- }
- /// <summary>
- /// Gets the text that is used to filter items in the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// item collection.
- /// </summary>
- /// <value>The text that is used to filter items in the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// item collection.</value>
- /// <remarks>
- /// The SearchText value is typically the same as the
- /// Text property, but is set after the TextChanged event occurs
- /// and before the Populating event.
- /// </remarks>
- public string SearchText
- {
- get { return _searchText; }
- private set
- {
- try
- {
- _allowWrite = true;
- SetAndRaise(SearchTextProperty, ref _searchText, value);
- }
- finally
- {
- _allowWrite = false;
- }
- }
- }
- /// <summary>
- /// Gets or sets how the text in the text box is used to filter items
- /// specified by the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// property for display in the drop-down.
- /// </summary>
- /// <value>One of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />
- /// values The default is
- /// <see cref="F:Avalonia.Controls.AutoCompleteFilterMode.StartsWith" />.</value>
- /// <exception cref="T:System.ArgumentException">The specified value is
- /// not a valid
- /// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />.</exception>
- /// <remarks>
- /// Use the FilterMode property to specify how possible matches are
- /// filtered. For example, possible matches can be filtered in a
- /// predefined or custom way. The search mode is automatically set to
- /// Custom if you set the ItemFilter property.
- /// </remarks>
- public AutoCompleteFilterMode FilterMode
- {
- get { return GetValue(FilterModeProperty); }
- set { SetValue(FilterModeProperty, value); }
- }
- public string Watermark
- {
- get { return GetValue(WatermarkProperty); }
- set { SetValue(WatermarkProperty, value); }
- }
- /// <summary>
- /// Gets or sets the custom method that uses user-entered text to filter
- /// the items specified by the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// property for display in the drop-down.
- /// </summary>
- /// <value>The custom method that uses the user-entered text to filter
- /// the items specified by the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// property. The default is null.</value>
- /// <remarks>
- /// The filter mode is automatically set to Custom if you set the
- /// ItemFilter property.
- /// </remarks>
- public AutoCompleteFilterPredicate<object> ItemFilter
- {
- get { return _itemFilter; }
- set { SetAndRaise(ItemFilterProperty, ref _itemFilter, value); }
- }
- /// <summary>
- /// Gets or sets the custom method that uses the user-entered text to
- /// filter items specified by the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// property in a text-based way for display in the drop-down.
- /// </summary>
- /// <value>The custom method that uses the user-entered text to filter
- /// items specified by the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
- /// property in a text-based way for display in the drop-down.</value>
- /// <remarks>
- /// The search mode is automatically set to Custom if you set the
- /// TextFilter property.
- /// </remarks>
- public AutoCompleteFilterPredicate<string> TextFilter
- {
- get { return _textFilter; }
- 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
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
- /// </summary>
- /// <value>The collection that is used to generate the items of the
- /// drop-down portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
- public IEnumerable Items
- {
- get { return _itemsEnumerable; }
- set { SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); }
- }
- /// <summary>
- /// Gets or sets the drop down popup control.
- /// </summary>
- private Popup DropDownPopup { get; set; }
- /// <summary>
- /// Gets or sets the Text template part.
- /// </summary>
- private TextBox TextBox
- {
- get { return _textBox; }
- set
- {
- _textBoxSubscriptions?.Dispose();
- _textBox = value;
- // Attach handlers
- if (_textBox != null)
- {
- _textBoxSubscriptions =
- _textBox.GetObservable(TextBox.TextProperty)
- .Subscribe(_ => OnTextBoxTextChanged());
- if (Text != null)
- {
- UpdateTextValue(Text);
- }
- }
- }
- }
- private int TextBoxSelectionStart
- {
- get
- {
- if (TextBox != null)
- {
- return Math.Min(TextBox.SelectionStart, TextBox.SelectionEnd);
- }
- else
- {
- return 0;
- }
- }
- }
- private int TextBoxSelectionLength
- {
- get
- {
- if (TextBox != null)
- {
- return Math.Abs(TextBox.SelectionEnd - TextBox.SelectionStart);
- }
- else
- {
- return 0;
- }
- }
- }
- /// <summary>
- /// Gets or sets the selection adapter used to populate the drop-down
- /// with a list of selectable items.
- /// </summary>
- /// <value>The selection adapter used to populate the drop-down with a
- /// list of selectable items.</value>
- /// <remarks>
- /// You can use this property when you create an automation peer to
- /// use with AutoCompleteBox or deriving from AutoCompleteBox to
- /// create a custom control.
- /// </remarks>
- protected ISelectionAdapter SelectionAdapter
- {
- get { return _adapter; }
- set
- {
- if (_adapter != null)
- {
- _adapter.SelectionChanged -= OnAdapterSelectionChanged;
- _adapter.Commit -= OnAdapterSelectionComplete;
- _adapter.Cancel -= OnAdapterSelectionCanceled;
- _adapter.Cancel -= OnAdapterSelectionComplete;
- _adapter.Items = null;
- }
- _adapter = value;
- if (_adapter != null)
- {
- _adapter.SelectionChanged += OnAdapterSelectionChanged;
- _adapter.Commit += OnAdapterSelectionComplete;
- _adapter.Cancel += OnAdapterSelectionCanceled;
- _adapter.Cancel += OnAdapterSelectionComplete;
- _adapter.Items = _view;
- }
- }
- }
- /// <summary>
- /// Returns the
- /// <see cref="T:Avalonia.Controls.ISelectionAdapter" /> part, if
- /// possible.
- /// </summary>
- /// <returns>
- /// A <see cref="T:Avalonia.Controls.ISelectionAdapter" /> object,
- /// if possible. Otherwise, null.
- /// </returns>
- protected virtual ISelectionAdapter GetSelectionAdapterPart(INameScope nameScope)
- {
- ISelectionAdapter adapter = null;
- SelectingItemsControl selector = nameScope.Find<SelectingItemsControl>(ElementSelector);
- if (selector != null)
- {
- // Check if it is already an IItemsSelector
- adapter = selector as ISelectionAdapter;
- if (adapter == null)
- {
- // Built in support for wrapping a Selector control
- adapter = new SelectingItemsControlSelectionAdapter(selector);
- }
- }
- if (adapter == null)
- {
- adapter = nameScope.Find<ISelectionAdapter>(ElementSelectionAdapter);
- }
- return adapter;
- }
- /// <summary>
- /// Builds the visual tree for the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
- /// when a new template is applied.
- /// </summary>
- protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
- {
- if (DropDownPopup != null)
- {
- DropDownPopup.Closed -= DropDownPopup_Closed;
- DropDownPopup = null;
- }
- // Set the template parts. Individual part setters remove and add
- // any event handlers.
- Popup popup = e.NameScope.Find<Popup>(ElementPopup);
- if (popup != null)
- {
- DropDownPopup = popup;
- DropDownPopup.Closed += DropDownPopup_Closed;
- }
- SelectionAdapter = GetSelectionAdapterPart(e.NameScope);
- TextBox = e.NameScope.Find<TextBox>(ElementTextBox);
- // If the drop down property indicates that the popup is open,
- // flip its value to invoke the changed handler.
- if (IsDropDownOpen && DropDownPopup != null && !DropDownPopup.IsOpen)
- {
- OpeningDropDown(false);
- }
- base.OnTemplateApplied(e);
- }
- /// <summary>
- /// Provides handling for the
- /// <see cref="E:Avalonia.InputElement.KeyDown" /> event.
- /// </summary>
- /// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
- /// that contains the event data.</param>
- protected override void OnKeyDown(KeyEventArgs e)
- {
- Contract.Requires<ArgumentNullException>(e != null);
- base.OnKeyDown(e);
- if (e.Handled || !IsEnabled)
- {
- return;
- }
- // The drop down is open, pass along the key event arguments to the
- // selection adapter. If it isn't handled by the adapter's logic,
- // then we handle some simple navigation scenarios for controlling
- // the drop down.
- if (IsDropDownOpen)
- {
- if (SelectionAdapter != null)
- {
- SelectionAdapter.HandleKeyDown(e);
- if (e.Handled)
- {
- return;
- }
- }
- if (e.Key == Key.Escape)
- {
- OnAdapterSelectionCanceled(this, new RoutedEventArgs());
- e.Handled = true;
- }
- }
- else
- {
- // The drop down is not open, the Down key will toggle it open.
- if (e.Key == Key.Down)
- {
- IsDropDownOpen = true;
- e.Handled = true;
- }
- }
- // Standard drop down navigation
- switch (e.Key)
- {
- case Key.F4:
- IsDropDownOpen = !IsDropDownOpen;
- e.Handled = true;
- break;
- case Key.Enter:
- OnAdapterSelectionComplete(this, new RoutedEventArgs());
- e.Handled = true;
- break;
- default:
- break;
- }
- }
- /// <summary>
- /// Provides handling for the
- /// <see cref="E:Avalonia.UIElement.GotFocus" /> event.
- /// </summary>
- /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
- /// that contains the event data.</param>
- protected override void OnGotFocus(GotFocusEventArgs e)
- {
- base.OnGotFocus(e);
- FocusChanged(HasFocus());
- }
- /// <summary>
- /// Provides handling for the
- /// <see cref="E:Avalonia.UIElement.LostFocus" /> event.
- /// </summary>
- /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
- /// that contains the event data.</param>
- protected override void OnLostFocus(RoutedEventArgs e)
- {
- base.OnLostFocus(e);
- FocusChanged(HasFocus());
- }
- /// <summary>
- /// Determines whether the text box or drop-down portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control has
- /// focus.
- /// </summary>
- /// <returns>true to indicate the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus;
- /// otherwise, false.</returns>
- protected bool HasFocus()
- {
- IVisual focused = FocusManager.Instance.Current;
- while (focused != null)
- {
- if (object.ReferenceEquals(focused, this))
- {
- return true;
- }
- // This helps deal with popups that may not be in the same
- // visual tree
- IVisual parent = focused.GetVisualParent();
- if (parent == null)
- {
- // Try the logical parent.
- IControl element = focused as IControl;
- if (element != null)
- {
- parent = element.Parent;
- }
- }
- focused = parent;
- }
- return false;
- }
- /// <summary>
- /// Handles the FocusChanged event.
- /// </summary>
- /// <param name="hasFocus">A value indicating whether the control
- /// currently has the focus.</param>
- private void FocusChanged(bool hasFocus)
- {
- // The OnGotFocus & OnLostFocus are asynchronously and cannot
- // reliably tell you that have the focus. All they do is let you
- // know that the focus changed sometime in the past. To determine
- // if you currently have the focus you need to do consult the
- // FocusManager (see HasFocus()).
- bool wasFocused = _isFocused;
- _isFocused = hasFocus;
- if (hasFocus)
- {
- if (!wasFocused && TextBox != null && TextBoxSelectionLength <= 0)
- {
- TextBox.Focus();
- TextBox.SelectionStart = 0;
- TextBox.SelectionEnd = TextBox.Text?.Length ?? 0;
- }
- }
- else
- {
- IsDropDownOpen = false;
- _userCalledPopulate = false;
- ClearTextBoxSelection();
- }
- _isFocused = hasFocus;
- }
- /// <summary>
- /// Occurs when the text in the text box portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> changes.
- /// </summary>
- public event EventHandler TextChanged;
- /// <summary>
- /// Occurs when the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> is
- /// populating the drop-down with possible matches based on the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
- /// property.
- /// </summary>
- /// <remarks>
- /// If the event is canceled, by setting the PopulatingEventArgs.Cancel
- /// property to true, the AutoCompleteBox will not automatically
- /// populate the selection adapter contained in the drop-down.
- /// In this case, if you want possible matches to appear, you must
- /// provide the logic for populating the selection adapter.
- /// </remarks>
- public event EventHandler<PopulatingEventArgs> Populating;
- /// <summary>
- /// Occurs when the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has
- /// populated the drop-down with possible matches based on the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
- /// property.
- /// </summary>
- public event EventHandler<PopulatedEventArgs> Populated;
- /// <summary>
- /// Occurs when the value of the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
- /// property is changing from false to true.
- /// </summary>
- public event EventHandler<CancelEventArgs> DropDownOpening;
- /// <summary>
- /// Occurs when the value of the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
- /// property has changed from false to true and the drop-down is open.
- /// </summary>
- public event EventHandler DropDownOpened;
- /// <summary>
- /// Occurs when the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
- /// property is changing from true to false.
- /// </summary>
- public event EventHandler<CancelEventArgs> DropDownClosing;
- /// <summary>
- /// Occurs when the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
- /// property was changed from true to false and the drop-down is open.
- /// </summary>
- public event EventHandler DropDownClosed;
- /// <summary>
- /// Occurs when the selected item in the drop-down portion of the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has
- /// changed.
- /// </summary>
- public event EventHandler<SelectionChangedEventArgs> SelectionChanged
- {
- add { AddHandler(SelectionChangedEvent, value); }
- remove { RemoveHandler(SelectionChangedEvent, value); }
- }
- /// <summary>
- /// Raises the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
- /// event.
- /// </summary>
- /// <param name="e">A
- /// <see cref="T:Avalonia.Controls.PopulatingEventArgs" /> that
- /// contains the event data.</param>
- protected virtual void OnPopulating(PopulatingEventArgs e)
- {
- Populating?.Invoke(this, e);
- }
- /// <summary>
- /// Raises the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
- /// event.
- /// </summary>
- /// <param name="e">A
- /// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />
- /// that contains the event data.</param>
- protected virtual void OnPopulated(PopulatedEventArgs e)
- {
- Populated?.Invoke(this, e);
- }
- /// <summary>
- /// Raises the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.SelectionChanged" />
- /// event.
- /// </summary>
- /// <param name="e">A
- /// <see cref="T:Avalonia.Controls.SelectionChangedEventArgs" />
- /// that contains the event data.</param>
- protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)
- {
- RaiseEvent(e);
- }
- /// <summary>
- /// Raises the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownOpening" />
- /// event.
- /// </summary>
- /// <param name="e">A
- /// <see cref="T:Avalonia.Controls.CancelEventArgs" />
- /// that contains the event data.</param>
- protected virtual void OnDropDownOpening(CancelEventArgs e)
- {
- DropDownOpening?.Invoke(this, e);
- }
- /// <summary>
- /// Raises the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownOpened" />
- /// event.
- /// </summary>
- /// <param name="e">A
- /// <see cref="T:System.EventArgs" />
- /// that contains the event data.</param>
- protected virtual void OnDropDownOpened(EventArgs e)
- {
- DropDownOpened?.Invoke(this, e);
- }
- /// <summary>
- /// Raises the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownClosing" />
- /// event.
- /// </summary>
- /// <param name="e">A
- /// <see cref="T:Avalonia.Controls.CancelEventArgs" />
- /// that contains the event data.</param>
- protected virtual void OnDropDownClosing(CancelEventArgs e)
- {
- DropDownClosing?.Invoke(this, e);
- }
- /// <summary>
- /// Raises the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownClosed" />
- /// event.
- /// </summary>
- /// <param name="e">A
- /// <see cref="T:System.EventArgs" />
- /// which contains the event data.</param>
- protected virtual void OnDropDownClosed(EventArgs e)
- {
- DropDownClosed?.Invoke(this, e);
- }
- /// <summary>
- /// Raises the
- /// <see cref="E:Avalonia.Controls.AutoCompleteBox.TextChanged" />
- /// event.
- /// </summary>
- /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
- /// that contains the event data.</param>
- protected virtual void OnTextChanged(RoutedEventArgs e)
- {
- TextChanged?.Invoke(this, e);
- }
- /// <summary>
- /// Begin closing the drop-down.
- /// </summary>
- /// <param name="oldValue">The original value.</param>
- private void ClosingDropDown(bool oldValue)
- {
- var args = new CancelEventArgs();
- OnDropDownClosing(args);
- if (args.Cancel)
- {
- _ignorePropertyChange = true;
- SetValue(IsDropDownOpenProperty, oldValue);
- }
- else
- {
- CloseDropDown();
- }
- UpdatePseudoClasses();
- }
- /// <summary>
- /// Begin opening the drop down by firing cancelable events, opening the
- /// drop-down or reverting, depending on the event argument values.
- /// </summary>
- /// <param name="oldValue">The original value, if needed for a revert.</param>
- private void OpeningDropDown(bool oldValue)
- {
- var args = new CancelEventArgs();
- // Opening
- OnDropDownOpening(args);
- if (args.Cancel)
- {
- _ignorePropertyChange = true;
- SetValue(IsDropDownOpenProperty, oldValue);
- }
- else
- {
- OpenDropDown();
- }
- UpdatePseudoClasses();
- }
- /// <summary>
- /// Connects to the DropDownPopup Closed event.
- /// </summary>
- /// <param name="sender">The source object.</param>
- /// <param name="e">The event data.</param>
- private void DropDownPopup_Closed(object sender, EventArgs e)
- {
- // Force the drop down dependency property to be false.
- if (IsDropDownOpen)
- {
- IsDropDownOpen = false;
- }
- // Fire the DropDownClosed event
- if (_popupHasOpened)
- {
- OnDropDownClosed(EventArgs.Empty);
- }
- }
- /// <summary>
- /// Handles the timer tick when using a populate delay.
- /// </summary>
- /// <param name="sender">The source object.</param>
- /// <param name="e">The event arguments.</param>
- private void PopulateDropDown(object sender, EventArgs e)
- {
- if (_delayTimer != null)
- {
- _delayTimer.Stop();
- }
- // 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
- // display process if Cancel is set to true.
- PopulatingEventArgs populating = new PopulatingEventArgs(SearchText);
- OnPopulating(populating);
- if (!populating.Cancel)
- {
- 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
- /// button, and then fires the Opened event.
- /// </summary>
- private void OpenDropDown()
- {
- if (DropDownPopup != null)
- {
- DropDownPopup.IsOpen = true;
- }
- _popupHasOpened = true;
- OnDropDownOpened(EventArgs.Empty);
- }
- /// <summary>
- /// Private method that directly closes the popup, flips the Checked
- /// value, and then fires the Closed event.
- /// </summary>
- private void CloseDropDown()
- {
- if (_popupHasOpened)
- {
- if (SelectionAdapter != null)
- {
- SelectionAdapter.SelectedItem = null;
- }
- if (DropDownPopup != null)
- {
- DropDownPopup.IsOpen = false;
- }
- OnDropDownClosed(EventArgs.Empty);
- }
- }
- /// <summary>
- /// Formats an Item for text comparisons based on Converter
- /// and ConverterCulture properties.
- /// </summary>
- /// <param name="value">The object to format.</param>
- /// <param name="clearDataContext">A value indicating whether to clear
- /// the data context after the lookup is performed.</param>
- /// <returns>Formatted Value.</returns>
- private string FormatValue(object value, bool clearDataContext)
- {
- string result = FormatValue(value);
- if(clearDataContext && _valueBindingEvaluator != null)
- {
- _valueBindingEvaluator.ClearDataContext();
- }
- return result;
- }
- /// <summary>
- /// Converts the specified object to a string by using the
- /// <see cref="P:Avalonia.Data.Binding.Converter" /> and
- /// <see cref="P:Avalonia.Data.Binding.ConverterCulture" /> values
- /// of the binding object specified by the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ValueMemberBinding" />
- /// property.
- /// </summary>
- /// <param name="value">The object to format as a string.</param>
- /// <returns>The string representation of the specified object.</returns>
- /// <remarks>
- /// Override this method to provide a custom string conversion.
- /// </remarks>
- protected virtual string FormatValue(object value)
- {
- if (_valueBindingEvaluator != null)
- {
- return _valueBindingEvaluator.GetDynamicValue(value) ?? String.Empty;
- }
- if (_valueMemberSelector != null)
- {
- value = _valueMemberSelector.Select(value);
- }
- return value == null ? String.Empty : value.ToString();
- }
- /// <summary>
- /// Handle the TextChanged event that is directly attached to the
- /// TextBox part. This ensures that only user initiated actions will
- /// result in an AutoCompleteBox suggestion and operation.
- /// </summary>
- private void OnTextBoxTextChanged()
- {
- //Uses Dispatcher.Post to allow the TextBox selection to update before processing
- Dispatcher.UIThread.Post(() =>
- {
- // Call the central updated text method as a user-initiated action
- TextUpdated(_textBox.Text, true);
- });
- }
- /// <summary>
- /// Updates both the text box value and underlying text dependency
- /// property value if and when they change. Automatically fires the
- /// text changed events when there is a change.
- /// </summary>
- /// <param name="value">The new string value.</param>
- private void UpdateTextValue(string value)
- {
- UpdateTextValue(value, null);
- }
- /// <summary>
- /// Updates both the text box value and underlying text dependency
- /// property value if and when they change. Automatically fires the
- /// text changed events when there is a change.
- /// </summary>
- /// <param name="value">The new string value.</param>
- /// <param name="userInitiated">A nullable bool value indicating whether
- /// the action was user initiated. In a user initiated mode, the
- /// underlying text dependency property is updated. In a non-user
- /// interaction, the text box value is updated. When user initiated is
- /// null, all values are updated.</param>
- private void UpdateTextValue(string value, bool? userInitiated)
- {
- bool callTextChanged = false;
- // Update the Text dependency property
- if ((userInitiated == null || userInitiated == true) && Text != value)
- {
- _ignoreTextPropertyChange++;
- Text = value;
- callTextChanged = true;
- }
- // Update the TextBox's Text dependency property
- if ((userInitiated == null || userInitiated == false) && TextBox != null && TextBox.Text != value)
- {
- _ignoreTextPropertyChange++;
- TextBox.Text = value ?? string.Empty;
- // Text dependency property value was set, fire event
- if (!callTextChanged && (Text == value || Text == null))
- {
- callTextChanged = true;
- }
- }
- if (callTextChanged)
- {
- OnTextChanged(new RoutedEventArgs());
- }
- }
- /// <summary>
- /// Handle the update of the text for the control from any source,
- /// including the TextBox part and the Text dependency property.
- /// </summary>
- /// <param name="newText">The new text.</param>
- /// <param name="userInitiated">A value indicating whether the update
- /// is a user-initiated action. This should be a True value when the
- /// TextUpdated method is called from a TextBox event handler.</param>
- private void TextUpdated(string newText, bool userInitiated)
- {
- // Only process this event if it is coming from someone outside
- // setting the Text dependency property directly.
- if (_ignoreTextPropertyChange > 0)
- {
- _ignoreTextPropertyChange--;
- return;
- }
- if (newText == null)
- {
- newText = string.Empty;
- }
- // The TextBox.TextChanged event was not firing immediately and
- // was causing an immediate update, even with wrapping. If there is
- // a selection currently, no update should happen.
- if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != TextBox.Text.Length)
- {
- return;
- }
- // Evaluate the conditions needed for completion.
- // 1. Minimum prefix length
- // 2. If a delay timer is in use, use it
- bool populateReady = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0;
- _userCalledPopulate = populateReady ? userInitiated : false;
- // Update the interface and values only as necessary
- UpdateTextValue(newText, userInitiated);
- if (populateReady)
- {
- _ignoreTextSelectionChange = true;
- if (_delayTimer != null)
- {
- _delayTimer.Start();
- }
- else
- {
- PopulateDropDown(this, EventArgs.Empty);
- }
- }
- else
- {
- SearchText = string.Empty;
- if (SelectedItem != null)
- {
- _skipSelectedItemTextUpdate = true;
- }
- SelectedItem = null;
- if (IsDropDownOpen)
- {
- IsDropDownOpen = false;
- }
- }
- }
- /// <summary>
- /// A simple helper method to clear the view and ensure that a view
- /// object is always present and not null.
- /// </summary>
- private void ClearView()
- {
- if (_view == null)
- {
- _view = new AvaloniaList<object>();
- }
- else
- {
- _view.Clear();
- }
- }
- /// <summary>
- /// Walks through the items enumeration. Performance is not going to be
- /// perfect with the current implementation.
- /// </summary>
- private void RefreshView()
- {
- if (_items == null)
- {
- ClearView();
- return;
- }
- // Cache the current text value
- string text = Text ?? string.Empty;
- // Determine if any filtering mode is on
- bool stringFiltering = TextFilter != null;
- bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null;
- int view_index = 0;
- int view_count = _view.Count;
- List<object> items = _items;
- foreach (object item in items)
- {
- bool inResults = !(stringFiltering || objectFiltering);
- if (!inResults)
- {
- inResults = stringFiltering ? TextFilter(text, FormatValue(item)) : ItemFilter(text, item);
- }
- if (view_count > view_index && inResults && _view[view_index] == item)
- {
- // Item is still in the view
- view_index++;
- }
- else if (inResults)
- {
- // Insert the item
- if (view_count > view_index && _view[view_index] != item)
- {
- // Replace item
- // Unfortunately replacing via index throws a fatal
- // exception: View[view_index] = item;
- // Cost: O(n) vs O(1)
- _view.RemoveAt(view_index);
- _view.Insert(view_index, item);
- view_index++;
- }
- else
- {
- // Add the item
- if (view_index == view_count)
- {
- // Constant time is preferred (Add).
- _view.Add(item);
- }
- else
- {
- _view.Insert(view_index, item);
- }
- view_index++;
- view_count++;
- }
- }
- else if (view_count > view_index && _view[view_index] == item)
- {
- // Remove the item
- _view.RemoveAt(view_index);
- view_count--;
- }
- }
- // Clear the evaluator to discard a reference to the last item
- if (_valueBindingEvaluator != null)
- {
- _valueBindingEvaluator.ClearDataContext();
- }
- }
- /// <summary>
- /// Handle any change to the ItemsSource dependency property, update
- /// the underlying ObservableCollection view, and set the selection
- /// adapter's ItemsSource to the view if appropriate.
- /// </summary>
- /// <param name="newValue">The new enumerable reference.</param>
- private void OnItemsChanged(IEnumerable newValue)
- {
- // Remove handler for oldValue.CollectionChanged (if present)
- _collectionChangeSubscription?.Dispose();
- _collectionChangeSubscription = null;
- // Add handler for newValue.CollectionChanged (if possible)
- if (newValue is INotifyCollectionChanged newValueINotifyCollectionChanged)
- {
- _collectionChangeSubscription = newValueINotifyCollectionChanged.WeakSubscribe(ItemsCollectionChanged);
- }
- // Store a local cached copy of the data
- _items = newValue == null ? null : new List<object>(newValue.Cast<object>().ToList());
- // Clear and set the view on the selection adapter
- ClearView();
- if (SelectionAdapter != null && SelectionAdapter.Items != _view)
- {
- SelectionAdapter.Items = _view;
- }
- if (IsDropDownOpen)
- {
- RefreshView();
- }
- }
- /// <summary>
- /// Method that handles the ObservableCollection.CollectionChanged event for the ItemsSource property.
- /// </summary>
- /// <param name="sender">The object that raised the event.</param>
- /// <param name="e">The event data.</param>
- private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- // Update the cache
- if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
- {
- for (int index = 0; index < e.OldItems.Count; index++)
- {
- _items.RemoveAt(e.OldStartingIndex);
- }
- }
- if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && _items.Count >= e.NewStartingIndex)
- {
- for (int index = 0; index < e.NewItems.Count; index++)
- {
- _items.Insert(e.NewStartingIndex + index, e.NewItems[index]);
- }
- }
- if (e.Action == NotifyCollectionChangedAction.Replace && e.NewItems != null && e.OldItems != null)
- {
- for (int index = 0; index < e.NewItems.Count; index++)
- {
- _items[e.NewStartingIndex] = e.NewItems[index];
- }
- }
- // Update the view
- if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace)
- {
- for (int index = 0; index < e.OldItems.Count; index++)
- {
- _view.Remove(e.OldItems[index]);
- }
- }
- if (e.Action == NotifyCollectionChangedAction.Reset)
- {
- // Significant changes to the underlying data.
- ClearView();
- if (Items != null)
- {
- _items = new List<object>(Items.Cast<object>().ToList());
- }
- }
- // Refresh the observable collection used in the selection adapter.
- RefreshView();
- }
- /// <summary>
- /// Notifies the
- /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> that the
- /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Items" />
- /// property has been set and the data can be filtered to provide
- /// possible matches in the drop-down.
- /// </summary>
- /// <remarks>
- /// Call this method when you are providing custom population of
- /// the drop-down portion of the AutoCompleteBox, to signal the control
- /// that you are done with the population process.
- /// Typically, you use PopulateComplete when the population process
- /// is a long-running process and you want to cancel built-in filtering
- /// of the ItemsSource items. In this case, you can handle the
- /// Populated event and set PopulatingEventArgs.Cancel to true.
- /// When the long-running process has completed you call
- /// PopulateComplete to indicate the drop-down is populated.
- /// </remarks>
- public void PopulateComplete()
- {
- // Apply the search filter
- RefreshView();
- // Fire the Populated event containing the read-only view data.
- PopulatedEventArgs populated = new PopulatedEventArgs(new ReadOnlyCollection<object>(_view));
- OnPopulated(populated);
- if (SelectionAdapter != null && SelectionAdapter.Items != _view)
- {
- SelectionAdapter.Items = _view;
- }
- bool isDropDownOpen = _userCalledPopulate && (_view.Count > 0);
- if (isDropDownOpen != IsDropDownOpen)
- {
- _ignorePropertyChange = true;
- IsDropDownOpen = isDropDownOpen;
- }
- if (IsDropDownOpen)
- {
- OpeningDropDown(false);
- }
- else
- {
- ClosingDropDown(true);
- }
- UpdateTextCompletion(_userCalledPopulate);
- }
- /// <summary>
- /// Performs text completion, if enabled, and a lookup on the underlying
- /// item values for an exact match. Will update the SelectedItem value.
- /// </summary>
- /// <param name="userInitiated">A value indicating whether the operation
- /// was user initiated. Text completion will not be performed when not
- /// directly initiated by the user.</param>
- private void UpdateTextCompletion(bool userInitiated)
- {
- // By default this method will clear the selected value
- object newSelectedItem = null;
- string text = Text;
- // Text search is StartsWith explicit and only when enabled, in
- // line with WPF's ComboBox lookup. When in use it will associate
- // a Value with the Text if it is found in ItemsSource. This is
- // only valid when there is data and the user initiated the action.
- if (_view.Count > 0)
- {
- if (IsTextCompletionEnabled && TextBox != null && userInitiated)
- {
- int currentLength = TextBox.Text.Length;
- int selectionStart = TextBoxSelectionStart;
- if (selectionStart == text.Length && selectionStart > _textSelectionStart)
- {
- // When the FilterMode dependency property is set to
- // either StartsWith or StartsWithCaseSensitive, the
- // first item in the view is used. This will improve
- // performance on the lookup. It assumes that the
- // FilterMode the user has selected is an acceptable
- // case sensitive matching function for their scenario.
- object top = FilterMode == AutoCompleteFilterMode.StartsWith || FilterMode == AutoCompleteFilterMode.StartsWithCaseSensitive
- ? _view[0]
- : TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
- // If the search was successful, update SelectedItem
- if (top != null)
- {
- newSelectedItem = top;
- string topString = FormatValue(top, true);
- // Only replace partially when the two words being the same
- int minLength = Math.Min(topString.Length, Text.Length);
- if (AutoCompleteSearch.Equals(Text.Substring(0, minLength), topString.Substring(0, minLength)))
- {
- // Update the text
- UpdateTextValue(topString);
- // Select the text past the user's caret
- TextBox.SelectionStart = currentLength;
- TextBox.SelectionEnd = topString.Length;
- }
- }
- }
- }
- else
- {
- // Perform an exact string lookup for the text. This is a
- // design change from the original Toolkit release when the
- // IsTextCompletionEnabled property behaved just like the
- // WPF ComboBox's IsTextSearchEnabled property.
- //
- // This change provides the behavior that most people expect
- // to find: a lookup for the value is always performed.
- newSelectedItem = TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive));
- }
- }
- // Update the selected item property
- if (SelectedItem != newSelectedItem)
- {
- _skipSelectedItemTextUpdate = true;
- }
- SelectedItem = newSelectedItem;
- // Restore updates for TextSelection
- if (_ignoreTextSelectionChange)
- {
- _ignoreTextSelectionChange = false;
- if (TextBox != null)
- {
- _textSelectionStart = TextBoxSelectionStart;
- }
- }
- }
- /// <summary>
- /// Attempts to look through the view and locate the specific exact
- /// text match.
- /// </summary>
- /// <param name="searchText">The search text.</param>
- /// <param name="view">The view reference.</param>
- /// <param name="predicate">The predicate to use for the partial or
- /// exact match.</param>
- /// <returns>Returns the object or null.</returns>
- private object TryGetMatch(string searchText, AvaloniaList<object> view, AutoCompleteFilterPredicate<string> predicate)
- {
- if (view != null && view.Count > 0)
- {
- foreach (object o in view)
- {
- if (predicate(searchText, FormatValue(o)))
- {
- return o;
- }
- }
- }
- return null;
- }
- private void UpdatePseudoClasses()
- {
- PseudoClasses.Set(":dropdownopen", IsDropDownOpen);
- }
- private void ClearTextBoxSelection()
- {
- if (TextBox != null)
- {
- int length = TextBox.Text?.Length ?? 0;
- TextBox.SelectionStart = length;
- TextBox.SelectionEnd = length;
- }
- }
- /// <summary>
- /// Called when the selected item is changed, updates the text value
- /// that is displayed in the text box part.
- /// </summary>
- /// <param name="newItem">The new item.</param>
- private void OnSelectedItemChanged(object newItem)
- {
- string text;
- if (newItem == null)
- {
- text = SearchText;
- }
- else
- {
- text = FormatValue(newItem, true);
- }
- // Update the Text property and the TextBox values
- UpdateTextValue(text);
- // Move the caret to the end of the text box
- ClearTextBoxSelection();
- }
- /// <summary>
- /// Handles the SelectionChanged event of the selection adapter.
- /// </summary>
- /// <param name="sender">The source object.</param>
- /// <param name="e">The selection changed event data.</param>
- private void OnAdapterSelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- SelectedItem = _adapter.SelectedItem;
- }
- //TODO Check UpdateTextCompletion
- /// <summary>
- /// Handles the Commit event on the selection adapter.
- /// </summary>
- /// <param name="sender">The source object.</param>
- /// <param name="e">The event data.</param>
- private void OnAdapterSelectionComplete(object sender, RoutedEventArgs e)
- {
- IsDropDownOpen = false;
- // Completion will update the selected value
- //UpdateTextCompletion(false);
- // Text should not be selected
- ClearTextBoxSelection();
- TextBox.Focus();
- }
- /// <summary>
- /// Handles the Cancel event on the selection adapter.
- /// </summary>
- /// <param name="sender">The source object.</param>
- /// <param name="e">The event data.</param>
- private void OnAdapterSelectionCanceled(object sender, RoutedEventArgs e)
- {
- UpdateTextValue(SearchText);
- // Completion will update the selected value
- UpdateTextCompletion(false);
- }
- /// <summary>
- /// A predefined set of filter functions for the known, built-in
- /// AutoCompleteFilterMode enumeration values.
- /// </summary>
- private static class AutoCompleteSearch
- {
- /// <summary>
- /// Index function that retrieves the filter for the provided
- /// AutoCompleteFilterMode.
- /// </summary>
- /// <param name="FilterMode">The built-in search mode.</param>
- /// <returns>Returns the string-based comparison function.</returns>
- public static AutoCompleteFilterPredicate<string> GetFilter(AutoCompleteFilterMode FilterMode)
- {
- switch (FilterMode)
- {
- case AutoCompleteFilterMode.Contains:
- return Contains;
- case AutoCompleteFilterMode.ContainsCaseSensitive:
- return ContainsCaseSensitive;
- case AutoCompleteFilterMode.ContainsOrdinal:
- return ContainsOrdinal;
- case AutoCompleteFilterMode.ContainsOrdinalCaseSensitive:
- return ContainsOrdinalCaseSensitive;
- case AutoCompleteFilterMode.Equals:
- return Equals;
- case AutoCompleteFilterMode.EqualsCaseSensitive:
- return EqualsCaseSensitive;
- case AutoCompleteFilterMode.EqualsOrdinal:
- return EqualsOrdinal;
- case AutoCompleteFilterMode.EqualsOrdinalCaseSensitive:
- return EqualsOrdinalCaseSensitive;
- case AutoCompleteFilterMode.StartsWith:
- return StartsWith;
- case AutoCompleteFilterMode.StartsWithCaseSensitive:
- return StartsWithCaseSensitive;
- case AutoCompleteFilterMode.StartsWithOrdinal:
- return StartsWithOrdinal;
- case AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive:
- return StartsWithOrdinalCaseSensitive;
- case AutoCompleteFilterMode.None:
- case AutoCompleteFilterMode.Custom:
- default:
- return null;
- }
- }
- /// <summary>
- /// An implementation of the Contains member of string that takes in a
- /// string comparison. The traditional .NET string Contains member uses
- /// StringComparison.Ordinal.
- /// </summary>
- /// <param name="s">The string.</param>
- /// <param name="value">The string value to search for.</param>
- /// <param name="comparison">The string comparison type.</param>
- /// <returns>Returns true when the substring is found.</returns>
- private static bool Contains(string s, string value, StringComparison comparison)
- {
- return s.IndexOf(value, comparison) >= 0;
- }
- /// <summary>
- /// Check if the string value begins with the text.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool StartsWith(string text, string value)
- {
- return value.StartsWith(text, StringComparison.CurrentCultureIgnoreCase);
- }
- /// <summary>
- /// Check if the string value begins with the text.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool StartsWithCaseSensitive(string text, string value)
- {
- return value.StartsWith(text, StringComparison.CurrentCulture);
- }
- /// <summary>
- /// Check if the string value begins with the text.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool StartsWithOrdinal(string text, string value)
- {
- return value.StartsWith(text, StringComparison.OrdinalIgnoreCase);
- }
- /// <summary>
- /// Check if the string value begins with the text.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool StartsWithOrdinalCaseSensitive(string text, string value)
- {
- return value.StartsWith(text, StringComparison.Ordinal);
- }
- /// <summary>
- /// Check if the prefix is contained in the string value. The current
- /// culture's case insensitive string comparison operator is used.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool Contains(string text, string value)
- {
- return Contains(value, text, StringComparison.CurrentCultureIgnoreCase);
- }
- /// <summary>
- /// Check if the prefix is contained in the string value.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool ContainsCaseSensitive(string text, string value)
- {
- return Contains(value, text, StringComparison.CurrentCulture);
- }
- /// <summary>
- /// Check if the prefix is contained in the string value.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool ContainsOrdinal(string text, string value)
- {
- return Contains(value, text, StringComparison.OrdinalIgnoreCase);
- }
- /// <summary>
- /// Check if the prefix is contained in the string value.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool ContainsOrdinalCaseSensitive(string text, string value)
- {
- return Contains(value, text, StringComparison.Ordinal);
- }
- /// <summary>
- /// Check if the string values are equal.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool Equals(string text, string value)
- {
- return value.Equals(text, StringComparison.CurrentCultureIgnoreCase);
- }
- /// <summary>
- /// Check if the string values are equal.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool EqualsCaseSensitive(string text, string value)
- {
- return value.Equals(text, StringComparison.CurrentCulture);
- }
- /// <summary>
- /// Check if the string values are equal.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool EqualsOrdinal(string text, string value)
- {
- return value.Equals(text, StringComparison.OrdinalIgnoreCase);
- }
- /// <summary>
- /// Check if the string values are equal.
- /// </summary>
- /// <param name="text">The AutoCompleteBox prefix text.</param>
- /// <param name="value">The item's string value.</param>
- /// <returns>Returns true if the condition is met.</returns>
- public static bool EqualsOrdinalCaseSensitive(string text, string value)
- {
- return value.Equals(text, StringComparison.Ordinal);
- }
- }
- /// <summary>
- /// A framework element that permits a binding to be evaluated in a new data
- /// context leaf node.
- /// </summary>
- /// <typeparam name="T">The type of dynamic binding to return.</typeparam>
- public class BindingEvaluator<T> : Control
- {
- /// <summary>
- /// Gets or sets the string value binding used by the control.
- /// </summary>
- private IBinding _binding;
- #region public T Value
- /// <summary>
- /// Identifies the Value dependency property.
- /// </summary>
- public static readonly StyledProperty<T> ValueProperty =
- AvaloniaProperty.Register<BindingEvaluator<T>, T>(nameof(Value));
- /// <summary>
- /// Gets or sets the data item value.
- /// </summary>
- public T Value
- {
- get { return GetValue(ValueProperty); }
- set { SetValue(ValueProperty, value); }
- }
- #endregion public string Value
- /// <summary>
- /// Gets or sets the value binding.
- /// </summary>
- public IBinding ValueBinding
- {
- get { return _binding; }
- set
- {
- _binding = value;
- AvaloniaObjectExtensions.Bind(this, ValueProperty, value);
- }
- }
- /// <summary>
- /// Initializes a new instance of the BindingEvaluator class.
- /// </summary>
- public BindingEvaluator()
- { }
- /// <summary>
- /// Initializes a new instance of the BindingEvaluator class,
- /// setting the initial binding to the provided parameter.
- /// </summary>
- /// <param name="binding">The initial string value binding.</param>
- public BindingEvaluator(IBinding binding)
- : this()
- {
- ValueBinding = binding;
- }
- /// <summary>
- /// Clears the data context so that the control does not keep a
- /// reference to the last-looked up item.
- /// </summary>
- public void ClearDataContext()
- {
- DataContext = null;
- }
- /// <summary>
- /// Updates the data context of the framework element and returns the
- /// updated binding value.
- /// </summary>
- /// <param name="o">The object to use as the data context.</param>
- /// <param name="clearDataContext">If set to true, this parameter will
- /// clear the data context immediately after retrieving the value.</param>
- /// <returns>Returns the evaluated T value of the bound dependency
- /// property.</returns>
- public T GetDynamicValue(object o, bool clearDataContext)
- {
- DataContext = o;
- T value = Value;
- if (clearDataContext)
- {
- DataContext = null;
- }
- return value;
- }
- /// <summary>
- /// Updates the data context of the framework element and returns the
- /// updated binding value.
- /// </summary>
- /// <param name="o">The object to use as the data context.</param>
- /// <returns>Returns the evaluated T value of the bound dependency
- /// property.</returns>
- public T GetDynamicValue(object o)
- {
- DataContext = o;
- return Value;
- }
- }
- }
- }
|