Im making Login Window in my app based on Caliburn.Micro mvvm framework. So, how to return an property (for example, true if user passed good data or false, if he pass bad credentials) from TryClose() method from my Login Window that is initialize by Caliburn.Micro? How to get information from window opened in IWindowManager.ShowDialog()?
First, my MainWindowViewModel.cs:
using Caliburn.Micro;
namespace TaskManager.ViewModels
{
class MainWindowViewModel : Conductor<IScreen>.Collection.OneActive
{
protected override void OnViewLoaded(object view)
{
IWindowManager manager = new WindowManager();
//Login page, context is data with user's lists
LoginViewModel loginView = new LoginViewModel(context);
manager.ShowDialog(loginView, null, null);
//here i want to get info, if i get logged properly or not
}
public void LoadUserInfoPage() //here starts "main" program
{
ActivateItem(new UserInfoViewModel());
}
//and so on...
}
}
My LoginViewModel.cs:
namespace TaskManager.ViewModels
{
class LoginViewModel : Screen
{
public string Login { get; set; }
public string Password { get; set; }
public LoginViewModel(FakeData context)
{
this.context = context;
}
public void LoginButton()
{
bool check = Services.Login.IsValid(Login, Password, context);
if(check) //if login is OK, check == true
TryClose();
}
private FakeData context { get; set; } //data is here
}
}
Then, my IsValid() Method:
namespace TaskManager.Services
{
static class Login
{
static public bool IsValid(string login, string password, FakeData context)
=> context.users.Any(i => i.Login == login);
//i know it is bad, but its only example
}
}
Buttons, opening and closing windows works great (reading from textboxes too). I want only get info (maybe by reference?) if user is pass good data.
THanks for your advices!
You can make use of EventAggregator for the purpose.
"An Event Aggregator is a service that provides the ability to publish
an object from one entity to another in a loosely based fashion. "
The first step would be create instance of EventAggregator in your ViewModels and subscribe to it. You can do it via DI in constructor of both ViewModels.
For LoginViewModel,
private IEventAggregator _eventAggregator;
public LoginViewModel(FakeData context,IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
And MainWindowViewModel,
private IEventAggregator _eventAggregator;
public MainWindowViewModel (IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
The next step is to create a Message Object, which can transmit the required information between the ViewModels.
public class OnLoginAttemptMessage
{
string UserName { get; set; }
bool IsLoginSuccessful { get; set; }
}
Finally, it is time to put everything together. In youg LoginButton method in LoginViewModel, we modify the code to raise the event on successfull login.
public void LoginButton()
{
bool check = Services.Login.IsValid(Login, Password, context);
if(check) //if login is OK, check == true
{
_eventAggregator.PublishOnUIThread(new OnLoginAttemptMessage
{
UserName = Login,
IsLoginSuccessful = check;
});
TryClose();
}
}
The last step is in MainWindowViewModel, where you need to implement the IHandle interface.
class MainWindowViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<OnLoginSuccessMessage>
{
public void Handle(OnLoginSuccessMessage message)
{
if(message.IsLoginSuccessful)
{
// Login is successfull, do next steps.
}
}
}
You can read more on EventAggregator here (https://caliburnmicro.com/documentation/event-aggregator)
Related
In Caliburn.Micro I have a Shell ViewModel that has 3 IShell properties corresponding to 3 content controls in the associated View. They are 'Full', 'List' and 'Detail'. 'Full' sits above the other two and is as wide as the host Form. 'List' is on the left hand 1 row down and 'Detail' is in the same row as 'List' 1 column to the right.
When the app starts, a Login ViewModel is bound to 'Full' and nothing is bound to the other two. The screen shows only the Login screen. The user should login, and when complete the 'Full' content control should switch from displaying the Login ViewModel, to an AccountViewModel.
For that to work I need the LoginViewModel to tell the ShellViewModel (its parent) to navigate to AccountViewModel.
How do I do that?
public class ShellViewModel : Screen
{
#region Fields
private string _title = "License Manager";
private Conductor<IScreen> _fullFrameConductor;
private Conductor<IScreen> _listFrameConductor;
private Conductor<IScreen> _detailFrameConductor;
#endregion
public ShellViewModel()
{
_fullFrameConductor = new Conductor<IScreen>();
_listFrameConductor = new Conductor<IScreen>();
_detailFrameConductor = new Conductor<IScreen>();
FullFrame = Framework.GetContainer().Resolve<LoginViewModel>();
}
#region Properties
public string Title { get => _title; set => _title = value; }
public IScreen FullFrame
{
get { return _fullFrameConductor.ActiveItem; }
set {
_fullFrameConductor.ActivateItem(value);
NotifyOfPropertyChange(nameof(FullFrame));
}
}
public IScreen ListFrame
{
get { return _listFrameConductor.ActiveItem; }
set {
_listFrameConductor.ActivateItem(value);
NotifyOfPropertyChange(nameof(ListFrame));
}
}
public IScreen DetailFrame
{
get { return _detailFrameConductor.ActiveItem; }
set {
_detailFrameConductor.ActivateItem(value);
NotifyOfPropertyChange(nameof(DetailFrame));
}
}
#endregion
#region Commands
public void ShowProducts()
{
ListFrame = Framework.GetContainer().Resolve<ProductListViewModel>();
DetailFrame = Framework.GetContainer().Resolve<ProductViewModel>();
}
public void ShowLicenses()
{
ListFrame = Framework.GetContainer().Resolve<LicenseListViewModel>();
DetailFrame = Framework.GetContainer().Resolve<LicenseViewModel>();
}
#endregion
}
public class LicenseViewModel : Screen
{
public void Login()
{
// This should process the login and then tell the Shell it is done
// then the shell should navigate to the Account ViewModel sharing
// the user info with the AccountViewModel via a memory cache
// How do I alert the screen ViewModel causing it to close this viewmodel
// without causing a threading problem?
}
}
You can make use of Event Aggregator to communicate between LoginViewModel and ShellViewModel. You can read more on Event Aggregator here.
First, you need to create a Message Class
public class AuthenticationSuccessMessage
{
public bool IsValidLogin{get;set;}
}
Then next step is to use EventAggregator to notify the ShellViewModel from the LicenseViewModel .
private IEventAggregator _eventAggregator;
public LicenseViewModel (IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
public void Login()
{
_eventAggregator.PublishOnUIThread(new AuthenticationSuccessMessage{IsValidLogin=true});
}
The final step is to subscribe to the Events in ShellViewModel.
public class ShellViewModel:Screen, IHandle<AuthenticationSuccessMessage>
{
private readonly IEventAggregator _eventAggregator;
public ShellViewModel:Screen(IEventAggregator eventAggregator) {
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
void Handle<AuthenticationSuccessMessage>(AuthenticationSuccessMessage message)
{
if(message.IsValidLogin)
{
// Do Task
}
}
}
You can read more on Event Aggregators here.
Update : Do not forget to subscribe to Event Aggregator in ShellViewModel.
I am trying to pass a value to a view model from another view model before navigating to the page attached to that view model.
I was previously passing it to the view, then passing it to the view model. This seems like a clumsy way of doing things.
I am not using any kind of framework so that is not an option.
At the moment the property is set as static and this works but im not sure if this is good practice.
The code:
View model 1:
This command opens the new page:
public void OpenRouteDetails()
{
RouteStopPopOverViewModel.RouteName = "TestRoute";
App.Page.Navigation.PushAsync(new RouteStopPopOverView());
}
View model 2: (RouteStopPopOverViewModel)
public static string RouteName { get; set; }
This does work but I would prefer not to use static as a way to achieve this.
Is there some way to set the RouteName property without using static or passing it through view-> view model.
I have seen some answers about this but they don't seem to answer to question clearly.
Share a controller class between view models.
The same instance has to be supplied to the constructor in both view models.
So you can set values, and listen for events in both view models.
The controller class becomes the intermediary.
public class SharedController : IControlSomething
{
private string _sharedValue;
public string SharedValue
{
get => _sharedValue;
set
{
if (_sharedValue == value)
return;
_sharedValue = value;
OnSharedValueUpdated();
}
}
public event EventHandler SharedValueUpdated;
protected virtual void OnSharedValueUpdated()
{
SharedValueUpdated?.Invoke(this, EventArgs.Empty);
}
}
public class ViewModel1
{
private readonly IControlSomething _controller;
public ViewModel1(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
public class ViewModel2
{
private readonly IControlSomething _controller;
public ViewModel2(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
here the sample you can achieve your requirement easily with navigation
public class ViewModelFrom : BaseViewModel
{
async Task ExecuteCommand()
{
string routeName="value to trasfer";
Navigation.PushAsync(new View(routeName));
}
}
public partial class View : ContentPage
{
public View(string routeName)
{
InitializeComponent();
BindingContext = new ViewModelTo(routeName);
}
}
public class ViewModelTo : BaseViewModel
{
public string RouteName { get; set; }
public ViewModelTo(string routeName)
{
RouteName=routeName;
}
}
If there is a hierarchy you could express that in a parent to both of them.
public class Route
{
private string Name;
}
public class RouteSelectedArgs : EventArgs
{
public Route Selected { get; set; }
}
public interface IRouteSelection
{
event EventHandler<RouteSelectedArgs> RouteSelected;
}
public interface IRouteDetails { }
public class RouteWizard
{
public UserControl view { get; set; }
private IRouteSelection _selection;
private IRouteDetails _details;
public RouteWizard(IRouteSelection selection, IRouteDetails details)
{
_selection = selection;
_details = details;
_selection.RouteSelected += Selection_RouteSelected;
view = MakeView(_selection);
}
private void Selection_RouteSelected(object sender, RouteSelectedArgs e)
{
_selection.RouteSelected -= Selection_RouteSelected;
view = MakeView(_details, e.Selected);
}
private UserControl MakeView(params object[] args)
{
////magic
throw new NotImplementedException();
}
}
As you are using the MVVM pattern, you can use one of the many MVVM Frameworks to achieve this.
I use FreshMvvm and it allow me to pass parameters between view models like this
await CoreMethods.PushPageModel<SecondPageModel>(myParameter, false);
Then in SecondPageModel I can see access the parameters in the Init method
private MyParamType _myParameter;
public override void Init(object initData)
{
base.Init(initData);
var param = initData as MyParamType;
if (param != null)
{
_myParameter = param;
}
}
You can find more details about FreshMvvm here although most MVVM frameworks have similar functionality.
Assume the following, simplified scenario:
//Model:
public class SessionModel: ObservableObject {
public bool IsChecked; // IPropertyChanged is implemented
}
//ViewModel:
public class SessionViewModel: ViewModel {
public IDialogService DialogService {get; set;}
public ObservableCollection<SessionModel> Items; // IPropertyChanged is implemented
public async Task DownloadFromUsbDevice() {
await = Task.Run(() => DownloadFromUsbDevice());
}
private void DownloadFromUsbDevice() {
// Get all checked items. If result is empty, show a message. If an error occurs, show error message
}
}
// Simple IDialogService...
public interface IDialogService
{
void ShowMessage(string message);
}
My first idea was, that the View of the ViewModel should implement this... This is possible, but requires me to manually assign the View to the ViewModel property in the views constructor...
//DialogService:
public class MainWindow: Window, IDialogService
{
public MainWindow()
{
ViewModel.DialogService = this;
}
public void ShowMessage(string message)
{
Xceed.Wpf.Toolkit.MessageBox.Show(this, message, string.Empty, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
Then i read that most of this services seems to be implemented standalone... But for modal dialogs and thread-safe-access i would need the Window/Dispatcher of the targeted view... So I would have to register the interface and its implementation to the SimpleIoc. Then i have to create the instance in the view constructor using a key that is specific to this View/ViewModel and assign the Window.
//DialogService:
public class StandaloneDialogService: IDialogService
{
public Window Parent { get; set; }
public void ShowMessage(string message)
{
Xceed.Wpf.Toolkit.MessageBox.Show(Parent, message, string.Empty, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
//ServiceLocator:
public class Locator
{
public Locator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IDialogService, StandaloneDialogService>();
SimpleIoc.Default.Register<SessionViewModel>();
}
public SessionViewModel Session => SimpleIoc.Default.GetInstance<Session>();
}
//View:
public class MainWindow: Window
{
public MainWindow()
{
SimpleIoc.Default.GetInstance<IDialogService>("x").Parent = this;
}
}
//ViewModel:
public class SessionViewModel: ViewModel {
public IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>("x");
public ObservableCollection<SessionModel> Items; // IPropertyChanged is implemented
public async Task DownloadFromUsbDevice()
{
await = Task.Run(() => DownloadFromUsbDevice());
}
private void DownloadFromUsbDevice()
{
// Get all checked items. If result is empty, show a message. If an error occurs, show error message
}
}
Long story short... Is one of those two the right way to show (modal/non-modal) messages from a worker-function of the ViewModel? Are there better solutions? Or am I completly wrong?
So I am a little confused as to how the MVVM architecture can help me and how to use it in this situation:
I am using Xamarin and have created my view and view controller in iOS as an example. I have implemented MVVMLight toolkit as well, and have created my ViewModel for the view and view controller.
I am creating a login screen, so the user inputs their username and password and they are updated in the model through RaisePropertyChanged() events. My question is where I need to call the function to validate this information and actually log them into the system?
I have implemented a RelayCommand that will call a method on the ViewModel whenever the button is clicked as I have seen in other tutorials and such, but I am not sure if I am supposed to call the validation code here.
Some examples of what I have:
LoginViewModel.cs:
public class LoginViewModel : ViewModelBase
{
private string _username;
private string _password;
public RelayCommand LoginButtonCommand { get; private set; }
public bool CanExecuteLoginCommand { get; set; }
public LoginViewModel()
{
LoginButtonCommand = new RelayCommand(HandleLoginButtonCommand, () => CanExecuteLoginCommand);
CanExecuteLoginCommand = true;
}
public string Username
{
get
{
return _username;
}
set
{
_username = value;
RaisePropertyChanged(() => Username);
}
}
public string Password
{
get
{
return _password;
}
set
{
_password = value;
RaisePropertyChanged(() => Password);
}
}
private void HandleLoginButtonCommand()
{
CanExecuteLoginCommand = false;
//Validate login?
CanExecuteLoginCommand = true;
}
}
LoginViewController.cs:
public partial class LoginViewController : UIViewController
{
private Binding _usernameTextFieldBinding;
private Binding _passwordTextFieldBinding;
private LoginViewModel _viewModel;
public LoginViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
_viewModel = Application.Locator.Login;
HideKeyboardHandling(UsernameTextField);
HideKeyboardHandling(PasswordTextField);
_usernameTextFieldBinding = this.SetBinding(
() => _viewModel.Username)
.ObserveSourceEvent("EditingDidEnd")
.WhenSourceChanges(() => _viewModel.Username = UsernameTextField.Text);
_passwordTextFieldBinding = this.SetBinding(
() => _viewModel.Username)
.ObserveSourceEvent("EditingDidEnd")
.WhenSourceChanges(() => _viewModel.Password = PasswordTextField.Text);
Loginbutton.SetCommand("TouchUpInside", _viewModel.LoginButtonCommand);
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
void HideKeyboardHandling(UITextField textField)
{
textField.ShouldReturn = TextField =>
{
TextField.ResignFirstResponder();
return true;
};
var gesture = new UITapGestureRecognizer(() => View.EndEditing(true));
gesture.CancelsTouchesInView = false;
View.AddGestureRecognizer(gesture);
}
}
It all depends on how strict you want to be with Single Responsibility Principle(SPR). Which in turn depends on how complex your application is. The more complex the application is, the more separated the responsibilities should be.
A typical MVVM implementation handles the commands in the ViewModel. And the ViewModel forwards the call into the Model. But his still puts two responsibilities(e.g. presentation and command handling) into a single component, a.k.a the ViewModel.
A more strict approach will be to have the ViewModel only handle presentation logic. Create a separate controller to host all the command handlers. And have the command handlers forward the calls to the Model.
A more relaxed approach will be to simply implement the business logic in the ViewModel. This implies you don't have a business logic layer. Which is fine if your application is simple enough that a business logic layer does not worth the effort.
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.