| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680 |
- // (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>
- 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>
- 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>
- 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 Func<string, CancellationToken, Task<IEnumerable<object>>> _asyncPopulator;
- private CancellationTokenSource _populationCancellationTokenSource;
- private bool _itemTemplateIsFromValueMemberBinding = true;
- private bool _settingItemTemplateFromValueMemberBinding;
- 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: IsValidMinimumPrefixLength);
- /// <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: IsValidMinimumPopulateDelay);
- /// <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: IsValidMaxDropDownHeight);
- /// <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: IsValidFilterMode);
- /// <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, 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 bool IsValidMinimumPrefixLength(int value) => value >= -1;
- private static bool IsValidMinimumPopulateDelay(TimeSpan value) => value.TotalMilliseconds >= 0.0;
- private static bool IsValidMaxDropDownHeight(double value) => value >= 0.0;
- 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;
- }
- }
- /// <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 (!_settingItemTemplateFromValueMemberBinding)
- _itemTemplateIsFromValueMemberBinding = false;
- }
- private void OnValueMemberBindingChanged(IBinding value)
- {
- if(_itemTemplateIsFromValueMemberBinding)
- {
- var template =
- new FuncDataTemplate(
- typeof(object),
- (o, _) =>
- {
- var control = new ContentControl();
- control.Bind(ContentControl.ContentProperty, value);
- return control;
- });
- _settingItemTemplateFromValueMemberBinding = true;
- ItemTemplate = template;
- _settingItemTemplateFromValueMemberBinding = false;
- }
- }
- static AutoCompleteBox()
- {
- FocusableProperty.OverrideDefaultValue<AutoCompleteBox>(true);
- MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnMinimumPopulateDelayChanged(e));
- IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnIsDropDownOpenChanged(e));
- SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSelectedItemPropertyChanged(e));
- TextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnTextPropertyChanged(e));
- SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSearchTextPropertyChanged(e));
- FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnFilterModePropertyChanged(e));
- ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemFilterPropertyChanged(e));
- ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemsPropertyChanged(e));
- IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnControlIsEnabledChanged(e));
- }
- /// <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 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;
- }
- 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 ?? 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;
- if(populateReady && MinimumPrefixLength == 0 && String.IsNullOrEmpty(newText) && String.IsNullOrEmpty(SearchText))
- {
- populateReady = false;
- }
- _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) && e.OldItems != null)
- {
- 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;
- }
- }
- }
- }
|