Skip to content

Commit 32d66f6

Browse files
2 parents 674f905 + aeb7710 commit 32d66f6

2 files changed

Lines changed: 83 additions & 62 deletions

File tree

Lines changed: 82 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
using Microsoft.UI.Xaml;
22
using Microsoft.UI.Xaml.Controls;
3+
using Microsoft.UI.Xaml.Media;
34
using System.Collections.Generic;
45
using System.Linq;
56
using Windows.System;
67

78
namespace AK.Toolkit.WinUI3;
89

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

27+
/// <summary>
28+
/// Identifies the <see cref="SuggestionForeground"/> dependency property.
29+
/// </summary>
30+
public static readonly DependencyProperty SuggestionForegroundProperty =
31+
DependencyProperty.Register(
32+
nameof(SuggestionForeground),
33+
typeof(Brush),
34+
typeof(AutoCompleteTextBox),
35+
new PropertyMetadata(null));
36+
3037
/// <summary>
3138
/// Identifies the <see cref="SuggestionsSource"/> dependency property.
3239
/// </summary>
@@ -66,6 +73,15 @@ public bool IsSuggestionCaseSensitive
6673
set => SetValue(IsSuggestionCaseSensitiveProperty, value);
6774
}
6875

76+
/// <summary>
77+
/// Gets or sets a brush that describes the suggestion foreground color.
78+
/// </summary>
79+
public Brush SuggestionForeground
80+
{
81+
get => (Brush)GetValue(SuggestionForegroundProperty);
82+
set => SetValue(SuggestionForegroundProperty, value);
83+
}
84+
6985
/// <summary>
7086
/// Gets or sets a collection of strings as a source of suggestions.
7187
/// </summary>
@@ -86,46 +102,56 @@ public string SuggestionSuffix
86102

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

89-
private string OriginalPlaceholderText { get; set; } = string.Empty;
90-
91-
private TextBlock? PlaceholderControl { get; set; }
105+
private TextBox SuggestionTextBox { get; } = new TextBox();
92106

93107
protected override void OnApplyTemplate()
94108
{
95109
base.OnApplyTemplate();
96110

97-
PlaceholderControl = GetTemplateChild(PlaceholderControlName) as TextBlock;
98-
99-
if (PlaceholderControl is not null)
111+
if (GetTemplateChild(PlaceholderControlName) is TextBlock placeHolder)
100112
{
101-
OriginalPlaceholderText = PlaceholderControl.Text;
113+
SuggestionTextBox.FontFamily = FontFamily;
114+
SuggestionTextBox.FontSize = FontSize;
115+
SuggestionTextBox.FontStyle = FontStyle;
116+
SuggestionTextBox.FontWeight = FontWeight;
117+
SuggestionTextBox.FontStretch = FontStretch;
118+
119+
SuggestionTextBox.Foreground = SuggestionForeground;
120+
SuggestionTextBox.IsHitTestVisible = false;
121+
SuggestionTextBox.Text = string.Empty;
122+
123+
Grid.SetColumn(SuggestionTextBox, Grid.GetColumn(placeHolder));
124+
Grid.SetColumnSpan(SuggestionTextBox, Grid.GetColumnSpan(placeHolder));
125+
Grid.SetRow(SuggestionTextBox, Grid.GetRow(placeHolder));
126+
Grid.SetRowSpan(SuggestionTextBox, Grid.GetRowSpan(placeHolder));
127+
128+
if (VisualTreeHelper.GetParent(placeHolder) is Grid parentGrid)
129+
{
130+
parentGrid.Children.Insert(0, SuggestionTextBox);
131+
}
132+
133+
TextChanged += (s, e) => UpdateSuggestion();
134+
135+
LostFocus += (s, e) => HideSuggestionControl();
102136

103-
TextChanged += (s, e) => UpdateSuggestion(acceptSuggestion: false);
137+
GotFocus += (s, e) => UpdateSuggestion();
104138

105139
KeyDown += (s, e) =>
106140
{
107141
if (e.Key is VirtualKey.Right)
108142
{
109-
UpdateSuggestion(acceptSuggestion: true);
143+
AcceptSuggestion();
110144
}
111145
};
112146

113-
LostFocus += (s, e) =>
114-
{
115-
if (Text.Length > 0)
116-
{
117-
PlaceholderControl.Visibility = Visibility.Collapsed;
118-
}
119-
else
120-
{
121-
UpdatePlaceholderControl(OriginalPlaceholderText, Visibility.Visible);
122-
}
123-
};
124-
125-
GettingFocus += (s, e) => UpdateSuggestion(acceptSuggestion: false);
147+
UpdateSuggestion();
126148
}
127149
}
128150

151+
private void HideSuggestionControl() => SuggestionTextBox.Visibility = Visibility.Collapsed;
152+
153+
private void ShowSuggestionControl() => SuggestionTextBox.Visibility = Visibility.Visible;
154+
129155
private static string GetSuggestion(string input, bool ignoreCase, IEnumerable<string> suggestionsSource)
130156
{
131157
string suggestion = string.Empty;
@@ -134,7 +160,7 @@ private static string GetSuggestion(string input, bool ignoreCase, IEnumerable<s
134160
{
135161
string? result = suggestionsSource.FirstOrDefault(x => x.StartsWith(input, ignoreCase, culture: null));
136162

137-
if (result is not null)
163+
if (result is not null && result.Equals(input) is not true)
138164
{
139165
suggestion = result;
140166
}
@@ -143,48 +169,46 @@ private static string GetSuggestion(string input, bool ignoreCase, IEnumerable<s
143169
return suggestion;
144170
}
145171

146-
private void UpdatePlaceholderControl(string text, Visibility visibility)
172+
private void AcceptSuggestion()
147173
{
148-
if (PlaceholderControl is not null)
174+
ClearSuggestion();
175+
176+
bool ignoreCase = (IsSuggestionCaseSensitive is false);
177+
string suggestion = GetSuggestion(Text, ignoreCase, SuggestionsSource);
178+
if (suggestion.Length > 0)
149179
{
150-
PlaceholderControl.Text = text;
151-
PlaceholderControl.Visibility = visibility;
180+
Text = suggestion;
181+
LastAcceptedSuggestion = Text;
182+
SelectionStart = Text.Length;
152183
}
153184
}
154185

155-
private void UpdateSuggestion(bool acceptSuggestion)
186+
private void ClearSuggestion()
156187
{
157-
if (Text.Length == 0 || LastAcceptedSuggestion.Equals(Text) is not true)
188+
SuggestionTextBox.Text = string.Empty;
189+
LastAcceptedSuggestion = string.Empty;
190+
}
191+
192+
private void UpdateSuggestion()
193+
{
194+
ShowSuggestionControl();
195+
196+
string suggestion = string.Empty;
197+
198+
if (LastAcceptedSuggestion.Equals(Text) is not true)
158199
{
159200
bool ignoreCase = (IsSuggestionCaseSensitive is false);
160-
string suggestion = GetSuggestion(Text, ignoreCase, SuggestionsSource);
201+
suggestion = GetSuggestion(Text, ignoreCase, SuggestionsSource);
161202

162203
if (suggestion.Length > 0)
163204
{
164-
string text = suggestion[Text.Length..].PadLeft(suggestion.Length);
165-
text += SuggestionSuffix;
166-
UpdatePlaceholderControl(text, Visibility.Visible);
167-
}
168-
else if (Text.Length == 0)
169-
{
170-
UpdatePlaceholderControl(OriginalPlaceholderText, Visibility.Visible);
171-
}
172-
else
173-
{
174-
UpdatePlaceholderControl(OriginalPlaceholderText, Visibility.Collapsed);
205+
SuggestionTextBox.Text = $"{Text}{suggestion[Text.Length..]}{SuggestionSuffix}";
175206
}
207+
}
176208

177-
if (acceptSuggestion is true && suggestion.Length > 0)
178-
{
179-
UpdatePlaceholderControl(OriginalPlaceholderText, Visibility.Collapsed);
180-
Text = suggestion;
181-
LastAcceptedSuggestion = suggestion;
182-
SelectionStart = Text.Length;
183-
}
184-
else
185-
{
186-
LastAcceptedSuggestion = string.Empty;
187-
}
209+
if (suggestion.Length == 0)
210+
{
211+
ClearSuggestion();
188212
}
189213
}
190-
}
214+
}
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:local="using:AK.Toolkit.WinUI3">
22

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

85
</ResourceDictionary>

0 commit comments

Comments
 (0)