Search code examples
c#windows-phone-8mvvmwindows-phonemvvm-light

WP8 call method in viewmodel from object in collection


I have a longlistselector and in each row I have ToggleSwitch and I would like to call http request via my ApiService when ToggleSwitch is changed. I have ApiService class in ViewModel thanks to injection and in ViewModel I have ObservableCollection of Modules which have switches. I bind it with datatemplate and there is no problem with bind ToggleSwitch to bool property. But what should I do in setter of that property?

Model - Modul.cs

public int IsLock
    {
        get { return isLock; }
        set { 
            Set(() => IsLock, ref isLock, value); 
            // What should I do here? How call ViewModel method?
        }
    }

ViewModel - ModuleListViewModel.cs

 public ObservableCollection<Module> Modules { get; private set; }

 // here I have apiService instance
 // and here I could call apiService.Lock(module) and so on

View - part of DataTemplate

 <toolkit:ToggleSwitch x:Name="LockSwitch" 
                                      IsChecked="{Binding IsLock, Mode=TwoWay}"/>

What's the right aproach for this? Maybe I could have ApiService class in each Modul class but I think that's very bad. I think ViewModel should somehow findout that Model was changed and it should call method.


Solution

  • I suggest using the ToggleSwitch's Command property -- that will get executed every time the user changes the toggle, and will allow you to bind to the parent data context. Use something like this in the XAML:

    <ItemsControl x:Name="items" ItemsSource="{Binding Modules}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <toolkit:ToggleSwitch x:Name="LockSwitch"
                    Command="{Binding ElementName=items,Path=DataContext.LockToggleCommand}"
                    CommandParameter="{Binding}"
                    IsChecked="{Binding IsLock, Mode=TwoWay}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    Then just add the "LockToggleCommand" to your main view model, and call the service, eg:

    public ObservableCollection<Module> Modules { get; private set; }
    
    public ICommand LockToggleCommand { get; private set; }
    
    public ViewModel()
    {
        LockToggleCommand = new DelegateCommand<Module>(module => {
            apiService.Lock(module);
        });
    }
    

    Here "DelegateCommand" is just the usual implementation of ICommand -- I am sure that MVVM Light has its own standard implementation.


    Edit

    I thought that ToggleSwitch supported Command, but since it doesn't, you can take a similar approach using an EventTrigger (if you are willing to add the System.Windows.Interactivity and Microsoft.Expression.Interactions DLLs to your project):

    <ItemsControl x:Name="items" ItemsSource="{Binding Modules}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <toolkit:ToggleSwitch x:Name="LockSwitch"
                    IsChecked="{Binding IsLock, Mode=TwoWay}">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Toggled">
                            <ei:CallMethodAction TargetObject="{Binding ElementName=items,Path=DataContext}"
                                                 MethodName="OnToggled"
                                                 />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    Now add the "OnToggled" method to the main view model -- use the "sender" parameter to get the current item, something like this:

    public void OnToggled(object sender, RoutedEventArgs e)
    {
        var toggleSwitch = (ToggleSwitch)sender;
        var module = (Module)toggleSwitch.DataContext;
        apiService.Lock(module);
    }