Reputation: 1635
I've been messing around with MVVM in WPF and have a quick question for folks. Right now I have:
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:
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).
Upvotes: 0
Views: 1032
Reputation: 132548
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
Upvotes: 2
Reputation: 174299
You are way off base here.
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.
When you use MVVM you normally don't have click handlers.
The correct way to handle this situation would be the following:
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.
Upvotes: 5