Ursa.Avalonia icon indicating copy to clipboard operation
Ursa.Avalonia copied to clipboard

TreeComboBox need supporting multiple-selection

Open WilliamQue opened this issue 10 months ago • 2 comments

At the beginning of the new year, I wish the request could be added into roadmap. Only supporting single-selection is a little unfair to this control with so wonderful functionalities.

WilliamQue avatar Jan 22 '25 10:01 WilliamQue

Is there any progress? The multi-select cascading selector control is very necessary for selecting individual stores or stores in a certain area within an organizational structure. Many web component libraries have similar controls. My capabilities are limited to do it. and I'm looking forward to seeing this suggestion realized in the 2.0 release.

WilliamQue avatar Aug 16 '25 15:08 WilliamQue

BLACK MAGIC workaround:

Item VM:

public class ItemVM(RawItem item, Action<ItemVM> isCheckedChanged) : ViewModelBase
{
    private bool isChecked;
    private bool isExpanded = true;

    public bool IsChecked
    {
        get => isChecked;
        set
        {
            this.RaiseAndSetIfChanged(ref isChecked, value);
            isCheckedChanged(this);
        }
    }

    public bool IsExpanded
    {
        get => isExpanded;
        set => this.RaiseAndSetIfChanged(ref isExpanded, value);
    }

    public ObservableCollection<ItemVM> Children { get; } = [];
}

View VM:

public class ViewVM : ViewModelBase
{
    public ObservableCollection<ItemVM> ItemRoots { get; private set; } = [];

    public ObservableCollection<ItemVM> SelectedItems { get; } = [];

    public void LoadItems()
    {
        var newItems = GetItems();
        ItemRoots = new(newItems.Select(item => new ItemVM(item, ItemCheckedChanged)));
        this.RaisePropertyChanged(nameof(ItemRoots));
    }
    
    private void ItemCheckedChanged(ItemVM item)
    {
        var exists = SelectedItems.FirstOrDefault(itemIn => itemIn.Key == item.Key);
        if (item.IsChecked && exists == null)
        {
            SelectedItems.Add(item);
        }
        else if (exists != null)
        {
            SelectedItems.Remove(exists);
        }
    }

    public void RemoveSelection(IDataContextProvider obj)
    {
        if (obj.DataContext is ItemVM item)
        {
            item.IsChecked = false;
        }
    }
}

In axaml:

<u:TreeComboBox ItemsSource="{Binding ItemRoots}">
    <u:TreeComboBox.Styles>
        <Style Selector="u|TreeComboBox /template/ TextBlock#PlaceholderTextBlock">
            <Setter Property="IsVisible" Value="{Binding !SelectedItems.Count}" />
        </Style>
    </u:TreeComboBox.Styles>
    <u:TreeComboBox.SelectedItemTemplate>
        <DataTemplate>
            <u:MultiComboBoxSelectedItemList x:DataType="vm:ViewVM"
                                             DataContext="{Binding $parent[u:TreeComboBox].DataContext}"
                                             ItemsSource="{Binding SelectedItems}"
                                             RemoveCommand="{Binding RemoveSelection}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel Background="Transparent" Orientation="Horizontal" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <u:MultiComboBoxSelectedItemList.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}" />
                    </DataTemplate>
                </u:MultiComboBoxSelectedItemList.ItemTemplate>
            </u:MultiComboBoxSelectedItemList>
        </DataTemplate>
    </u:TreeComboBox.SelectedItemTemplate>
    <u:TreeComboBox.ItemContainerTheme>
        <ControlTheme x:DataType="vm:ItemVM"
                      BasedOn="{StaticResource {x:Type u:TreeComboBoxItem}}"
                      TargetType="u:TreeComboBoxItem">

            <!--  ** MAGIC HERE ** -->
            <!-- Not use TreeComboBox selection -->
            <Setter Property="IsSelectable" Value="False" />

            <!-- Multiple selection has implement in based control but protected -->
            <Setter Property="SelectingItemsControl.IsSelected" Value="{Binding IsChecked}" />
            <Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
        </ControlTheme>
    </u:TreeComboBox.ItemContainerTheme>
    <u:TreeComboBox.ItemTemplate>
        <TreeDataTemplate ItemsSource="{Binding Children}">

            <CheckBox VerticalAlignment="Center" Content="{Binding Name}"
                      IsChecked="{Binding IsChecked}"
                      UseLayoutRounding="True" />

        </TreeDataTemplate>
    </u:TreeComboBox.ItemTemplate>
</u:TreeComboBox>

Flithor avatar Aug 29 '25 04:08 Flithor