Browse Source

Merge branch 'master' into feature/BorderThicknessCornerRadius

danwalmsley 7 years ago
parent
commit
dbd2406db6

+ 6 - 0
samples/ControlCatalog/ControlCatalog.csproj

@@ -41,6 +41,9 @@
     <EmbeddedResource Include="Pages\DialogsPage.xaml">
     <EmbeddedResource Include="Pages\DialogsPage.xaml">
       <SubType>Designer</SubType>
       <SubType>Designer</SubType>
     </EmbeddedResource>
     </EmbeddedResource>
+    <EmbeddedResource Include="Pages\AutoCompleteBoxPage.xaml">
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
     <EmbeddedResource Include="Pages\BorderPage.xaml">
     <EmbeddedResource Include="Pages\BorderPage.xaml">
       <SubType>Designer</SubType>
       <SubType>Designer</SubType>
     </EmbeddedResource>
     </EmbeddedResource>
@@ -116,6 +119,9 @@
     <Compile Include="Pages\BorderPage.xaml.cs">
     <Compile Include="Pages\BorderPage.xaml.cs">
       <DependentUpon>BorderPage.xaml</DependentUpon>
       <DependentUpon>BorderPage.xaml</DependentUpon>
     </Compile>
     </Compile>
+    <Compile Include="Pages\AutoCompleteBoxPage.xaml.cs">
+      <DependentUpon>AutoCompleteBoxPage.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Pages\ButtonPage.xaml.cs">
     <Compile Include="Pages\ButtonPage.xaml.cs">
       <DependentUpon>ButtonPage.xaml</DependentUpon>
       <DependentUpon>ButtonPage.xaml</DependentUpon>
     </Compile>
     </Compile>

+ 3 - 2
samples/ControlCatalog/MainView.xaml

@@ -5,10 +5,11 @@
     <TabControl.Transition>
     <TabControl.Transition>
       <CrossFade Duration="0.25"/>
       <CrossFade Duration="0.25"/>
     </TabControl.Transition>
     </TabControl.Transition>
+    <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
     <TabItem Header="Border"><pages:BorderPage/></TabItem>
     <TabItem Header="Border"><pages:BorderPage/></TabItem>
     <TabItem Header="Button"><pages:ButtonPage/></TabItem>
     <TabItem Header="Button"><pages:ButtonPage/></TabItem>
     <TabItem Header="ButtonSpinner"><pages:ButtonSpinnerPage/></TabItem>
     <TabItem Header="ButtonSpinner"><pages:ButtonSpinnerPage/></TabItem>
-    <TabItem Header="Calendar"><pages:CalendarPage/></TabItem> 
+    <TabItem Header="Calendar"><pages:CalendarPage/></TabItem>
     <TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
     <TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
     <TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
     <TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
     <TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
     <TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
@@ -27,4 +28,4 @@
     <TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
     <TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
     <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
     <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
   </TabControl>
   </TabControl>
-</UserControl>
+</UserControl>

+ 59 - 0
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@@ -0,0 +1,59 @@
+<UserControl xmlns="https://github.com/avaloniaui">
+  <StackPanel Orientation="Vertical" Gap="4">
+    <TextBlock Classes="h1">AutoCompleteBox</TextBlock>
+    <TextBlock Classes="h2">A control into which the user can input text</TextBlock>
+
+    <StackPanel Orientation="Horizontal"
+              Margin="0,16,0,0"
+              HorizontalAlignment="Center"
+              Gap="8">
+      <StackPanel Orientation="Vertical">
+        <TextBlock Text="MinimumPrefixLength: 1"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         MinimumPrefixLength="1"/>
+        <TextBlock Text="MinimumPrefixLength: 3"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         MinimumPrefixLength="3"/>
+        <TextBlock Text="MinimumPopulateDelay: 1 Second"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         MinimumPopulateDelay="1"/>
+        <TextBlock Text="MaxDropDownHeight: 60"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         MaxDropDownHeight="60"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         Watermark="Watermark"/>
+        <TextBlock Text="Disabled"/>
+        <AutoCompleteBox Width="200"
+                         IsEnabled="False"/>
+      </StackPanel>
+      
+
+      <StackPanel Orientation="Vertical">
+        
+        <TextBlock Text="ValueMemeberSelector"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         ValueMemberSelector="Capital"/>
+        <TextBlock Text="ValueMemberBinding"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         ValueMemberBinding="{Binding Capital}"/>
+        <TextBlock Text="Multi-Binding"/>
+        <AutoCompleteBox Name="MultiBindingBox"
+                         Width="200"
+                         Margin="0,0,0,8"
+                         FilterMode="Contains"/>
+        <TextBlock Text="Async Populate"/>
+        <AutoCompleteBox Name="AsyncBox"
+                         Width="200"
+                         Margin="0,0,0,8"
+                         FilterMode="None"/>
+      </StackPanel>
+    </StackPanel>
+  </StackPanel>
+</UserControl>

