Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewKeepCoding committed Apr 25, 2022
2 parents 674f905 + aeb7710 commit 32d66f6
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 62 deletions.
140 changes: 82 additions & 58 deletions WinUI3/AK.Toolkit.WinUI3.AutoCompleteTextBox/AutoCompleteTextBox.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System.Collections.Generic;
using System.Linq;
using Windows.System;

namespace AK.Toolkit.WinUI3;

/// <summary>
/// A TextBox control that shows a suggestion based on input.
/// The suggestion is shown inside the TextBox control by overriding the placeholder feature.
/// A TextBox control that shows a suggestion "inside it self".
/// Suggestions need to be provided by the SuggestionsSource property.
/// </summary>
/// <remarks>
/// If you need to change the "FontFamily", use a "Monospaced" font. Otherwise the suggestion might show up misaligned.
/// </remarks>
[TemplatePart(Name = PlaceholderControlName, Type = typeof(TextBlock))]
public sealed class AutoCompleteTextBox : TextBox
{
Expand All @@ -27,6 +24,16 @@ public sealed class AutoCompleteTextBox : TextBox
typeof(AutoCompleteTextBox),
new PropertyMetadata(false));

/// <summary>
/// Identifies the <see cref="SuggestionForeground"/> dependency property.
/// </summary>
public static readonly DependencyProperty SuggestionForegroundProperty =
DependencyProperty.Register(
nameof(SuggestionForeground),
typeof(Brush),
typeof(AutoCompleteTextBox),
new PropertyMetadata(null));

/// <summary>
/// Identifies the <see cref="SuggestionsSource"/> dependency property.
/// </summary>
Expand Down Expand Up @@ -66,6 +73,15 @@ public bool IsSuggestionCaseSensitive
set => SetValue(IsSuggestionCaseSensitiveProperty, value);
}

/// <summary>
/// Gets or sets a brush that describes the suggestion foreground color.
/// </summary>
public Brush SuggestionForeground
{
get => (Brush)GetValue(SuggestionForegroundProperty);
set => SetValue(SuggestionForegroundProperty, value);
}

/// <summary>
/// Gets or sets a collection of strings as a source of suggestions.
/// </summary>
Expand All @@ -86,46 +102,56 @@ public string SuggestionSuffix

private string LastAcceptedSuggestion { get; set; } = string.Empty;

private string OriginalPlaceholderText { get; set; } = string.Empty;

private TextBlock? PlaceholderControl { get; set; }
private TextBox SuggestionTextBox { get; } = new TextBox();

protected override void OnApplyTemplate()
{
base.OnApplyTemplate();

PlaceholderControl = GetTemplateChild(PlaceholderControlName) as TextBlock;

if (PlaceholderControl is not null)
if (GetTemplateChild(PlaceholderControlName) is TextBlock placeHolder)
{
OriginalPlaceholderText = PlaceholderControl.Text;
SuggestionTextBox.FontFamily = FontFamily;
SuggestionTextBox.FontSize = FontSize;
SuggestionTextBox.FontStyle = FontStyle;
SuggestionTextBox.FontWeight = FontWeight;
SuggestionTextBox.FontStretch = FontStretch;

SuggestionTextBox.Foreground = SuggestionForeground;
SuggestionTextBox.IsHitTestVisible = false;
SuggestionTextBox.Text = string.Empty;

Grid.SetColumn(SuggestionTextBox, Grid.GetColumn(placeHolder));
Grid.SetColumnSpan(SuggestionTextBox, Grid.GetColumnSpan(placeHolder));
Grid.SetRow(SuggestionTextBox, Grid.GetRow(placeHolder));
Grid.SetRowSpan(SuggestionTextBox, Grid.GetRowSpan(placeHolder));

if (VisualTreeHelper.GetParent(placeHolder) is Grid parentGrid)
{
parentGrid.Children.Insert(0, SuggestionTextBox);
}

TextChanged += (s, e) => UpdateSuggestion();

LostFocus += (s, e) => HideSuggestionControl();

TextChanged += (s, e) => UpdateSuggestion(acceptSuggestion: false);
GotFocus += (s, e) => UpdateSuggestion();

KeyDown += (s, e) =>
{
if (e.Key is VirtualKey.Right)
{
UpdateSuggestion(acceptSuggestion: true);
AcceptSuggestion();
}
};

LostFocus += (s, e) =>
{
if (Text.Length > 0)
{
PlaceholderControl.Visibility = Visibility.Collapsed;
}
else
{
UpdatePlaceholderControl(OriginalPlaceholderText, Visibility.Visible);
}
};

GettingFocus += (s, e) => UpdateSuggestion(acceptSuggestion: false);
UpdateSuggestion();
}
}

