Skip to content

Commit 5ec45a3

Browse files
corvinszKeboo
andauthored
AutoSuggestBox content alignment and interactive content (#3942)
* Refactor FieldsViewModel and enhance AutoSuggestBox Refactored `FieldsViewModel` to use `CommunityToolkit.Mvvm` attributes, reducing boilerplate code and improving maintainability. Added `AutoSuggestBox3` with dynamic filtering, interactive item templates, and a command to remove suggestions. Enhanced `AutoSuggestBox` behavior to support interactive elements like buttons without closing the popup. Updated `Fields.xaml` to include new `AutoSuggestBox` variations and bindings. Introduced a new `AutoSuggestTextBoxWithInteractiveTemplate` sample to demonstrate interactive item templates. Added tests to validate interactive `AutoSuggestBox` behavior. Performed general cleanup and modernization, including concise syntax and collection initializers. * Make _autoSuggestBox3Suggestions nullable * Add Hyperlink as interactive element in AutoSuggestBox Included System.Windows.Documents namespace to support Hyperlink functionality. Updated IsInteractiveElement method to recognize Hyperlink as an interactive element, alongside ButtonBase, TextBoxBase, and ComboBox. * Adds IsInteractiveElement attached property Introduces an `IsInteractiveElement` attached dependency property to `AutoSuggestBox`, updates `OnLostFocus` to prevent popup closure when the suggestion list is focused, refactors interactivity detection to prioritize the new attached property and perform visual tree traversal, and adds new UI tests to validate this functionality. --------- Co-authored-by: Kevin Bost <kitokeboo@gmail.com>
1 parent c3e6a51 commit 5ec45a3

File tree

7 files changed

+424
-121
lines changed

7 files changed

+424
-121
lines changed
Lines changed: 72 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,35 @@
11
using System.Collections.ObjectModel;
22
using System.Windows.Media;
3+
using CommunityToolkit.Mvvm.ComponentModel;
4+
using CommunityToolkit.Mvvm.Input;
35
using MaterialDesignDemo.Shared.Domain;
46

57
namespace MaterialDesignDemo.Domain;
6-
public class FieldsViewModel : ViewModelBase
8+
public partial class FieldsViewModel : ObservableObject
79
{
8-
private string? _name;
9-
private string? _name2;
10-
private string? _password1 = string.Empty;
11-
private string? _password2 = "pre-filled";
1210
private string? _password1Validated = "pre-filled";
1311
private string? _password2Validated = "pre-filled";
14-
private string? _text1;
15-
private string? _text2;
16-
private ObservableCollection<string>? _autoSuggestBox1Suggestions;
17-
private string? _autoSuggestBox1Text;
1812
private readonly List<string>? _originalAutoSuggestBox1Suggestions;
19-
private ObservableCollection<KeyValuePair<string, Color>>? _autoSuggestBox2Suggestions;
20-
private string? _autoSuggestBox2Text;
2113
private readonly List<KeyValuePair<string, Color>>? _originalAutoSuggestBox2Suggestions;
14+
private readonly List<string> _originalAutoSuggestBox3Suggestions;
2215

23-
public string? Name
24-
{
25-
get => _name;
26-
set => SetProperty(ref _name, value);
27-
}
16+
[ObservableProperty]
17+
private string? _name;
2818

29-
public string? Name2
30-
{
31-
get => _name2;
32-
set => SetProperty(ref _name2, value);
33-
}
19+
[ObservableProperty]
20+
private string? _name2;
3421

35-
public string? Text1
36-
{
37-
get => _text1;
38-
set => SetProperty(ref _text1, value);
39-
}
22+
[ObservableProperty]
23+
private string? _text1;
4024

41-
public string? Text2
42-
{
43-
get => _text2;
44-
set => SetProperty(ref _text2, value);
45-
}
25+
[ObservableProperty]
26+
private string? _text2;
4627

47-
public string? Password1
48-
{
49-
get => _password1;
50-
set => SetProperty(ref _password1, value);
51-
}
28+
[ObservableProperty]
29+
private string? _password1 = string.Empty;
5230

53-
public string? Password2
54-
{
55-
get => _password2;
56-
set => SetProperty(ref _password2, value);
57-
}
31+
[ObservableProperty]
32+
private string? _password2 = "pre-filled";
5833

5934
public string? Password1Validated
6035
{
@@ -80,43 +55,64 @@ public string? Password2Validated
8055

8156
public FieldsTestObject TestObject => new() { Name = "Mr. Test" };
8257

83-
public ObservableCollection<string>? AutoSuggestBox1Suggestions
58+
[ObservableProperty]
59+
private ObservableCollection<string>? _autoSuggestBox1Suggestions;
60+
61+
[ObservableProperty]
62+
private ObservableCollection<KeyValuePair<string, Color>>? _autoSuggestBox2Suggestions;
63+
64+
[ObservableProperty]
65+
private List<string>? _autoSuggestBox3Suggestions;
66+
67+
68+
[ObservableProperty]
69+
private string? _autoSuggestBox1Text;
70+
71+
partial void OnAutoSuggestBox1TextChanged(string? value)
8472
{
85-
get => _autoSuggestBox1Suggestions;
86-
set => SetProperty(ref _autoSuggestBox1Suggestions, value);
73+
if (_originalAutoSuggestBox1Suggestions != null && value != null)
74+
{
75+
var searchResult = _originalAutoSuggestBox1Suggestions.Where(x => IsMatch(x, value));
76+
AutoSuggestBox1Suggestions = new(searchResult);
77+
}
8778
}
8879

89-
public ObservableCollection<KeyValuePair<string, Color>>? AutoSuggestBox2Suggestions
80+
[ObservableProperty]
81+
private string? _autoSuggestBox2Text;
82+
83+
partial void OnAutoSuggestBox2TextChanged(string? value)
9084
{
91-
get => _autoSuggestBox2Suggestions;
92-
set => SetProperty(ref _autoSuggestBox2Suggestions, value);
85+
if (_originalAutoSuggestBox2Suggestions != null && value != null)
86+
{
87+
var searchResult = _originalAutoSuggestBox2Suggestions.Where(x => IsMatch(x.Key, value));
88+
AutoSuggestBox2Suggestions = new(searchResult);
89+
}
9390
}
9491

95-
public string? AutoSuggestBox1Text
92+
[ObservableProperty]
93+
private string? _autoSuggestBox3Text;
94+
95+
partial void OnAutoSuggestBox3TextChanged(string? value)
9696
{
97-
get => _autoSuggestBox1Text;
98-
set
97+
if (value is not null)
9998
{
100-
if (SetProperty(ref _autoSuggestBox1Text, value) &&
101-
_originalAutoSuggestBox1Suggestions != null && value != null)
102-
{
103-
var searchResult = _originalAutoSuggestBox1Suggestions.Where(x => IsMatch(x, value));
104-
AutoSuggestBox1Suggestions = new ObservableCollection<string>(searchResult);
105-
}
99+
var searchResult = _originalAutoSuggestBox3Suggestions.Where(x => IsMatch(x, value));
100+
AutoSuggestBox3Suggestions = new(searchResult);
106101
}
107102
}
108103

109-
public string? AutoSuggestBox2Text
104+
[RelayCommand]
105+
private void RemoveAutoSuggestBox3Suggestion(string suggestion)
110106
{
111-
get => _autoSuggestBox2Text;
112-
set
107+
_originalAutoSuggestBox3Suggestions.Remove(suggestion);
108+
if (string.IsNullOrEmpty(AutoSuggestBox3Text))
109+
{
110+
AutoSuggestBox3Suggestions = new(_originalAutoSuggestBox3Suggestions);
111+
}
112+
else
113113
{
114-
if (SetProperty(ref _autoSuggestBox2Text, value) &&
115-
_originalAutoSuggestBox2Suggestions != null && value != null)
116-
{
117-
var searchResult = _originalAutoSuggestBox2Suggestions.Where(x => IsMatch(x.Key, value));
118-
AutoSuggestBox2Suggestions = new ObservableCollection<KeyValuePair<string, Color>>(searchResult);
119-
}
114+
var searchResult = _originalAutoSuggestBox3Suggestions.Where(x => IsMatch(x, AutoSuggestBox3Text!));
115+
AutoSuggestBox3Suggestions = new(searchResult);
120116
}
121117
}
122118

@@ -128,12 +124,16 @@ public FieldsViewModel()
128124
SetPassword1FromViewModelCommand = new AnotherCommandImplementation(_ => Password1 = "Set from ViewModel!");
129125
SetPassword2FromViewModelCommand = new AnotherCommandImplementation(_ => Password2 = "Set from ViewModel!");
130126

131-
_originalAutoSuggestBox1Suggestions = new List<string>()
132-
{
127+
_originalAutoSuggestBox1Suggestions =
128+
[
133129
"Burger", "Fries", "Shake", "Lettuce"
134-
};
130+
];
135131

136-
_originalAutoSuggestBox2Suggestions = new List<KeyValuePair<string, Color>>(GetColors());
132+
_originalAutoSuggestBox2Suggestions = new(GetColors());
133+
_originalAutoSuggestBox3Suggestions =
134+
[
135+
"jsmith", "jdoe", "mscott", "pparker", "bwilliams", "ljohnson", "abrown", "dlee", "cmiller", "tmoore"
136+
];
137137

138138
AutoSuggestBox1Suggestions = new ObservableCollection<string>(_originalAutoSuggestBox1Suggestions);
139139
}
@@ -158,20 +158,10 @@ private static IEnumerable<KeyValuePair<string, Color>> GetColors()
158158
}
159159
}
160160

