AutoCompleteBox.cs 106 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791
  1. // (c) Copyright Microsoft Corporation.
  2. // This source is subject to the Microsoft Public License (Ms-PL).
  3. // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  4. // All other rights reserved.
  5. using System;
  6. using System.Collections;
  7. using System.Collections.Generic;
  8. using System.Collections.ObjectModel;
  9. using System.Collections.Specialized;
  10. using System.ComponentModel;
  11. using System.Linq;
  12. using System.Reactive.Linq;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using Avalonia.Collections;
  16. using Avalonia.Controls.Primitives;
  17. using Avalonia.Controls.Templates;
  18. using Avalonia.Controls.Utils;
  19. using Avalonia.Data;
  20. using Avalonia.Input;
  21. using Avalonia.Interactivity;
  22. using Avalonia.Threading;
  23. using Avalonia.VisualTree;
  24. namespace Avalonia.Controls
  25. {
  26. /// <summary>
  27. /// Provides data for the
  28. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
  29. /// event.
  30. /// </summary>
  31. public class PopulatedEventArgs : EventArgs
  32. {
  33. /// <summary>
  34. /// Gets the list of possible matches added to the drop-down portion of
  35. /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
  36. /// control.
  37. /// </summary>
  38. /// <value>The list of possible matches added to the
  39. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
  40. public IEnumerable Data { get; private set; }
  41. /// <summary>
  42. /// Initializes a new instance of the
  43. /// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />.
  44. /// </summary>
  45. /// <param name="data">The list of possible matches added to the
  46. /// drop-down portion of the
  47. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
  48. public PopulatedEventArgs(IEnumerable data)
  49. {
  50. Data = data;
  51. }
  52. }
  53. /// <summary>
  54. /// Provides data for the
  55. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
  56. /// event.
  57. /// </summary>
  58. public class PopulatingEventArgs : CancelEventArgs
  59. {
  60. /// <summary>
  61. /// Gets the text that is used to determine which items to display in
  62. /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
  63. /// control.
  64. /// </summary>
  65. /// <value>The text that is used to determine which items to display in
  66. /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
  67. public string Parameter { get; private set; }
  68. /// <summary>
  69. /// Initializes a new instance of the
  70. /// <see cref="T:Avalonia.Controls.PopulatingEventArgs" />.
  71. /// </summary>
  72. /// <param name="parameter">The value of the
  73. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
  74. /// property, which is used to filter items for the
  75. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
  76. public PopulatingEventArgs(string parameter)
  77. {
  78. Parameter = parameter;
  79. }
  80. }
  81. /// <summary>
  82. /// Represents the filter used by the
  83. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to
  84. /// determine whether an item is a possible match for the specified text.
  85. /// </summary>
  86. /// <returns>true to indicate <paramref name="item" /> is a possible match
  87. /// for <paramref name="search" />; otherwise false.</returns>
  88. /// <param name="search">The string used as the basis for filtering.</param>
  89. /// <param name="item">The item that is compared with the
  90. /// <paramref name="search" /> parameter.</param>
  91. /// <typeparam name="T">The type used for filtering the
  92. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" />. This type can
  93. /// be either a string or an object.</typeparam>
  94. public delegate bool AutoCompleteFilterPredicate<T>(string search, T item);
  95. /// <summary>
  96. /// Specifies how text in the text box portion of the
  97. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control is used
  98. /// to filter items specified by the
  99. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  100. /// property for display in the drop-down.
  101. /// </summary>
  102. public enum AutoCompleteFilterMode
  103. {
  104. /// <summary>
  105. /// Specifies that no filter is used. All items are returned.
  106. /// </summary>
  107. None = 0,
  108. /// <summary>
  109. /// Specifies a culture-sensitive, case-insensitive filter where the
  110. /// returned items start with the specified text. The filter uses the
  111. /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
  112. /// method, specifying
  113. /// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
  114. /// the string comparison criteria.
  115. /// </summary>
  116. StartsWith = 1,
  117. /// <summary>
  118. /// Specifies a culture-sensitive, case-sensitive filter where the
  119. /// returned items start with the specified text. The filter uses the
  120. /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
  121. /// method, specifying
  122. /// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
  123. /// comparison criteria.
  124. /// </summary>
  125. StartsWithCaseSensitive = 2,
  126. /// <summary>
  127. /// Specifies an ordinal, case-insensitive filter where the returned
  128. /// items start with the specified text. The filter uses the
  129. /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
  130. /// method, specifying
  131. /// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
  132. /// string comparison criteria.
  133. /// </summary>
  134. StartsWithOrdinal = 3,
  135. /// <summary>
  136. /// Specifies an ordinal, case-sensitive filter where the returned items
  137. /// start with the specified text. The filter uses the
  138. /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
  139. /// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
  140. /// the string comparison criteria.
  141. /// </summary>
  142. StartsWithOrdinalCaseSensitive = 4,
  143. /// <summary>
  144. /// Specifies a culture-sensitive, case-insensitive filter where the
  145. /// returned items contain the specified text.
  146. /// </summary>
  147. Contains = 5,
  148. /// <summary>
  149. /// Specifies a culture-sensitive, case-sensitive filter where the
  150. /// returned items contain the specified text.
  151. /// </summary>
  152. ContainsCaseSensitive = 6,
  153. /// <summary>
  154. /// Specifies an ordinal, case-insensitive filter where the returned
  155. /// items contain the specified text.
  156. /// </summary>
  157. ContainsOrdinal = 7,
  158. /// <summary>
  159. /// Specifies an ordinal, case-sensitive filter where the returned items
  160. /// contain the specified text.
  161. /// </summary>
  162. ContainsOrdinalCaseSensitive = 8,
  163. /// <summary>
  164. /// Specifies a culture-sensitive, case-insensitive filter where the
  165. /// returned items equal the specified text. The filter uses the
  166. /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
  167. /// method, specifying
  168. /// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
  169. /// the search comparison criteria.
  170. /// </summary>
  171. Equals = 9,
  172. /// <summary>
  173. /// Specifies a culture-sensitive, case-sensitive filter where the
  174. /// returned items equal the specified text. The filter uses the
  175. /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
  176. /// method, specifying
  177. /// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
  178. /// comparison criteria.
  179. /// </summary>
  180. EqualsCaseSensitive = 10,
  181. /// <summary>
  182. /// Specifies an ordinal, case-insensitive filter where the returned
  183. /// items equal the specified text. The filter uses the
  184. /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
  185. /// method, specifying
  186. /// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
  187. /// string comparison criteria.
  188. /// </summary>
  189. EqualsOrdinal = 11,
  190. /// <summary>
  191. /// Specifies an ordinal, case-sensitive filter where the returned items
  192. /// equal the specified text. The filter uses the
  193. /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
  194. /// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
  195. /// the string comparison criteria.
  196. /// </summary>
  197. EqualsOrdinalCaseSensitive = 12,
  198. /// <summary>
  199. /// Specifies that a custom filter is used. This mode is used when the
  200. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
  201. /// or
  202. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
  203. /// properties are set.
  204. /// </summary>
  205. Custom = 13,
  206. }
  207. /// <summary>
  208. /// Represents the selector used by the
  209. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to
  210. /// determine how the specified text should be modified with an item.
  211. /// </summary>
  212. /// <returns>
  213. /// Modified text that will be used by the
  214. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.
  215. /// </returns>
  216. /// <param name="search">The string used as the basis for filtering.</param>
  217. /// <param name="item">
  218. /// The selected item that should be combined with the
  219. /// <paramref name="search" /> parameter.
  220. /// </param>
  221. /// <typeparam name="T">
  222. /// The type used for filtering the
  223. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.
  224. /// This type can be either a string or an object.
  225. /// </typeparam>
  226. public delegate string AutoCompleteSelector<T>(string search, T item);
  227. /// <summary>
  228. /// Represents a control that provides a text box for user input and a
  229. /// drop-down that contains possible matches based on the input in the text
  230. /// box.
  231. /// </summary>
  232. public class AutoCompleteBox : TemplatedControl
  233. {
  234. /// <summary>
  235. /// Specifies the name of the selection adapter TemplatePart.
  236. /// </summary>
  237. private const string ElementSelectionAdapter = "PART_SelectionAdapter";
  238. /// <summary>
  239. /// Specifies the name of the Selector TemplatePart.
  240. /// </summary>
  241. private const string ElementSelector = "PART_SelectingItemsControl";
  242. /// <summary>
  243. /// Specifies the name of the Popup TemplatePart.
  244. /// </summary>
  245. private const string ElementPopup = "PART_Popup";
  246. /// <summary>
  247. /// The name for the text box part.
  248. /// </summary>
  249. private const string ElementTextBox = "PART_TextBox";
  250. private IEnumerable _itemsEnumerable;
  251. /// <summary>
  252. /// Gets or sets a local cached copy of the items data.
  253. /// </summary>
  254. private List<object> _items;
  255. /// <summary>
  256. /// Gets or sets the observable collection that contains references to
  257. /// all of the items in the generated view of data that is provided to
  258. /// the selection-style control adapter.
  259. /// </summary>
  260. private AvaloniaList<object> _view;
  261. /// <summary>
  262. /// Gets or sets a value to ignore a number of pending change handlers.
  263. /// The value is decremented after each use. This is used to reset the
  264. /// value of properties without performing any of the actions in their
  265. /// change handlers.
  266. /// </summary>
  267. /// <remarks>The int is important as a value because the TextBox
  268. /// TextChanged event does not immediately fire, and this will allow for
  269. /// nested property changes to be ignored.</remarks>
  270. private int _ignoreTextPropertyChange;
  271. /// <summary>
  272. /// Gets or sets a value indicating whether to ignore calling a pending
  273. /// change handlers.
  274. /// </summary>
  275. private bool _ignorePropertyChange;
  276. /// <summary>
  277. /// Gets or sets a value indicating whether to ignore the selection
  278. /// changed event.
  279. /// </summary>
  280. private bool _ignoreTextSelectionChange;
  281. /// <summary>
  282. /// Gets or sets a value indicating whether to skip the text update
  283. /// processing when the selected item is updated.
  284. /// </summary>
  285. private bool _skipSelectedItemTextUpdate;
  286. /// <summary>
  287. /// Gets or sets the last observed text box selection start location.
  288. /// </summary>
  289. private int _textSelectionStart;
  290. /// <summary>
  291. /// Gets or sets a value indicating whether the user initiated the
  292. /// current populate call.
  293. /// </summary>
  294. private bool _userCalledPopulate;
  295. /// <summary>
  296. /// A value indicating whether the popup has been opened at least once.
  297. /// </summary>
  298. private bool _popupHasOpened;
  299. /// <summary>
  300. /// Gets or sets the DispatcherTimer used for the MinimumPopulateDelay
  301. /// condition for auto completion.
  302. /// </summary>
  303. private DispatcherTimer _delayTimer;
  304. /// <summary>
  305. /// Gets or sets a value indicating whether a read-only dependency
  306. /// property change handler should allow the value to be set. This is
  307. /// used to ensure that read-only properties cannot be changed via
  308. /// SetValue, etc.
  309. /// </summary>
  310. private bool _allowWrite;
  311. /// <summary>
  312. /// The TextBox template part.
  313. /// </summary>
  314. private TextBox _textBox;
  315. private IDisposable _textBoxSubscriptions;
  316. /// <summary>
  317. /// The SelectionAdapter.
  318. /// </summary>
  319. private ISelectionAdapter _adapter;
  320. /// <summary>
  321. /// A control that can provide updated string values from a binding.
  322. /// </summary>
  323. private BindingEvaluator<string> _valueBindingEvaluator;
  324. /// <summary>
  325. /// A weak subscription for the collection changed event.
  326. /// </summary>
  327. private IDisposable _collectionChangeSubscription;
  328. private Func<string, CancellationToken, Task<IEnumerable<object>>> _asyncPopulator;
  329. private CancellationTokenSource _populationCancellationTokenSource;
  330. private bool _itemTemplateIsFromValueMemberBinding = true;
  331. private bool _settingItemTemplateFromValueMemberBinding;
  332. private object _selectedItem;
  333. private bool _isDropDownOpen;
  334. private bool _isFocused = false;
  335. private string _text = string.Empty;
  336. private string _searchText = string.Empty;
  337. private AutoCompleteFilterPredicate<object> _itemFilter;
  338. private AutoCompleteFilterPredicate<string> _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith);
  339. private AutoCompleteSelector<object> _itemSelector;
  340. private AutoCompleteSelector<string> _textSelector;
  341. public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
  342. RoutedEvent.Register<SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox));
  343. public static readonly StyledProperty<string> WatermarkProperty =
  344. TextBox.WatermarkProperty.AddOwner<AutoCompleteBox>();
  345. /// <summary>
  346. /// Identifies the
  347. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
  348. /// dependency property.
  349. /// </summary>
  350. /// <value>The identifier for the
  351. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
  352. /// dependency property.</value>
  353. public static readonly StyledProperty<int> MinimumPrefixLengthProperty =
  354. AvaloniaProperty.Register<AutoCompleteBox, int>(
  355. nameof(MinimumPrefixLength), 1,
  356. validate: IsValidMinimumPrefixLength);
  357. /// <summary>
  358. /// Identifies the
  359. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
  360. /// dependency property.
  361. /// </summary>
  362. /// <value>The identifier for the
  363. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
  364. /// dependency property.</value>
  365. public static readonly StyledProperty<TimeSpan> MinimumPopulateDelayProperty =
  366. AvaloniaProperty.Register<AutoCompleteBox, TimeSpan>(
  367. nameof(MinimumPopulateDelay),
  368. TimeSpan.Zero,
  369. validate: IsValidMinimumPopulateDelay);
  370. /// <summary>
  371. /// Identifies the
  372. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" />
  373. /// dependency property.
  374. /// </summary>
  375. /// <value>The identifier for the
  376. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" />
  377. /// dependency property.</value>
  378. public static readonly StyledProperty<double> MaxDropDownHeightProperty =
  379. AvaloniaProperty.Register<AutoCompleteBox, double>(
  380. nameof(MaxDropDownHeight),
  381. double.PositiveInfinity,
  382. validate: IsValidMaxDropDownHeight);
  383. /// <summary>
  384. /// Identifies the
  385. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
  386. /// dependency property.
  387. /// </summary>
  388. /// <value>The identifier for the
  389. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
  390. /// dependency property.</value>
  391. public static readonly StyledProperty<bool> IsTextCompletionEnabledProperty =
  392. AvaloniaProperty.Register<AutoCompleteBox, bool>(nameof(IsTextCompletionEnabled));
  393. /// <summary>
  394. /// Identifies the
  395. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" />
  396. /// dependency property.
  397. /// </summary>
  398. /// <value>The identifier for the
  399. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" />
  400. /// dependency property.</value>
  401. public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
  402. AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(nameof(ItemTemplate));
  403. /// <summary>
  404. /// Identifies the
  405. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
  406. /// dependency property.
  407. /// </summary>
  408. /// <value>The identifier for the
  409. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
  410. /// dependency property.</value>
  411. public static readonly DirectProperty<AutoCompleteBox, bool> IsDropDownOpenProperty =
  412. AvaloniaProperty.RegisterDirect<AutoCompleteBox, bool>(
  413. nameof(IsDropDownOpen),
  414. o => o.IsDropDownOpen,
  415. (o, v) => o.IsDropDownOpen = v);
  416. /// <summary>
  417. /// Identifies the
  418. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
  419. /// dependency property.
  420. /// </summary>
  421. /// <value>The identifier the
  422. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
  423. /// dependency property.</value>
  424. public static readonly DirectProperty<AutoCompleteBox, object> SelectedItemProperty =
  425. AvaloniaProperty.RegisterDirect<AutoCompleteBox, object>(
  426. nameof(SelectedItem),
  427. o => o.SelectedItem,
  428. (o, v) => o.SelectedItem = v);
  429. /// <summary>
  430. /// Identifies the
  431. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
  432. /// dependency property.
  433. /// </summary>
  434. /// <value>The identifier for the
  435. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
  436. /// dependency property.</value>
  437. public static readonly DirectProperty<AutoCompleteBox, string> TextProperty =
  438. TextBlock.TextProperty.AddOwnerWithDataValidation<AutoCompleteBox>(
  439. o => o.Text,
  440. (o, v) => o.Text = v,
  441. defaultBindingMode: BindingMode.TwoWay,
  442. enableDataValidation: true);
  443. /// <summary>
  444. /// Identifies the
  445. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
  446. /// dependency property.
  447. /// </summary>
  448. /// <value>The identifier for the
  449. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
  450. /// dependency property.</value>
  451. public static readonly DirectProperty<AutoCompleteBox, string> SearchTextProperty =
  452. AvaloniaProperty.RegisterDirect<AutoCompleteBox, string>(
  453. nameof(SearchText),
  454. o => o.SearchText,
  455. unsetValue: string.Empty);
  456. /// <summary>
  457. /// Gets the identifier for the
  458. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.FilterMode" />
  459. /// dependency property.
  460. /// </summary>
  461. public static readonly StyledProperty<AutoCompleteFilterMode> FilterModeProperty =
  462. AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterMode>(
  463. nameof(FilterMode),
  464. defaultValue: AutoCompleteFilterMode.StartsWith,
  465. validate: IsValidFilterMode);
  466. /// <summary>
  467. /// Identifies the
  468. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
  469. /// dependency property.
  470. /// </summary>
  471. /// <value>The identifier for the
  472. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
  473. /// dependency property.</value>
  474. public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<object>> ItemFilterProperty =
  475. AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<object>>(
  476. nameof(ItemFilter),
  477. o => o.ItemFilter,
  478. (o, v) => o.ItemFilter = v);
  479. /// <summary>
  480. /// Identifies the
  481. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
  482. /// dependency property.
  483. /// </summary>
  484. /// <value>The identifier for the
  485. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
  486. /// dependency property.</value>
  487. public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<string>> TextFilterProperty =
  488. AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<string>>(
  489. nameof(TextFilter),
  490. o => o.TextFilter,
  491. (o, v) => o.TextFilter = v,
  492. unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
  493. /// <summary>
  494. /// Identifies the
  495. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemSelector" />
  496. /// dependency property.
  497. /// </summary>
  498. /// <value>The identifier for the
  499. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemSelector" />
  500. /// dependency property.</value>
  501. public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<object>> ItemSelectorProperty =
  502. AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<object>>(
  503. nameof(ItemSelector),
  504. o => o.ItemSelector,
  505. (o, v) => o.ItemSelector = v);
  506. /// <summary>
  507. /// Identifies the
  508. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextSelector" />
  509. /// dependency property.
  510. /// </summary>
  511. /// <value>The identifier for the
  512. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextSelector" />
  513. /// dependency property.</value>
  514. public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<string>> TextSelectorProperty =
  515. AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<string>>(
  516. nameof(TextSelector),
  517. o => o.TextSelector,
  518. (o, v) => o.TextSelector = v);
  519. /// <summary>
  520. /// Identifies the
  521. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  522. /// dependency property.
  523. /// </summary>
  524. /// <value>The identifier for the
  525. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  526. /// dependency property.</value>
  527. public static readonly DirectProperty<AutoCompleteBox, IEnumerable> ItemsProperty =
  528. AvaloniaProperty.RegisterDirect<AutoCompleteBox, IEnumerable>(
  529. nameof(Items),
  530. o => o.Items,
  531. (o, v) => o.Items = v);
  532. public static readonly DirectProperty<AutoCompleteBox, Func<string, CancellationToken, Task<IEnumerable<object>>>> AsyncPopulatorProperty =
  533. AvaloniaProperty.RegisterDirect<AutoCompleteBox, Func<string, CancellationToken, Task<IEnumerable<object>>>>(
  534. nameof(AsyncPopulator),
  535. o => o.AsyncPopulator,
  536. (o, v) => o.AsyncPopulator = v);
  537. private static bool IsValidMinimumPrefixLength(int value) => value >= -1;
  538. private static bool IsValidMinimumPopulateDelay(TimeSpan value) => value.TotalMilliseconds >= 0.0;
  539. private static bool IsValidMaxDropDownHeight(double value) => value >= 0.0;
  540. private static bool IsValidFilterMode(AutoCompleteFilterMode mode)
  541. {
  542. switch (mode)
  543. {
  544. case AutoCompleteFilterMode.None:
  545. case AutoCompleteFilterMode.StartsWith:
  546. case AutoCompleteFilterMode.StartsWithCaseSensitive:
  547. case AutoCompleteFilterMode.StartsWithOrdinal:
  548. case AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive:
  549. case AutoCompleteFilterMode.Contains:
  550. case AutoCompleteFilterMode.ContainsCaseSensitive:
  551. case AutoCompleteFilterMode.ContainsOrdinal:
  552. case AutoCompleteFilterMode.ContainsOrdinalCaseSensitive:
  553. case AutoCompleteFilterMode.Equals:
  554. case AutoCompleteFilterMode.EqualsCaseSensitive:
  555. case AutoCompleteFilterMode.EqualsOrdinal:
  556. case AutoCompleteFilterMode.EqualsOrdinalCaseSensitive:
  557. case AutoCompleteFilterMode.Custom:
  558. return true;
  559. default:
  560. return false;
  561. }
  562. }
  563. /// <summary>
  564. /// Handle the change of the IsEnabled property.
  565. /// </summary>
  566. /// <param name="e">The event data.</param>
  567. private void OnControlIsEnabledChanged(AvaloniaPropertyChangedEventArgs e)
  568. {
  569. bool isEnabled = (bool)e.NewValue;
  570. if (!isEnabled)
  571. {
  572. IsDropDownOpen = false;
  573. }
  574. }
  575. /// <summary>
  576. /// MinimumPopulateDelayProperty property changed handler. Any current
  577. /// dispatcher timer will be stopped. The timer will not be restarted
  578. /// until the next TextUpdate call by the user.
  579. /// </summary>
  580. /// <param name="e">Event arguments.</param>
  581. private void OnMinimumPopulateDelayChanged(AvaloniaPropertyChangedEventArgs e)
  582. {
  583. var newValue = (TimeSpan)e.NewValue;
  584. // Stop any existing timer
  585. if (_delayTimer != null)
  586. {
  587. _delayTimer.Stop();
  588. if (newValue == TimeSpan.Zero)
  589. {
  590. _delayTimer = null;
  591. }
  592. }
  593. if (newValue > TimeSpan.Zero)
  594. {
  595. // Create or clear a dispatcher timer instance
  596. if (_delayTimer == null)
  597. {
  598. _delayTimer = new DispatcherTimer();
  599. _delayTimer.Tick += PopulateDropDown;
  600. }
  601. // Set the new tick interval
  602. _delayTimer.Interval = newValue;
  603. }
  604. }
  605. /// <summary>
  606. /// IsDropDownOpenProperty property changed handler.
  607. /// </summary>
  608. /// <param name="e">Event arguments.</param>
  609. private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e)
  610. {
  611. // Ignore the change if requested
  612. if (_ignorePropertyChange)
  613. {
  614. _ignorePropertyChange = false;
  615. return;
  616. }
  617. bool oldValue = (bool)e.OldValue;
  618. bool newValue = (bool)e.NewValue;
  619. if (newValue)
  620. {
  621. TextUpdated(Text, true);
  622. }
  623. else
  624. {
  625. ClosingDropDown(oldValue);
  626. }
  627. UpdatePseudoClasses();
  628. }
  629. private void OnSelectedItemPropertyChanged(AvaloniaPropertyChangedEventArgs e)
  630. {
  631. if (_ignorePropertyChange)
  632. {
  633. _ignorePropertyChange = false;
  634. return;
  635. }
  636. // Update the text display
  637. if (_skipSelectedItemTextUpdate)
  638. {
  639. _skipSelectedItemTextUpdate = false;
  640. }
  641. else
  642. {
  643. OnSelectedItemChanged(e.NewValue);
  644. }
  645. // Fire the SelectionChanged event
  646. List<object> removed = new List<object>();
  647. if (e.OldValue != null)
  648. {
  649. removed.Add(e.OldValue);
  650. }
  651. List<object> added = new List<object>();
  652. if (e.NewValue != null)
  653. {
  654. added.Add(e.NewValue);
  655. }
  656. OnSelectionChanged(new SelectionChangedEventArgs(SelectionChangedEvent, removed, added));
  657. }
  658. /// <summary>
  659. /// TextProperty property changed handler.
  660. /// </summary>
  661. /// <param name="e">Event arguments.</param>
  662. private void OnTextPropertyChanged(AvaloniaPropertyChangedEventArgs e)
  663. {
  664. TextUpdated((string)e.NewValue, false);
  665. }
  666. private void OnSearchTextPropertyChanged(AvaloniaPropertyChangedEventArgs e)
  667. {
  668. if (_ignorePropertyChange)
  669. {
  670. _ignorePropertyChange = false;
  671. return;
  672. }
  673. // Ensure the property is only written when expected
  674. if (!_allowWrite)
  675. {
  676. // Reset the old value before it was incorrectly written
  677. _ignorePropertyChange = true;
  678. SetValue(e.Property, e.OldValue);
  679. throw new InvalidOperationException("Cannot set read-only property SearchText.");
  680. }
  681. }
  682. /// <summary>
  683. /// FilterModeProperty property changed handler.
  684. /// </summary>
  685. /// <param name="e">Event arguments.</param>
  686. private void OnFilterModePropertyChanged(AvaloniaPropertyChangedEventArgs e)
  687. {
  688. AutoCompleteFilterMode mode = (AutoCompleteFilterMode)e.NewValue;
  689. // Sets the filter predicate for the new value
  690. TextFilter = AutoCompleteSearch.GetFilter(mode);
  691. }
  692. /// <summary>
  693. /// ItemFilterProperty property changed handler.
  694. /// </summary>
  695. /// <param name="e">Event arguments.</param>
  696. private void OnItemFilterPropertyChanged(AvaloniaPropertyChangedEventArgs e)
  697. {
  698. AutoCompleteFilterPredicate<object> value = e.NewValue as AutoCompleteFilterPredicate<object>;
  699. // If null, revert to the "None" predicate
  700. if (value == null)
  701. {
  702. FilterMode = AutoCompleteFilterMode.None;
  703. }
  704. else
  705. {
  706. FilterMode = AutoCompleteFilterMode.Custom;
  707. TextFilter = null;
  708. }
  709. }
  710. /// <summary>
  711. /// ItemsSourceProperty property changed handler.
  712. /// </summary>
  713. /// <param name="e">Event arguments.</param>
  714. private void OnItemsPropertyChanged(AvaloniaPropertyChangedEventArgs e)
  715. {
  716. OnItemsChanged((IEnumerable)e.NewValue);
  717. }
  718. private void OnItemTemplatePropertyChanged(AvaloniaPropertyChangedEventArgs e)
  719. {
  720. if (!_settingItemTemplateFromValueMemberBinding)
  721. _itemTemplateIsFromValueMemberBinding = false;
  722. }
  723. private void OnValueMemberBindingChanged(IBinding value)
  724. {
  725. if(_itemTemplateIsFromValueMemberBinding)
  726. {
  727. var template =
  728. new FuncDataTemplate(
  729. typeof(object),
  730. (o, _) =>
  731. {
  732. var control = new ContentControl();
  733. control.Bind(ContentControl.ContentProperty, value);
  734. return control;
  735. });
  736. _settingItemTemplateFromValueMemberBinding = true;
  737. ItemTemplate = template;
  738. _settingItemTemplateFromValueMemberBinding = false;
  739. }
  740. }
  741. static AutoCompleteBox()
  742. {
  743. FocusableProperty.OverrideDefaultValue<AutoCompleteBox>(true);
  744. MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnMinimumPopulateDelayChanged(e));
  745. IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnIsDropDownOpenChanged(e));
  746. SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSelectedItemPropertyChanged(e));
  747. TextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnTextPropertyChanged(e));
  748. SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSearchTextPropertyChanged(e));
  749. FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnFilterModePropertyChanged(e));
  750. ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemFilterPropertyChanged(e));
  751. ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemsPropertyChanged(e));
  752. IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnControlIsEnabledChanged(e));
  753. }
  754. /// <summary>
  755. /// Initializes a new instance of the
  756. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> class.
  757. /// </summary>
  758. public AutoCompleteBox()
  759. {
  760. ClearView();
  761. }
  762. /// <summary>
  763. /// Gets or sets the minimum number of characters required to be entered
  764. /// in the text box before the
  765. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> displays
  766. /// possible matches.
  767. /// matches.
  768. /// </summary>
  769. /// <value>
  770. /// The minimum number of characters to be entered in the text box
  771. /// before the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
  772. /// displays possible matches. The default is 1.
  773. /// </value>
  774. /// <remarks>
  775. /// If you set MinimumPrefixLength to -1, the AutoCompleteBox will
  776. /// not provide possible matches. There is no maximum value, but
  777. /// setting MinimumPrefixLength to value that is too large will
  778. /// prevent the AutoCompleteBox from providing possible matches as well.
  779. /// </remarks>
  780. public int MinimumPrefixLength
  781. {
  782. get { return GetValue(MinimumPrefixLengthProperty); }
  783. set { SetValue(MinimumPrefixLengthProperty, value); }
  784. }
  785. /// <summary>
  786. /// Gets or sets a value indicating whether the first possible match
  787. /// found during the filtering process will be displayed automatically
  788. /// in the text box.
  789. /// </summary>
  790. /// <value>
  791. /// True if the first possible match found will be displayed
  792. /// automatically in the text box; otherwise, false. The default is
  793. /// false.
  794. /// </value>
  795. public bool IsTextCompletionEnabled
  796. {
  797. get { return GetValue(IsTextCompletionEnabledProperty); }
  798. set { SetValue(IsTextCompletionEnabledProperty, value); }
  799. }
  800. /// <summary>
  801. /// Gets or sets the <see cref="T:Avalonia.DataTemplate" /> used
  802. /// to display each item in the drop-down portion of the control.
  803. /// </summary>
  804. /// <value>The <see cref="T:Avalonia.DataTemplate" /> used to
  805. /// display each item in the drop-down. The default is null.</value>
  806. /// <remarks>
  807. /// You use the ItemTemplate property to specify the visualization
  808. /// of the data objects in the drop-down portion of the AutoCompleteBox
  809. /// control. If your AutoCompleteBox is bound to a collection and you
  810. /// do not provide specific display instructions by using a
  811. /// DataTemplate, the resulting UI of each item is a string
  812. /// representation of each object in the underlying collection.
  813. /// </remarks>
  814. public IDataTemplate ItemTemplate
  815. {
  816. get { return GetValue(ItemTemplateProperty); }
  817. set { SetValue(ItemTemplateProperty, value); }
  818. }
  819. /// <summary>
  820. /// Gets or sets the minimum delay, after text is typed
  821. /// in the text box before the
  822. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
  823. /// populates the list of possible matches in the drop-down.
  824. /// </summary>
  825. /// <value>The minimum delay, after text is typed in
  826. /// the text box, but before the
  827. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> populates
  828. /// the list of possible matches in the drop-down. The default is 0.</value>
  829. public TimeSpan MinimumPopulateDelay
  830. {
  831. get { return GetValue(MinimumPopulateDelayProperty); }
  832. set { SetValue(MinimumPopulateDelayProperty, value); }
  833. }
  834. /// <summary>
  835. /// Gets or sets the maximum height of the drop-down portion of the
  836. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
  837. /// </summary>
  838. /// <value>The maximum height of the drop-down portion of the
  839. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
  840. /// The default is <see cref="F:System.Double.PositiveInfinity" />.</value>
  841. /// <exception cref="T:System.ArgumentException">The specified value is less than 0.</exception>
  842. public double MaxDropDownHeight
  843. {
  844. get { return GetValue(MaxDropDownHeightProperty); }
  845. set { SetValue(MaxDropDownHeightProperty, value); }
  846. }
  847. /// <summary>
  848. /// Gets or sets a value indicating whether the drop-down portion of
  849. /// the control is open.
  850. /// </summary>
  851. /// <value>
  852. /// True if the drop-down is open; otherwise, false. The default is
  853. /// false.
  854. /// </value>
  855. public bool IsDropDownOpen
  856. {
  857. get { return _isDropDownOpen; }
  858. set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
  859. }
  860. /// <summary>
  861. /// Gets or sets the <see cref="T:Avalonia.Data.Binding" /> that
  862. /// is used to get the values for display in the text portion of
  863. /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
  864. /// control.
  865. /// </summary>
  866. /// <value>The <see cref="T:Avalonia.Data.IBinding" /> object used
  867. /// when binding to a collection property.</value>
  868. [AssignBinding]
  869. public IBinding ValueMemberBinding
  870. {
  871. get { return _valueBindingEvaluator?.ValueBinding; }
  872. set
  873. {
  874. if (ValueMemberBinding != value)
  875. {
  876. _valueBindingEvaluator = new BindingEvaluator<string>(value);
  877. OnValueMemberBindingChanged(value);
  878. }
  879. }
  880. }
  881. /// <summary>
  882. /// Gets or sets the selected item in the drop-down.
  883. /// </summary>
  884. /// <value>The selected item in the drop-down.</value>
  885. /// <remarks>
  886. /// If the IsTextCompletionEnabled property is true and text typed by
  887. /// the user matches an item in the ItemsSource collection, which is
  888. /// then displayed in the text box, the SelectedItem property will be
  889. /// a null reference.
  890. /// </remarks>
  891. public object SelectedItem
  892. {
  893. get { return _selectedItem; }
  894. set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); }
  895. }
  896. /// <summary>
  897. /// Gets or sets the text in the text box portion of the
  898. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
  899. /// </summary>
  900. /// <value>The text in the text box portion of the
  901. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
  902. public string Text
  903. {
  904. get { return _text; }
  905. set { SetAndRaise(TextProperty, ref _text, value); }
  906. }
  907. /// <summary>
  908. /// Gets the text that is used to filter items in the
  909. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  910. /// item collection.
  911. /// </summary>
  912. /// <value>The text that is used to filter items in the
  913. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  914. /// item collection.</value>
  915. /// <remarks>
  916. /// The SearchText value is typically the same as the
  917. /// Text property, but is set after the TextChanged event occurs
  918. /// and before the Populating event.
  919. /// </remarks>
  920. public string SearchText
  921. {
  922. get { return _searchText; }
  923. private set
  924. {
  925. try
  926. {
  927. _allowWrite = true;
  928. SetAndRaise(SearchTextProperty, ref _searchText, value);
  929. }
  930. finally
  931. {
  932. _allowWrite = false;
  933. }
  934. }
  935. }
  936. /// <summary>
  937. /// Gets or sets how the text in the text box is used to filter items
  938. /// specified by the
  939. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  940. /// property for display in the drop-down.
  941. /// </summary>
  942. /// <value>One of the
  943. /// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />
  944. /// values The default is
  945. /// <see cref="F:Avalonia.Controls.AutoCompleteFilterMode.StartsWith" />.</value>
  946. /// <exception cref="T:System.ArgumentException">The specified value is
  947. /// not a valid
  948. /// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />.</exception>
  949. /// <remarks>
  950. /// Use the FilterMode property to specify how possible matches are
  951. /// filtered. For example, possible matches can be filtered in a
  952. /// predefined or custom way. The search mode is automatically set to
  953. /// Custom if you set the ItemFilter property.
  954. /// </remarks>
  955. public AutoCompleteFilterMode FilterMode
  956. {
  957. get { return GetValue(FilterModeProperty); }
  958. set { SetValue(FilterModeProperty, value); }
  959. }
  960. public string Watermark
  961. {
  962. get { return GetValue(WatermarkProperty); }
  963. set { SetValue(WatermarkProperty, value); }
  964. }
  965. /// <summary>
  966. /// Gets or sets the custom method that uses user-entered text to filter
  967. /// the items specified by the
  968. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  969. /// property for display in the drop-down.
  970. /// </summary>
  971. /// <value>The custom method that uses the user-entered text to filter
  972. /// the items specified by the
  973. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  974. /// property. The default is null.</value>
  975. /// <remarks>
  976. /// The filter mode is automatically set to Custom if you set the
  977. /// ItemFilter property.
  978. /// </remarks>
  979. public AutoCompleteFilterPredicate<object> ItemFilter
  980. {
  981. get { return _itemFilter; }
  982. set { SetAndRaise(ItemFilterProperty, ref _itemFilter, value); }
  983. }
  984. /// <summary>
  985. /// Gets or sets the custom method that uses the user-entered text to
  986. /// filter items specified by the
  987. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  988. /// property in a text-based way for display in the drop-down.
  989. /// </summary>
  990. /// <value>The custom method that uses the user-entered text to filter
  991. /// items specified by the
  992. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  993. /// property in a text-based way for display in the drop-down.</value>
  994. /// <remarks>
  995. /// The search mode is automatically set to Custom if you set the
  996. /// TextFilter property.
  997. /// </remarks>
  998. public AutoCompleteFilterPredicate<string> TextFilter
  999. {
  1000. get { return _textFilter; }
  1001. set { SetAndRaise(TextFilterProperty, ref _textFilter, value); }
  1002. }
  1003. /// <summary>
  1004. /// Gets or sets the custom method that combines the user-entered
  1005. /// text and one of the items specified by the
  1006. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />.
  1007. /// </summary>
  1008. /// <value>
  1009. /// The custom method that combines the user-entered
  1010. /// text and one of the items specified by the
  1011. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />.
  1012. /// </value>
  1013. public AutoCompleteSelector<object> ItemSelector
  1014. {
  1015. get { return _itemSelector; }
  1016. set { SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); }
  1017. }
  1018. /// <summary>
  1019. /// Gets or sets the custom method that combines the user-entered
  1020. /// text and one of the items specified by the
  1021. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  1022. /// in a text-based way.
  1023. /// </summary>
  1024. /// <value>
  1025. /// The custom method that combines the user-entered
  1026. /// text and one of the items specified by the
  1027. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
  1028. /// in a text-based way.
  1029. /// </value>
  1030. public AutoCompleteSelector<string> TextSelector
  1031. {
  1032. get { return _textSelector; }
  1033. set { SetAndRaise(TextSelectorProperty, ref _textSelector, value); }
  1034. }
  1035. public Func<string, CancellationToken, Task<IEnumerable<object>>> AsyncPopulator
  1036. {
  1037. get { return _asyncPopulator; }
  1038. set { SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); }
  1039. }
  1040. /// <summary>
  1041. /// Gets or sets a collection that is used to generate the items for the
  1042. /// drop-down portion of the
  1043. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
  1044. /// </summary>
  1045. /// <value>The collection that is used to generate the items of the
  1046. /// drop-down portion of the
  1047. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
  1048. public IEnumerable Items
  1049. {
  1050. get { return _itemsEnumerable; }
  1051. set { SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); }
  1052. }
  1053. /// <summary>
  1054. /// Gets or sets the drop down popup control.
  1055. /// </summary>
  1056. private Popup DropDownPopup { get; set; }
  1057. /// <summary>
  1058. /// Gets or sets the Text template part.
  1059. /// </summary>
  1060. private TextBox TextBox
  1061. {
  1062. get { return _textBox; }
  1063. set
  1064. {
  1065. _textBoxSubscriptions?.Dispose();
  1066. _textBox = value;
  1067. // Attach handlers
  1068. if (_textBox != null)
  1069. {
  1070. _textBoxSubscriptions =
  1071. _textBox.GetObservable(TextBox.TextProperty)
  1072. .Skip(1)
  1073. .Subscribe(_ => OnTextBoxTextChanged());
  1074. if (Text != null)
  1075. {
  1076. UpdateTextValue(Text);
  1077. }
  1078. }
  1079. }
  1080. }
  1081. private int TextBoxSelectionStart
  1082. {
  1083. get
  1084. {
  1085. if (TextBox != null)
  1086. {
  1087. return Math.Min(TextBox.SelectionStart, TextBox.SelectionEnd);
  1088. }
  1089. else
  1090. {
  1091. return 0;
  1092. }
  1093. }
  1094. }
  1095. private int TextBoxSelectionLength
  1096. {
  1097. get
  1098. {
  1099. if (TextBox != null)
  1100. {
  1101. return Math.Abs(TextBox.SelectionEnd - TextBox.SelectionStart);
  1102. }
  1103. else
  1104. {
  1105. return 0;
  1106. }
  1107. }
  1108. }
  1109. /// <summary>
  1110. /// Gets or sets the selection adapter used to populate the drop-down
  1111. /// with a list of selectable items.
  1112. /// </summary>
  1113. /// <value>The selection adapter used to populate the drop-down with a
  1114. /// list of selectable items.</value>
  1115. /// <remarks>
  1116. /// You can use this property when you create an automation peer to
  1117. /// use with AutoCompleteBox or deriving from AutoCompleteBox to
  1118. /// create a custom control.
  1119. /// </remarks>
  1120. protected ISelectionAdapter SelectionAdapter
  1121. {
  1122. get { return _adapter; }
  1123. set
  1124. {
  1125. if (_adapter != null)
  1126. {
  1127. _adapter.SelectionChanged -= OnAdapterSelectionChanged;
  1128. _adapter.Commit -= OnAdapterSelectionComplete;
  1129. _adapter.Cancel -= OnAdapterSelectionCanceled;
  1130. _adapter.Cancel -= OnAdapterSelectionComplete;
  1131. _adapter.Items = null;
  1132. }
  1133. _adapter = value;
  1134. if (_adapter != null)
  1135. {
  1136. _adapter.SelectionChanged += OnAdapterSelectionChanged;
  1137. _adapter.Commit += OnAdapterSelectionComplete;
  1138. _adapter.Cancel += OnAdapterSelectionCanceled;
  1139. _adapter.Cancel += OnAdapterSelectionComplete;
  1140. _adapter.Items = _view;
  1141. }
  1142. }
  1143. }
  1144. /// <summary>
  1145. /// Returns the
  1146. /// <see cref="T:Avalonia.Controls.ISelectionAdapter" /> part, if
  1147. /// possible.
  1148. /// </summary>
  1149. /// <returns>
  1150. /// A <see cref="T:Avalonia.Controls.ISelectionAdapter" /> object,
  1151. /// if possible. Otherwise, null.
  1152. /// </returns>
  1153. protected virtual ISelectionAdapter GetSelectionAdapterPart(INameScope nameScope)
  1154. {
  1155. ISelectionAdapter adapter = null;
  1156. SelectingItemsControl selector = nameScope.Find<SelectingItemsControl>(ElementSelector);
  1157. if (selector != null)
  1158. {
  1159. // Check if it is already an IItemsSelector
  1160. adapter = selector as ISelectionAdapter;
  1161. if (adapter == null)
  1162. {
  1163. // Built in support for wrapping a Selector control
  1164. adapter = new SelectingItemsControlSelectionAdapter(selector);
  1165. }
  1166. }
  1167. if (adapter == null)
  1168. {
  1169. adapter = nameScope.Find<ISelectionAdapter>(ElementSelectionAdapter);
  1170. }
  1171. return adapter;
  1172. }
  1173. /// <summary>
  1174. /// Builds the visual tree for the
  1175. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
  1176. /// when a new template is applied.
  1177. /// </summary>
  1178. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  1179. {
  1180. if (DropDownPopup != null)
  1181. {
  1182. DropDownPopup.Closed -= DropDownPopup_Closed;
  1183. DropDownPopup = null;
  1184. }
  1185. // Set the template parts. Individual part setters remove and add
  1186. // any event handlers.
  1187. Popup popup = e.NameScope.Find<Popup>(ElementPopup);
  1188. if (popup != null)
  1189. {
  1190. DropDownPopup = popup;
  1191. DropDownPopup.Closed += DropDownPopup_Closed;
  1192. }
  1193. SelectionAdapter = GetSelectionAdapterPart(e.NameScope);
  1194. TextBox = e.NameScope.Find<TextBox>(ElementTextBox);
  1195. // If the drop down property indicates that the popup is open,
  1196. // flip its value to invoke the changed handler.
  1197. if (IsDropDownOpen && DropDownPopup != null && !DropDownPopup.IsOpen)
  1198. {
  1199. OpeningDropDown(false);
  1200. }
  1201. base.OnApplyTemplate(e);
  1202. }
  1203. /// <summary>
  1204. /// Called to update the validation state for properties for which data validation is
  1205. /// enabled.
  1206. /// </summary>
  1207. /// <param name="property">The property.</param>
  1208. /// <param name="value">The new binding value for the property.</param>
  1209. protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
  1210. {
  1211. if (property == TextProperty)
  1212. {
  1213. DataValidationErrors.SetError(this, value.Error);
  1214. }
  1215. }
  1216. /// <summary>
  1217. /// Provides handling for the
  1218. /// <see cref="E:Avalonia.InputElement.KeyDown" /> event.
  1219. /// </summary>
  1220. /// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
  1221. /// that contains the event data.</param>
  1222. protected override void OnKeyDown(KeyEventArgs e)
  1223. {
  1224. Contract.Requires<ArgumentNullException>(e != null);
  1225. base.OnKeyDown(e);
  1226. if (e.Handled || !IsEnabled)
  1227. {
  1228. return;
  1229. }
  1230. // The drop down is open, pass along the key event arguments to the
  1231. // selection adapter. If it isn't handled by the adapter's logic,
  1232. // then we handle some simple navigation scenarios for controlling
  1233. // the drop down.
  1234. if (IsDropDownOpen)
  1235. {
  1236. if (SelectionAdapter != null)
  1237. {
  1238. SelectionAdapter.HandleKeyDown(e);
  1239. if (e.Handled)
  1240. {
  1241. return;
  1242. }
  1243. }
  1244. if (e.Key == Key.Escape)
  1245. {
  1246. OnAdapterSelectionCanceled(this, new RoutedEventArgs());
  1247. e.Handled = true;
  1248. }
  1249. }
  1250. else
  1251. {
  1252. // The drop down is not open, the Down key will toggle it open.
  1253. if (e.Key == Key.Down)
  1254. {
  1255. IsDropDownOpen = true;
  1256. e.Handled = true;
  1257. }
  1258. }
  1259. // Standard drop down navigation
  1260. switch (e.Key)
  1261. {
  1262. case Key.F4:
  1263. IsDropDownOpen = !IsDropDownOpen;
  1264. e.Handled = true;
  1265. break;
  1266. case Key.Enter:
  1267. OnAdapterSelectionComplete(this, new RoutedEventArgs());
  1268. e.Handled = true;
  1269. break;
  1270. default:
  1271. break;
  1272. }
  1273. }
  1274. /// <summary>
  1275. /// Provides handling for the
  1276. /// <see cref="E:Avalonia.UIElement.GotFocus" /> event.
  1277. /// </summary>
  1278. /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
  1279. /// that contains the event data.</param>
  1280. protected override void OnGotFocus(GotFocusEventArgs e)
  1281. {
  1282. base.OnGotFocus(e);
  1283. FocusChanged(HasFocus());
  1284. }
  1285. /// <summary>
  1286. /// Provides handling for the
  1287. /// <see cref="E:Avalonia.UIElement.LostFocus" /> event.
  1288. /// </summary>
  1289. /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
  1290. /// that contains the event data.</param>
  1291. protected override void OnLostFocus(RoutedEventArgs e)
  1292. {
  1293. base.OnLostFocus(e);
  1294. FocusChanged(HasFocus());
  1295. }
  1296. /// <summary>
  1297. /// Determines whether the text box or drop-down portion of the
  1298. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control has
  1299. /// focus.
  1300. /// </summary>
  1301. /// <returns>true to indicate the
  1302. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus;
  1303. /// otherwise, false.</returns>
  1304. protected bool HasFocus()
  1305. {
  1306. IVisual focused = FocusManager.Instance.Current;
  1307. while (focused != null)
  1308. {
  1309. if (object.ReferenceEquals(focused, this))
  1310. {
  1311. return true;
  1312. }
  1313. // This helps deal with popups that may not be in the same
  1314. // visual tree
  1315. IVisual parent = focused.GetVisualParent();
  1316. if (parent == null)
  1317. {
  1318. // Try the logical parent.
  1319. IControl element = focused as IControl;
  1320. if (element != null)
  1321. {
  1322. parent = element.Parent;
  1323. }
  1324. }
  1325. focused = parent;
  1326. }
  1327. return false;
  1328. }
  1329. /// <summary>
  1330. /// Handles the FocusChanged event.
  1331. /// </summary>
  1332. /// <param name="hasFocus">A value indicating whether the control
  1333. /// currently has the focus.</param>
  1334. private void FocusChanged(bool hasFocus)
  1335. {
  1336. // The OnGotFocus & OnLostFocus are asynchronously and cannot
  1337. // reliably tell you that have the focus. All they do is let you
  1338. // know that the focus changed sometime in the past. To determine
  1339. // if you currently have the focus you need to do consult the
  1340. // FocusManager (see HasFocus()).
  1341. bool wasFocused = _isFocused;
  1342. _isFocused = hasFocus;
  1343. if (hasFocus)
  1344. {
  1345. if (!wasFocused && TextBox != null && TextBoxSelectionLength <= 0)
  1346. {
  1347. TextBox.Focus();
  1348. TextBox.SelectionStart = 0;
  1349. TextBox.SelectionEnd = TextBox.Text?.Length ?? 0;
  1350. }
  1351. }
  1352. else
  1353. {
  1354. IsDropDownOpen = false;
  1355. _userCalledPopulate = false;
  1356. ClearTextBoxSelection();
  1357. }
  1358. _isFocused = hasFocus;
  1359. }
  1360. /// <summary>
  1361. /// Occurs when the text in the text box portion of the
  1362. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> changes.
  1363. /// </summary>
  1364. public event EventHandler TextChanged;
  1365. /// <summary>
  1366. /// Occurs when the
  1367. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> is
  1368. /// populating the drop-down with possible matches based on the
  1369. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
  1370. /// property.
  1371. /// </summary>
  1372. /// <remarks>
  1373. /// If the event is canceled, by setting the PopulatingEventArgs.Cancel
  1374. /// property to true, the AutoCompleteBox will not automatically
  1375. /// populate the selection adapter contained in the drop-down.
  1376. /// In this case, if you want possible matches to appear, you must
  1377. /// provide the logic for populating the selection adapter.
  1378. /// </remarks>
  1379. public event EventHandler<PopulatingEventArgs> Populating;
  1380. /// <summary>
  1381. /// Occurs when the
  1382. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has
  1383. /// populated the drop-down with possible matches based on the
  1384. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
  1385. /// property.
  1386. /// </summary>
  1387. public event EventHandler<PopulatedEventArgs> Populated;
  1388. /// <summary>
  1389. /// Occurs when the value of the
  1390. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
  1391. /// property is changing from false to true.
  1392. /// </summary>
  1393. public event EventHandler<CancelEventArgs> DropDownOpening;
  1394. /// <summary>
  1395. /// Occurs when the value of the
  1396. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
  1397. /// property has changed from false to true and the drop-down is open.
  1398. /// </summary>
  1399. public event EventHandler DropDownOpened;
  1400. /// <summary>
  1401. /// Occurs when the
  1402. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
  1403. /// property is changing from true to false.
  1404. /// </summary>
  1405. public event EventHandler<CancelEventArgs> DropDownClosing;
  1406. /// <summary>
  1407. /// Occurs when the
  1408. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
  1409. /// property was changed from true to false and the drop-down is open.
  1410. /// </summary>
  1411. public event EventHandler DropDownClosed;
  1412. /// <summary>
  1413. /// Occurs when the selected item in the drop-down portion of the
  1414. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has
  1415. /// changed.
  1416. /// </summary>
  1417. public event EventHandler<SelectionChangedEventArgs> SelectionChanged
  1418. {
  1419. add { AddHandler(SelectionChangedEvent, value); }
  1420. remove { RemoveHandler(SelectionChangedEvent, value); }
  1421. }
  1422. /// <summary>
  1423. /// Raises the
  1424. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
  1425. /// event.
  1426. /// </summary>
  1427. /// <param name="e">A
  1428. /// <see cref="T:Avalonia.Controls.PopulatingEventArgs" /> that
  1429. /// contains the event data.</param>
  1430. protected virtual void OnPopulating(PopulatingEventArgs e)
  1431. {
  1432. Populating?.Invoke(this, e);
  1433. }
  1434. /// <summary>
  1435. /// Raises the
  1436. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
  1437. /// event.
  1438. /// </summary>
  1439. /// <param name="e">A
  1440. /// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />
  1441. /// that contains the event data.</param>
  1442. protected virtual void OnPopulated(PopulatedEventArgs e)
  1443. {
  1444. Populated?.Invoke(this, e);
  1445. }
  1446. /// <summary>
  1447. /// Raises the
  1448. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.SelectionChanged" />
  1449. /// event.
  1450. /// </summary>
  1451. /// <param name="e">A
  1452. /// <see cref="T:Avalonia.Controls.SelectionChangedEventArgs" />
  1453. /// that contains the event data.</param>
  1454. protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)
  1455. {
  1456. RaiseEvent(e);
  1457. }
  1458. /// <summary>
  1459. /// Raises the
  1460. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownOpening" />
  1461. /// event.
  1462. /// </summary>
  1463. /// <param name="e">A
  1464. /// <see cref="T:Avalonia.Controls.CancelEventArgs" />
  1465. /// that contains the event data.</param>
  1466. protected virtual void OnDropDownOpening(CancelEventArgs e)
  1467. {
  1468. DropDownOpening?.Invoke(this, e);
  1469. }
  1470. /// <summary>
  1471. /// Raises the
  1472. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownOpened" />
  1473. /// event.
  1474. /// </summary>
  1475. /// <param name="e">A
  1476. /// <see cref="T:System.EventArgs" />
  1477. /// that contains the event data.</param>
  1478. protected virtual void OnDropDownOpened(EventArgs e)
  1479. {
  1480. DropDownOpened?.Invoke(this, e);
  1481. }
  1482. /// <summary>
  1483. /// Raises the
  1484. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownClosing" />
  1485. /// event.
  1486. /// </summary>
  1487. /// <param name="e">A
  1488. /// <see cref="T:Avalonia.Controls.CancelEventArgs" />
  1489. /// that contains the event data.</param>
  1490. protected virtual void OnDropDownClosing(CancelEventArgs e)
  1491. {
  1492. DropDownClosing?.Invoke(this, e);
  1493. }
  1494. /// <summary>
  1495. /// Raises the
  1496. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownClosed" />
  1497. /// event.
  1498. /// </summary>
  1499. /// <param name="e">A
  1500. /// <see cref="T:System.EventArgs" />
  1501. /// which contains the event data.</param>
  1502. protected virtual void OnDropDownClosed(EventArgs e)
  1503. {
  1504. DropDownClosed?.Invoke(this, e);
  1505. }
  1506. /// <summary>
  1507. /// Raises the
  1508. /// <see cref="E:Avalonia.Controls.AutoCompleteBox.TextChanged" />
  1509. /// event.
  1510. /// </summary>
  1511. /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
  1512. /// that contains the event data.</param>
  1513. protected virtual void OnTextChanged(RoutedEventArgs e)
  1514. {
  1515. TextChanged?.Invoke(this, e);
  1516. }
  1517. /// <summary>
  1518. /// Begin closing the drop-down.
  1519. /// </summary>
  1520. /// <param name="oldValue">The original value.</param>
  1521. private void ClosingDropDown(bool oldValue)
  1522. {
  1523. var args = new CancelEventArgs();
  1524. OnDropDownClosing(args);
  1525. if (args.Cancel)
  1526. {
  1527. _ignorePropertyChange = true;
  1528. SetValue(IsDropDownOpenProperty, oldValue);
  1529. }
  1530. else
  1531. {
  1532. CloseDropDown();
  1533. }
  1534. UpdatePseudoClasses();
  1535. }
  1536. /// <summary>
  1537. /// Begin opening the drop down by firing cancelable events, opening the
  1538. /// drop-down or reverting, depending on the event argument values.
  1539. /// </summary>
  1540. /// <param name="oldValue">The original value, if needed for a revert.</param>
  1541. private void OpeningDropDown(bool oldValue)
  1542. {
  1543. var args = new CancelEventArgs();
  1544. // Opening
  1545. OnDropDownOpening(args);
  1546. if (args.Cancel)
  1547. {
  1548. _ignorePropertyChange = true;
  1549. SetValue(IsDropDownOpenProperty, oldValue);
  1550. }
  1551. else
  1552. {
  1553. OpenDropDown();
  1554. }
  1555. UpdatePseudoClasses();
  1556. }
  1557. /// <summary>
  1558. /// Connects to the DropDownPopup Closed event.
  1559. /// </summary>
  1560. /// <param name="sender">The source object.</param>
  1561. /// <param name="e">The event data.</param>
  1562. private void DropDownPopup_Closed(object sender, EventArgs e)
  1563. {
  1564. // Force the drop down dependency property to be false.
  1565. if (IsDropDownOpen)
  1566. {
  1567. IsDropDownOpen = false;
  1568. }
  1569. // Fire the DropDownClosed event
  1570. if (_popupHasOpened)
  1571. {
  1572. OnDropDownClosed(EventArgs.Empty);
  1573. }
  1574. }
  1575. /// <summary>
  1576. /// Handles the timer tick when using a populate delay.
  1577. /// </summary>
  1578. /// <param name="sender">The source object.</param>
  1579. /// <param name="e">The event arguments.</param>
  1580. private void PopulateDropDown(object sender, EventArgs e)
  1581. {
  1582. if (_delayTimer != null)
  1583. {
  1584. _delayTimer.Stop();
  1585. }
  1586. // Update the prefix/search text.
  1587. SearchText = Text;
  1588. if(TryPopulateAsync(SearchText))
  1589. {
  1590. return;
  1591. }
  1592. // The Populated event enables advanced, custom filtering. The
  1593. // client needs to directly update the ItemsSource collection or
  1594. // call the Populate method on the control to continue the
  1595. // display process if Cancel is set to true.
  1596. PopulatingEventArgs populating = new PopulatingEventArgs(SearchText);
  1597. OnPopulating(populating);
  1598. if (!populating.Cancel)
  1599. {
  1600. PopulateComplete();
  1601. }
  1602. }
  1603. private bool TryPopulateAsync(string searchText)
  1604. {
  1605. _populationCancellationTokenSource?.Cancel(false);
  1606. _populationCancellationTokenSource?.Dispose();
  1607. _populationCancellationTokenSource = null;
  1608. if(_asyncPopulator == null)
  1609. {
  1610. return false;
  1611. }
  1612. _populationCancellationTokenSource = new CancellationTokenSource();
  1613. var task = PopulateAsync(searchText, _populationCancellationTokenSource.Token);
  1614. if (task.Status == TaskStatus.Created)
  1615. task.Start();
  1616. return true;
  1617. }
  1618. private async Task PopulateAsync(string searchText, CancellationToken cancellationToken)
  1619. {
  1620. try
  1621. {
  1622. IEnumerable<object> result = await _asyncPopulator.Invoke(searchText, cancellationToken);
  1623. var resultList = result.ToList();
  1624. if (cancellationToken.IsCancellationRequested)
  1625. {
  1626. return;
  1627. }
  1628. await Dispatcher.UIThread.InvokeAsync(() =>
  1629. {
  1630. if (!cancellationToken.IsCancellationRequested)
  1631. {
  1632. Items = resultList;
  1633. PopulateComplete();
  1634. }
  1635. });
  1636. }
  1637. catch (TaskCanceledException)
  1638. { }
  1639. finally
  1640. {
  1641. _populationCancellationTokenSource?.Dispose();
  1642. _populationCancellationTokenSource = null;
  1643. }
  1644. }
  1645. /// <summary>
  1646. /// Private method that directly opens the popup, checks the expander
  1647. /// button, and then fires the Opened event.
  1648. /// </summary>
  1649. private void OpenDropDown()
  1650. {
  1651. if (DropDownPopup != null)
  1652. {
  1653. DropDownPopup.IsOpen = true;
  1654. }
  1655. _popupHasOpened = true;
  1656. OnDropDownOpened(EventArgs.Empty);
  1657. }
  1658. /// <summary>
  1659. /// Private method that directly closes the popup, flips the Checked
  1660. /// value, and then fires the Closed event.
  1661. /// </summary>
  1662. private void CloseDropDown()
  1663. {
  1664. if (_popupHasOpened)
  1665. {
  1666. if (SelectionAdapter != null)
  1667. {
  1668. SelectionAdapter.SelectedItem = null;
  1669. }
  1670. if (DropDownPopup != null)
  1671. {
  1672. DropDownPopup.IsOpen = false;
  1673. }
  1674. OnDropDownClosed(EventArgs.Empty);
  1675. }
  1676. }
  1677. /// <summary>
  1678. /// Formats an Item for text comparisons based on Converter
  1679. /// and ConverterCulture properties.
  1680. /// </summary>
  1681. /// <param name="value">The object to format.</param>
  1682. /// <param name="clearDataContext">A value indicating whether to clear
  1683. /// the data context after the lookup is performed.</param>
  1684. /// <returns>Formatted Value.</returns>
  1685. private string FormatValue(object value, bool clearDataContext)
  1686. {
  1687. string result = FormatValue(value);
  1688. if(clearDataContext && _valueBindingEvaluator != null)
  1689. {
  1690. _valueBindingEvaluator.ClearDataContext();
  1691. }
  1692. return result;
  1693. }
  1694. /// <summary>
  1695. /// Converts the specified object to a string by using the
  1696. /// <see cref="P:Avalonia.Data.Binding.Converter" /> and
  1697. /// <see cref="P:Avalonia.Data.Binding.ConverterCulture" /> values
  1698. /// of the binding object specified by the
  1699. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ValueMemberBinding" />
  1700. /// property.
  1701. /// </summary>
  1702. /// <param name="value">The object to format as a string.</param>
  1703. /// <returns>The string representation of the specified object.</returns>
  1704. /// <remarks>
  1705. /// Override this method to provide a custom string conversion.
  1706. /// </remarks>
  1707. protected virtual string FormatValue(object value)
  1708. {
  1709. if (_valueBindingEvaluator != null)
  1710. {
  1711. return _valueBindingEvaluator.GetDynamicValue(value) ?? String.Empty;
  1712. }
  1713. return value == null ? String.Empty : value.ToString();
  1714. }
  1715. /// <summary>
  1716. /// Handle the TextChanged event that is directly attached to the
  1717. /// TextBox part. This ensures that only user initiated actions will
  1718. /// result in an AutoCompleteBox suggestion and operation.
  1719. /// </summary>
  1720. private void OnTextBoxTextChanged()
  1721. {
  1722. //Uses Dispatcher.Post to allow the TextBox selection to update before processing
  1723. Dispatcher.UIThread.Post(() =>
  1724. {
  1725. // Call the central updated text method as a user-initiated action
  1726. TextUpdated(_textBox.Text, true);
  1727. });
  1728. }
  1729. /// <summary>
  1730. /// Updates both the text box value and underlying text dependency
  1731. /// property value if and when they change. Automatically fires the
  1732. /// text changed events when there is a change.
  1733. /// </summary>
  1734. /// <param name="value">The new string value.</param>
  1735. private void UpdateTextValue(string value)
  1736. {
  1737. UpdateTextValue(value, null);
  1738. }
  1739. /// <summary>
  1740. /// Updates both the text box value and underlying text dependency
  1741. /// property value if and when they change. Automatically fires the
  1742. /// text changed events when there is a change.
  1743. /// </summary>
  1744. /// <param name="value">The new string value.</param>
  1745. /// <param name="userInitiated">A nullable bool value indicating whether
  1746. /// the action was user initiated. In a user initiated mode, the
  1747. /// underlying text dependency property is updated. In a non-user
  1748. /// interaction, the text box value is updated. When user initiated is
  1749. /// null, all values are updated.</param>
  1750. private void UpdateTextValue(string value, bool? userInitiated)
  1751. {
  1752. bool callTextChanged = false;
  1753. // Update the Text dependency property
  1754. if ((userInitiated ?? true) && Text != value)
  1755. {
  1756. _ignoreTextPropertyChange++;
  1757. Text = value;
  1758. callTextChanged = true;
  1759. }
  1760. // Update the TextBox's Text dependency property
  1761. if ((userInitiated == null || userInitiated == false) && TextBox != null && TextBox.Text != value)
  1762. {
  1763. _ignoreTextPropertyChange++;
  1764. TextBox.Text = value ?? string.Empty;
  1765. // Text dependency property value was set, fire event
  1766. if (!callTextChanged && (Text == value || Text == null))
  1767. {
  1768. callTextChanged = true;
  1769. }
  1770. }
  1771. if (callTextChanged)
  1772. {
  1773. OnTextChanged(new RoutedEventArgs());
  1774. }
  1775. }
  1776. /// <summary>
  1777. /// Handle the update of the text for the control from any source,
  1778. /// including the TextBox part and the Text dependency property.
  1779. /// </summary>
  1780. /// <param name="newText">The new text.</param>
  1781. /// <param name="userInitiated">A value indicating whether the update
  1782. /// is a user-initiated action. This should be a True value when the
  1783. /// TextUpdated method is called from a TextBox event handler.</param>
  1784. private void TextUpdated(string newText, bool userInitiated)
  1785. {
  1786. // Only process this event if it is coming from someone outside
  1787. // setting the Text dependency property directly.
  1788. if (_ignoreTextPropertyChange > 0)
  1789. {
  1790. _ignoreTextPropertyChange--;
  1791. return;
  1792. }
  1793. if (newText == null)
  1794. {
  1795. newText = string.Empty;
  1796. }
  1797. // The TextBox.TextChanged event was not firing immediately and
  1798. // was causing an immediate update, even with wrapping. If there is
  1799. // a selection currently, no update should happen.
  1800. if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != TextBox.Text.Length)
  1801. {
  1802. return;
  1803. }
  1804. // Evaluate the conditions needed for completion.
  1805. // 1. Minimum prefix length
  1806. // 2. If a delay timer is in use, use it
  1807. bool populateReady = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0;
  1808. if(populateReady && MinimumPrefixLength == 0 && String.IsNullOrEmpty(newText) && String.IsNullOrEmpty(SearchText))
  1809. {
  1810. populateReady = false;
  1811. }
  1812. _userCalledPopulate = populateReady ? userInitiated : false;
  1813. // Update the interface and values only as necessary
  1814. UpdateTextValue(newText, userInitiated);
  1815. if (populateReady)
  1816. {
  1817. _ignoreTextSelectionChange = true;
  1818. if (_delayTimer != null)
  1819. {
  1820. _delayTimer.Start();
  1821. }
  1822. else
  1823. {
  1824. PopulateDropDown(this, EventArgs.Empty);
  1825. }
  1826. }
  1827. else
  1828. {
  1829. SearchText = string.Empty;
  1830. if (SelectedItem != null)
  1831. {
  1832. _skipSelectedItemTextUpdate = true;
  1833. }
  1834. SelectedItem = null;
  1835. if (IsDropDownOpen)
  1836. {
  1837. IsDropDownOpen = false;
  1838. }
  1839. }
  1840. }
  1841. /// <summary>
  1842. /// A simple helper method to clear the view and ensure that a view
  1843. /// object is always present and not null.
  1844. /// </summary>
  1845. private void ClearView()
  1846. {
  1847. if (_view == null)
  1848. {
  1849. _view = new AvaloniaList<object>();
  1850. }
  1851. else
  1852. {
  1853. _view.Clear();
  1854. }
  1855. }
  1856. /// <summary>
  1857. /// Walks through the items enumeration. Performance is not going to be
  1858. /// perfect with the current implementation.
  1859. /// </summary>
  1860. private void RefreshView()
  1861. {
  1862. if (_items == null)
  1863. {
  1864. ClearView();
  1865. return;
  1866. }
  1867. // Cache the current text value
  1868. string text = Text ?? string.Empty;
  1869. // Determine if any filtering mode is on
  1870. bool stringFiltering = TextFilter != null;
  1871. bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null;
  1872. int view_index = 0;
  1873. int view_count = _view.Count;
  1874. List<object> items = _items;
  1875. foreach (object item in items)
  1876. {
  1877. bool inResults = !(stringFiltering || objectFiltering);
  1878. if (!inResults)
  1879. {
  1880. inResults = stringFiltering ? TextFilter(text, FormatValue(item)) : ItemFilter(text, item);
  1881. }
  1882. if (view_count > view_index && inResults && _view[view_index] == item)
  1883. {
  1884. // Item is still in the view
  1885. view_index++;
  1886. }
  1887. else if (inResults)
  1888. {
  1889. // Insert the item
  1890. if (view_count > view_index && _view[view_index] != item)
  1891. {
  1892. // Replace item
  1893. // Unfortunately replacing via index throws a fatal
  1894. // exception: View[view_index] = item;
  1895. // Cost: O(n) vs O(1)
  1896. _view.RemoveAt(view_index);
  1897. _view.Insert(view_index, item);
  1898. view_index++;
  1899. }
  1900. else
  1901. {
  1902. // Add the item
  1903. if (view_index == view_count)
  1904. {
  1905. // Constant time is preferred (Add).
  1906. _view.Add(item);
  1907. }
  1908. else
  1909. {
  1910. _view.Insert(view_index, item);
  1911. }
  1912. view_index++;
  1913. view_count++;
  1914. }
  1915. }
  1916. else if (view_count > view_index && _view[view_index] == item)
  1917. {
  1918. // Remove the item
  1919. _view.RemoveAt(view_index);
  1920. view_count--;
  1921. }
  1922. }
  1923. // Clear the evaluator to discard a reference to the last item
  1924. if (_valueBindingEvaluator != null)
  1925. {
  1926. _valueBindingEvaluator.ClearDataContext();
  1927. }
  1928. }
  1929. /// <summary>
  1930. /// Handle any change to the ItemsSource dependency property, update
  1931. /// the underlying ObservableCollection view, and set the selection
  1932. /// adapter's ItemsSource to the view if appropriate.
  1933. /// </summary>
  1934. /// <param name="newValue">The new enumerable reference.</param>
  1935. private void OnItemsChanged(IEnumerable newValue)
  1936. {
  1937. // Remove handler for oldValue.CollectionChanged (if present)
  1938. _collectionChangeSubscription?.Dispose();
  1939. _collectionChangeSubscription = null;
  1940. // Add handler for newValue.CollectionChanged (if possible)
  1941. if (newValue is INotifyCollectionChanged newValueINotifyCollectionChanged)
  1942. {
  1943. _collectionChangeSubscription = newValueINotifyCollectionChanged.WeakSubscribe(ItemsCollectionChanged);
  1944. }
  1945. // Store a local cached copy of the data
  1946. _items = newValue == null ? null : new List<object>(newValue.Cast<object>().ToList());
  1947. // Clear and set the view on the selection adapter
  1948. ClearView();
  1949. if (SelectionAdapter != null && SelectionAdapter.Items != _view)
  1950. {
  1951. SelectionAdapter.Items = _view;
  1952. }
  1953. if (IsDropDownOpen)
  1954. {
  1955. RefreshView();
  1956. }
  1957. }
  1958. /// <summary>
  1959. /// Method that handles the ObservableCollection.CollectionChanged event for the ItemsSource property.
  1960. /// </summary>
  1961. /// <param name="sender">The object that raised the event.</param>
  1962. /// <param name="e">The event data.</param>
  1963. private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  1964. {
  1965. // Update the cache
  1966. if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
  1967. {
  1968. for (int index = 0; index < e.OldItems.Count; index++)
  1969. {
  1970. _items.RemoveAt(e.OldStartingIndex);
  1971. }
  1972. }
  1973. if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && _items.Count >= e.NewStartingIndex)
  1974. {
  1975. for (int index = 0; index < e.NewItems.Count; index++)
  1976. {
  1977. _items.Insert(e.NewStartingIndex + index, e.NewItems[index]);
  1978. }
  1979. }
  1980. if (e.Action == NotifyCollectionChangedAction.Replace && e.NewItems != null && e.OldItems != null)
  1981. {
  1982. for (int index = 0; index < e.NewItems.Count; index++)
  1983. {
  1984. _items[e.NewStartingIndex] = e.NewItems[index];
  1985. }
  1986. }
  1987. // Update the view
  1988. if ((e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) && e.OldItems != null)
  1989. {
  1990. for (int index = 0; index < e.OldItems.Count; index++)
  1991. {
  1992. _view.Remove(e.OldItems[index]);
  1993. }
  1994. }
  1995. if (e.Action == NotifyCollectionChangedAction.Reset)
  1996. {
  1997. // Significant changes to the underlying data.
  1998. ClearView();
  1999. if (Items != null)
  2000. {
  2001. _items = new List<object>(Items.Cast<object>().ToList());
  2002. }
  2003. }
  2004. // Refresh the observable collection used in the selection adapter.
  2005. RefreshView();
  2006. }
  2007. /// <summary>
  2008. /// Notifies the
  2009. /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> that the
  2010. /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Items" />
  2011. /// property has been set and the data can be filtered to provide
  2012. /// possible matches in the drop-down.
  2013. /// </summary>
  2014. /// <remarks>
  2015. /// Call this method when you are providing custom population of
  2016. /// the drop-down portion of the AutoCompleteBox, to signal the control
  2017. /// that you are done with the population process.
  2018. /// Typically, you use PopulateComplete when the population process
  2019. /// is a long-running process and you want to cancel built-in filtering
  2020. /// of the ItemsSource items. In this case, you can handle the
  2021. /// Populated event and set PopulatingEventArgs.Cancel to true.
  2022. /// When the long-running process has completed you call
  2023. /// PopulateComplete to indicate the drop-down is populated.
  2024. /// </remarks>
  2025. public void PopulateComplete()
  2026. {
  2027. // Apply the search filter
  2028. RefreshView();
  2029. // Fire the Populated event containing the read-only view data.
  2030. PopulatedEventArgs populated = new PopulatedEventArgs(new ReadOnlyCollection<object>(_view));
  2031. OnPopulated(populated);
  2032. if (SelectionAdapter != null && SelectionAdapter.Items != _view)
  2033. {
  2034. SelectionAdapter.Items = _view;
  2035. }
  2036. bool isDropDownOpen = _userCalledPopulate && (_view.Count > 0);
  2037. if (isDropDownOpen != IsDropDownOpen)
  2038. {
  2039. _ignorePropertyChange = true;
  2040. IsDropDownOpen = isDropDownOpen;
  2041. }
  2042. if (IsDropDownOpen)
  2043. {
  2044. OpeningDropDown(false);
  2045. }
  2046. else
  2047. {
  2048. ClosingDropDown(true);
  2049. }
  2050. UpdateTextCompletion(_userCalledPopulate);
  2051. }
  2052. /// <summary>
  2053. /// Performs text completion, if enabled, and a lookup on the underlying
  2054. /// item values for an exact match. Will update the SelectedItem value.
  2055. /// </summary>
  2056. /// <param name="userInitiated">A value indicating whether the operation
  2057. /// was user initiated. Text completion will not be performed when not
  2058. /// directly initiated by the user.</param>
  2059. private void UpdateTextCompletion(bool userInitiated)
  2060. {
  2061. // By default this method will clear the selected value
  2062. object newSelectedItem = null;
  2063. string text = Text;
  2064. // Text search is StartsWith explicit and only when enabled, in
  2065. // line with WPF's ComboBox lookup. When in use it will associate
  2066. // a Value with the Text if it is found in ItemsSource. This is
  2067. // only valid when there is data and the user initiated the action.
  2068. if (_view.Count > 0)
  2069. {
  2070. if (IsTextCompletionEnabled && TextBox != null && userInitiated)
  2071. {
  2072. int currentLength = TextBox.Text.Length;
  2073. int selectionStart = TextBoxSelectionStart;
  2074. if (selectionStart == text.Length && selectionStart > _textSelectionStart)
  2075. {
  2076. // When the FilterMode dependency property is set to
  2077. // either StartsWith or StartsWithCaseSensitive, the
  2078. // first item in the view is used. This will improve
  2079. // performance on the lookup. It assumes that the
  2080. // FilterMode the user has selected is an acceptable
  2081. // case sensitive matching function for their scenario.
  2082. object top = FilterMode == AutoCompleteFilterMode.StartsWith || FilterMode == AutoCompleteFilterMode.StartsWithCaseSensitive
  2083. ? _view[0]
  2084. : TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
  2085. // If the search was successful, update SelectedItem
  2086. if (top != null)
  2087. {
  2088. newSelectedItem = top;
  2089. string topString = FormatValue(top, true);
  2090. // Only replace partially when the two words being the same
  2091. int minLength = Math.Min(topString.Length, Text.Length);
  2092. if (AutoCompleteSearch.Equals(Text.Substring(0, minLength), topString.Substring(0, minLength)))
  2093. {
  2094. // Update the text
  2095. UpdateTextValue(topString);
  2096. // Select the text past the user's caret
  2097. TextBox.SelectionStart = currentLength;
  2098. TextBox.SelectionEnd = topString.Length;
  2099. }
  2100. }
  2101. }
  2102. }
  2103. else
  2104. {
  2105. // Perform an exact string lookup for the text. This is a
  2106. // design change from the original Toolkit release when the
  2107. // IsTextCompletionEnabled property behaved just like the
  2108. // WPF ComboBox's IsTextSearchEnabled property.
  2109. //
  2110. // This change provides the behavior that most people expect
  2111. // to find: a lookup for the value is always performed.
  2112. newSelectedItem = TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive));
  2113. }
  2114. }
  2115. // Update the selected item property
  2116. if (SelectedItem != newSelectedItem)
  2117. {
  2118. _skipSelectedItemTextUpdate = true;
  2119. }
  2120. SelectedItem = newSelectedItem;
  2121. // Restore updates for TextSelection
  2122. if (_ignoreTextSelectionChange)
  2123. {
  2124. _ignoreTextSelectionChange = false;
  2125. if (TextBox != null)
  2126. {
  2127. _textSelectionStart = TextBoxSelectionStart;
  2128. }
  2129. }
  2130. }
  2131. /// <summary>
  2132. /// Attempts to look through the view and locate the specific exact
  2133. /// text match.
  2134. /// </summary>
  2135. /// <param name="searchText">The search text.</param>
  2136. /// <param name="view">The view reference.</param>
  2137. /// <param name="predicate">The predicate to use for the partial or
  2138. /// exact match.</param>
  2139. /// <returns>Returns the object or null.</returns>
  2140. private object TryGetMatch(string searchText, AvaloniaList<object> view, AutoCompleteFilterPredicate<string> predicate)
  2141. {
  2142. if (view != null && view.Count > 0)
  2143. {
  2144. foreach (object o in view)
  2145. {
  2146. if (predicate(searchText, FormatValue(o)))
  2147. {
  2148. return o;
  2149. }
  2150. }
  2151. }
  2152. return null;
  2153. }
  2154. private void UpdatePseudoClasses()
  2155. {
  2156. PseudoClasses.Set(":dropdownopen", IsDropDownOpen);
  2157. }
  2158. private void ClearTextBoxSelection()
  2159. {
  2160. if (TextBox != null)
  2161. {
  2162. int length = TextBox.Text?.Length ?? 0;
  2163. TextBox.SelectionStart = length;
  2164. TextBox.SelectionEnd = length;
  2165. }
  2166. }
  2167. /// <summary>
  2168. /// Called when the selected item is changed, updates the text value
  2169. /// that is displayed in the text box part.
  2170. /// </summary>
  2171. /// <param name="newItem">The new item.</param>
  2172. private void OnSelectedItemChanged(object newItem)
  2173. {
  2174. string text;
  2175. if (newItem == null)
  2176. {
  2177. text = SearchText;
  2178. }
  2179. else if (TextSelector != null)
  2180. {
  2181. text = TextSelector(SearchText, FormatValue(newItem, true));
  2182. }
  2183. else if (ItemSelector != null)
  2184. {
  2185. text = ItemSelector(SearchText, newItem);
  2186. }
  2187. else
  2188. {
  2189. text = FormatValue(newItem, true);
  2190. }
  2191. // Update the Text property and the TextBox values
  2192. UpdateTextValue(text);
  2193. // Move the caret to the end of the text box
  2194. ClearTextBoxSelection();
  2195. }
  2196. /// <summary>
  2197. /// Handles the SelectionChanged event of the selection adapter.
  2198. /// </summary>
  2199. /// <param name="sender">The source object.</param>
  2200. /// <param name="e">The selection changed event data.</param>
  2201. private void OnAdapterSelectionChanged(object sender, SelectionChangedEventArgs e)
  2202. {
  2203. SelectedItem = _adapter.SelectedItem;
  2204. }
  2205. //TODO Check UpdateTextCompletion
  2206. /// <summary>
  2207. /// Handles the Commit event on the selection adapter.
  2208. /// </summary>
  2209. /// <param name="sender">The source object.</param>
  2210. /// <param name="e">The event data.</param>
  2211. private void OnAdapterSelectionComplete(object sender, RoutedEventArgs e)
  2212. {
  2213. IsDropDownOpen = false;
  2214. // Completion will update the selected value
  2215. //UpdateTextCompletion(false);
  2216. // Text should not be selected
  2217. ClearTextBoxSelection();
  2218. TextBox.Focus();
  2219. }
  2220. /// <summary>
  2221. /// Handles the Cancel event on the selection adapter.
  2222. /// </summary>
  2223. /// <param name="sender">The source object.</param>
  2224. /// <param name="e">The event data.</param>
  2225. private void OnAdapterSelectionCanceled(object sender, RoutedEventArgs e)
  2226. {
  2227. UpdateTextValue(SearchText);
  2228. // Completion will update the selected value
  2229. UpdateTextCompletion(false);
  2230. }
  2231. /// <summary>
  2232. /// A predefined set of filter functions for the known, built-in
  2233. /// AutoCompleteFilterMode enumeration values.
  2234. /// </summary>
  2235. private static class AutoCompleteSearch
  2236. {
  2237. /// <summary>
  2238. /// Index function that retrieves the filter for the provided
  2239. /// AutoCompleteFilterMode.
  2240. /// </summary>
  2241. /// <param name="FilterMode">The built-in search mode.</param>
  2242. /// <returns>Returns the string-based comparison function.</returns>
  2243. public static AutoCompleteFilterPredicate<string> GetFilter(AutoCompleteFilterMode FilterMode)
  2244. {
  2245. switch (FilterMode)
  2246. {
  2247. case AutoCompleteFilterMode.Contains:
  2248. return Contains;
  2249. case AutoCompleteFilterMode.ContainsCaseSensitive:
  2250. return ContainsCaseSensitive;
  2251. case AutoCompleteFilterMode.ContainsOrdinal:
  2252. return ContainsOrdinal;
  2253. case AutoCompleteFilterMode.ContainsOrdinalCaseSensitive:
  2254. return ContainsOrdinalCaseSensitive;
  2255. case AutoCompleteFilterMode.Equals:
  2256. return Equals;
  2257. case AutoCompleteFilterMode.EqualsCaseSensitive:
  2258. return EqualsCaseSensitive;
  2259. case AutoCompleteFilterMode.EqualsOrdinal:
  2260. return EqualsOrdinal;
  2261. case AutoCompleteFilterMode.EqualsOrdinalCaseSensitive:
  2262. return EqualsOrdinalCaseSensitive;
  2263. case AutoCompleteFilterMode.StartsWith:
  2264. return StartsWith;
  2265. case AutoCompleteFilterMode.StartsWithCaseSensitive:
  2266. return StartsWithCaseSensitive;
  2267. case AutoCompleteFilterMode.StartsWithOrdinal:
  2268. return StartsWithOrdinal;
  2269. case AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive:
  2270. return StartsWithOrdinalCaseSensitive;
  2271. case AutoCompleteFilterMode.None:
  2272. case AutoCompleteFilterMode.Custom:
  2273. default:
  2274. return null;
  2275. }
  2276. }
  2277. /// <summary>
  2278. /// An implementation of the Contains member of string that takes in a
  2279. /// string comparison. The traditional .NET string Contains member uses
  2280. /// StringComparison.Ordinal.
  2281. /// </summary>
  2282. /// <param name="s">The string.</param>
  2283. /// <param name="value">The string value to search for.</param>
  2284. /// <param name="comparison">The string comparison type.</param>
  2285. /// <returns>Returns true when the substring is found.</returns>
  2286. private static bool Contains(string s, string value, StringComparison comparison)
  2287. {
  2288. return s.IndexOf(value, comparison) >= 0;
  2289. }
  2290. /// <summary>
  2291. /// Check if the string value begins with the text.
  2292. /// </summary>
  2293. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2294. /// <param name="value">The item's string value.</param>
  2295. /// <returns>Returns true if the condition is met.</returns>
  2296. public static bool StartsWith(string text, string value)
  2297. {
  2298. return value.StartsWith(text, StringComparison.CurrentCultureIgnoreCase);
  2299. }
  2300. /// <summary>
  2301. /// Check if the string value begins with the text.
  2302. /// </summary>
  2303. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2304. /// <param name="value">The item's string value.</param>
  2305. /// <returns>Returns true if the condition is met.</returns>
  2306. public static bool StartsWithCaseSensitive(string text, string value)
  2307. {
  2308. return value.StartsWith(text, StringComparison.CurrentCulture);
  2309. }
  2310. /// <summary>
  2311. /// Check if the string value begins with the text.
  2312. /// </summary>
  2313. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2314. /// <param name="value">The item's string value.</param>
  2315. /// <returns>Returns true if the condition is met.</returns>
  2316. public static bool StartsWithOrdinal(string text, string value)
  2317. {
  2318. return value.StartsWith(text, StringComparison.OrdinalIgnoreCase);
  2319. }
  2320. /// <summary>
  2321. /// Check if the string value begins with the text.
  2322. /// </summary>
  2323. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2324. /// <param name="value">The item's string value.</param>
  2325. /// <returns>Returns true if the condition is met.</returns>
  2326. public static bool StartsWithOrdinalCaseSensitive(string text, string value)
  2327. {
  2328. return value.StartsWith(text, StringComparison.Ordinal);
  2329. }
  2330. /// <summary>
  2331. /// Check if the prefix is contained in the string value. The current
  2332. /// culture's case insensitive string comparison operator is used.
  2333. /// </summary>
  2334. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2335. /// <param name="value">The item's string value.</param>
  2336. /// <returns>Returns true if the condition is met.</returns>
  2337. public static bool Contains(string text, string value)
  2338. {
  2339. return Contains(value, text, StringComparison.CurrentCultureIgnoreCase);
  2340. }
  2341. /// <summary>
  2342. /// Check if the prefix is contained in the string value.
  2343. /// </summary>
  2344. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2345. /// <param name="value">The item's string value.</param>
  2346. /// <returns>Returns true if the condition is met.</returns>
  2347. public static bool ContainsCaseSensitive(string text, string value)
  2348. {
  2349. return Contains(value, text, StringComparison.CurrentCulture);
  2350. }
  2351. /// <summary>
  2352. /// Check if the prefix is contained in the string value.
  2353. /// </summary>
  2354. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2355. /// <param name="value">The item's string value.</param>
  2356. /// <returns>Returns true if the condition is met.</returns>
  2357. public static bool ContainsOrdinal(string text, string value)
  2358. {
  2359. return Contains(value, text, StringComparison.OrdinalIgnoreCase);
  2360. }
  2361. /// <summary>
  2362. /// Check if the prefix is contained in the string value.
  2363. /// </summary>
  2364. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2365. /// <param name="value">The item's string value.</param>
  2366. /// <returns>Returns true if the condition is met.</returns>
  2367. public static bool ContainsOrdinalCaseSensitive(string text, string value)
  2368. {
  2369. return Contains(value, text, StringComparison.Ordinal);
  2370. }
  2371. /// <summary>
  2372. /// Check if the string values are equal.
  2373. /// </summary>
  2374. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2375. /// <param name="value">The item's string value.</param>
  2376. /// <returns>Returns true if the condition is met.</returns>
  2377. public static bool Equals(string text, string value)
  2378. {
  2379. return value.Equals(text, StringComparison.CurrentCultureIgnoreCase);
  2380. }
  2381. /// <summary>
  2382. /// Check if the string values are equal.
  2383. /// </summary>
  2384. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2385. /// <param name="value">The item's string value.</param>
  2386. /// <returns>Returns true if the condition is met.</returns>
  2387. public static bool EqualsCaseSensitive(string text, string value)
  2388. {
  2389. return value.Equals(text, StringComparison.CurrentCulture);
  2390. }
  2391. /// <summary>
  2392. /// Check if the string values are equal.
  2393. /// </summary>
  2394. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2395. /// <param name="value">The item's string value.</param>
  2396. /// <returns>Returns true if the condition is met.</returns>
  2397. public static bool EqualsOrdinal(string text, string value)
  2398. {
  2399. return value.Equals(text, StringComparison.OrdinalIgnoreCase);
  2400. }
  2401. /// <summary>
  2402. /// Check if the string values are equal.
  2403. /// </summary>
  2404. /// <param name="text">The AutoCompleteBox prefix text.</param>
  2405. /// <param name="value">The item's string value.</param>
  2406. /// <returns>Returns true if the condition is met.</returns>
  2407. public static bool EqualsOrdinalCaseSensitive(string text, string value)
  2408. {
  2409. return value.Equals(text, StringComparison.Ordinal);
  2410. }
  2411. }
  2412. /// <summary>
  2413. /// A framework element that permits a binding to be evaluated in a new data
  2414. /// context leaf node.
  2415. /// </summary>
  2416. /// <typeparam name="T">The type of dynamic binding to return.</typeparam>
  2417. public class BindingEvaluator<T> : Control
  2418. {
  2419. /// <summary>
  2420. /// Gets or sets the string value binding used by the control.
  2421. /// </summary>
  2422. private IBinding _binding;
  2423. #region public T Value
  2424. /// <summary>
  2425. /// Identifies the Value dependency property.
  2426. /// </summary>
  2427. public static readonly StyledProperty<T> ValueProperty =
  2428. AvaloniaProperty.Register<BindingEvaluator<T>, T>(nameof(Value));
  2429. /// <summary>
  2430. /// Gets or sets the data item value.
  2431. /// </summary>
  2432. public T Value
  2433. {
  2434. get { return GetValue(ValueProperty); }
  2435. set { SetValue(ValueProperty, value); }
  2436. }
  2437. #endregion public string Value
  2438. /// <summary>
  2439. /// Gets or sets the value binding.
  2440. /// </summary>
  2441. public IBinding ValueBinding
  2442. {
  2443. get { return _binding; }
  2444. set
  2445. {
  2446. _binding = value;
  2447. AvaloniaObjectExtensions.Bind(this, ValueProperty, value);
  2448. }
  2449. }
  2450. /// <summary>
  2451. /// Initializes a new instance of the BindingEvaluator class.
  2452. /// </summary>
  2453. public BindingEvaluator()
  2454. { }
  2455. /// <summary>
  2456. /// Initializes a new instance of the BindingEvaluator class,
  2457. /// setting the initial binding to the provided parameter.
  2458. /// </summary>
  2459. /// <param name="binding">The initial string value binding.</param>
  2460. public BindingEvaluator(IBinding binding)
  2461. : this()
  2462. {
  2463. ValueBinding = binding;
  2464. }
  2465. /// <summary>
  2466. /// Clears the data context so that the control does not keep a
  2467. /// reference to the last-looked up item.
  2468. /// </summary>
  2469. public void ClearDataContext()
  2470. {
  2471. DataContext = null;
  2472. }
  2473. /// <summary>
  2474. /// Updates the data context of the framework element and returns the
  2475. /// updated binding value.
  2476. /// </summary>
  2477. /// <param name="o">The object to use as the data context.</param>
  2478. /// <param name="clearDataContext">If set to true, this parameter will
  2479. /// clear the data context immediately after retrieving the value.</param>
  2480. /// <returns>Returns the evaluated T value of the bound dependency
  2481. /// property.</returns>
  2482. public T GetDynamicValue(object o, bool clearDataContext)
  2483. {
  2484. DataContext = o;
  2485. T value = Value;
  2486. if (clearDataContext)
  2487. {
  2488. DataContext = null;
  2489. }
  2490. return value;
  2491. }
  2492. /// <summary>
  2493. /// Updates the data context of the framework element and returns the
  2494. /// updated binding value.
  2495. /// </summary>
  2496. /// <param name="o">The object to use as the data context.</param>
  2497. /// <returns>Returns the evaluated T value of the bound dependency
  2498. /// property.</returns>
  2499. public T GetDynamicValue(object o)
  2500. {
  2501. DataContext = o;
  2502. return Value;
  2503. }
  2504. }
  2505. }
  2506. }