private void HideSuggestionControl() => SuggestionTextBox.Visibility = Visibility.Collapsed;

private void ShowSuggestionControl() => SuggestionTextBox.Visibility = Visibility.Visible;

private static string GetSuggestion(string input, bool ignoreCase, IEnumerable<string> suggestionsSource)
{
string suggestion = string.Empty;
Expand All @@ -134,7 +160,7 @@ private static string GetSuggestion(string input, bool ignoreCase, IEnumerable<s
{
string? result = suggestionsSource.FirstOrDefault(x => x.StartsWith(input, ignoreCase, culture: null));

if (result is not null)
if (result is not null && result.Equals(input) is not true)
{
suggestion = result;
}
Expand All @@ -143,48 +169,46 @@ private static string GetSuggestion(string input, bool ignoreCase, IEnumerable<s
return suggestion;
}

private void UpdatePlaceholderControl(string text, Visibility visibility)
private void AcceptSuggestion()
{
if (PlaceholderControl is not null)
ClearSuggestion();

bool ignoreCase = (IsSuggestionCaseSensitive is false);
string suggestion = GetSuggestion(Text, ignoreCase, SuggestionsSource);
if (suggestion.Length > 0)
{
PlaceholderControl.Text = text;
PlaceholderControl.Visibility = visibility;
Text = suggestion;
LastAcceptedSuggestion = Text;
SelectionStart = Text.Length;
}
}

private void UpdateSuggestion(bool acceptSuggestion)
private void ClearSuggestion()
{
if (Text.Length == 0 || LastAcceptedSuggestion.Equals(Text) is not true)
SuggestionTextBox.Text = string.Empty;
LastAcceptedSuggestion = string.Empty;
}

private void UpdateSuggestion()
{
ShowSuggestionControl();

string suggestion = string.Empty;

if (LastAcceptedSuggestion.Equals(Text) is not true)
{
bool ignoreCase = (IsSuggestionCaseSensitive is false);
string suggestion = GetSuggestion(Text, ignoreCase, SuggestionsSource);
suggestion = GetSuggestion(Text, ignoreCase, SuggestionsSource);

if (suggestion.Length > 0)
{
string text = suggestion[Text.Length..].PadLeft(suggestion.Length);
text += SuggestionSuffix;
UpdatePlaceholderControl(text, Visibility.Visible);
}
else if (Text.Length == 0)
{
UpdatePlaceholderControl(OriginalPlaceholderText, Visibility.Visible);
}
else
{
UpdatePlaceholderControl(OriginalPlaceholderText, Visibility.Collapsed);
SuggestionTextBox.Text = $"{Text}{suggestion[Text.Length..]}{SuggestionSuffix}";
}
}

if (acceptSuggestion is true && suggestion.Length > 0)
{
UpdatePlaceholderControl(OriginalPlaceholderText, Visibility.Collapsed);
Text = suggestion;
LastAcceptedSuggestion = suggestion;
SelectionStart = Text.Length;
}
else
{
LastAcceptedSuggestion = string.Empty;
}
if (suggestion.Length == 0)
{
ClearSuggestion();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:local="using:AK.Toolkit.WinUI3">

<Style BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="local:AutoCompleteTextBox">
<!-- "Monospaced" font -->
<Setter Property="FontFamily" Value="MS Gothic" />
</Style>
<Style BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="local:AutoCompleteTextBox" />

</ResourceDictionary>

0 comments on commit 32d66f6

Please sign in to comment.