161-
public class FieldsTestObject : ViewModelBase
161+
public partial class FieldsTestObject : ObservableObject
162162
{
163+
[ObservableProperty]
163164
private string? _name;
165+
[ObservableProperty]
164166
private string? _content;
165-
166-
public string? Name
167-
{
168-
get => _name;
169-
set => SetProperty(ref _name, value);
170-
}
171-
172-
public string? Content
173-
{
174-
get => _content;
175-
set => SetProperty(ref _content, value);
176-
}
177167
}

src/MainDemo.Wpf/Fields.xaml

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -706,36 +706,36 @@
706706
<WrapPanel>
707707
<StackPanel Width="256" Margin="0,0,16,16">
708708
<TextBlock Margin="0,0,0,8"
709-
VerticalAlignment="Center"
710-
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
711-
Text="Simple source list" />
709+
VerticalAlignment="Center"
710+
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
711+
Text="Simple source list" />
712712

713713
<smtx:XamlDisplay UniqueKey="fields_autosuggestion_1">
714714
<materialDesign:AutoSuggestBox VerticalAlignment="Bottom"
715-
Suggestions="{Binding AutoSuggestBox1Suggestions}"
716-
Text="{Binding AutoSuggestBox1Text, UpdateSourceTrigger=PropertyChanged}" />
715+
Suggestions="{Binding AutoSuggestBox1Suggestions}"
716+
Text="{Binding AutoSuggestBox1Text, UpdateSourceTrigger=PropertyChanged}" />
717717
</smtx:XamlDisplay>
718718
</StackPanel>
719719

