When using AutoCompleteBox - I have a list of items that have the matching display text - but different ID value.
[{Text:"John",Id:1},{Text:"John",Id:2},{Text:"John Doe",Id:3}]
When I select the second row (John #2) - the first value (John #1) is set to the SelectedItem
property. When selecting a distinct value (John Doe #3) - it works correctly.
My observation is that if there are more matching items - it always takes the first of them.
What can I do so that the correct item (John #2) is set to SelectedItem?
<controls2:AutoCompleteBox
EraseTextIfNull="False"
ValueMemberBinding="{Binding Converter={StaticResource AutocompleteItemTextConverter}}"
ItemsSource="{Binding TestItemSource, RelativeSource={RelativeSource TemplatedParent}}"
SelectedItem="{Binding TestSelectedItem,Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Text="{Binding TestText, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
ItemTemplate="{Binding Source={StaticResource TestItemTemplate}}"
/>
ViewModel:
public class TestItemDto
{
public int Id { get; set; }
public string Text { get; set; }
}
private ObservableCollection<TestItemDto> _testItemSource = new ObservableCollection<TestItemDto>
{
new TestItemDto
{
Id = 1,
Text = "John"
},
new TestItemDto
{
Id = 2,
Text = "John"
},
new TestItemDto
{
Id = 3,
Text = "John Doe"
},
};
public ObservableCollection<TestItemDto> TestItemSource
{
get => _testItemSource;
set
{
_testItemSource = value;
}
}
private string _testText;
public string TestText
{
get { return _testText; }
set
{
_testText = value;
Console.WriteLine($"TestText:{TestText}");
}
}
private TestItemDto _testSelectedItem;
public TestItemDto TestSelectedItem
{
get { return _testSelectedItem; }
set
{
_testSelectedItem = value;
Console.WriteLine($"TestText:{TestSelectedItem?.Id}:{TestSelectedItem?.Text}");
}
}
Converter:
public class AutocompleteItemTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var adresarSearchResultItemDto = value as TestItemDto;
return adresarSearchResultItemDto?.Text;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
The problem was with TryGetMatch
method searching for a matching text and then choosing the first matched item.
I added a priorityView
argument (where SelectedItem
is passed). Therefore before searching in the rest of the ItemSource - first it checks if the currently SelectedItem is already matching the text:
private object TryGetMatch(string searchText, object priorityView,ObservableCollection<object> view, AutoCompleteFilterPredicate<string> predicate)
{
if (priorityView != null)
{
if (predicate(searchText, FormatValue(priorityView)))
{
return priorityView;
}
}
if (view != null && view.Count > 0)
{
foreach (object o in view)
{
if (predicate(searchText, FormatValue(o)))
{
return o;
}
}
}
return null;
}
This was also merged into the original repository