-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement ComboBox (IsEditable) #205
Comments
How does this compare to Dropdown? |
ComboBox is like a combined DropDown and TextBox. |
So:
then based on the |
My thinking was that they would be separate controls - they are in most ui frameworks I've come across, and e.g. the Text property only applies to ComboBox. |
I will give this a shot. |
As I mentioned in #1070, I'm wondering if an old-style |
Any implementation of multi-select combobox will be useful for filter panels. |
@grokys I guess its implemented and ticket can be closed? |
@Krakean we've renamed our |
Note that neither is UWP's combobox afaik. |
Hello! When does a combobox appear IsEditable? |
Any updates about the IsEditable property of the ComboCox control? |
@frapendev nobody yet started implementing editable combo box. |
I've made a quick and dirty version that works for me for text only items, might be a helpful starting point if anyone wants to pick this up (I don't understand the framework enough yet to try doing anything with this). The code is quite bad for efficiency when doing the lookup for string matching and could easily be improved for speed by caching the items .ToString in a hashset but for my current use case it's not important since it only has a small quantity of items. EditableComboBox.csusing Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
namespace AvaloniaTest.Controls;
[TemplatePart("PART_Popup", typeof(Popup))]
[TemplatePart("PART_InputText", typeof(TextBox))]
[PseudoClasses(pcDropdownOpen, pcPressed)]
public class EditableComboBox : ComboBox
{
private string? _text = string.Empty;
public string? Text
{
get => _text;
set => SetAndRaise(TextProperty, ref _text, value);
}
public static readonly DirectProperty<EditableComboBox, string?> TextProperty =
TextBlock.TextProperty.AddOwner<EditableComboBox>(
x => x.Text,
(x, v) => x.Text = v,
unsetValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay);
public StringComparer ItemTextComparer { get; set; } = StringComparer.CurrentCultureIgnoreCase;
public static readonly DirectProperty<EditableComboBox, StringComparer> ItemTextComparerProperty =
AvaloniaProperty.RegisterDirect<EditableComboBox, StringComparer>(nameof(ItemTextComparer),
x => x.ItemTextComparer,
(x, v) => x.ItemTextComparer = v,
unsetValue: StringComparer.CurrentCultureIgnoreCase);
static EditableComboBox()
{
TextProperty.Changed.AddClassHandler<EditableComboBox>((x, e) => x.TextChanged(e));
SelectedItemProperty.Changed.AddClassHandler<EditableComboBox>((x, e) => x.SelectedItemChanged(e));
//when the items change we need to simulate a text change to validate the text being an item or not and selecting it
ItemsProperty.Changed.AddClassHandler<EditableComboBox>((x, e) => x.TextChanged(
new AvaloniaPropertyChangedEventArgs<string?>(e.Sender, TextProperty, x.Text, x.Text, e.Priority)));
}
TextBox? _inputText;
bool _supressSelectedItemChange;
private void TextChanged(AvaloniaPropertyChangedEventArgs e)
{
//don't check for an item if there are no items or if we are already processing a change
if(Items == null || _supressSelectedItemChange)
return;
string newVal = e.GetNewValue<string>();
int selectedIdx = -1;
object? selectedItem = null;
int i = -1;
foreach(object o in Items)
{
i++;
if (ItemTextComparer.Equals(newVal, o.ToString()))
{
selectedIdx = i;
selectedItem = o;
break;
}
}
bool clearingSelectedItem = SelectedIndex > -1 && selectedIdx == -1;
bool settingSelectedItem = SelectedIndex == -1 && selectedIdx > -1;
_supressSelectedItemChange = true;
SelectedIndex = selectedIdx;
SelectedItem = selectedItem;
//set the text to the Item.ToString() if an item has been selected (or text matched)
if (settingSelectedItem)
Text = SelectedItem?.ToString() ?? newVal;
_supressSelectedItemChange = false;
}
private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e)
{
if (_supressSelectedItemChange)
return;
_supressSelectedItemChange = true;
Text = e.NewValue?.ToString() ?? string.Empty;
_supressSelectedItemChange = false;
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
//if the use clicks in the text box we don't want to open the dropdown
if (_inputText != null && e.Source is StyledElement styledElement && styledElement.TemplatedParent == _inputText)
return;
base.OnPointerReleased(e);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_inputText = e.NameScope.Get<TextBox>("PART_InputText");
base.OnApplyTemplate(e);
}
} EditableComboBox.axaml - taken from ComboBox and changed textblock to textbox<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AvaloniaTest.Controls">
<ControlTheme x:Key="{x:Type local:EditableComboBox}"
TargetType="local:EditableComboBox">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="4" />
<Setter Property="MinHeight" Value="20" />
<Setter Property="PlaceholderForeground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid ColumnDefinitions="*,Auto">
<TextBox Name="PART_InputText"
Grid.Column="0"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Foreground="{TemplateBinding PlaceholderForeground}"
Text="{TemplateBinding Text, Mode=TwoWay}"
BorderThickness="0"/>
<ToggleButton Name="toggle"
Grid.Column="1"
Background="Transparent"
BorderThickness="0"
ClickMode="Press"
Focusable="False"
IsChecked="{TemplateBinding IsDropDownOpen,
Mode=TwoWay}">
<Path Width="8"
Height="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z"
Fill="{DynamicResource ThemeForegroundBrush}"
Stretch="Uniform" />
</ToggleButton>
<Popup Name="PART_Popup"
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsDropDownOpen,
Mode=TwoWay}"
PlacementTarget="{TemplateBinding}">
<Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ItemsPresenter Name="PART_ItemsPresenter"
ItemTemplate="{TemplateBinding ItemTemplate}"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
VirtualizationMode="{TemplateBinding VirtualizationMode}" />
</ScrollViewer>
</Border>
</Popup>
</Grid>
</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>
</ControlTheme>
</ResourceDictionary> |
Nice, though it tells me the pseudo classes weren't found, AddOwner doesn't take 4 arguments and ItemsProperty doesn't exist. I'm not sure if it's outdated or I'm not smart enough to c+p. Before trying to fix that or trying to understand it I'm probably gonna use a different control |
@MathiasLui you can also use AutoCompleteBox or check out https://github.com/AvaloniaCommunity/awesome-avalonia for 3rd party libs providing such a control. I know that FluentAvalonia has a editable combo box. |
@MathiasLui Stuff has changed a lot since I posted that, this version should work (untested): EditableComboBox.cspublic class EditableComboBox : ComboBox
{
public string? Text
{
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly StyledProperty<string?> TextProperty =
TextBlock.TextProperty.AddOwner<EditableComboBox>(new(string.Empty, BindingMode.TwoWay));
public StringComparer ItemTextComparer { get; set; } = StringComparer.CurrentCultureIgnoreCase;
public static readonly DirectProperty<EditableComboBox, StringComparer> ItemTextComparerProperty =
AvaloniaProperty.RegisterDirect<EditableComboBox, StringComparer>(nameof(ItemTextComparer),
x => x.ItemTextComparer,
(x, v) => x.ItemTextComparer = v,
unsetValue: StringComparer.CurrentCultureIgnoreCase);
static EditableComboBox()
{
TextProperty.Changed.AddClassHandler<EditableComboBox>((x, e) => x.TextChanged(e));
SelectedItemProperty.Changed.AddClassHandler<EditableComboBox>((x, e) => x.SelectedItemChanged(e));
//when the items change we need to simulate a text change to validate the text being an item or not and selecting it
ItemsSourceProperty.Changed.AddClassHandler<EditableComboBox>((x, e) => x.TextChanged(
new AvaloniaPropertyChangedEventArgs<string?>(e.Sender, TextProperty, x.Text, x.Text, e.Priority)));
}
TextBox? _inputText;
bool _supressSelectedItemChange;
private void TextChanged(AvaloniaPropertyChangedEventArgs e)
{
//don't check for an item if there are no items or if we are already processing a change
if (Items == null || _supressSelectedItemChange)
return;
string newVal = e.GetNewValue<string>();
int selectedIdx = -1;
object? selectedItem = null;
int i = -1;
foreach (object o in Items)
{
i++;
if (ItemTextComparer.Equals(newVal, o.ToString()))
{
selectedIdx = i;
selectedItem = o;
break;
}
}
bool clearingSelectedItem = SelectedIndex > -1 && selectedIdx == -1;
bool settingSelectedItem = SelectedIndex == -1 && selectedIdx > -1;
_supressSelectedItemChange = true;
SelectedIndex = selectedIdx;
SelectedItem = selectedItem;
//set the text to the Item.ToString() if an item has been selected (or text matched)
if (settingSelectedItem)
SetCurrentValue(TextProperty, SelectedItem?.ToString() ?? newVal);
_supressSelectedItemChange = false;
}
private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e)
{
if (_supressSelectedItemChange)
return;
_supressSelectedItemChange = true;
SetCurrentValue(TextProperty, e.NewValue?.ToString() ?? string.Empty);
_supressSelectedItemChange = false;
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
//if the use clicks in the text box we don't want to open the dropdown
if (_inputText != null && e.Source is StyledElement styledElement && styledElement.TemplatedParent == _inputText)
return;
base.OnPointerReleased(e);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_inputText = e.NameScope.Get<TextBox>("PART_InputText");
base.OnApplyTemplate(e);
}
} As timunie said tho, you probably want to check out https://github.com/amwx/FluentAvalonia which has an editable combo box, my version was more of an aid to anyone who might look to make a pull request to include the feature |
I thank you both for the quick and helpful replies :) I wasn't aware of Awesome Avalonia or Fluent Avalonia so I'll take a look at those first |
Does anyone have an update on this feature? This would be really helpful to have this property added to the standard Avalonia ComboBox control. I looked at the FluentAvalonia implementation and could not get it working. |
please use AutoCompleteBox |
It's not the same thing, when I last used it (see pull request #18094) pressing backspace clears the whole box, which isn't the same as an editable text box. I don't get why there is such push back on adding support that's been part of windows and WPF for years 🤷 |
AutoCompleteBox does not have the features that I need. In my case, I have a combo-box, (Drop-down) that must display items from a list as well as the currently selected item by default. The user will not necessarily know the item names in advance in order to be able to type them out. ComboBox also allows me to take advantage of the SelectedIndex property. Unless I'm missing something, I don't see how AutoCompleteBox is a proper replacement for an editable ComboBox. |
No I cannot reproduce this behavior
OMG I don't even know WPF has such a feature. Definitely will take a look at it. |
I'd have to try it again, but there also wasn't an option to open a dropdown to view all and you also had to use an item template for the display value which gets very tedious just for basic number/string properties.
Yep, combo box in WPF is very powerful, no need for data templates, you can bind the display member of an object and then bind the value into a property into another object. Take a look at https://stackoverflow.com/questions/4902039/difference-between-selecteditem-selectedvalue-and-selectedvaluepath |
No description provided.
The text was updated successfully, but these errors were encountered: