AutoCompleteBox.cs 102 KB

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