Skip to content
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

Open
kekekeks opened this issue Sep 25, 2015 · 24 comments
Open

Implement ComboBox (IsEditable) #205

kekekeks opened this issue Sep 25, 2015 · 24 comments
Labels
enhancement help-wanted A contribution from the community would be most welcome.

Comments

@kekekeks
Copy link
Member

No description provided.

@kekekeks kekekeks added enhancement help-wanted A contribution from the community would be most welcome. labels Sep 25, 2015
@kekekeks kekekeks added this to the Alpha 3 milestone Sep 25, 2015
@ncarrillo-zz
Copy link
Contributor

How does this compare to Dropdown?

@grokys
Copy link
Member

grokys commented Sep 26, 2015

ComboBox is like a combined DropDown and TextBox.

@ncarrillo-zz
Copy link
Contributor

ncarrillo-zz commented Sep 26, 2015

So:

  • Rename to ComboBox(?)
  • Implement IsEditable

then based on the IsEditable property you could toggle a different template. ComboBox has some autocomplete support as well, though I don't know if its necessary for alpha 3

@grokys
Copy link
Member

grokys commented Sep 26, 2015

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.

@ghost
Copy link

ghost commented Sep 27, 2015

I will give this a shot.

@grokys grokys removed this from the Alpha 3 milestone Nov 27, 2015
@grokys grokys added this to the Beta 1 milestone Aug 3, 2016
@grokys grokys removed the help-wanted A contribution from the community would be most welcome. label Aug 20, 2017
@grokys grokys modified the milestones: Beta 1, Beta 2 Oct 18, 2017
@grokys
Copy link
Member

grokys commented Oct 18, 2017

As I mentioned in #1070, I'm wondering if an old-style ComboBox such as this is even needed in a modern day application? Some kind of Autocomplete control would be far more useful IMO.

@MonkAlex
Copy link
Contributor

Any implementation of multi-select combobox will be useful for filter panels.

@grokys grokys modified the milestones: 0.7.0, 0.8.0 Jul 5, 2018
@grokys grokys modified the milestones: 0.8.0, 0.9 Apr 5, 2019
@Krakean
Copy link

Krakean commented May 12, 2019

@grokys I guess its implemented and ticket can be closed?

@kekekeks
Copy link
Member Author

@Krakean we've renamed our DropDown to ComboBox for consistency with WPF/UWP/xamlstandard. It's not an actual combobox, though.

@grokys
Copy link
Member

grokys commented May 12, 2019

It's not an actual combobox, though.

Note that neither is UWP's combobox afaik.

@vlad0s777
Copy link

Hello! When does a combobox appear IsEditable?

@frapendev
Copy link

frapendev commented Mar 16, 2021

Any updates about the IsEditable property of the ComboCox control?

@maxkatz6
Copy link
Member

@frapendev nobody yet started implementing editable combo box.
If somebody has time and willing to help, PRs are welcome :)

@maxkatz6 maxkatz6 changed the title Implement ComboBox Implement ComboBox (IsEditable) May 24, 2021
@maxkatz6 maxkatz6 removed this from the 0.9 milestone May 24, 2021
@maxkatz6 maxkatz6 added the help-wanted A contribution from the community would be most welcome. label May 24, 2021
@almightyju
Copy link
Contributor

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.cs
using 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>

@MathiasLui
Copy link

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

@timunie
Copy link
Contributor

timunie commented Sep 7, 2023

@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.

@almightyju
Copy link
Contributor

@MathiasLui Stuff has changed a lot since I posted that, this version should work (untested):

EditableComboBox.cs
public 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

@MathiasLui
Copy link

@timunie @almightyju

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

@reelthymeos
Copy link

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.

@rabbitism
Copy link
Contributor

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

@almightyju
Copy link
Contributor

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 🤷

@reelthymeos
Copy link

reelthymeos commented Mar 3, 2025

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

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.

@rabbitism
Copy link
Contributor

pressing backspace clears the whole box

No I cannot reproduce this behavior

I don't get why there is such push back on adding support that's been part of windows and WPF for years 🤷

OMG I don't even know WPF has such a feature. Definitely will take a look at it.

@almightyju
Copy link
Contributor

No I cannot reproduce this behavior

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.

OMG I don't even know WPF has such a feature. Definitely will take a look at it.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement help-wanted A contribution from the community would be most welcome.
Projects
None yet
Development

No branches or pull requests