简体   繁体   中英

Action on ComboBox selection changed

I'm using MVVM to bind a ComboBox to a ViewModel, and I have few question about heavy actions and selection change.

I want to trigger some actions when the selected item is changed, my initial approach was to put the logic in the setter of the field to which the selected item is binded.

So my first question is, is this good practice or there is a better approach?

Those actions may be very expensive in time and resources (need to retrieve data through a web service) and I don't want the UI to freeze, so lately I've started to send a message from the set which is received in the view's code-behind and that call a ViewModel command asynchronously.

Am I just wasting time or does this make any sense?

The problem is that when I'm debugging the UI sometimes freeze anyway (it doesn't happened on release). Reading here and there I've come to know that it may be debugger related, can anyone confirm this behavior on VS 2015?

Additional information

As requested I provide some examples. This is my first approach:

(XAML)
<ComboBox SelectedItem="{Binding SelectedField}"/>

(ViewModel)

public class ViewModel  
{
    private MyObject _selectedField = null;
    public MyObject SelectedField
    {
        get
        {
            return _selectedField;
        }
        set
        {
            if(_selectedField != value)
            {
                // Expensive action
                _selectedField = value;
                RaisePropertyChanged(() => SelectedField);
            }
        }
    }
}

The expensive action make some web service calls and may take long, is this design good or is there a better way to achieve this?

My second approach is through messages, as shown in this example:
(ViewModel)

public class ViewModel  
{
    private MyObject _selectedField = null;
    public MyObject SelectedField
    {
        get
        {
            return _selectedField;
        }
        set
        {
            if(_selectedField != value)
            {
                Messenger.Default.Send(new DoStuffMessage());
                _selectedField = value;
                RaisePropertyChanged(() => SelectedField);
            }
        }
    }    
    private RelayCommand _doStuffCommand = null;
    public ICommand DoStuffCommand
    {
        get
        {
            if (_doStuffCommand == null)
                _doStuffCommand = new RelayCommand(async () => await DoStuff());
            return _doStuffCommand;
        }
    }

    private async Task DoStuff()
    {
        // Expensive action
    }
}

(Code-behind)

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Messenger.Default.Register<DoStuffMessage>(this, DoStuffMessage_Handler);
    }

    private void DoStuffMessage_Handler(DoStuffMessage msg)
    {
        (DataContext as ViewModel).DoStuffCommand.Execute(null);
    }

}

Is this approach better or is just bad and useless?

For MVVM, I prefer to use RelayCommands to bind an EventTrigger in XAML to an ICommand in the viewmodel. I feel this creates the best separation of code and is clearer than adding a lot of logic to my setters, where it might be overlooked during troubleshooting. Here is an overview of the process: https://msdn.microsoft.com/en-us/magazine/dn237302.aspx

This is to wire up a button and pass in a parameter, so obviously you would need to modify it for your use case, but it will show the basic technique. In XAML:

<Button Content="Click Me">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <Custom:EventToCommand Command="{Binding MyCommand}" CommandParameter="foo"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

In your VM:

public static ICommand MyCommand { get; set; } // declare an ICommand - bind to this!

public MainViewModel(IDataService dataService)
{
    // associate your ICommand with a method.  If you don't use a parameter, you don't need the lambda expression here.
    MyCommand = new RelayCommand<string>((paramater) => MyCommandMethod(parameter));
}

public void MyCommandMethod(string parameter)
{
    Debug.WriteLine("This is the code I want to run in my VM. The parameter is " + parameter);
}

I use the [free] MVVMLight toolkit for my applications, which was written by the guy who wrote the article that I linked to, but a lot of this is baked into .Net also. Using Expression Blend can make it easier to wire this stuff up when you are designing.

You can do whatever you like in setter as long as it is async.

private string _test;
    public string Test
    {
        get { return _test; }
        set
        {
           Task.Run(() =>
           {
               //do stuff
           });
            _test = value;
        }
    }

If you don't want to place logic in setter, because for example the Single Responsibility principle is violated, you should use interactions to catch the SelectionChange event and call a command in VM which should call an async method.

Here you have a sample that uses interactions : cute link

That's it!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM