0
votes

I've been messing around with MVVM in WPF and have a quick question for folks. Right now I have:

  • MainWindow which consists of a MenuBar and a UserControl
  • UserControl (mentioned above) contains basically a Grid.

I expose access to Grid Properties as I need in the UserControl, but the User Control knows nothing nor interacts with the MainWindow.

I also have a class I call ViewModel which does manipulation of the MainWindow/UserControl for me. My understanding is that the ViewModel knows about the View (MainWindow/UserControl) and how to manipulate it while the View generally knows nothing about the ViewModel.

If I have that right, here is my question:

  1. When I do button clicks on the MainWindow MenuBar I want to perform actions. Right now those actions are bound to say a EventHandler in the MainWindow and the EventHandler instantiates the ViewModel and calls the method for handling like such:

    private void RunQueryMenuItemAdvClick(object pSender, RoutedEventArgs pRoutedEventArgs)
    {
        ViewModel vViewModel = new ViewModel(this);
        vViewModel.RunQuery();
    }
    

The View Model looks something like this:

    public class ViewModel
{
    private DataProvider fDataProvider;

    private MainWindow fMainWindow;

    private BackgroundWorker fQueryWorker = new BackgroundWorker();

    public ViewModel(MainWindow pMainWindow)
    {
        fDataProvider = new DataProvider();
        fMainWindow = pMainWindow;

        //Query Worker
        fQueryWorker.DoWork += QueryWorkerDoWork;
        fQueryWorker.RunWorkerCompleted += QueryWorkerCompleted;
    }

    private void QueryWorkerCompleted(object pSender, RunWorkerCompletedEventArgs pRunWorkerCompletedEventArgs)
    {
        fMainWindow.UserControl_Data.busyIndicator1.IsBusy = false;
        fMainWindow.UserControl_Data.DataToPresent = pRunWorkerCompletedEventArgs.Result;
    }

    private void QueryWorkerDoWork(object pSender, DoWorkEventArgs pDoWorkEventArgs)
    {
        pDoWorkEventArgs.Result = this.fDataProvider.GetParticipantsData();
    }

    public void RunQuery()
    {
        if (!fQueryWorker.IsBusy)
        {
            fMainWindow.UserControl_Data.busyIndicator1.IsBusy = true;
            fQueryWorker.RunWorkerAsync();
        }
    }
}

Am I way off base with my approach here?

EDIT New Solution: First, thanks to everyone for their response. I'd like to provide my new solution. This may not be 100% MVVM, but it has to be at least 80% better than what I had!

My ViewModel:

    public class ViewModel : ObservableObject
{
    private DataProvider fDataProvider;

    private BackgroundWorker fQueryWorker = new BackgroundWorker();


    public ViewModel()
    {
        fDataProvider = new DataProvider();

        //Query Worker
        fQueryWorker.DoWork += QueryWorkerDoWork;
        fQueryWorker.RunWorkerCompleted += QueryWorkerCompleted;
    }

    //This is my Command for the MainWindow.MenuItem to bind to to run a query
    RelayCommand fRunQueryCommand;
    public ICommand RunQueryCommand
    {
        get
        {
            if (this.fRunQueryCommand == null)
            {
                this.fRunQueryCommand = new RelayCommand(param => this.RunQuery(),
                    param => true);
            }
            return this.fRunQueryCommand;
        }
    }

    //This is my Property for the UserControl.progressBar to bind to
    private bool fIsBusy;
    public bool IsBusy
    {
        get { return this.fIsBusy; }
        set
        {
            if (value != this.fIsBusy)
            {
                this.fIsBusy = value;
                OnPropertyChanged("IsBusy");
            }
        }
    }

    //This is my Property for the UserControl.gridControl.ItemSource to bind to
    private object fSource;
    public object Source
    {
        get { return this.fSource; }
        set
        {
            if (value != this.fSource)
            {
                this.fSource = value;
                OnPropertyChanged("Source");
            }
        }
    }

    private void QueryWorkerCompleted(object pSender, RunWorkerCompletedEventArgs pRunWorkerCompletedEventArgs)
    {
        this.IsBusy = false;
        Source = pRunWorkerCompletedEventArgs.Result;
    }

    private void QueryWorkerDoWork(object pSender, DoWorkEventArgs pDoWorkEventArgs)
    {
        pDoWorkEventArgs.Result = this.fDataProvider.GetParticipantsData();
    }

    public void RunQuery()
    {
        if (!fQueryWorker.IsBusy)
        {
            this.IsBusy = true;
            fQueryWorker.RunWorkerAsync();
        }
    }

I've removed all of my code from behind the MainWindow and the UserControl and replaced it with XAML for Binding the elements that I needed to the two properties in ViewModel and the 1 Command. Feel free to provide additional feedback on what I may or may not have picked up on with the re-factoring. (Aside from the lack of a Model usage).

2

2 Answers

5
votes

You are way off base here.

  1. It is the other way around: The View knows about the ViewModel and the ViewModel knows nothing about the View.
    Having references to the MainWindow and the UserControl in your ViewModel is anything but MVVM.

  2. When you use MVVM you normally don't have click handlers.

The correct way to handle this situation would be the following:

  • Expose an ICommand in your ViewModel as a property. The MainWindow can bind its button to that command.
  • When the command is invoked inside the ViewModel, execute RunQuery, but you would simply set IsBusy on the ViewModel to true. The UserControl in turn would bind to that property.

All of this works by setting the DataContext of the View to an instance of the ViewModel.

2
votes

Daniel is correct that you seem to be misunderstanding of the MVVM design pattern, and that your ViewModels should never actually reference any UI objects.

The best way I can think of to describe the pattern is your ViewModels are your actual application, your Models are your data objects, and your Views are simply a user-friendly way to let users interact with your ViewModels. In a perfect world, you can run your application using test scripts entirely and never use the View layer at all.

For example, your ViewModel is your application, so it might have an List<ICommand> MenuCommands, with each ICommand being a RelayCommand or DelegateCommand that points to a method in your code, and a boolean IsBusy property.

Your View (Window) simply reflects your ViewModel by binding your <Menu> to the MenuCommands collection, and perhaps showing some loading graphic based on the IsBusy boolean.

I have a fairly basic MVVM example on my blog if you're interested in seeing a simple MVVM example from start to finish