+ 143 - 0
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@@ -0,0 +1,143 @@
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
+using Avalonia.Markup;
+using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ControlCatalog.Pages
+{
+    public class AutoCompleteBoxPage : UserControl
+    {
+        public class StateData
+        {
+            public string Name { get; private set; }
+            public string Abbreviation { get; private set; }
+            public string Capital { get; private set; }
+
+            public StateData(string name, string abbreviatoin, string capital)
+            {
+                Name = name;
+                Abbreviation = abbreviatoin;
+                Capital = capital;
+            }
+
+            public override string ToString()
+            {
+                return Name;
+            }
+        }
+
+        private StateData[] BuildAllStates()
+        {
+            return new StateData[]
+            {
+                new StateData("Alabama","AL","Montgomery"),
+                new StateData("Alaska","AK","Juneau"),
+                new StateData("Arizona","AZ","Phoenix"),
+                new StateData("Arkansas","AR","Little Rock"),
+                new StateData("California","CA","Sacramento"),
+                new StateData("Colorado","CO","Denver"),
+                new StateData("Connecticut","CT","Hartford"),
+                new StateData("Delaware","DE","Dover"),
+                new StateData("Florida","FL","Tallahassee"),
+                new StateData("Georgia","GA","Atlanta"),
+                new StateData("Hawaii","HI","Honolulu"),
+                new StateData("Idaho","ID","Boise"),
+                new StateData("Illinois","IL","Springfield"),
+                new StateData("Indiana","IN","Indianapolis"),
+                new StateData("Iowa","IA","Des Moines"),
+                new StateData("Kansas","KS","Topeka"),
+                new StateData("Kentucky","KY","Frankfort"),
+                new StateData("Louisiana","LA","Baton Rouge"),
+                new StateData("Maine","ME","Augusta"),
+                new StateData("Maryland","MD","Annapolis"),
+                new StateData("Massachusetts","MA","Boston"),
+                new StateData("Michigan","MI","Lansing"),
+                new StateData("Minnesota","MN","St. Paul"),
+                new StateData("Mississippi","MS","Jackson"),
+                new StateData("Missouri","MO","Jefferson City"),
+                new StateData("Montana","MT","Helena"),
+                new StateData("Nebraska","NE","Lincoln"),
+                new StateData("Nevada","NV","Carson City"),
+                new StateData("New Hampshire","NH","Concord"),
+                new StateData("New Jersey","NJ","Trenton"),
+                new StateData("New Mexico","NM","Santa Fe"),
+                new StateData("New York","NY","Albany"),
+                new StateData("North Carolina","NC","Raleigh"),
+                new StateData("North Dakota","ND","Bismarck"),
+                new StateData("Ohio","OH","Columbus"),
+                new StateData("Oklahoma","OK","Oklahoma City"),
+                new StateData("Oregon","OR","Salem"),
+                new StateData("Pennsylvania","PA","Harrisburg"),
+                new StateData("Rhode Island","RI","Providence"),
+                new StateData("South Carolina","SC","Columbia"),
+                new StateData("South Dakota","SD","Pierre"),
+                new StateData("Tennessee","TN","Nashville"),
+                new StateData("Texas","TX","Austin"),
+                new StateData("Utah","UT","Salt Lake City"),
+                new StateData("Vermont","VT","Montpelier"),
+                new StateData("Virginia","VA","Richmond"),
+                new StateData("Washington","WA","Olympia"),
+                new StateData("West Virginia","WV","Charleston"),
+                new StateData("Wisconsin","WI","Madison"),
+                new StateData("Wyoming","WY","Cheyenne"),
+            };
+        }
+        public StateData[] States { get; private set; }
+        
+        public AutoCompleteBoxPage()
+        {
+            this.InitializeComponent();
+
+            States = BuildAllStates();
+
+            foreach (AutoCompleteBox box in GetAllAutoCompleteBox())
+            {
+                box.Items = States;
+            }
+
+            var converter = new FuncMultiValueConverter<string, string>(parts =>
+            {
+                return String.Format("{0} ({1})", parts.ToArray());
+            });
+            var binding = new MultiBinding { Converter = converter };
+            binding.Bindings.Add(new Binding("Name"));
+            binding.Bindings.Add(new Binding("Abbreviation"));
+
+            var multibindingBox = this.FindControl<AutoCompleteBox>("MultiBindingBox");
+            multibindingBox.ValueMemberBinding = binding;
+
+            var asyncBox = this.FindControl<AutoCompleteBox>("AsyncBox");
+            asyncBox.AsyncPopulator = PopulateAsync;
+        }
+        private IEnumerable<AutoCompleteBox> GetAllAutoCompleteBox()
+        {
+            return
+                this.GetLogicalDescendants()
+                    .OfType<AutoCompleteBox>();
+        }
+
+        private bool StringContains(string str, string query)
+        {
+            return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
+        }
+        private async Task<IEnumerable<object>> PopulateAsync(string searchText, CancellationToken cancellationToken)
+        {
+            await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken);
+
+            return
+                States.Where(data => StringContains(data.Name, searchText) || StringContains(data.Capital, searchText))
+                      .ToList();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 3 - 1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@@ -17,7 +17,9 @@
       <None Remove="MiniCube.fx" />
       <None Remove="MiniCube.fx" />
     </ItemGroup>
     </ItemGroup>
     <ItemGroup>
     <ItemGroup>
-      <EmbeddedResource Include="MiniCube.fx" />
+      <EmbeddedResource Include="MiniCube.fx">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </EmbeddedResource>
     </ItemGroup>
     </ItemGroup>
     <ItemGroup>
     <ItemGroup>
         <ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
         <ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />

+ 2726 - 0
src/Avalonia.Controls/AutoCompleteBox.cs

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

+ 25 - 4
src/Avalonia.Controls/TextBox.cs

@@ -85,6 +85,7 @@ namespace Avalonia.Controls
         private int _selectionEnd;
         private int _selectionEnd;
         private TextPresenter _presenter;
         private TextPresenter _presenter;
         private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
         private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
+        private bool _isUndoingRedoing;
         private bool _ignoreTextChanges;
         private bool _ignoreTextChanges;
         private static readonly string[] invalidCharacters = new String[1]{"\u007f"};
         private static readonly string[] invalidCharacters = new String[1]{"\u007f"};
 
 
@@ -198,7 +199,11 @@ namespace Avalonia.Controls
                 if (!_ignoreTextChanges)
                 if (!_ignoreTextChanges)
                 {
                 {
                     CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0);
                     CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0);
-                    SetAndRaise(TextProperty, ref _text, value);
+
+                    if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing)
+                    {
+                        _undoRedoHelper.Clear();
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -367,14 +372,30 @@ namespace Avalonia.Controls
                 case Key.Z:
                 case Key.Z:
                     if (modifiers == InputModifiers.Control)
                     if (modifiers == InputModifiers.Control)
                     {
                     {
-                        _undoRedoHelper.Undo();
+                        try
+                        {
+                            _isUndoingRedoing = true;
+                            _undoRedoHelper.Undo();
+                        }
+                        finally
+                        {
+                            _isUndoingRedoing = false;
+                        }
                         handled = true;
                         handled = true;
                     }
                     }
                     break;
                     break;
                 case Key.Y:
                 case Key.Y:
                     if (modifiers == InputModifiers.Control)
                     if (modifiers == InputModifiers.Control)
                     {
                     {
-                        _undoRedoHelper.Redo();
+                        try
+                        {
+                            _isUndoingRedoing = true;
+                            _undoRedoHelper.Redo();
+                        }
+                        finally
+                        {
+                            _isUndoingRedoing = false;
+                        }
                         handled = true;
                         handled = true;
                     }
                     }
                     break;
                     break;
@@ -791,7 +812,7 @@ namespace Avalonia.Controls
             int pos = 0;
             int pos = 0;
             int i;
             int i;
 
 
-            for (i = 0; i < lines.Count; ++i)
+            for (i = 0; i < lines.Count - 1; ++i)
             {
             {
                 var line = lines[i];
                 var line = lines[i];
                 pos += line.Length;
                 pos += line.Length;

+ 64 - 0
src/Avalonia.Controls/Utils/ISelectionAdapter.cs

@@ -0,0 +1,64 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System;
+using System.Collections;
+using Avalonia.Interactivity;
+using Avalonia.Input;
+
+namespace Avalonia.Controls.Utils
+{
+    /// <summary>
+    /// Defines an item collection, selection members, and key handling for the
+    /// selection adapter contained in the drop-down portion of an
+    /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
+    /// </summary>
+    public interface ISelectionAdapter
+    {
+        /// <summary>
+        /// Gets or sets the selected item.
+        /// </summary>
+        /// <value>The currently selected item.</value>
+        object SelectedItem { get; set; }
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="P:Avalonia.Controls.Utils.ISelectionAdapter.SelectedItem" />
+        /// property value changes.
+        /// </summary>
+        event EventHandler<SelectionChangedEventArgs> SelectionChanged;
+        
+        /// <summary>
+        /// Gets or sets a collection that is used to generate content for the
+        /// selection adapter.
+        /// </summary>
+        /// <value>The collection that is used to generate content for the
+        /// selection adapter.</value>
+        IEnumerable Items { get; set; }
+
+        /// <summary>
+        /// Occurs when a selected item is not cancelled and is committed as the
+        /// selected item.
+        /// </summary>
+        event EventHandler<RoutedEventArgs> Commit;
+
+        /// <summary>
+        /// Occurs when a selection has been canceled.
+        /// </summary>
+        event EventHandler<RoutedEventArgs> Cancel;
+
+        /// <summary>
+        /// Provides handling for the
+        /// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event that occurs
+        /// when a key is pressed while the drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus.
+        /// </summary>
+        /// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
+        /// that contains data about the
+        /// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event.</param>
+        void HandleKeyDown(KeyEventArgs e);
+    }
+
+}

+ 342 - 0
src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs

@@ -0,0 +1,342 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+using Avalonia.Input;
+using Avalonia.LogicalTree;
+using System.Collections;
+using System.Diagnostics;
+
+namespace Avalonia.Controls.Utils
+{
+    /// <summary>
+    /// Represents the selection adapter contained in the drop-down portion of
+    /// an <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
+    /// </summary>
+    public class SelectingItemsControlSelectionAdapter : ISelectionAdapter
+    {
+        /// <summary>
+        /// The SelectingItemsControl instance.
+        /// </summary>
+        private SelectingItemsControl _selector;
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the selection change event 
+        /// should not be fired.
+        /// </summary>
+        private bool IgnoringSelectionChanged { get; set; }
+
+        /// <summary>
+        /// Gets or sets the underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        /// <value>The underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.</value>
+        public SelectingItemsControl SelectorControl
+        {
+            get { return _selector; }
+
+            set
+            {
+                if (_selector != null)
+                {
+                    _selector.SelectionChanged -= OnSelectionChanged;
+                    _selector.PointerReleased -= OnSelectorPointerReleased;
+                }
+
+                _selector = value;
+
+                if (_selector != null)
+                {
+                    _selector.SelectionChanged += OnSelectionChanged;
+                    _selector.PointerReleased += OnSelectorPointerReleased;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="P:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.SelectedItem" />
+        /// property value changes.
+        /// </summary>
+        public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
+
+        /// <summary>
+        /// Occurs when an item is selected and is committed to the underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        public event EventHandler<RoutedEventArgs> Commit;
+
+        /// <summary>
+        /// Occurs when a selection is canceled before it is committed.
+        /// </summary>
+        public event EventHandler<RoutedEventArgs> Cancel;
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />
+        /// class.
+        /// </summary>
+        public SelectingItemsControlSelectionAdapter()
+        {
+
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapterr" />
+        /// class with the specified
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        /// <param name="selector">The
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" /> control
+        /// to wrap as a
+        /// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />.</param>
+        public SelectingItemsControlSelectionAdapter(SelectingItemsControl selector)
+        {
+            SelectorControl = selector;
+        }
+
+        /// <summary>
+        /// Gets or sets the selected item of the selection adapter.
+        /// </summary>
+        /// <value>The selected item of the underlying selection adapter.</value>
+        public object SelectedItem
+        {
+            get
+            {
+                return SelectorControl?.SelectedItem;
+            }
+
+            set
+            {
+                IgnoringSelectionChanged = true;
+                if (SelectorControl != null)
+                {
+                    SelectorControl.SelectedItem = value;
+                }
+
+                // Attempt to reset the scroll viewer's position
+                if (value == null)
+                {
+                    ResetScrollViewer();
+                }
+
+                IgnoringSelectionChanged = false;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets a collection that is used to generate the content of
+        /// the selection adapter.
+        /// </summary>
+        /// <value>The collection used to generate content for the selection
+        /// adapter.</value>
+        public IEnumerable Items
+        {
+            get
+            {
+                return SelectorControl?.Items;
+            }
+            set
+            {
+                if (SelectorControl != null)
+                {
+                    SelectorControl.Items = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// If the control contains a ScrollViewer, this will reset the viewer 
+        /// to be scrolled to the top.
+        /// </summary>
+        private void ResetScrollViewer()
+        {
+            if (SelectorControl != null)
+            {
+                ScrollViewer sv = SelectorControl.GetLogicalDescendants().OfType<ScrollViewer>().FirstOrDefault();
+                if (sv != null)
+                {
+                    sv.Offset = new Vector(0, 0);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handles the mouse left button up event on the selector control.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event data.</param>
+        private void OnSelectorPointerReleased(object sender, PointerReleasedEventArgs e)
+        {
+            if (e.MouseButton == MouseButton.Left)
+            {
+                OnCommit();
+            }
+        }
+
+        /// <summary>
+        /// Handles the SelectionChanged event on the SelectingItemsControl control.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The selection changed event data.</param>
+        private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            if (IgnoringSelectionChanged)
+            {
+                return;
+            }
+
+            SelectionChanged?.Invoke(sender, e);
+        }
+
+        /// <summary>
+        /// Increments the
+        /// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
+        /// property of the underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        protected void SelectedIndexIncrement()
+        {
+            if (SelectorControl != null)
+            {
+                SelectorControl.SelectedIndex = SelectorControl.SelectedIndex + 1 >= SelectorControl.ItemCount ? -1 : SelectorControl.SelectedIndex + 1;
+            }
+        }
+
+        /// <summary>
+        /// Decrements the
+        /// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
+        /// property of the underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        protected void SelectedIndexDecrement()
+        {
+            if (SelectorControl != null)
+            {
+                int index = SelectorControl.SelectedIndex;
+                if (index >= 0)
+                {
+                    SelectorControl.SelectedIndex--;
+                }
+                else if (index == -1)
+                {
+                    SelectorControl.SelectedIndex = SelectorControl.ItemCount - 1;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Provides handling for the
+        /// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event that occurs
+        /// when a key is pressed while the drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus.
+        /// </summary>
+        /// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
+        /// that contains data about the
+        /// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event.</param>
+        public void HandleKeyDown(KeyEventArgs e)
+        {
+            switch (e.Key)
+            {
+                case Key.Enter:
+                    OnCommit();
+                    e.Handled = true;
+                    break;
+
+                case Key.Up:
+                    SelectedIndexDecrement();
+                    e.Handled = true;
+                    break;
+
+                case Key.Down:
+                    if ((e.Modifiers & InputModifiers.Alt) == InputModifiers.None)
+                    {
+                        SelectedIndexIncrement();
+                        e.Handled = true;
+                    }
+                    break;
+
+                case Key.Escape:
+                    OnCancel();
+                    e.Handled = true;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Commit" />
+        /// event.
+        /// </summary>
+        protected virtual void OnCommit()
+        {
+            OnCommit(this, new RoutedEventArgs());
+        }
+
+        /// <summary>
+        /// Fires the Commit event.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event data.</param>
+        private void OnCommit(object sender, RoutedEventArgs e)
+        {
+            Commit?.Invoke(sender, e);
+
+            AfterAdapterAction();
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Cancel" />
+        /// event.
+        /// </summary>
+        protected virtual void OnCancel()
+        {
+            OnCancel(this, new RoutedEventArgs());
+        }
+
+        /// <summary>
+        /// Fires the Cancel event.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event data.</param>
+        private void OnCancel(object sender, RoutedEventArgs e)
+        {
+            Cancel?.Invoke(sender, e);
+
+            AfterAdapterAction();
+        }
+
+        /// <summary>
+        /// Change the selection after the actions are complete.
+        /// </summary>
+        private void AfterAdapterAction()
+        {
+            IgnoringSelectionChanged = true;
+            if (SelectorControl != null)
+            {
+                SelectorControl.SelectedItem = null;
+                SelectorControl.SelectedIndex = -1;
+            }
+            IgnoringSelectionChanged = false;
+        }
+    }
+}

+ 5 - 0
src/Avalonia.Controls/Utils/UndoRedoHelper.cs

@@ -91,6 +91,11 @@ namespace Avalonia.Controls.Utils
             }
             }
         }
         }
 
 
+        public void Clear()
+        {
+            _states.Clear();
+        }
+
         bool WeakTimer.IWeakTimerSubscriber.Tick()
         bool WeakTimer.IWeakTimerSubscriber.Tick()
         {
         {
             Snapshot();
             Snapshot();

+ 1 - 1
src/Avalonia.Input/KeyboardDevice.cs

@@ -46,13 +46,13 @@ namespace Avalonia.Input
             if (element != FocusedElement)
             if (element != FocusedElement)
             {
             {
                 var interactive = FocusedElement as IInteractive;
                 var interactive = FocusedElement as IInteractive;
+                FocusedElement = element;
 
 
                 interactive?.RaiseEvent(new RoutedEventArgs
                 interactive?.RaiseEvent(new RoutedEventArgs
                 {
                 {
                     RoutedEvent = InputElement.LostFocusEvent,
                     RoutedEvent = InputElement.LostFocusEvent,
                 });
                 });
 
 
-                FocusedElement = element;
                 interactive = element as IInteractive;
                 interactive = element as IInteractive;
 
 
                 interactive?.RaiseEvent(new GotFocusEventArgs
                 interactive?.RaiseEvent(new GotFocusEventArgs

+ 43 - 0
src/Avalonia.Themes.Default/AutoCompleteBox.xaml

@@ -0,0 +1,43 @@
+<Styles xmlns="https://github.com/avaloniaui">
+  <Style Selector="AutoCompleteBox">
+    <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
+    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
+    <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
+    <Setter Property="Padding" Value="4"/>
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Panel>
+          <TextBox Name="PART_TextBox"
+                   Background="{TemplateBinding Background}" 
+                   BorderBrush="{TemplateBinding BorderBrush}" 
+                   BorderThickness="{TemplateBinding BorderThickness}"
+                   Padding="{TemplateBinding Padding}"
+                   Watermark="{TemplateBinding Watermark}"
+                   DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" />
+          
+          <Popup Name="PART_Popup"
+                 MinWidth="{TemplateBinding Bounds.Width}"
+                 MaxHeight="{TemplateBinding MaxDropDownHeight}"
+                 PlacementTarget="{TemplateBinding}"
+                 StaysOpen="False">
+            <Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
+                    BorderThickness="1">
+              <ListBox Name="PART_SelectingItemsControl"
+                       BorderThickness="0"
+                       Background="{TemplateBinding Background}"
+                       Foreground="{TemplateBinding Foreground}"
+                       ItemTemplate="{TemplateBinding ItemTemplate}"
+                       MemberSelector="{TemplateBinding ValueMemberSelector}"
+                       ScrollViewer.HorizontalScrollBarVisibility="Auto"
+                       ScrollViewer.VerticalScrollBarVisibility="Auto" />
+            </Border>
+          </Popup>
+        </Panel>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+  
+  <Style Selector="AutoCompleteBox ListBoxItem:pointerover">
+    <Setter Property="Background" Value="#ffd0d0d0"/>
+  </Style>
+</Styles>

+ 2 - 1
src/Avalonia.Themes.Default/DefaultTheme.xaml

@@ -23,7 +23,7 @@
   <StyleInclude Source="resm:Avalonia.Themes.Default.RadioButton.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.RadioButton.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.RepeatButton.xaml?assembly=Avalonia.Themes.Default" />
   <StyleInclude Source="resm:Avalonia.Themes.Default.RepeatButton.xaml?assembly=Avalonia.Themes.Default" />
   <StyleInclude Source="resm:Avalonia.Themes.Default.Separator.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.Separator.xaml?assembly=Avalonia.Themes.Default"/>
-  <StyleInclude Source="resm:Avalonia.Themes.Default.Slider.xaml?assembly=Avalonia.Themes.Default"/>  
+  <StyleInclude Source="resm:Avalonia.Themes.Default.Slider.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ScrollBar.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ScrollBar.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ScrollViewer.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ScrollViewer.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.TabStrip.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.TabStrip.xaml?assembly=Avalonia.Themes.Default"/>
@@ -44,4 +44,5 @@
   <StyleInclude Source="resm:Avalonia.Themes.Default.DatePicker.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.DatePicker.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ButtonSpinner.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ButtonSpinner.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.NumericUpDown.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.NumericUpDown.xaml?assembly=Avalonia.Themes.Default"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Default.AutoCompleteBox.xaml?assembly=Avalonia.Themes.Default"/>
 </Styles>
 </Styles>

+ 1 - 1
src/Avalonia.Visuals/Media/GradientBrush.cs

@@ -22,7 +22,7 @@ namespace Avalonia.Media
         /// Defines the <see cref="GradientStops"/> property.
         /// Defines the <see cref="GradientStops"/> property.
         /// </summary>
         /// </summary>
         public static readonly StyledProperty<IList<GradientStop>> GradientStopsProperty =
         public static readonly StyledProperty<IList<GradientStop>> GradientStopsProperty =
-            AvaloniaProperty.Register<GradientBrush, IList<GradientStop>>(nameof(Opacity));
+            AvaloniaProperty.Register<GradientBrush, IList<GradientStop>>(nameof(GradientStops));
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="GradientBrush"/> class.
         /// Initializes a new instance of the <see cref="GradientBrush"/> class.

+ 1042 - 0
tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs

@@ -0,0 +1,1042 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+using Avalonia.Markup.Xaml.Data;
+using Avalonia.Platform;
+using Avalonia.Threading;
+using Avalonia.UnitTests;
+using Moq;
+using Xunit;
+using System.Collections.ObjectModel;
+
+namespace Avalonia.Controls.UnitTests
+{
+    public class AutoCompleteBoxTests
+    {
+        [Fact]
+        public void Search_Filters()
+        {
+            Assert.True(GetFilter(AutoCompleteFilterMode.Contains)("am", "name"));
+            Assert.True(GetFilter(AutoCompleteFilterMode.Contains)("AME", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.Contains)("hello", "name"));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.ContainsCaseSensitive)("na", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.ContainsCaseSensitive)("AME", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.ContainsCaseSensitive)("hello", "name"));
+
+            Assert.Null(GetFilter(AutoCompleteFilterMode.Custom));
+            Assert.Null(GetFilter(AutoCompleteFilterMode.None));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.Equals)("na", "na"));
+            Assert.True(GetFilter(AutoCompleteFilterMode.Equals)("na", "NA"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.Equals)("hello", "name"));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive)("na", "na"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive)("na", "NA"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive)("hello", "name"));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.StartsWith)("na", "name"));
+            Assert.True(GetFilter(AutoCompleteFilterMode.StartsWith)("NAM", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.StartsWith)("hello", "name"));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.StartsWithCaseSensitive)("na", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithCaseSensitive)("NAM", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithCaseSensitive)("hello", "name"));
+        }
+
+        [Fact]
+        public void Ordinal_Search_Filters()
+        {
+            Assert.True(GetFilter(AutoCompleteFilterMode.ContainsOrdinal)("am", "name"));
+            Assert.True(GetFilter(AutoCompleteFilterMode.ContainsOrdinal)("AME", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.ContainsOrdinal)("hello", "name"));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.ContainsOrdinalCaseSensitive)("na", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.ContainsOrdinalCaseSensitive)("AME", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.ContainsOrdinalCaseSensitive)("hello", "name"));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.EqualsOrdinal)("na", "na"));
+            Assert.True(GetFilter(AutoCompleteFilterMode.EqualsOrdinal)("na", "NA"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.EqualsOrdinal)("hello", "name"));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.EqualsOrdinalCaseSensitive)("na", "na"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.EqualsOrdinalCaseSensitive)("na", "NA"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.EqualsOrdinalCaseSensitive)("hello", "name"));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.StartsWithOrdinal)("na", "name"));
+            Assert.True(GetFilter(AutoCompleteFilterMode.StartsWithOrdinal)("NAM", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithOrdinal)("hello", "name"));
+
+            Assert.True(GetFilter(AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive)("na", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive)("NAM", "name"));
+            Assert.False(GetFilter(AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive)("hello", "name"));
+        }
+
+        [Fact]
+        public void Fires_DropDown_Events()
+        {
+            RunTest((control, textbox) =>
+            {
+                bool openEvent = false;
+                bool closeEvent = false;
+                control.DropDownOpened += (s, e) => openEvent = true;
+                control.DropDownClosed += (s, e) => closeEvent = true;
+                control.Items = CreateSimpleStringArray();
+
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.True(control.SearchText == "a");
+                Assert.True(control.IsDropDownOpen);
+                Assert.True(openEvent);
+
+                textbox.Text = String.Empty;
+                Dispatcher.UIThread.RunJobs();
+                Assert.True(control.SearchText == String.Empty);
+                Assert.False(control.IsDropDownOpen);
+                Assert.True(closeEvent);
+            });
+        }
+
+        [Fact]
+        public void Text_Completion_Via_Text_Property()
+        {
+            RunTest((control, textbox) =>
+            {
+                control.IsTextCompletionEnabled = true;
+
+                Assert.Equal(String.Empty, control.Text);
+                control.Text = "close";
+                Assert.NotNull(control.SelectedItem);
+            });
+        }
+
+        [Fact]
+        public void Text_Completion_Selects_Text()
+        {
+            RunTest((control, textbox) =>
+            {
+                control.IsTextCompletionEnabled = true;
+
+                textbox.Text = "ac";
+                textbox.SelectionEnd = textbox.SelectionStart = 2;
+                Dispatcher.UIThread.RunJobs();
+
+                Assert.True(control.IsDropDownOpen);
+                Assert.True(Math.Abs(textbox.SelectionEnd - textbox.SelectionStart) > 2);
+            });
+        }
+
+        [Fact]
+        public void TextChanged_Event_Fires()
+        {
+            RunTest((control, textbox) =>
+            {
+                bool textChanged = false;
+                control.TextChanged += (s, e) => textChanged = true;
+
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.True(textChanged);
+
+                textChanged = false;
+                control.Text = "conversati";
+                Dispatcher.UIThread.RunJobs();
+                Assert.True(textChanged);
+
+                textChanged = false;
+                control.Text = null;
+                Dispatcher.UIThread.RunJobs();
+                Assert.True(textChanged);
+            });
+        }
+
+        [Fact]
+        public void MinimumPrefixLength_Works()
+        {
+            RunTest((control, textbox) =>
+            {
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.True(control.IsDropDownOpen);
+
+
+                textbox.Text = String.Empty;
+                Dispatcher.UIThread.RunJobs();
+                Assert.False(control.IsDropDownOpen);
+
+                control.MinimumPrefixLength = 3;
+
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.False(control.IsDropDownOpen);
+
+                textbox.Text = "acc";
+                Dispatcher.UIThread.RunJobs();
+                Assert.True(control.IsDropDownOpen);
+            });
+        }
+
+        [Fact]
+        public void Can_Cancel_DropDown_Opening()
+        {
+            RunTest((control, textbox) =>
+            {
+                control.DropDownOpening += (s, e) => e.Cancel = true;
+
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.False(control.IsDropDownOpen);
+            });
+        }
+
+        [Fact]
+        public void Can_Cancel_DropDown_Closing()
+        {
+            RunTest((control, textbox) =>
+            {
+                control.DropDownClosing += (s, e) => e.Cancel = true;
+
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.True(control.IsDropDownOpen);
+
+                control.IsDropDownOpen = false;
+                Assert.True(control.IsDropDownOpen);
+            });
+        }
+
+        [Fact]
+        public void Can_Cancel_Population()
+        {
+            RunTest((control, textbox) =>
+            {
+                bool populating = false;
+                bool populated = false;
+                control.FilterMode = AutoCompleteFilterMode.None;
+                control.Populating += (s, e) =>
+                {
+                    e.Cancel = true;
+                    populating = true;
+                };
+                control.Populated += (s, e) => populated = true;
+
+                textbox.Text = "accounti";
+                Dispatcher.UIThread.RunJobs();
+
+                Assert.True(populating);
+                Assert.False(populated);
+            });
+        }
+
+        [Fact]
+        public void Custom_Population_Supported()
+        {
+            RunTest((control, textbox) =>
+            {
+                string custom = "Custom!";
+                string search = "accounti";
+                bool populated = false;
+                bool populatedOk = false;
+                control.FilterMode = AutoCompleteFilterMode.None;
+                control.Populating += (s, e) =>
+                {
+                    control.Items = new string[] { custom };
+                    Assert.Equal(search, e.Parameter);
+                };
+                control.Populated += (s, e) =>
+                {
+                    populated = true;
+                    ReadOnlyCollection<object> collection = e.Data as ReadOnlyCollection<object>;
+                    populatedOk = collection != null && collection.Count == 1;
+                };
+
+                textbox.Text = search;
+                Dispatcher.UIThread.RunJobs();
+
+                Assert.True(populated);
+                Assert.True(populatedOk);
+            });
+        }
+
+        [Fact]
+        public void Text_Completion()
+        {
+            RunTest((control, textbox) =>
+            {
+                control.IsTextCompletionEnabled = true;
+                textbox.Text = "accounti";
+                textbox.SelectionStart = textbox.SelectionEnd = textbox.Text.Length;
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal("accounti", control.SearchText);
+                Assert.Equal("accounting", textbox.Text);
+            });
+        }
+
+        [Fact]
+        public void String_Search()
+        {
+            RunTest((control, textbox) =>
+            {
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "acc";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "cook";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "accept";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "cook";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+            });
+        }
+
+        [Fact]
+        public void Item_Search()
+        {
+            RunTest((control, textbox) =>
+            {
+                control.FilterMode = AutoCompleteFilterMode.Custom;
+                control.ItemFilter = (search, item) =>
+                {
+                    string s = item as string;
+                    return s == null ? false : true;
+                };
+
+                // Just set to null briefly to exercise that code path
+                AutoCompleteFilterPredicate<object> filter = control.ItemFilter;
+                Assert.NotNull(filter);
+                control.ItemFilter = null;
+                Assert.Null(control.ItemFilter);
+                control.ItemFilter = filter;
+                Assert.NotNull(control.ItemFilter);
+
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "acc";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "a";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "cook";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "accept";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+
+                textbox.Text = "cook";
+                Dispatcher.UIThread.RunJobs();
+                Assert.Equal(textbox.Text, control.Text);
+            });
+        }
+        
+        /// <summary>
+        /// Retrieves a defined predicate filter through a new AutoCompleteBox 
+        /// control instance.
+        /// </summary>
+        /// <param name="mode">The FilterMode of interest.</param>
+        /// <returns>Returns the predicate instance.</returns>
+        private static AutoCompleteFilterPredicate<string> GetFilter(AutoCompleteFilterMode mode)
+        {
+            return new AutoCompleteBox { FilterMode = mode }
+                .TextFilter;
+        }
+
+        /// <summary>
+        /// Creates a large list of strings for AutoCompleteBox testing.
+        /// </summary>
+        /// <returns>Returns a new List of string values.</returns>
+        private IList<string> CreateSimpleStringArray()
+        {
+            return new List<string>
+            {
+            "a",
+            "abide",
+            "able",
+            "about",
+            "above",
+            "absence",
+            "absurd",
+            "accept",
+            "acceptance",
+            "accepted",
+            "accepting",
+            "access",
+            "accessed",
+            "accessible",
+            "accident",
+            "accidentally",
+            "accordance",
+            "account",
+            "accounting",
+            "accounts",
+            "accusation",
+            "accustomed",
+            "ache",
+            "across",
+            "act",
+            "active",
+            "actual",
+            "actually",
+            "ada",
+            "added",
+            "adding",
+            "addition",
+            "additional",
+            "additions",
+            "address",
+            "addressed",
+            "addresses",
+            "addressing",
+            "adjourn",
+            "adoption",
+            "advance",
+            "advantage",
+            "adventures",
+            "advice",
+            "advisable",
+            "advise",
+            "affair",
+            "affectionately",
+            "afford",
+            "afore",
+            "afraid",
+            "after",
+            "afterwards",
+            "again",
+            "against",
+            "age",
+            "aged",
+            "agent",
+            "ago",
+            "agony",
+            "agree",
+            "agreed",
+            "agreement",
+            "ah",
+            "ahem",
+            "air",
+            "airs",
+            "ak",
+            "alarm",
+            "alarmed",
+            "alas",
+            "alice",
+            "alive",
+            "all",
+            "allow",
+            "almost",
+            "alone",
+            "along",
+            "aloud",
+            "already",
+            "also",
+            "alteration",
+            "altered",
+            "alternate",
+            "alternately",
+            "altogether",
+            "always",
+            "am",
+            "ambition",
+            "among",
+            "an",
+            "ancient",
+            "and",
+            "anger",
+            "angrily",
+            "angry",
+            "animal",
+            "animals",
+            "ann",
+            "annoy",
+            "annoyed",
+            "another",
+            "answer",
+            "answered",
+            "answers",
+            "antipathies",
+            "anxious",
+            "anxiously",
+            "any",
+            "anyone",
+            "anything",
+            "anywhere",
+            "appealed",
+            "appear",
+            "appearance",
+            "appeared",
+            "appearing",
+            "appears",
+            "applause",
+            "apple",
+            "apples",
+            "applicable",
+            "apply",
+            "approach",
+            "arch",
+            "archbishop",
+            "arches",
+            "archive",
+            "are",
+            "argue",
+            "argued",
+            "argument",
+            "arguments",
+            "arise",
+            "arithmetic",
+            "arm",
+            "arms",
+            "around",
+            "arranged",
+            "array",
+            "arrived",
+            "arrow",
+            "arrum",
+            "as",
+            "ascii",
+            "ashamed",
+            "ask",
+            "askance",
+            "asked",
+            "asking",
+            "asleep",
+            "assembled",
+            "assistance",
+            "associated",
+            "at",
+            "ate",
+            "atheling",
+            "atom",
+            "attached",
+            "attempt",
+            "attempted",
+            "attempts",
+            "attended",
+            "attending",
+            "attends",
+            "audibly",
+            "australia",
+            "author",
+            "authority",
+            "available",
+            "avoid",
+            "away",
+            "awfully",
+            "axes",
+            "axis",
+            "b",
+            "baby",
+            "back",
+            "backs",
+            "bad",
+            "bag",
+            "baked",
+            "balanced",
+            "bank",
+            "banks",
+            "banquet",
+            "bark",
+            "barking",
+            "barley",
+            "barrowful",
+            "based",
+            "bat",
+            "bathing",
+            "bats",
+            "bawled",
+            "be",
+            "beak",
+            "bear",
+            "beast",
+            "beasts",
+            "beat",
+            "beating",
+            "beau",
+            "beauti",
+            "beautiful",
+            "beautifully",
+            "beautify",
+            "became",
+            "because",
+            "become",
+            "becoming",
+            "bed",
+            "beds",
+            "bee",
+            "been",
+            "before",
+            "beg",
+            "began",
+            "begged",
+            "begin",
+            "beginning",
+            "begins",
+            "begun",
+            "behead",
+            "beheaded",
+            "beheading",
+            "behind",
+            "being",
+            "believe",
+            "believed",
+            "bells",
+            "belong",
+            "belongs",
+            "beloved",
+            "below",
+            "belt",
+            "bend",
+            "bent",
+            "besides",
+            "best",
+            "better",
+            "between",
+            "bill",
+            "binary",
+            "bird",
+            "birds",
+            "birthday",
+            "bit",
+            "bite",
+            "bitter",
+            "blacking",
+            "blades",
+            "blame",
+            "blasts",
+            "bleeds",
+            "blew",
+            "blow",
+            "blown",
+            "blows",
+            "body",
+            "boldly",
+            "bone",
+            "bones",
+            "book",
+            "books",
+            "boon",
+            "boots",
+            "bore",
+            "both",
+            "bother",
+            "bottle",
+            "bottom",
+            "bough",
+            "bound",
+            "bowed",
+            "bowing",
+            "box",
+            "boxed",
+            "boy",
+            "brain",
+            "branch",
+            "branches",
+            "brandy",
+            "brass",
+            "brave",
+            "breach",
+            "bread",
+            "break",
+            "breath",
+            "breathe",
+            "breeze",
+            "bright",
+            "brightened",
+            "bring",
+            "bringing",
+            "bristling",
+            "broke",
+            "broken",
+            "brother",
+            "brought",
+            "brown",
+            "brush",
+            "brushing",
+            "burn",
+            "burning",
+            "burnt",
+            "burst",
+            "bursting",
+            "busily",
+            "business",
+            "business@pglaf",
+            "busy",
+            "but",
+            "butter",
+            "buttercup",
+            "buttered",
+            "butterfly",
+            "buttons",
+            "by",
+            "bye",
+            "c",
+            "cackled",
+            "cake",
+            "cakes",
+            "calculate",
+            "calculated",
+            "call",
+            "called",
+            "calling",
+            "calmly",
+            "came",
+            "camomile",
+            "can",
+            "canary",
+            "candle",
+            "cannot",
+            "canterbury",
+            "canvas",
+            "capering",
+            "capital",
+            "card",
+            "cardboard",
+            "cards",
+            "care",
+            "carefully",
+            "cares",
+            "carried",
+            "carrier",
+            "carroll",
+            "carry",
+            "carrying",
+            "cart",
+            "cartwheels",
+            "case",
+            "cat",
+            "catch",
+            "catching",
+            "caterpillar",
+            "cats",
+            "cattle",
+            "caucus",
+            "caught",
+            "cauldron",
+            "cause",
+            "caused",
+            "cautiously",
+            "cease",
+            "ceiling",
+            "centre",
+            "certain",
+            "certainly",
+            "chain",
+            "chains",
+            "chair",
+            "chance",
+            "chanced",
+            "change",
+            "changed",
+            "changes",
+            "changing",
+            "chapter",
+            "character",
+            "charge",
+            "charges",
+            "charitable",
+            "charities",
+            "chatte",
+            "cheap",
+            "cheated",
+            "check",
+            "checked",
+            "checks",
+            "cheeks",
+            "cheered",
+            "cheerfully",
+            "cherry",
+            "cheshire",
+            "chief",
+            "child",
+            "childhood",
+            "children",
+            "chimney",
+            "chimneys",
+            "chin",
+            "choice",
+            "choke",
+            "choked",
+            "choking",
+            "choose",
+            "choosing",
+            "chop",
+            "chorus",
+            "chose",
+            "christmas",
+            "chrysalis",
+            "chuckled",
+            "circle",
+            "circumstances",
+            "city",
+            "civil",
+            "claim",
+            "clamour",
+            "clapping",
+            "clasped",
+            "classics",
+            "claws",
+            "clean",
+            "clear",
+            "cleared",
+            "clearer",
+            "clearly",
+            "clever",
+            "climb",
+            "clinging",
+            "clock",
+            "close",
+            "closed",
+            "closely",
+            "closer",
+            "clubs",
+            "coast",
+            "coaxing",
+            "codes",
+            "coils",
+            "cold",
+            "collar",
+            "collected",
+            "collection",
+            "come",
+            "comes",
+            "comfits",
+            "comfort",
+            "comfortable",
+            "comfortably",
+            "coming",
+            "commercial",
+            "committed",
+            "common",
+            "commotion",
+            "company",
+            "compilation",
+            "complained",
+            "complaining",
+            "completely",
+            "compliance",
+            "comply",
+            "complying",
+            "compressed",
+            "computer",
+            "computers",
+            "concept",
+            "concerning",
+            "concert",
+            "concluded",
+            "conclusion",
+            "condemn",
+            "conduct",
+            "confirmation",
+            "confirmed",
+            "confused",
+            "confusing",
+            "confusion",
+            "conger",
+            "conqueror",
+            "conquest",
+            "consented",
+            "consequential",
+            "consider",
+            "considerable",
+            "considered",
+            "considering",
+            "constant",
+            "consultation",
+            "contact",
+            "contain",
+            "containing",
+            "contempt",
+            "contemptuous",
+            "contemptuously",
+            "content",
+            "continued",
+            "contract",
+            "contradicted",
+            "contributions",
+            "conversation",
+            "conversations",
+            "convert",
+            "cook",
+            "cool",
+            "copied",
+            "copies",
+            "copy",
+            "copying",
+            "copyright",
+            "corner",
+            "corners",
+            "corporation",
+            "corrupt",
+            "cost",
+            "costs",
+            "could",
+            "couldn",
+            "counting",
+            "countries",
+            "country",
+            "couple",
+            "couples",
+            "courage",
+            "course",
+            "court",
+            "courtiers",
+            "coward",
+            "crab",
+            "crash",
+            "crashed",
+            "crawled",
+            "crawling",
+            "crazy",
+            "created",
+            "creating",
+            "creation",
+            "creature",
+            "creatures",
+            "credit",
+            "creep",
+            "crept",
+            "cried",
+            "cries",
+            "crimson",
+            "critical",
+            "crocodile",
+            "croquet",
+            "croqueted",
+            "croqueting",
+            "cross",
+            "crossed",
+            "crossly",
+            "crouched",
+            "crowd",
+            "crowded",
+            "crown",
+            "crumbs",
+            "crust",
+            "cry",
+            "crying",
+            "cucumber",
+            "cunning",
+            "cup",
+            "cupboards",
+            "cur",
+            "curiosity",
+            "curious",
+            "curiouser",
+            "curled",
+            "curls",
+            "curly",
+            "currants",
+            "current",
+            "curtain",
+            "curtsey",
+            "curtseying",
+            "curving",
+            "cushion",
+            "custard",
+            "custody",
+            "cut",
+            "cutting",
+            };
+        }
+        private void RunTest(Action<AutoCompleteBox, TextBox> test)
+        {
+            using (UnitTestApplication.Start(Services))
+            {
+                AutoCompleteBox control = CreateControl();
+                control.Items = CreateSimpleStringArray();
+                TextBox textBox = GetTextBox(control);
+                Dispatcher.UIThread.RunJobs();
+                test.Invoke(control, textBox);
+            }
+        }
+
+        private static TestServices Services => TestServices.StyledWindow;
+
+        /*private static TestServices Services => TestServices.MockThreadingInterface.With(
+            standardCursorFactory: Mock.Of<IStandardCursorFactory>(),
+            windowingPlatform: new MockWindowingPlatform());*/
+
+        private AutoCompleteBox CreateControl()
+        {
+            var datePicker =
+                new AutoCompleteBox
+                {
+                    Template = CreateTemplate()
+                };
+
+            datePicker.ApplyTemplate();
+            return datePicker;
+        }
+        private TextBox GetTextBox(AutoCompleteBox control)
+        {
+            return control.GetTemplateChildren()
+                          .OfType<TextBox>()
+                          .First();
+        }
+        private IControlTemplate CreateTemplate()
+        {
+            return new FuncControlTemplate<AutoCompleteBox>(control =>
+            {
+                var textBox =
+                    new TextBox
+                    {
+                        Name = "PART_TextBox"
+                    };
+                var listbox =
+                    new ListBox
+                    {
+                        Name = "PART_SelectingItemsControl"
+                    };
+                var popup =
+                    new Popup
+                    {
+                        Name = "PART_Popup"
+                    };
+
+                var panel = new Panel();
+                panel.Children.Add(textBox);
+                panel.Children.Add(popup);
+                panel.Children.Add(listbox);
+
+                return panel;
+            });
+        }
+    }
+}