720720
<StackPanel Width="256" Margin="0,0,16,16">
721721
<TextBlock Margin="0,0,0,8"
722-
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
723-
Text="AutoSuggestBox with ItemTemplate" />
722+
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
723+
Text="AutoSuggestBox with ItemTemplate" />
724724
<smtx:XamlDisplay UniqueKey="fields_autosuggestion_2">
725725
<materialDesign:AutoSuggestBox materialDesign:HintAssist.HelperText="Select color"
726-
materialDesign:HintAssist.Hint="Color"
727-
materialDesign:TextFieldAssist.HasClearButton="True"
728-
DropDownElevation="Dp0"
729-
Suggestions="{Binding AutoSuggestBox2Suggestions}"
730-
Text="{Binding AutoSuggestBox2Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
731-
ValueMember="Key">
726+
materialDesign:HintAssist.Hint="Color"
727+
materialDesign:TextFieldAssist.HasClearButton="True"
728+
DropDownElevation="Dp0"
729+
Suggestions="{Binding AutoSuggestBox2Suggestions}"
730+
Text="{Binding AutoSuggestBox2Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
731+
ValueMember="Key">
732732
<materialDesign:AutoSuggestBox.ItemTemplate>
733733
<DataTemplate>
734734
<DockPanel>
735735
<Border Width="20"
736-
Height="20"
737-
Background="{Binding Value, Converter={StaticResource ColorToBrushConverter}}"
738-
CornerRadius="10" />
736+
Height="20"
737+
Background="{Binding Value, Converter={StaticResource ColorToBrushConverter}}"
738+
CornerRadius="10" />
739739
<TextBlock Margin="10,0,0,0" Text="{Binding Key}" />
740740
</DockPanel>
741741
</DataTemplate>
@@ -747,23 +747,23 @@
747747

748748
<StackPanel Width="256" Margin="0,0,16,16">
749749
<TextBlock Margin="0,0,0,8"
750-
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
751-
Text="Filled AutoSuggestBox" />
750+
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
751+
Text="Filled AutoSuggestBox" />
752752
<smtx:XamlDisplay UniqueKey="fields_autosuggestion_3">
753753
<materialDesign:AutoSuggestBox materialDesign:HintAssist.Hint="Color"
754-
materialDesign:TextFieldAssist.HasClearButton="True"
755-
DropDownElevation="Dp0"
756-
Style="{StaticResource MaterialDesignFilledAutoSuggestBox}"
757-
Suggestions="{Binding AutoSuggestBox2Suggestions}"
758-
Text="{Binding AutoSuggestBox2Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
759-
ValueMember="Key">
754+
materialDesign:TextFieldAssist.HasClearButton="True"
755+
DropDownElevation="Dp0"
756+
Style="{StaticResource MaterialDesignFilledAutoSuggestBox}"
757+
Suggestions="{Binding AutoSuggestBox2Suggestions}"
758+
Text="{Binding AutoSuggestBox2Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
759+
ValueMember="Key">
760760
<materialDesign:AutoSuggestBox.ItemTemplate>
761761
<DataTemplate>
762762
<DockPanel>
763763
<Border Width="20"
764-
Height="20"
765-
Background="{Binding Value, Converter={StaticResource ColorToBrushConverter}}"
766-
CornerRadius="10" />
764+
Height="20"
765+
Background="{Binding Value, Converter={StaticResource ColorToBrushConverter}}"
766+
CornerRadius="10" />
767767
<TextBlock Margin="10,0,0,0" Text="{Binding Key}" />
768768
</DockPanel>
769769
</DataTemplate>
@@ -774,30 +774,74 @@
774774

