AutoCompleteBox.cs 103 KB

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