IsEditable combox box (#18094)
* IsEditable combox box with text bindings * Update after #18405 api review * ComboBox tests for IsEditable * remove unneeded error being throw when combox is editable and doesn't have text binding properties * Changes after code review * only do naviagtion check if combo box is editable * Fix editable ComboBox tab navigation * combobox code review improvements --------- Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
This commit is contained in:
@@ -124,7 +124,7 @@
|
||||
</ComboBox.SelectionBoxItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<ComboBox WrapSelection="{Binding WrapSelection}" ItemsSource="{Binding Values}" >
|
||||
<ComboBox WrapSelection="{Binding WrapSelection}" ItemsSource="{Binding Values}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
@@ -134,6 +134,18 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<StackPanel Spacing="5">
|
||||
<ComboBox WrapSelection="{Binding WrapSelection}" PlaceholderText="Editable"
|
||||
ItemsSource="{Binding Values}" DisplayMemberBinding="{Binding Name}"
|
||||
IsEditable="True" Text="{Binding TextValue}"
|
||||
TextSearch.TextBinding="{Binding SearchText, DataType=viewModels:IdAndName}"
|
||||
SelectedItem="{Binding SelectedItem}" />
|
||||
|
||||
<TextBlock Text="Editable text is bound to SearchText. Display is bound to Name" />
|
||||
<TextBlock Text="{Binding TextValue, StringFormat=Text Value: {0}}" />
|
||||
<TextBlock Text="{Binding SelectedItem.Name, StringFormat=Selected Item: {0}}" />
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace ControlCatalog.ViewModels
|
||||
public class ComboBoxPageViewModel : ViewModelBase
|
||||
{
|
||||
private bool _wrapSelection;
|
||||
private string _textValue = string.Empty;
|
||||
private IdAndName? _selectedItem = null;
|
||||
|
||||
public bool WrapSelection
|
||||
{
|
||||
@@ -17,6 +19,18 @@ namespace ControlCatalog.ViewModels
|
||||
set => this.RaiseAndSetIfChanged(ref _wrapSelection, value);
|
||||
}
|
||||
|
||||
public string TextValue
|
||||
{
|
||||
get => _textValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _textValue, value);
|
||||
}
|
||||
|
||||
public IdAndName? SelectedItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<IdAndName> Values { get; set; } = new ObservableCollection<IdAndName>
|
||||
{
|
||||
new IdAndName(){ Id = "Id 1", Name = "Name 1", SearchText = "A" },
|
||||
|
||||
@@ -5,9 +5,9 @@ using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Shapes;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Controls.Utils;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Metadata;
|
||||
@@ -20,6 +20,7 @@ namespace Avalonia.Controls
|
||||
/// A drop-down list control.
|
||||
/// </summary>
|
||||
[TemplatePart("PART_Popup", typeof(Popup), IsRequired = true)]
|
||||
[TemplatePart("PART_EditableTextBox", typeof(TextBox), IsRequired = false)]
|
||||
[PseudoClasses(pcDropdownOpen, pcPressed)]
|
||||
public class ComboBox : SelectingItemsControl
|
||||
{
|
||||
@@ -38,6 +39,12 @@ namespace Avalonia.Controls
|
||||
public static readonly StyledProperty<bool> IsDropDownOpenProperty =
|
||||
AvaloniaProperty.Register<ComboBox, bool>(nameof(IsDropDownOpen));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="IsEditable"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> IsEditableProperty =
|
||||
AvaloniaProperty.Register<ComboBox, bool>(nameof(IsEditable));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="MaxDropDownHeight"/> property.
|
||||
/// </summary>
|
||||
@@ -73,7 +80,13 @@ namespace Avalonia.Controls
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
|
||||
ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Text"/> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<string?> TextProperty =
|
||||
TextBlock.TextProperty.AddOwner<ComboBox>(new(string.Empty, BindingMode.TwoWay));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="SelectionBoxItemTemplate"/> property.
|
||||
/// </summary>
|
||||
@@ -95,6 +108,10 @@ namespace Avalonia.Controls
|
||||
private object? _selectionBoxItem;
|
||||
private readonly CompositeDisposable _subscriptionsOnOpen = new CompositeDisposable();
|
||||
|
||||
private TextBox? _inputTextBox;
|
||||
private BindingEvaluator<string?>? _textValueBindingEvaluator = null;
|
||||
private bool _skipNextTextChanged = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="ComboBox"/> class.
|
||||
/// </summary>
|
||||
@@ -124,6 +141,15 @@ namespace Avalonia.Controls
|
||||
set => SetValue(IsDropDownOpenProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the control is editable
|
||||
/// </summary>
|
||||
public bool IsEditable
|
||||
{
|
||||
get => GetValue(IsEditableProperty);
|
||||
set => SetValue(IsEditableProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum height for the dropdown list.
|
||||
/// </summary>
|
||||
@@ -188,6 +214,16 @@ namespace Avalonia.Controls
|
||||
set => SetValue(SelectionBoxItemTemplateProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text used when <see cref="IsEditable"/> is true.
|
||||
/// Does nothing if not <see cref="IsEditable"/>.
|
||||
/// </summary>
|
||||
public string? Text
|
||||
{
|
||||
get => GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
@@ -229,7 +265,7 @@ namespace Avalonia.Controls
|
||||
SetCurrentValue(IsDropDownOpenProperty, false);
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (!IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space))
|
||||
else if (!IsDropDownOpen && !IsEditable && (e.Key == Key.Enter || e.Key == Key.Space))
|
||||
{
|
||||
SetCurrentValue(IsDropDownOpenProperty, true);
|
||||
e.Handled = true;
|
||||
@@ -315,6 +351,15 @@ namespace Avalonia.Controls
|
||||
/// <inheritdoc/>
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||
{
|
||||
//if the user clicked in the input text we don't want to open the dropdown
|
||||
if (_inputTextBox != null
|
||||
&& !e.Handled
|
||||
&& e.Source is StyledElement styledSource
|
||||
&& styledSource.TemplatedParent == _inputTextBox)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.Handled && e.Source is Visual source)
|
||||
{
|
||||
if (_popup?.IsInsidePopup(source) == true)
|
||||
@@ -348,6 +393,8 @@ namespace Avalonia.Controls
|
||||
_popup = e.NameScope.Get<Popup>("PART_Popup");
|
||||
_popup.Opened += PopupOpened;
|
||||
_popup.Closed += PopupClosed;
|
||||
|
||||
_inputTextBox = e.NameScope.Find<TextBox>("PART_EditableTextBox");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -357,6 +404,7 @@ namespace Avalonia.Controls
|
||||
{
|
||||
UpdateSelectionBoxItem(change.NewValue);
|
||||
TryFocusSelectedItem();
|
||||
UpdateInputTextFromSelection(change.NewValue);
|
||||
}
|
||||
else if (change.Property == IsDropDownOpenProperty)
|
||||
{
|
||||
@@ -366,6 +414,30 @@ namespace Avalonia.Controls
|
||||
{
|
||||
CoerceValue(SelectionBoxItemTemplateProperty);
|
||||
}
|
||||
else if (change.Property == IsEditableProperty && change.GetNewValue<bool>())
|
||||
{
|
||||
UpdateInputTextFromSelection(SelectedItem);
|
||||
}
|
||||
else if (change.Property == TextProperty)
|
||||
{
|
||||
TextChanged(change.GetNewValue<string>());
|
||||
}
|
||||
else if (change.Property == ItemsSourceProperty)
|
||||
{
|
||||
//the base handler deselects the current item (and resets Text) so we want to run the base first, then try match by text
|
||||
string? text = Text;
|
||||
base.OnPropertyChanged(change);
|
||||
SetCurrentValue(TextProperty, text);
|
||||
return;
|
||||
}
|
||||
else if (change.Property == DisplayMemberBindingProperty)
|
||||
{
|
||||
HandleTextValueBindingValueChanged(null, change);
|
||||
}
|
||||
else if (change.Property == TextSearch.TextBindingProperty)
|
||||
{
|
||||
HandleTextValueBindingValueChanged(change, null);
|
||||
}
|
||||
base.OnPropertyChanged(change);
|
||||
}
|
||||
|
||||
@@ -374,6 +446,17 @@ namespace Avalonia.Controls
|
||||
return new ComboBoxAutomationPeer(this);
|
||||
}
|
||||
|
||||
protected override void OnGotFocus(GotFocusEventArgs e)
|
||||
{
|
||||
if (IsEditable && _inputTextBox != null)
|
||||
{
|
||||
_inputTextBox.Focus();
|
||||
_inputTextBox.SelectAll();
|
||||
}
|
||||
|
||||
base.OnGotFocus(e);
|
||||
}
|
||||
|
||||
internal void ItemFocused(ComboBoxItem dropDownItem)
|
||||
{
|
||||
if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid)
|
||||
@@ -386,6 +469,11 @@ namespace Avalonia.Controls
|
||||
{
|
||||
_subscriptionsOnOpen.Clear();
|
||||
|
||||
if(IsEditable && CanFocus(this))
|
||||
{
|
||||
Focus();
|
||||
}
|
||||
|
||||
DropDownClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
@@ -502,6 +590,14 @@ namespace Avalonia.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInputTextFromSelection(object? item)
|
||||
{
|
||||
//if we are modifying the text box which has deselected a value we don't want to update the textbox value
|
||||
if (_skipNextTextChanged)
|
||||
return;
|
||||
SetCurrentValue(TextProperty, GetItemTextValue(item));
|
||||
}
|
||||
|
||||
private void SelectFocusedItem()
|
||||
{
|
||||
foreach (var dropdownItem in GetRealizedContainers())
|
||||
@@ -561,5 +657,69 @@ namespace Avalonia.Controls
|
||||
SelectedItem = null;
|
||||
SelectedIndex = -1;
|
||||
}
|
||||
|
||||
private void HandleTextValueBindingValueChanged(AvaloniaPropertyChangedEventArgs? textSearchPropChange,
|
||||
AvaloniaPropertyChangedEventArgs? displayMemberPropChange)
|
||||
{
|
||||
IBinding? textValueBinding;
|
||||
//prioritise using the TextSearch.TextBindingProperty if possible
|
||||
if (textSearchPropChange == null && TextSearch.GetTextBinding(this) is IBinding textSearchBinding)
|
||||
textValueBinding = textSearchBinding;
|
||||
|
||||
else if (textSearchPropChange != null && textSearchPropChange.NewValue is IBinding eventTextSearchBinding)
|
||||
textValueBinding = eventTextSearchBinding;
|
||||
|
||||
else if (displayMemberPropChange != null && displayMemberPropChange.NewValue is IBinding eventDisplayMemberBinding)
|
||||
textValueBinding = eventDisplayMemberBinding;
|
||||
|
||||
else
|
||||
textValueBinding = null;
|
||||
|
||||
if (_textValueBindingEvaluator == null)
|
||||
_textValueBindingEvaluator = BindingEvaluator<string?>.TryCreate(textValueBinding);
|
||||
else if (textValueBinding == null)
|
||||
_textValueBindingEvaluator = null;
|
||||
else
|
||||
_textValueBindingEvaluator.UpdateBinding(textValueBinding);
|
||||
|
||||
//if the binding is set we want to set the initial value for the selected item so the text box has the correct value
|
||||
if (_textValueBindingEvaluator != null)
|
||||
_textValueBindingEvaluator.Value = GetItemTextValue(SelectedValue);
|
||||
}
|
||||
|
||||
private void TextChanged(string? newValue)
|
||||
{
|
||||
if (!IsEditable || _skipNextTextChanged)
|
||||
return;
|
||||
|
||||
int selectedIdx = -1;
|
||||
object? selectedItem = null;
|
||||
int i = -1;
|
||||
foreach (object? item in Items)
|
||||
{
|
||||
i++;
|
||||
string itemText = GetItemTextValue(item);
|
||||
if (string.Equals(newValue, itemText, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
selectedIdx = i;
|
||||
selectedItem = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_skipNextTextChanged = true;
|
||||
try
|
||||
{
|
||||
SelectedIndex = selectedIdx;
|
||||
SelectedItem = selectedItem;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_skipNextTextChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetItemTextValue(object? item)
|
||||
=> TextSearch.GetEffectiveText(item, _textValueBindingEvaluator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,15 @@ internal sealed class BindingEvaluator<T> : StyledElement, IDisposable
|
||||
public static readonly StyledProperty<T> ValueProperty =
|
||||
AvaloniaProperty.Register<BindingEvaluator<T>, T>("Value");
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the data item value.
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get => GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public T Evaluate(object? dataContext)
|
||||
{
|
||||
// Only update the DataContext if necessary
|
||||
@@ -49,6 +58,7 @@ internal sealed class BindingEvaluator<T> : StyledElement, IDisposable
|
||||
DataContext = null;
|
||||
}
|
||||
|
||||
[return: NotNullIfNotNull(nameof(binding))]
|
||||
public static BindingEvaluator<T>? TryCreate(IBinding? binding)
|
||||
{
|
||||
if (binding is null)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<ComboBoxItem>Item 1</ComboBoxItem>
|
||||
<ComboBoxItem>Item 2</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<ComboBox PlaceholderText="Error">
|
||||
<DataValidationErrors.Error>
|
||||
<sys:Exception>
|
||||
@@ -25,6 +26,25 @@
|
||||
</sys:Exception>
|
||||
</DataValidationErrors.Error>
|
||||
</ComboBox>
|
||||
|
||||
<ComboBox SelectedIndex="1" IsEditable="True">
|
||||
<ComboBoxItem>Item A</ComboBoxItem>
|
||||
<ComboBoxItem>Item b</ComboBoxItem>
|
||||
<ComboBoxItem>Item c</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<ComboBox SelectedIndex="0">
|
||||
<ComboBox.SelectionBoxItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Padding="20" BorderBrush="Red" BorderThickness="1">
|
||||
<TextBlock Text="{ReflectionBinding}"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ComboBox.SelectionBoxItemTemplate>
|
||||
<ComboBoxItem>Item A</ComboBoxItem>
|
||||
<ComboBoxItem>Item b</ComboBoxItem>
|
||||
<ComboBoxItem>Item c</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
@@ -80,17 +100,42 @@
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Text="{TemplateBinding PlaceholderText}"
|
||||
Foreground="{TemplateBinding PlaceholderForeground}"
|
||||
IsVisible="{TemplateBinding SelectionBoxItem, Converter={x:Static ObjectConverters.IsNull}}" />
|
||||
Foreground="{TemplateBinding PlaceholderForeground}">
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="SelectionBoxItem" RelativeSource="{RelativeSource TemplatedParent}" Converter="{x:Static ObjectConverters.IsNull}" />
|
||||
<Binding Path="!IsEditable" RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
<ContentControl x:Name="ContentPresenter"
|
||||
Content="{TemplateBinding SelectionBoxItem}"
|
||||
Grid.Column="0"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
IsVisible="{TemplateBinding IsEditable, Converter={x:Static BoolConverters.Not}}">
|
||||
</ContentControl>
|
||||
|
||||
<TextBox Name="PART_EditableTextBox"
|
||||
Grid.Column="0"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Background="Transparent"
|
||||
Text="{TemplateBinding Text, Mode=TwoWay}"
|
||||
Watermark="{TemplateBinding PlaceholderText}"
|
||||
BorderThickness="0"
|
||||
IsVisible="{TemplateBinding IsEditable}">
|
||||
<TextBox.Resources>
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused">Transparent</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver">Transparent</SolidColorBrush>
|
||||
<Thickness x:Key="TextControlBorderThemeThicknessFocused">0</Thickness>
|
||||
</TextBox.Resources>
|
||||
</TextBox>
|
||||
|
||||
<Border x:Name="DropDownOverlay"
|
||||
Grid.Column="1"
|
||||
Background="Transparent"
|
||||
@@ -203,6 +248,11 @@
|
||||
<Setter Property="Foreground" Value="{DynamicResource ComboBoxDropDownGlyphForegroundDisabled}" />
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
<Style Selector="^[IsEditable=true]">
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="KeyboardNavigation.TabNavigation" Value="Local" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -27,15 +27,37 @@
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Foreground="{TemplateBinding PlaceholderForeground}"
|
||||
IsVisible="{TemplateBinding SelectionBoxItem,
|
||||
Converter={x:Static ObjectConverters.IsNull}}"
|
||||
Text="{TemplateBinding PlaceholderText}" />
|
||||
Text="{TemplateBinding PlaceholderText}">
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="SelectionBoxItem" RelativeSource="{RelativeSource TemplatedParent}" Converter="{x:Static ObjectConverters.IsNull}" />
|
||||
<Binding Path="!IsEditable" RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
<ContentControl Margin="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding SelectionBoxItem}"
|
||||
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}">
|
||||
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
|
||||
IsVisible="{TemplateBinding IsEditable, Converter={x:Static BoolConverters.Not}}">
|
||||
</ContentControl>
|
||||
<TextBox Name="PART_EditableTextBox"
|
||||
Grid.Column="0"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Background="Transparent"
|
||||
Text="{TemplateBinding Text, Mode=TwoWay}"
|
||||
BorderThickness="0"
|
||||
IsVisible="{TemplateBinding IsEditable}">
|
||||
<TextBox.Resources>
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused">Transparent</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver">Transparent</SolidColorBrush>
|
||||
<Thickness x:Key="TextControlBorderThemeThicknessFocused">0</Thickness>
|
||||
</TextBox.Resources>
|
||||
</TextBox>
|
||||
<ToggleButton Name="toggle"
|
||||
Grid.Column="1"
|
||||
Background="Transparent"
|
||||
@@ -75,11 +97,18 @@
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Style Selector="^:pointerover /template/ Border#border">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:disabled /template/ Border#border">
|
||||
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^[IsEditable=true]">
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="KeyboardNavigation.TabNavigation" Value="Local" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -209,6 +209,10 @@ namespace Avalonia.Controls.UnitTests
|
||||
ItemsPanel = new FuncTemplate<Panel>(() => new VirtualizingStackPanel()),
|
||||
}.RegisterInNameScope(scope)
|
||||
}.RegisterInNameScope(scope)
|
||||
}.RegisterInNameScope(scope),
|
||||
new TextBox
|
||||
{
|
||||
Name = "PART_InputText"
|
||||
}.RegisterInNameScope(scope)
|
||||
}
|
||||
};
|
||||
@@ -635,5 +639,81 @@ namespace Avalonia.Controls.UnitTests
|
||||
}
|
||||
|
||||
private sealed record Item(string Value, string Display);
|
||||
|
||||
[Fact]
|
||||
public void When_Editable_Input_Text_Matches_An_Item_It_Is_Selected()
|
||||
{
|
||||
var target = new ComboBox
|
||||
{
|
||||
DisplayMemberBinding = new Binding(),
|
||||
IsEditable = true,
|
||||
ItemsSource = new[] { "foo", "bar" }
|
||||
};
|
||||
|
||||
target.SelectedItem = null;
|
||||
Assert.Null(target.SelectedItem);
|
||||
|
||||
target.Text = "foo";
|
||||
Assert.NotNull(target.SelectedItem);
|
||||
Assert.Equal(target.SelectedItem, "foo");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_Editable_TextSearch_TextBinding_Is_Prioritised_Over_DisplayMember()
|
||||
{
|
||||
var items = new[]
|
||||
{
|
||||
new Item("Value 1", "Display 1"),
|
||||
new Item("Value 2", "Display 2")
|
||||
};
|
||||
var target = new ComboBox
|
||||
{
|
||||
DisplayMemberBinding = new Binding("Display"),
|
||||
IsEditable = true,
|
||||
ItemsSource = items
|
||||
};
|
||||
TextSearch.SetTextBinding(target, new Binding("Value"));
|
||||
|
||||
target.SelectedItem = null;
|
||||
Assert.Null(target.SelectedItem);
|
||||
|
||||
target.Text = "Value 1";
|
||||
Assert.NotNull(target.SelectedItem);
|
||||
Assert.Equal(target.SelectedItem, items[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void When_Items_Source_Changes_It_Selects_An_Item_By_Text()
|
||||
{
|
||||
var items = new[]
|
||||
{
|
||||
new Item("Value 1", "Display 1"),
|
||||
new Item("Value 2", "Display 2")
|
||||
};
|
||||
var items2 = new[]
|
||||
{
|
||||
new Item("Value 1", "Display 3"),
|
||||
new Item("Value 2", "Display 4")
|
||||
};
|
||||
var target = new ComboBox
|
||||
{
|
||||
DisplayMemberBinding = new Binding("Display"),
|
||||
IsEditable = true,
|
||||
ItemsSource = items
|
||||
};
|
||||
TextSearch.SetTextBinding(target, new Binding("Value"));
|
||||
|
||||
target.SelectedItem = null;
|
||||
Assert.Null(target.SelectedItem);
|
||||
|
||||
target.Text = "Value 1";
|
||||
Assert.NotNull(target.SelectedItem);
|
||||
Assert.Equal(target.SelectedItem, items[0]);
|
||||
|
||||
target.ItemsSource = items2;
|
||||
Assert.NotNull(target.SelectedItem);
|
||||
Assert.Equal(target.SelectedItem, items2[0]);
|
||||
Assert.Equal(target.Text, "Value 1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user