Search code examples
c#wpfxamlmvvmapplication-settings

View to ViewModel to Settings


Is it possible to refactor the currentDevices into a collection? Basically, I have three comboboxes in which the selectedvalue is bound to currentDevices. then the currentDevices are taken from a settings file.

View

<ComboBox ItemsSource="{Binding availableDevices}"
          SelectedValue="{Binding currentDevice1}">
</ComboBox>
<ComboBox ItemsSource="{Binding availableDevices}"
          SelectedValue="{Binding currentDevice2}">
</ComboBox>
<ComboBox ItemsSource="{Binding availableDevices}"
          SelectedValue="{Binding currentDevice3}">
</ComboBox>

ViewModel

public string currentDevice1 {
    get
    {
        return SampleSettings.Default.Device1;
    }
    set
    {
        SampleSettings.Default.Device1 = value;
    }

}
public string currentDevice2
{
    get
    {
        return SampleSettings.Default.Device2;
    }
    set
    {
        SampleSettings.Default.Device2 = value;
    }
}
public string currentDevice3
{
    get
    {
        return SampleSettings.Default.Device3;
    }
    set
    {
        SampleSettings.Default.Device3 = value;
    }
}

Solution

  • I'd write a DeviceOptionViewModel, and give the main viewmodel an ObservableCollection.

    DeviceOptionViewModel.cs

    public class DeviceOptionViewModel : INotifyPropertyChanged 
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        private string _currentDevice;
        public String CurrentDevice {
            get { return _currentDevice; }
            set { 
                _currentDevice = value; 
                PropertyChanged?.Invoke(this, 
                    new PropertyChangedEventArgs(nameof(CurrentDevice));
            }
        }
    
        //  Parent event assigns this to his own availableDevices
        //  when he creates this.
        public IEnumerable AvailableDevices { get; set; }
    }
    

    Main VM:

        public ObservableCollection<DeviceOptionViewModel> 
            CurrentDevices { get; private set; }
                = new ObservableCollection<DeviceOptionViewModel>();
    

    XAML:

    <ItemsControl
        ItemsSource="{Binding CurrentDevices}"
        >
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <!-- DataContext here is DeviceOptionViewModel. We gave it its 
                     own reference to AvailableDevices to simplify binding. -->
                <ComboBox 
                    ItemsSource="{Binding AvailableDevices}"
                    SelectedValue="{Binding CurrentDevice}"
                    />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    Back to main viewmodel:

    protected void PopulateCurrentDevices(IEnumerable<String> stringsFromWherever)
    {
        CurrentDevices.Clear();
    
        foreach (var device in stringsFromWherever)
        {
            var dovm = new DeviceOptionViewModel() {
                    CurrentDevice = device,
                    AvailableDevices = this.availableDevices
                };
    
            dovm.PropertyChanged += DeviceOptionViewModel_PropertyChangedHandler;
    
            CurrentDevices.Add(dovm);
        }
    }
    
    protected void DeviceOptionViewModel_PropertyChangedHandler(object sender, 
         PropertyChangedEventArgs e)
    {
        var dopt = sender as DeviceOptionViewModel;
    
        if (e.PropertyName == nameof(DeviceOptionViewModel.CurrentDevice))
        {
            //  Do stuff
        }
    }
    

    So you populate and repopulate CurrentDevices in your viewmodel as needed, and the UI will magically appear if all the notifications are done correctly.

    If you create a new ObservableCollection and assign that to the CurrentDevices property, you'll need to raise PropertyChanged(nameof(CurrentDevices)) on the main viewmodel. I made the setter private to avoid having to implement that detail. If it's not a huge collection, may as well just Clear() and Add() on the same old instance.