Background loading in MVVM and Constructor Injection - c#

I have a question about how and where to load a large amount of data with ViewModel in WPF .NET 4.0 (so no async/await :/ ).
Here is my ViewModel:
public class PersonsViewModel : ViewModelBase
{
private readonly IRepository<Person> _personRepository;
private IEnumerable<Person> _persons;
public IEnumerable<Person> Persons
{
get { return _persons; }
private set { _persons = value; OnPropertyChanged("Persons"); }
}
public PersonsViewModel(IRepository<Person> personRepository)
{
if (personRepository == null)
throw new ArgumentNullException("personRepository");
_personRepository = personRepository;
}
}
This ViewModel is used in a Window and I need to load all the persons when the Window opens. I thought of many solutions but I can't figure which is the best (or maybe there's a better way to do this). I have two contraints:
- all the data must be loaded in another thread because it can take seconds to load (huge amount of data in the database) and I don't want to freeze the UI.
- the ViewModel must be testable.
--=[ First solution: Lazy loading ]=--
public class PersonsViewModel : ViewModelBase
{
private IEnumerable<Person> _persons;
public IEnumerable<Person> Persons
{
get
{
if (_persons == null)
_persons = _personRepository.GetAll();
return _persons;
}
}
}
I don't like this solution because the data is loaded in the main thread.
--=[ Second solution: Loaded event ]=--
public class PersonsViewModel : ViewModelBase
{
// ...
private Boolean _isDataLoaded;
public Boolean IsDataLoaded
{
get { return _isDataLoaded; }
private set { _isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
}
public void LoadDataAsync()
{
if(this.IsDataLoaded)
return;
var bwLoadData = new BackgroundWorker();
bwLoadData.DoWork +=
(sender, e) => e.Result = _personRepository.GetAll();
bwLoadData.RunWorkerCompleted +=
(sender, e) =>
{
this.Persons = (IEnumerable<Person>)e.Result;
this.IsDataLoaded = true;
};
bwLoadData.RunWorkerAsync();
}
}
public class PersonWindow : Window
{
private readonly PersonsViewModel _personsViewModel;
public PersonWindow(IRepository<Person> personRepository)
{
_personsViewModel = new PersonsViewModel(personRepository);
this.Loaded += PersonWindow_Loaded;
}
private void PersonWindow_Loaded(Object sender, RoutedEventArgs e)
{
this.Loaded -= PersonWindow_Loaded;
_personsViewModel.LoadDataAsync();
}
}
I don't really like this solution because it forces the user of the ViewModel to call the LoadDataAsync method.
--=[ Third solution: load data in the ViewModel constructor ]=--
public class PersonsViewModel : ViewModelBase
{
// ...
public PersonsViewModel(IRepository<Person> personRepository)
{
if (personRepository == null)
throw new ArgumentNullException("personRepository");
_personRepository = personRepository;
this.LoadDataAsync();
}
private Boolean _isDataLoaded;
public Boolean IsDataLoaded
{
get { return _isDataLoaded; }
private set { _isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
}
public void LoadDataAsync()
{
if(this.IsDataLoaded)
return;
var bwLoadData = new BackgroundWorker();
bwLoadData.DoWork +=
(sender, e) => e.Result = _personRepository.GetAll();
bwLoadData.RunWorkerCompleted +=
(sender, e) =>
{
this.Persons = (IEnumerable<Person>)e.Result;
this.IsDataLoaded = true;
};
bwLoadData.RunWorkerAsync();
}
}
In this solution, the user of the ViewModel don't need to call an extra method to load data, but it violates the Single Responsability Principle as Mark Seeman says in his book "Dependency Injection" : "Keep the constructor free of any logic. The SRP implies that members should do only one thing, and now that we use the constructor to inject DEPENDENCIES, we should prefer to keep it free of other concerns".
Any ideas to resolve this problem in a proper way?

Difficult to give an accurate answer without knowing how you tie your ViewModels to your Views.
One practice is to have a "navigation aware" ViewModel (a ViewModel which implements a certain interface like INavigationAware and have your navigation service call this method when it instantiates the ViewModel/View and tie them together. It's the way how Prism's FrameNavigationService works.
i.e.
public interface INavigationAware
{
Task NavigatedTo(object param);
Task NavigatedFrom(...)
}
public class PersonWindow : ViewModelBase, INavigationAware
{
}
and implement your initialization code within NavigatedTo which would be called from the navigation service, if the ViewModel implements INavigationAware.
Prism for Windows Store Apps References:
INavigationAware
FrameNavigationService => Look at the NavigateToCurrentViewModel Method

Related

Passing a variable to a second window using MVVMLight?

I am building an application to teach myself MVVM and with some Googling (and some trial an error) I have managed to get to the point where I can open a second window from the ViewModel but not to pass a variable from one page to the other. This is my ViewModel.
public VendorSelectViewModel()
{
Ping ping = new Ping();
PingReply pingresult = ping.Send("192.168.1.10");
if (pingresult.Status.ToString() == "Success")
{
LoadVendorsAsync();
}
else
{
LoadVendors();
}
NextCommand = new RelayCommand(NextWindow);
}
public ICommand NextCommand { get; private set; }
void NextWindow()
{
Console.WriteLine(selectedVendor.VendorName);
Messenger.Default.Send(new NotificationMessage("NextWindow"));
}
In my view I have this
public VendorSelectWindow()
{
InitializeComponent();
_vm = new Biz.Invoicer.VendorSelectViewModel();
DataContext = _vm;
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "NextWindow")
{
var invoicerWindow = new InvoicerWindow();
invoicerWindow.Show();
}
}
So I know (or I think I know) this may not be a "Best Practice" but I will come back to this and refactor as I get to know the MVVM patern and MVVM Light better. Currently I am trying to pass a variable from the ViewModel of the first page (VendorSelectViewModel) to the Second page (InvoicerWindow) but I haven't managed to the syntax correct.
What do I need to do to pass a variable from one page to the next?
First of all you can pass an arbitrary object as the parameter of the IMessenger.Send<TMessage> method - the TMessage type parameter is not restricted. E.g.:
//ViewModel:
void NextWindow()
{
//...
int someValue = 10;
Messenger.Default.Send(someValue);
}
//View:
public VendorSelectWindow()
{
//...
Messenger.Default.Register<int>(this, MessageReceived);
}
private void MessageReceived(int value)
{
//...
}
If however you find the NotificationMessage class particularly useful in your case you could make use of the generic NotificationMessage<T> version, which exposes additional property Content of arbitrary type T:
//ViewModel:
void NextWindow()
{
//...
int someValue = 10;
Messenger.Default.Send(new NotificationMessage<int>(someValue, "Notification text"));
}
//View:
public VendorSelectWindow()
{
//...
Messenger.Default.Register<NotificationMessage<int>>(this, MessageReceived);
}
private void MessageReceived(NotificationMessage<int> message)
{
var someValue = message.Content;
//...
}
Or, if that does not suit you, you could create your own class deriving from NotificationMessage and exposing additional members and use that as the message object.
Instead of passing a NotificationMessage to the messenger, you could pass an instance of your own custom type which may carry as many values you want:
void NextWindow()
{
Console.WriteLine(selectedVendor.VendorName);
Messenger.Default.Send(new YourPayload() {WindowName = "NextWindow", Parameter = "some value..:");
}
...
public VendorSelectWindow()
{
InitializeComponent();
_vm = new Biz.Invoicer.VendorSelectViewModel();
DataContext = _vm;
Messenger.Default.Register<YourPayload>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(YourPayload msg)
{
if (msg.WindowName == "NextWindow")
{
string param = msg.Parameter;
var invoicerWindow = new InvoicerWindow();
invoicerWindow.Show();
}
}
YourPayload is a custom class with two properties, WindowName and Parameter.

Notify ViewModel of Asynchronous changes in Model

I'm relatively new to MVVM and I'm wondering about the best way to structure my application. Here is a sample of my models:
public class ModelSource : ModelBase
{
#region Fields
private int _isLoading;
private BackgroundWorker worker = new BackgroundWorker();
private ObservableCollection<PCDatabase> _databases;
#endregion //Fields
#region Properties
public ObservableCollection<PCDatabase>Databases
{
get
{
if (_databases == null)
{
_databases = new ObservableCollection<PCDatabase>();
}
return _databases;
}
set
{
_databases = value;
this.OnPropertyChanged("Databases");
}
}
public int IsLoading
{
get
{
return _isLoading;
}
set
{
_isLoading = value;
OnPropertyChanged("IsLoading");
}
}
#endregion
#region Methods
/// <summary>
/// Gets all Databases from the Server
/// </summary>
public void getDatabasesAsync(ConfigDatabaseConnection _currentConfig)
{
//execute SQL Query...
}
(ModelBase implements INotifyPropertyChanged).
Here is my corresponding ViewModel:
namespace DbRestore.ViewModel
{
public class ViewModelSource : ViewModelBase
{
private ObservableCollection<PCDatabase> _databases;
private ModelSource _modelSource;
private ICommand _populateDatabaseCommand;
public ConfigDatabaseConnection _currentConfig;
public ViewModelSource()
{
this.ModelSource = new ModelSource();
}
#region Commands
/// <summary>
/// Command that opens a Database Connection Dialog
/// </summary>
public ICommand OpenDataBaseConnectionCommand
{
get
{
if (_populateDatabaseCommand == null)
{
_populateDatabaseCommand = new RelayCommand(
param => this.PopulateDatabases()
);
}
return _populateDatabaseCommand;
}
}
public ObservableCollection<PCDatabase> Databases
{
get
{
return _databases;
}
set
{
_databases = value;
OnPropertyChanged("Databases");
}
}
#endregion //Commands
public void PopulateDatabases()
{
ModelSource.getDatabasesAsync(_currentConfig);
}
Calling ModelSource.getDatabasesAsync(_currentConfig) gets my SQL Data in my model. Due to some of my SQL queries being quite complex, I've implemented a Background Worker that runs these queries asynchronously.
How do I get the data into my ViewModel, which is bound to my View? Or is my design approach as a whole faulty?
Things I've considered and tried:
Binding directly to the model: Works, but I've been told that this is a
bad practice, and the application logic should reside in the Model.
Moving the SQL queries into the ViewModel: Also works, but then my Model
class seems to be redundant - it would be nothing but a custom datatype.
Run the queries synchronously and directly assign the Observable
Collection in my model to the Observable Collection in my ViewModel. Also
works, but then I'm running into problems with my BackgroundWorker,
because the ViewModel won't know when the Query is actually finished.
Move all your database logic into a service (aka repository) class.
It is OK to bind directly to the model properties instead of creating a dozen ViewModel proxy classes for each Model, as soon as you don't need any special view-related logic around a particular model. So exposing a collection of PCDatabase is OK.
Since you're using BackgroundWorker, I assume you use .NET Framework 3.5 and don't have TPL.
public interface IPCDatabaseRepository
{
void GetPCDatabasesAsync(Action<IList<PCDatabase>> resultHandler);
}
public class PCDatabaseRepository : IPCDatabaseRepository
{
public void GetPCDatabasesAsync(Action<IList<PCDatabase>> resultHandler)
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
args.Result = // Execute SQL query...
};
worker.RunWorkerCompleted += (sender, args) =>
{
resultHandler(args.Result as IList<PCDatabase>);
worker.Dispose();
};
worker.RunWorkerAsync();
}
}
public class ViewModelSource : ViewModelBase
{
private readonly IPCDatabaseRepository _databaseRepository;
private ObservableCollection<PCDatabase> _databases;
private bool _isBusy;
public ViewModelSource(IPCDatabaseRepository databaseRepository /*Dependency injection goes here*/)
{
_databaseRepository = databaseRepository;
LoadDatabasesCommand = new RelayCommand(LoadDatabases, () => !IsBusy);
}
public ICommand LoadDatabasesCommand { get; private set; }
public ObservableCollection<PCDatabase> Databases
{
get { return _databases; }
set { _databases = value; OnPropertyChanged("Databases"); }
}
public bool IsBusy
{
get { return _isBusy; }
set { _isBusy = value; OnPropertyChanged("IsBusy"); CommandManager.InvalidateRequerySuggested(); }
}
public void LoadDatabases()
{
IsBusy = true;
_databaseRepository.GetPCDatabasesAsync(results =>
{
Databases = new ObservableCollection(results);
IsBusy = false;
});
}
Have you seen these articles?
Async Programming : Patterns for Asynchronous MVVM Applications: Data Binding
https://msdn.microsoft.com/en-us/magazine/dn605875.aspx
Async Programming : Patterns for Asynchronous MVVM Applications: Commands
https://msdn.microsoft.com/en-us/magazine/dn630647.aspx
These should cover a good strategy especially when working with async/await.

caliburn.micro Condutor not working

We are using caliburn.micro for one of our projects and I'm currently having a puzzling problem:
we have the following classes:
ToolViewerViewModel : Conductor<Screen>.Collection.OneActive
DocViewerViewModel : Conductor<DocumentViewModel>
and various document-views, all with this base class:
DocumentViewModel : Screen
The ToolViewerViewModel is to manage multiple dock-able tool views which allow the user to control different aspects of the program.
The DocViewerViewModel is to show the user the data he's working on/with. It is here to present one of the many DocumentViewModel to the user. and is implemented as a special dock-able view which can not be closed or detached from the ToolViewerView. For every aspect of the data a specific DocumentViewModel is generated by the DocViewerViewModel and presented to the user.
The DocumentViewModel is the base class for all presentation aspects of the data. One may present the data as a table an other may present it as a chart, and so on...
We now encounter problems in terms of OnActivate() and OnDeactivate() which are not called when we expect them to be called.
First Problem:
The system is up and running; The DocumentViewModel is displayed in the DocViewerViewModel which is embedded in the ToolViewerViewModel along with one or two other dock-able views. The currently selected dock-able view is the DocViewerViewModel. When the user now selects one of the other dock-able views the OnDeactivate() method from the DocumentViewModel is being called. Which makes absolutely no sense to me. I'd expect the DocViewerViewModel.OnDeactivate() to be called.
Second Problem:
The system is up and running; The DocumentViewModel is displayed in the DocViewerViewModel which is embedded in the ToolViewerViewModel along with one or two other dock-able views. The currently selected dock-able view is the view that enables the user to change the DocumentViewModel presented by the DocViewerViewModel. When the user now selects an other DocumentViewModel the following code is being executed within the DocViewerViewModel:
DocViewerViewModel.DeactivateItem(oldDocumentViewModel, true);
DocViewerViewModel.ActivateItem(new DocumentViewModel());
I'd expect the DocumentViewModel.OnDeactivate() to be called upon the DocViewerViewModel.DeactivateItem(oldDocumentViewModel, true) call. but that never happens.
Conclusion:
The only proper working Conductor is the ToolViewerViewModel which is managing everything. But this behavior is not what we want or expect to happen: We'd like to have the ToolViewerViewModel only Conduct the dock-able views and the DocViewerViewModel to conduct the DocumentViewModel. This is important because there are two different use cases in place: One to manage multiple instances at the same time and the other where only one instance is active and used, the old instance shall be thrown away.
Hopefully anyone here can help me to get the behavior I'm looking for.
I Now have an example code for you:
public class ToolViewerViewModel : Conductor<Screen>.Collection.OneActive
{
private readonly IDockManager _dockManager;
private readonly DocViewerViewModel _docViewerViewModel;
private readonly IList<DockableViewModel> _toolViews = new List<DockableViewModel>();
public ToolViewerViewModel(IViewModelFactory viewModelFactory, DocViewerViewModel docViewerViewModel, IDockManager dockManager)
{
_dockManager = dockManager;
_viewModelFactory = viewModelFactory;
_docViewerViewModel = docViewerViewModel;
}
protected override void OnViewLoaded(object view)
{
_dockManager.Link(this);
_dockManager.CreateSpecialPaneFor(_docViewerViewModel);
ActivateItem(_docViewerViewModel);
ShowToolView<ProjectExplorerViewModel>();
base.OnViewLoaded(view);
}
public void ShowToolView<T>() where T : DockableViewModel
{
if (!IsToolViewOpen<T>())
{
var viewModel = _viewModelFactory.Create<T>();
ActivateItem(viewModel);
RefreshMenu(typeof(T));
}
}
}
Next class:
public class DocViewerViewModel : Conductor<DocumentViewModel>
{
private readonly IViewModelFactory _viewModelFactory;
public DocViewerViewModel(IViewModelFactory viewModelFactory)
{
_viewModelFactory = viewModelFactory;
}
public bool ShowInMainView<T>() where T : DocumentViewModel
{
return ShowInMainView(typeof(T));
}
private bool ShowInMainView(Type viewModelType)
{
var ret = false;
// close the current view
if (ActiveItem != null)
{
DeactivateItem(ActiveItem, true); //The close flag is on true since we want to remove the current instance from the memory
}
// check whether the current viewModel has been closed successfully
if (ActiveItem == null)
{
try
{
var viewModel = _viewModelFactory.Create(viewModelType) as DocumentViewModel;
if (viewModel != null)
{
ActivateItem(viewModel);
ret = true;
}
else
{
ActivateItem(_viewModelFactory.Create<NoDataViewModel>());
}
}
catch (Exception ex)
{
ActivateItem(_viewModelFactory.Create<NoDataViewModel>());
}
}
return ret;
}
}
and the last one:
public abstract class DocumentViewModel : Screen
{
private bool _isDirty;
protected IViewModelFactory ViewModelFactory { get; private set; }
protected IEventAggregator EventAggregator { get; private set; }
public bool IsDirty
{
get
{
return _isDirty;
}
protected set
{
if (value.Equals(_isDirty))
{
return;
}
_isDirty = value;
NotifyOfPropertyChange(() => IsDirty);
}
}
protected DocumentViewModel(IViewModelFactory viewModelFactory, IEventAggregator eventAggregator)
{
ViewModelFactory = viewModelFactory;
EventAggregator = eventAggregator;
}
protected override void OnDeactivate(bool close)
{
if (close)
{
if (EventAggregator != null)
{
EventAggregator.Unsubscribe(this);
}
}
base.OnDeactivate(close);
}
protected override void OnActivate()
{
if (EventAggregator != null)
{
EventAggregator.Subscribe(this);
}
base.OnActivate();
}
public override void CanClose(Action<bool> callback)
{
var ret = true;
if (IsDirty && (ViewModelFactory != null))
{
var saveDialog = ViewModelFactory.Create<SaveDialogViewModel>();
saveDialog.Show();
if (saveDialog.DialogResult == DialogResult.Cancel)
{
ret = false;
}
else
{
if (saveDialog.DialogResult == DialogResult.Yes)
{
Save();
}
else
{
Discard();
}
IsDirty = false;
}
}
callback(ret);
}
public abstract void Save();
public virtual void Discard()
{
}
}
With this code the only time the DocumentViewModel.OnDeactivate() is being called when the user brings an other dock-able view into focus while the DocViewerViewModel was having the focus. This should not happen!
When the user is changing the focus between the dock-able views the DocumentViewModel.OnDeactivate() should not get call. But it must get called when ever the Method DocViewerViewModel.ShowInMainView<SomeDocumentViewModel>() is being called. Which isn't the case currently.
As far as I can tell, there is nothing wrong with the way your code is written. Since you are using MVVM, I suggest you design a test case like I've provided here.
And here's a snippet of the test case
// TestHarness.cs
[TestMethod]
public void CheckDeactivation()
{
// We'd like to have the ToolViewerViewModel only Conduct the dock-able views
// and the DocViewerViewModel to conduct the DocumentViewModel.
IViewModelFactory factory = new ViewModelFactory();
DocViewerViewModel docViewer = new DocViewerViewModel(factory);
IDockManager dockManager = null;
var toolViewer = new ToolViewerViewModel(factory, docViewer, dockManager);
var mockToolView = new UserControl();
(toolViewer as IViewAware).AttachView(mockToolView);
DocumentViewModel docView1 = new NoDataViewModel();
DocumentViewModel docView2 = new NoDataViewModel();
docViewer.ActivateItem(docView1);
docViewer.ActivateItem(docView2);
Assert.AreEqual(0, docViewer.CountDeactivated());
}
I have had the exact same problem as you, and ended up using PropertyChangedBase instead of Screen and got the problem to disappear.
Later, after reading the docs on Screens and Conductors here, I realized that I wasn't activating the conductor itself further up in the view hierarchy!
So have a look at wherever you use your ToolViewerViewModel, and make sure you activate that instance!
Thank you very much for your Test. Even thought it is really nice code it tests the wrong code part. Your code simply tests whether the Method ActivateItem() or DeactivateItem() is being called:
public override void ActivateItem(DocumentViewModel item)
{
_countActivated++;
base.ActivateItem(item);
}
public override void DeactivateItem(DocumentViewModel item, bool close)
{
_countDeactivated++;
base.DeactivateItem(item, close);
}
But since these Methods are being called explicitly we don't need to test for that...
The real Problem is that the Conductor is not calling the OnActivate() or OnDeactivate() on the DocumentViewModel class. To enhance your test I used the following code:
public class DummyViewModelFactory : IViewModelFactory
{
private readonly Dictionary<Type, Func<object>> _registredCreators = new Dictionary<Type, Func<object>>();
public T Create<T>() where T : PropertyChangedBase
{
return Create(typeof(T)) as T;
}
public object Create(Type type)
{
if (type == null)
{
return null;
}
if (_registredCreators.ContainsKey(type))
{
return _registredCreators[type]();
}
return null;
}
public void Release(object instance)
{
}
public void RegisterCreatorFor<T>(Func<T> creatorFunction)
{
_registredCreators.Add(typeof(T), () => creatorFunction());
}
}
As concrete DocumentViewModel implementation I made:
public class NoDataViewModel : DocumentViewModel
{
public NoDataViewModel(IEventAggregator eventAggregator,
IViewModelFactory viewModelFactory)
: base(viewModelFactory, eventAggregator, )
{
}
public override void Save()
{
// nothing to do
}
public override void Reload()
{
// nothing to do
}
}
public class NoDataViewModelMock : NoDataViewModel
{
private static int activationCounterForTesting = 0;
private static int deactivationCounterForTesting = 0;
public static int ActivationCounterForTesting
{
get
{
return activationCounterForTesting;
}
}
public static int DeactivationCounterForTesting
{
get
{
return deactivationCounterForTesting;
}
}
public NoDataViewModelMock()
: base(null, null)
{
}
protected override void OnActivate()
{
activationCounterForTesting++;
base.OnActivate();
}
protected override void OnDeactivate(bool close)
{
deactivationCounterForTesting++;
base.OnDeactivate(close);
}
}
And I changed your Testmethod to this:
[TestMethod]
public void CheckDeactivation()
{
var viewModelFactory = new DummyViewModelFactory();
viewModelFactory.RegisterCreatorFor<NoDataViewModel>(() => new NoDataViewModelMock());
var docViewer = new DocViewerViewModel(viewModelFactory);
IDockManager dockManager = null;
var toolViewer = new ToolViewerViewModel(viewModelFactory, docViewer, dockManager);
var mockToolView = new UserControl();
(toolViewer as IViewAware).AttachView(mockToolView);
docViewerViewModel.ShowInMainView<NoDataViewModel>();
docViewerViewModel.ShowInMainView<NoDataViewModel>();
docViewerViewModel.ShowInMainView<NoDataViewModel>();
Assert.AreEqual(3, NoDataViewModelMock.ActivationCounterForTesting);
Assert.AreEqual(2, NoDataViewModelMock.DeactivationCounterForTesting);
}
Then you'll see that the OnActivate() and OnDeactivate() methods are never been called.
With a little more advanced test you'd also see that they are being called but from the ToolViewerViewModel directly. I'd like to know why and how I can change this behavior to fit my needs:
The DocumentViewModel.OnActivate() method should get called when the DocViewerViewModel.ShowInMainView<T>() method gets called.
The DocumentViewModel.OnDeactivate() method should get called on the old DocumentViewModel when a new one is being shown by calling the DocViewerViewModel.ShowInMainView<T>()
Our Solution for that Problem is to remove the use Screen as BaseClass for DocViewerViewModel an implement the Conductor Logic our self.

Implementing "close window" command with MVVM

So my first attempt did everything out of the code behind, and now I'm trying to refactor my code to use the MVVM pattern, following the guidance of the MVVM in the box information.
I've created a viewmodel class to match my view class, and I'm moving the code out of the code behind into the viewmodel starting with the commands.
My first snag is trying to implement a 'Close' button that closes the window if the data has not been modified. I've rigged up a CloseCommand to replace the 'onClick' method and all is good except for where the code tries to run this.Close(). Obviously, since the code has been moved from a window to a normal class, 'this' isn't a window and therefore isn't closeable. However, according to MVVM, the viewmodel doesn't know about the view, so i can't call view.Close().
Can someone suggest how I can close the window from the viewmodel command?
I personally use a very simple approach: for every ViewModel that is related to a closeable View, I created a base ViewModel like this following example:
public abstract class CloseableViewModel
{
public event EventHandler ClosingRequest;
protected void OnClosingRequest()
{
if (this.ClosingRequest != null)
{
this.ClosingRequest(this, EventArgs.Empty);
}
}
}
Then in your ViewModel that inherits from CloseableViewModel, simply call this.OnClosingRequest(); for the Close command.
In the view:
public class YourView
{
...
var vm = new ClosableViewModel();
this.Datacontext = vm;
vm.ClosingRequest += (sender, e) => this.Close();
}
You don't need to pass the View instance to your ViewModel layer. You can access the main window like this -
Application.Current.MainWindow.Close()
I see no issue in accessing your main window in ViewModel class as stated above. As per MVVM principle there should not be tight coupling between your View and ViewModel i.e. they should work be oblivious of others operation. Here, we are not passing anything to ViewModel from View. If you want to look for other options this might help you - Close window using MVVM
My solution to close a window from view model while clicking a button is as follows:
In view model
public RelayCommand CloseWindow;
Constructor()
{
CloseWindow = new RelayCommand(CloseWin);
}
public void CloseWin(object obj)
{
Window win = obj as Window;
win.Close();
}
In View, set as follows
<Button Command="{Binding CloseWindowCommand}" CommandParameter="{Binding ElementName=WindowNameTobeClose}" Content="Cancel" />
I do it by creating a attached property called DialogResult:
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null && (bool?)e.NewValue == true)
window.Close();
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
then write this to you XAML, in the window tag
WindowActions:DialogCloser.DialogResult="{Binding Close}"
finally in the ViewModel
private bool _close;
public bool Close
{
get { return _close; }
set
{
if (_close == value)
return;
_close = value;
NotifyPropertyChanged("Close");
}
}
if you change the Close to true, the window will be closed
Close = True;
Here is the simplest and pure MVVM solution
ViewModel Code
public class ViewModel
{
public Action CloseAction { get; set; }
private void CloseCommandFunction()
{
CloseAction();
}
}
Here is XAML View Code
public partial class DialogWindow : Window
{
public DialogWindow()
{
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.CloseAction = Close;
}
}
This solution is quick and easy. Downside is that there is some coupling between the layers.
In your viewmodel:
public class MyWindowViewModel: ViewModelBase
{
public Command.StandardCommand CloseCommand
{
get
{
return new Command.StandardCommand(Close);
}
}
public void Close()
{
foreach (System.Windows.Window window in System.Windows.Application.Current.Windows)
{
if (window.DataContext == this)
{
window.Close();
}
}
}
}
MVVM-light with a custom message notification to avoid the window to process every notificationmessage
In the viewmodel:
public class CloseDialogMessage : NotificationMessage
{
public CloseDialogMessage(object sender) : base(sender, "") { }
}
private void OnClose()
{
Messenger.Default.Send(new CloseDialogMessage(this));
}
Register the message in the window constructor:
Messenger.Default.Register<CloseDialogMessage>(this, nm =>
{
Close();
});
This is very similar to eoldre's answer. It's functionally the same in that it looks through the same Windows collection for a window that has the view model as its datacontext; but I've used a RelayCommand and some LINQ to achieve the same result.
public RelayCommand CloseCommand
{
get
{
return new RelayCommand(() => Application.Current.Windows
.Cast<Window>()
.Single(w => w.DataContext == this)
.Close());
}
}
using MVVM-light toolkit:
In the ViewModel:
public void notifyWindowToClose()
{
Messenger.Default.Send<NotificationMessage>(
new NotificationMessage(this, "CloseWindowsBoundToMe")
);
}
And in the View:
Messenger.Default.Register<NotificationMessage>(this, (nm) =>
{
if (nm.Notification == "CloseWindowsBoundToMe")
{
if (nm.Sender == this.DataContext)
this.Close();
}
});
This is taken from ken2k answer (thanks!), just adding the CloseCommand also to the base CloseableViewModel.
public class CloseableViewModel
{
public CloseableViewModel()
{
CloseCommand = new RelayCommand(this.OnClosingRequest);
}
public event EventHandler ClosingRequest;
protected void OnClosingRequest()
{
if (this.ClosingRequest != null)
{
this.ClosingRequest(this, EventArgs.Empty);
}
}
public RelayCommand CloseCommand
{
get;
private set;
}
}
Your view model, inherits it
public class MyViewModel : CloseableViewModel
Then on you view
public MyView()
{
var viewModel = new StudyDataStructureViewModel(studyId);
this.DataContext = viewModel;
//InitializeComponent(); ...
viewModel.ClosingRequest += (sender, e) => this.Close();
}
Given a way, Please check
https://stackoverflow.com/a/30546407/3659387
Short Description
Derive your ViewModel from INotifyPropertyChanged
Create a observable property CloseDialog in ViewModel, Change CloseDialog property whenever you want to close the dialog.
Attach a Handler in View for this property change
Now you are almost done. In the event handler make DialogResult = true
first of all give your window a name like
x:Name="AboutViewWindow"
on my close button I've defined Command and Command Parameter like
CommandParameter="{Binding ElementName=AboutViewWindow}"
Command="{Binding CancelCommand}"
then in my view model
private ICommand _cancelCommand;
public ICommand CancelCommand
{
get
{
if (_cancelCommand == null)
{
_cancelCommand = new DelegateCommand<Window>(
x =>
{
x?.Close();
});
}
return _cancelCommand;
}
}
Most MVVM-compliant solution using HanumanInstitute.MvvmDialogs
Implement ICloseable interface in your ViewModel and that's it!
No code in your view whatsoever.

In WPF, how can a Command's CanExecute method gain visibility of other UI elements?

I've been using WPF for a while but I'm new to Commands, but would like to start using them properly for once. Following a code example, I've established a separate static Commands class to hold all of my commands, and it looks like this.
public static class Commands
{
public static RoutedUICommand OpenDocument { get; set; }
static Commands()
{
OpenDocument = new RoutedUICommand("Open Document", "OpenDocument", typeof(Commands));
}
public static void BindCommands(Window window)
{
window.CommandBindings.Add(new CommandBinding(OpenDocument, OpenDocument_Executed, OpenDocument_CanExecute));
}
private static void OpenDocument_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Should be set to true if an item is selected in the datagrid.
}
private static void OpenDocument_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
}
My problem is that although the command is going to be bound to a Button control in MainWindow.xaml, the OpenDocument_CanExecute method needs to look at a DataGrid in MainWindow.xaml to see if an item is selected.
How can I wire things up such that the method can see the DataGrid?
SOLUTION
Inspired by Ken's reply (thanks again!), I put the following in place, which works perfectly.
MainWindow.xaml.cs
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += delegate
{
DataContext = ViewModel.Current;
Commands.BindCommands(this);
};
}
}
ViewModel.cs
public class ViewModel
{
private static ViewModel _current;
public static ViewModel Current
{
get { return _current ?? (_current = new ViewModel()); }
set { _current = value; }
}
public object SelectedItem { get; set; }
}
Commands.cs
public static class Commands
{
public static RoutedUICommand OpenDocument { get; set; }
static Commands()
{
OpenDocument = new RoutedUICommand("Open Document", "OpenDocument", typeof(Commands));
}
public static void BindCommands(Window window)
{
window.CommandBindings.Add(new CommandBinding(OpenDocument, OpenDocument_Executed, OpenDocument_CanExecute));
}
private static void OpenDocument_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ViewModel.Current.SelectedItem != null;
}
private static void OpenDocument_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
}
ICommand implementations work best in the MVVM pattern:
class ViewModel : INotifyPropertyChanged {
class OpenDocumentCommand : ICommand {
public bool CanExecute(object parameter) {
return ViewModel.ItemIsSelected;
}
public OpenDocumentCommand(ViewModel viewModel) {
viewModel.PropertyChanged += (s, e) => {
if ("ItemIsSelected" == e.PropertyName) {
RaiseCanExecuteChanged();
}
};
}
}
private bool _ItemIsSelected;
public bool ItemIsSelected {
get { return _ItemIsSelected; }
set {
if (value == _ItemIsSelected) return;
_ItemIsSelected = value;
RaisePropertyChanged("ItemIsSelected");
}
}
public ICommand OpenDocument {
get { return new OpenDocumentCommand(this); }
}
}
Obviously, I left out a whole bunch of stuff. But this pattern has worked well for me in the past.
why even implement a command if you are tightly coupling it to UI implementation? Just respond to datagrid.SelectionChanged and code in what supposed to happen.
Otherwise, put it in the ViewModel. Have the ViewModel monitor it's state and evaluate when CanExe is true.
Edit
On the other hand, you can pass a parameter to your command, as well as Exe() & CanExe() methods
//where T is the type you want to operate on
public static RoutedUICommand<T> OpenDocument { get; set; }
If you are doing an MVVM solution, this would be the perfect time to implement a publish / subscribe aggregator that allows controls to "talk" to each other. The gist behind it is that the datagrid would publish an event, 'Open Document'. Subsequent controls could subscribe to the event and react to the call to 'Open Document'. The publish / subscribe pattern prevents tightly coupling the datagrid and the control. Do some searches for event aggregators and I think you'll be on your way.

Categories

Resources