Search code examples
c#mvvmavaloniauiavalonia

How to update Avalonia UI ComboBox when ObservableCollection was updated


I have a C# Avalonia project to communicate any Serial (RS232) devices, currently i am at the beginning of developing this app. I decided to use Avalonia to build cross-platform application (Windows, Linux). However i faced an issue when component (ComboBox) source IList / IObservableCollection is updated i can't update combobox content.

My Ports list looks like:

    <ComboBox Name="PortsListSelect" ItemsSource="{Binding Path=Ports, Mode=TwoWay}"
                                                   SelectedItem="{Binding SelectedPortNumber, Mode=TwoWay}"
                                                   DropDownOpened="OnPortNumberListOpened"
                                                   Classes="ConnOptCombo" Margin="5 0 0 0" Width="100"/>

On dropdown is opened i handle event in MainWindow:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        _context = new MainWindowViewModel();
        DataContext = _context;
    }

    private void OnPortNumberListOpened(object? sender, EventArgs e)
    {
        _context.ReEnumeratePorts();
    }
        
    private readonly MainWindowViewModel _context;
}

In the event handler i call ReEnumeratePorts fucntion to check what ports we have in the system. In the my MainWindowViewModel i was trying to use dfferent approaches but still can't get it work:

public class MainWindowViewModel : ViewModelBase
{
    public MainWindowViewModel()
    {
        SerialOptions = new SerialDefaultsModel();
        _ports = new ObservableCollection<string (Rs232PortsEnumerator.GetAvailablePorts()?.ToList() ?? new List<string>());
        SelectedPortNumber = Ports.Any() ? Ports.First() : null;
    }

    // 
    public void ReEnumeratePorts()
    {
        this.RaisePropertyChanging("Ports");
        Ports = new ObservableCollection<string>(Rs232PortsEnumerator.GetAvailablePorts());
        SelectedPortNumber = Ports.Any() ? Ports.First() : null; 
        this.RaisePropertyChanged("Ports");
        this.RaisePropertyChanged("SelectedPortNumber");
    }

    public ObservableCollection<string> Ports
    {
        get { return _ports; }
        set
        {

            this.RaiseAndSetIfChanged(ref _ports, value);
        }
    }
    // ... other methods && props

    private ObservableCollection<string> _ports;
}

My Question is: How to enforce ComboBox to update it content by updating Binded ItemsSource value.


Solution

  • Finally I was able to do this but not via ObservableCollection, AvaloniaList or other "observable collection" (tutorials aren't working) instead of this i just used old well known Windows Forms Event Handling

    1. I replaced ObservableCollection with classic IList:
    public class MainWindowViewModel : ViewModelBase
    {
        public MainWindowViewModel()
        {
            _ports = new List<string (Rs232PortsEnumerator.GetAvailablePorts().ToList());
            SelectedPortNumber = Ports.Any() ? Ports.First() : null;
            // other initialize
        }
    
        // ReEnumeratePorts returns List!
        public IList<string> ReEnumeratePorts()
        {
            Ports.Clear();
            IList<string> newPorts = Rs232PortsEnumerator.GetAvailablePorts();
            Ports = newPorts;
            return newPorts;
        }
    
        // other methods ...
        
        // Ports property
        public IList<string> Ports
        {
            get { return _ports; }
            set
            {
                _ports = value;
                this.RaisePropertyChanged();
            }
        }
    
        // other props ...
        private IList<string> _ports;
    }
    
    
    1. After some practical survey i discovered that PointerEntered is a most suitable event to nadle, therefore my axaml part shown below:
    <ComboBox Name="PortsListSelect" ItemsSource="{Binding Path=Ports, Mode=OneWay}" SelectedItem="{Binding SelectedPortNumber, Mode=TwoWay}"
                                          PointerEntered="OnPortsPointerControlMouseOver" Classes="ConnOptCombo" Margin="5 0 0 0" Width="100"/>
    
    1. And i added following OnPortsPointerControlMouseOver method to my window code:
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            _context = new MainWindowViewModel();
            DataContext = _context;
        }
            
        private void OnPortsPointerControlMouseOver(object? sender, PointerEventArgs e)
        {
            IList<string> ports = _context.ReEnumeratePorts();
            PortsListSelect.ItemsSource = ports as IEnumerable;
            PortsListSelect.SelectedItem = ports.Any() ? ports [0]: null;
        }
            
        private readonly MainWindowViewModel _context;
    }
    
    

    Unfortunately i was unable to do the same via MVVM with an observables and a property change event raise but my solution is working, a little bit late i'll edit answer and post a link to video with demo of how all this works.

    Full example could be found in github repo.

    P.S. @Tarazed, thanks for attempts to help me to solve this issue.