简体   繁体   中英

MVVM showDialog with owner and close when background worker finishes

I'm just trying to get a grasp of MVVM pattern in WPF (currently without any framework).

Scenario:

I have a main window, I click a button "Start work" that is bound to some command in the viewmodel. Progress dialog should open with "Cancel" button, it should show on the center of the owner window(so I need to pass the owner), I press cancel and I invoke "CancelAsync" method on background worker.

The principle of MVVM is that the view model should never know anything about the view and in my case I'm violating this rule.

Code-behind (No MVVM) solution:

Main window part:

private void Button_Click(object sender, RoutedEventArgs e)
{
    backgroundWorker.RunWorkerAsync();

    progressWindow = new ProgressWindow(backgroundWorker);
    progressWindow.Owner = this;
    progressWindow.ShowDialog();
}

private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    progressWindow.Close();
}

Progress window part:

private void btnCancel_Click(object sender, RoutedEventArgs e)
{
    backgroundWorker.CancelAsync();
}

My attempt to convert this code to MVVM (this is wrong)

public class MainViewModel
{
    public ICommand DoSomething { get; }
    private BackgroundWorker backgroundWorker;

    private PleaseWaitView pleaseWaitView;

    public MainViewModel()
    {
        backgroundWorker = new BackgroundWorker() { WorkerSupportsCancellation = true };
        backgroundWorker.DoWork += BackgroundWorker_DoWork;
        backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;

        var pleaseWaitViewModel = new PleaseWaitViewModel(backgroundWorker);
        pleaseWaitView = new PleaseWaitView();
        pleaseWaitView.Owner = Application.Current.MainWindow;
        pleaseWaitView.DataContext = pleaseWaitViewModel;

        DoSomething = new ActionCommand<object>(DoSomethingImpl);
    }

    private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        pleaseWaitView.Close();
    }

    private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        // Some work
        Thread.Sleep(5000);
    }

    private void DoSomethingImpl(object parameter)
    {
        pleaseWaitView.ShowDialog();
    }
}

How to solve this? I did what I wanted in code-behind in a matter of 20 minutes, I wanted to try MVVM pattern and it takes me few hours to solve simple problem.

I was looking at some solutions with EventAggregator but that requires using a framework like Prism, Caliburn.Micro. So I get some kind of communication between VM and the View.

You can pass an interface to MainViewModel which contains the needed methods

interface IMainView
{
    void Init(PleaseWaitViewModel viewModel);
    void ShowDialog();
    void Close();
}

public class MainViewModel
{
     private IMainView _view;
     public MainViewModel(IMainView view)
     {
         _view = view;


        backgroundWorker = new BackgroundWorker() { WorkerSupportsCancellation = true };
        backgroundWorker.DoWork += BackgroundWorker_DoWork;
        backgroundWorker.RunWorkerCompleted += 
        BackgroundWorker_RunWorkerCompleted;

        var pleaseWaitViewModel = new PleaseWaitViewModel(backgroundWorker);
        _view.Init(pleaseWaitViewModel);
     }

    private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    _view.Close();
}

private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Some work
    Thread.Sleep(5000);
}

private void DoSomethingImpl(object parameter)
{
    _view.ShowDialog();
}
}

Messenger approach

public class PersonsViewModel
{
        private RelayCommand _addPersonCommand = null;
        public RelayCommand AddPersonCommand
        {
            get
            {
                return _addPersonCommand ?? (_addPersonCommand = new RelayCommand(
                    () =>
                    {
                        Action<Person> callback = (person) =>
                        {
                            _persons.Add(person);
                            RaisePropertyChanged("Persons");
                        };

                        Messenger.Default.Send<NotificationMessageAction<Person>>(new NotificationMessageAction<Person>(this, new Person(), "myNotification", callback), this);          
                    }));
            }
        }
}

private PersonsViewModel _viewModel = null;
public PersonsView()
{
     InitializeComponent();

     DataContext = _viewModel = new PersonsViewModel();
     Messenger.Default.Register<NotificationMessageAction<Person>>(this, _viewModel, message => 
     {
          if(message.Notification == "myNotification")
          {
                Person person = (Person)message.Target;
                Action<Person> callback = message.Execute;
                ModalView view = new ModalView(person);
                if(true == view.ShowDialog())
                {
                      callback.Invoke(view.Person);
                }
          }
      });
}

Action property on view model approach
1) Add action property on the viewmodel
2) Wire it up in the view code behind
3) Invoke action it in the viewmodel logic where needed

    using System;
    using System.Windows;
    using System.Windows.Input;

    namespace WpfApp1
    {
        /// <summary>
        ///     Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();

                // Wire up CancelAction in the View
                var windowToClose = new Window();
                var castedContext = (ViewModel) DataContext;
                castedContext.CancelAction = () => windowToClose.Close();
            }
        }

        public class ViewModel
        {
            private ICommand _doSomethingCommand;
            public Action CancelAction { get; set; }

            public ICommand DoSomethingCommand
            {
                get
                {
                    if (_doSomethingCommand != null)
                        return _doSomethingCommand;

                    _doSomethingCommand = new MyCommandImplementation(() =>
                    {
                        // Perform Logic

                        // If need to cancel - invoke cancel action
                        CancelAction.Invoke();
                    });
                    return _doSomethingCommand;
                }
            }
        }

        // Stubbed out for the sake of complete code
        public class MyCommandImplementation : ICommand
        {
            public MyCommandImplementation(Action action)
            {
                throw new NotImplementedException();
            }

            public bool CanExecute(object parameter)
            {
                throw new NotImplementedException();
            }

            public void Execute(object parameter)
            {
                throw new NotImplementedException();
            }

            public event EventHandler CanExecuteChanged;
        }
    }

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