11using Microsoft . UI . Xaml ;
22using Microsoft . UI . Xaml . Controls ;
3+ using Microsoft . UI . Xaml . Media ;
34using System . Collections . Generic ;
45using System . Linq ;
56using Windows . System ;
67
78namespace 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 ) ) ]
1815public 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+ }
0 commit comments