775775
<StackPanel Width="256" Margin="0,0,16,16">
776776
<TextBlock Margin="0,0,0,8"
777-
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
778-
Text="Outlined AutoSuggestBox" />
777+
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
778+
Text="Outlined AutoSuggestBox" />
779779
<smtx:XamlDisplay UniqueKey="fields_autosuggestion_4">
780780
<materialDesign:AutoSuggestBox materialDesign:HintAssist.Hint="Color"
781-
materialDesign:TextFieldAssist.HasClearButton="True"
782-
DropDownElevation="Dp0"
783-
Style="{StaticResource MaterialDesignOutlinedAutoSuggestBox}"
784-
Suggestions="{Binding AutoSuggestBox2Suggestions}"
785-
Text="{Binding AutoSuggestBox2Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
786-
ValueMember="Key">
781+
materialDesign:TextFieldAssist.HasClearButton="True"
782+
DropDownElevation="Dp0"
783+
Style="{StaticResource MaterialDesignOutlinedAutoSuggestBox}"
784+
Suggestions="{Binding AutoSuggestBox2Suggestions}"
785+
Text="{Binding AutoSuggestBox2Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
786+
ValueMember="Key">
787787
<materialDesign:AutoSuggestBox.ItemTemplate>
788788
<DataTemplate>
789789
<DockPanel>
790790
<Border Width="20"
791-
Height="20"
792-
Background="{Binding Value, Converter={StaticResource ColorToBrushConverter}}"
793-
CornerRadius="10" />
791+
Height="20"
792+
Background="{Binding Value, Converter={StaticResource ColorToBrushConverter}}"
793+
CornerRadius="10" />
794794
<TextBlock Margin="10,0,0,0" Text="{Binding Key}" />
795795
</DockPanel>
796796
</DataTemplate>
797797
</materialDesign:AutoSuggestBox.ItemTemplate>
798798
</materialDesign:AutoSuggestBox>
799799
</smtx:XamlDisplay>
800800
</StackPanel>
801+
802+
<StackPanel Width="512" Margin="0,0,16,16">
803+
<TextBlock Margin="0,0,0,8"
804+
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
805+
Text="AutoSuggestBox with interactable content" />
806+
<smtx:XamlDisplay UniqueKey="fields_autosuggestion_5">
807+
<materialDesign:AutoSuggestBox materialDesign:HintAssist.Hint="Username"
808+
materialDesign:TextFieldAssist.HasLeadingIcon="True"
809+
materialDesign:TextFieldAssist.LeadingIcon="User"
810+
DropDownBackground="{DynamicResource MaterialDesign.Brush.ToolBar.Background}"
811+
DropDownElevation="Dp24"
812+
DropDownMaxHeight="500"
813+
Suggestions="{Binding AutoSuggestBox3Suggestions}"
814+
Text="{Binding AutoSuggestBox3Text, UpdateSourceTrigger=PropertyChanged}">
815+
<materialDesign:AutoSuggestBox.ItemTemplate>
816+
<DataTemplate>
817+
<Grid HorizontalAlignment="Stretch">
818+
<Grid.ColumnDefinitions>
819+
<ColumnDefinition Width="*" />
820+
<ColumnDefinition Width="auto" />
821+
</Grid.ColumnDefinitions>
822+
<TextBlock Grid.Column="0"
823+
VerticalAlignment="Center"
824+
Text="{Binding .}" />
825+
<Button Grid.Column="1"
826+
Padding="2,0,0,0"
827+
Command="{Binding DataContext.RemoveAutoSuggestBox3SuggestionCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
828+
CommandParameter="{Binding .}"
829+
Focusable="False"
830+
Style="{StaticResource MaterialDesignToolButton}">
831+
<materialDesign:PackIcon Width="16"
832+
Height="16"
833+
Margin="0"
834+
Kind="CloseCircle" />
835+
</Button>
836+
</Grid>
837+
</DataTemplate>
838+
</materialDesign:AutoSuggestBox.ItemTemplate>
839+
</materialDesign:AutoSuggestBox>
840+
</smtx:XamlDisplay>
841+
</StackPanel>
842+
843+
844+
801845
</WrapPanel>
802846
</StackPanel>
803847
</UserControl>

0 commit comments

Comments
 (0)