Following my learning of MVVM using Caliburn.micro framework... I'm trying to communicate two viewModels sending data through the EventAggregator like this (code with "no sense", just for test):
MainWindowViewModel.cs
namespace TOP
{
[Export(typeof(MainWindowViewModel))]
public class MainWindowViewModel : Conductor<IScreen>.Collection.OneActive
{
readonly IWindowManager windowManager;
private readonly IEventAggregator events;
private bool _Logged;
[ImportingConstructor]
public MainWindowViewModel(IWindowManager windowManager, IEventAggregator events)
{
DisplayName = "TOP";
this.events = events;
events.Subscribe(this);
this.windowManager = windowManager;
windowManager.ShowDialog(new LoginViewModel(events));
}
public bool Logged
{
get { return _Logged; }
set
{
_Logged = value;
if(_Logged== true)
InitiateApp();
}
}
public void Handle(LoginEvent message)
{
Logged = message.Logged;
}
private void InitiateApp() {
ActivateItem(new TwoWindowViewModel());
}
}
}
LoginViewModel.cs
namespace TOP{
[Export(typeof(IScreen))]
public class LoginViewModel : Screen
{
private readonly IEventAggregator _events;
[ImportingConstructor]
public LoginViewModel(IEventAggregator events)
{
DisplayName = "Login";
_events = events;
Login();
}
public void Login()
{
_events.Publish(new LoginEvent(true));
}
}
}
LoginEvent.cs
namespace TOP
{
public class LoginEvent
{
public LoginEvent(bool logged)
{
Logged = logged;
}
public bool Logged { get; private set; }
}
}
Why the Handle method of MainWindowViewModel is not picking up the published message from LoginViewModel?
Thank you for your responses.
Your MainWindowViewModel needs to implement IHandle<LoginEvent>. You already defined the method with the correct signature, so you only are missing the part where you actually tell the compiler that you implement the interface:
public class MainWindowViewModel
: Conductor<IScreen>.Collection.OneActive, IHandle<LoginEvent>
More info can be found in the documentation.
Related
I've finally given in and started learning and applying MVVM architecture on my EFCore project. However, I haven't understood how scoped services work. On my previous question I was told I needed to use one scope per viewmodel otherwise, since I'm working with async methods, I would be getting concurrent transactions on the same DbContext (which is not allowed).
I've also read here that one way of using dialog windows while still following MVVM would be to have a service that would create a dialog window container with a dynamically filled viewmodel.
So far, so good. However I've hit a snag:
I'm on my MainView, and I run a command that opens a new dialog window (with a new scope, I think). And on that dialog, I run a command that should open another dialog window. However, when trying to close, somehow I manage to close the parent dialog rather than the focused window. My relevant code is as follows:
What am I doing wrong here?
PS.: I translated the names to english for better readability, but I might've let one or another slip by - let me know if I can fix anything.
public class PurchasesViewModel : ViewModelBase
{
private readonly IDialogGenerator _purchaseDialogGenerator;
private readonly IDialogViewModelFactory _purchaseRecordVMFactory;
public ICommand CreateNewPurchase { get; set; }
public PurchasesViewModel(IServiceProvider services, IDialogGenerator purchaseVMDialogGenerator)
{
_purchaseDialogGenerator = purchaseVMDialogGenerator;
IServiceScope purchaseVMScope = services.CreateScope();
IServiceProvider provider = purchaseVMScope.ServiceProvider;
_purchaseRecordVMFactory = provider.GetRequiredService<IDialogViewModelFactory>();
CreateNewPurchase = new CreatePurchaseCommand(_purchaseDialogGenerator, _purchaseRecordVMFactory);
}
}
internal class CreatePurchaseCommand : ICommand
{
private IDialogGenerator _purchaseDialogGenerator;
private IDialogViewModelFactory _purchaseVMFactory;
public CreatePurchaseCommand(IDialogGenerator PurchaseRecordDialogGenerator, IDialogViewModelFactory PurchaseRecordVMFactory)
{
_purchaseDialogGenerator = PurchaseRecordDialogGenerator;
_purchaseVMFactory = PurchaseRecordVMFactory;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_purchaseDialogGenerator.ShownViewModel = _purchaseVMFactory.CreateDialogContentViewModel(DialogType.RecordPurchases);
_purchaseDialogGenerator.ShowDialog();
}
}
public class PurchaseRecordViewModel : DialogContentViewModelBase
{
private readonly IMessaging<PURCHASE> _purchaseMessaging;
private readonly IDialogGenerator _purchaseDialogGenerator;
private readonly IDialogGenerator _importViaXMLDialogGenerator;
private readonly IDialogViewModelFactory _importViaXMLVMFactory;
public ICommand SavePurchase { get; set; }
public ICommand ImportXML { get; set; }
public PurchaseRecordViewModel(IServiceProvider servicos, IDialogGenerator PurchaseRecordDialogGenerator)
{
_purchaseDialogGenerator = PurchaseRecordDialogGenerator;
IServiceScope scope = servicos.CreateScope();
IServiceProvider provider = scope.ServiceProvider;
_importViaXMLDialogGenerator = provider.GetRequiredService<IDialogGenerator>();
_importViaXMLVMFactory = provider.GetRequiredService<IDialogViewModelFactory>();
_purchaseMessaging = provider.GetRequiredService<IMessaging<PURCHASE>>();
SavePurchase = new SalvaPurchaseCommandAsync(PurchaseRecordDialogGenerator);
ImportXML = new ImportXMLDePurchaseCommand(_importViaXMLDialogGenerator, _importViaXMLVMFactory);
}
}
internal class ImportXMLFromPurchaseCommand : ICommand
{
private readonly IDialogGenerator _importviaXMLDialogGenerator;
private readonly IDialogViewModelFactory _importXMLVMFactory;
public ImportXMLFromPurchaseCommand(
IDialogGenerator importviaXMLDialogGenerator,
IDialogViewModelFactory importXMLVMFactory)
{
_importviaXMLDialogGenerator = importviaXMLDialogGenerator;
_importXMLVMFactory = importXMLVMFactory;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_importviaXMLDialogGenerator.ShownViewModel = _importXMLVMFactory.CreateDialogContentViewModel(DialogType.ImportXML);
_importviaXMLDialogGenerator.ShowDialog();
}
}
internal class SalvaPurchaseCommandAsync : ICommand
{
private readonly IDialogGenerator _dialogGenerator;
public SalvaPurchaseCommandAsync(IDialogGenerator dialogGenerator)
{
_dialogGenerator = dialogGenerator;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_dialogGenerator.Result = DialogResult.OK;
_dialogGenerator.Close();
}
}
public class ImportViaXMLViewModel : DialogContentViewModelBase
{
public ICommand ImportArquivoXML { get; set; }
public ICommand Import { get; set; }
private readonly IDialogGenerator _importViaXMLDialogGenerator;
public ImportViaXMLViewModel(IMessaging<PURCHASE> messaging,
IDialogGenerator importViaXMLDialogGenerator)
{
_importViaXMLDialogGenerator = importViaXMLDialogGenerator;
Import = new ImportCommand(this, messaging, _importViaXMLDialogGenerator);
}
}
internal class ImportCommand : ICommand
{
private readonly IMessaging<PURCHASE> _messaging;
private readonly IDialogGenerator _dialogGenerator;
public ImportCommand(IMessaging<PURCHASE> messaging, IDialogGenerator dialogGenerator)
{
_messaging = messaging;
_dialogGenerator = dialogGenerator;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_messaging.Message = new COMPRA();
_dialogGenerator.Result = DialogResult.OK;
_dialogGenerator.Close();
}
}
public abstract class AsyncCommandBase : ICommand
{
private bool _isExecuting;
private readonly Action<Exception> _onException;
public bool IsExecuting
{
get { return _isExecuting; }
set {
_isExecuting = value;
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
public AsyncCommandBase(Action<Exception> onException)
{
this._onException = onException;
}
public virtual event EventHandler CanExecuteChanged;
public virtual bool CanExecute(object parameter)
{
return !IsExecuting;
}
public async void Execute(object parameter)
{
IsExecuting = true;
try
{
await ExecuteAsync(parameter);
}
catch (Exception ex)
{
_onException(ex);
}
IsExecuting = false;
}
protected abstract Task ExecuteAsync(object parameter);
}
I found out a way to work it out. I found an answer after reading Good or bad practice for Dialogs in wpf with MVVM?.
I have created a singleton dialogStore in which I keep all the currently opened dialogs. Whenever I need a new dialog window, I "register" a new dialogGenerator with the dialogStore, and it'll open it for me. When I need to close it, the store is also responsible for that. Furthermore, so far, I've been using only one modal dialog active at a time, so whenever I close a dialog, I'll be sure it's the latest added one. But in the future, each dialogGenerator will know its set dialogStore index, and will be able to close themselves (and other dialogs) in any order needed.
My dialogStore is as follows:
public class DialogsStore : IDialogsStore
{
public List<IDialogGenerator> OpenDialogs { get; set; } = new List<IDialogGenerator>();
public void CloseDialog(DialogResult dialogResult)
{
IDialogGenerator lastDialogGenerator = OpenDialogs[OpenDialogs.Count - 1];
lastDialogGenerator.Resultado = dialogResult;
//lastDialogGenerator.DialogClosed = null;
lastDialogGenerator.Close();
}
public int RegisterDialog(IDialogGenerator dialog)
{
OpenDialogs.Add(dialog);
dialog.DialogClosed += Dialog_DialogClosed;
dialog.ShowDialog();
return OpenDialogs.Count - 1;
}
private void Dialog_DialogClosed()
{
OpenDialogs.RemoveAt(OpenDialogs.Count - 1);
}
}
One important point I have to deal with is that closing the dialog and removing it from the store didn't dispose of the scoped dialogGenerator, so I will still need to dispose from anything inside my dialogGenerator before removing it from my dialogStore.
If anyone has a better way to deal with dialog windows in MVVM with Dependency Injection, I'll be glad to hear it.
I am using Prism in my WPF. When I add IEventAggregator as a parameter to the ViewModel constructor I get this error: An exception of type 'Microsoft.Practices.ServiceLocation.ActivationException' occurred in Microsoft.Practices.ServiceLocation.dll.
Additional information: Activation error occurred while trying to get instance of type Object, key "CategoriesView"
The exception is triggered in this line:
private void NavigateToCategoriesRadioButton_Click(object sender, RoutedEventArgs e)
{
this.regionManager.RequestNavigate(RegionNames.ConfigurationContentRegion, categoriesViewUri);
}
where categoriesViewUri is:
private static Uri categoriesViewUri = new Uri("/CategoriesView", UriKind.Relative);
This is my view model class:
[Export]
public class CategoriesViewModel : BindableBase
{
private readonly IRegionManager regionManager;
private readonly IEventAggregator eventAggregator;
private readonly IConfigurationCategoriesService categoriesService;
private readonly ObservableCollection<Category> categoriesCollection;
private readonly ICollectionView categoriesView;
private readonly DelegateCommand<object> deleteCategoryCommand;
[ImportingConstructor]
public CategoriesViewModel(IEventAggregator eventAggregator, IConfigurationCategoriesService categoriesService, IRegionManager regionManager)
{
this.categoriesService = categoriesService;
this.regionManager = regionManager;
this.eventAggregator = eventAggregator;
this.deleteCategoryCommand = new DelegateCommand<object>(this.DeleteCategory, this.CanDeleteCategory);
this.categoriesCollection = new ObservableCollection<Category>(categoriesService.GetCategories());
this.categoriesView = new ListCollectionView(this.categoriesCollection);
this.categoriesView.CurrentChanged += (s, e) => this.deleteCategoryCommand.RaiseCanExecuteChanged();
}
public ICollectionView Categories
{
get { return this.categoriesView; }
}
public ICommand DeleteCategoryCommand
{
get { return this.deleteCategoryCommand; }
}
private void DeleteCategory(object ignored)
{
var category = this.categoriesView.CurrentItem as Category;
if (category != null)
{
categoriesService.DeleteCategory(category);
}
}
private bool CanDeleteCategory(object ignored)
{
return true;
}
}
It looks like CatagoriesViewModel cannot get an instance of IEventAggregator on the constructor but this is done automatically by Prism, isn't it? In the example I have from Prism Documentation (StockTraderRI_Desktop) I donĀ“t see anywhere where the EventAggregator is instantiated. Can anyone see what am I getting wrong? Thanks in advance
Editted:
The Navitagion item view is registerd in the CategoriesModule class:
[ModuleExport(typeof(CategoriesModule))]
public class CategoriesModule : IModule
{
[Import]
public IRegionManager regionManager;
public void Initialize()
{
this.regionManager.RegisterViewWithRegion(RegionNames.ConfigurationNavigationRegion, typeof(CategoriesNavigationItemView));
}
}
And CategoriesView code-behind is:
[Export("CategoriesView")]
public partial class CategoriesView : UserControl
{
public CategoriesView()
{
InitializeComponent();
}
[Import]
public IRegionManager regionManager;
[Import]
public CategoriesViewModel ViewModel
{
get { return this.DataContext as CategoriesViewModel; }
set { this.DataContext = value; }
}
}
I solved this issue adding the following using statement:
using Microsoft.Practices.Prism.PubSubEvents;
instead of
using Prism.Events;
I also switched to Unity instead of MEF by recommendation on this site.
Trying to pass data from one ViewModel to another using prism EventAggregator, but when I debug on the subscriber, the data is null.
Using version 5.0 of prism.
Update
Okay, I have tried implement the EventAggregator using prism 5.0 version. It still dosen't work, but here is what I have done.
1: Create event class
public class RoomsSelectedEvent : PubSubEvent<ObservableCollection<Room>>
{
}
2: Inject IEventAggregator on publisher ViewModel (BookingViewModel)
public class BookingViewModel : INotifyPropertyChanged, IViewBookingViewModel
{
//aggregator
protected readonly IEventAggregator _eventAggregator;
//commands
public ICommand ContinueCommand { get; set; }
public ObservableCollection<Room> RoomsList { get; private set; }
public ObservableCollection<RoomList> DropDownRooms { get; private set; }
public ObservableCollection<CustomerList> DropDownCustomers { get; private set; }
//enities
private readonly IDialogService<ContactDetailsView> _dialogServiceContactView;
private readonly IGetRoomsService _getRoomsService;
public BookingViewModel(IDialogService<ContactDetailsView> dialogServiceContactview, IGetRoomsService GetRoomsService, IEventAggregator eventAggregator)
{
// Injection
_dialogServiceContactView = dialogServiceContactview;
_getRoomsService = GetRoomsService;
_eventAggregator = eventAggregator;
//commands
ContinueCommand = new RelayCommand(ContinueCommand_DoWork, () => true);
}
// Continue Command
public void ContinueCommand_DoWork(object obj)
{
ObservableCollection<Room> RoomsSelected = new ObservableCollection<Room>();
RoomsSelected = _getRoomsService.FilterSelectedRooms(RoomsList);
//Publish event:
_eventAggregator.GetEvent<RoomsSelectedEvent>().Publish(RoomsSelected);
// Open new dialog
_dialogServiceContactView.ShowDialog();
}
}
3: Inject IEventAggregator in subscriber viewModel (ContactViewModel)
public class ContactViewModel : IViewContactViewModel, INotifyPropertyChanged
{
//aggregator
protected readonly IEventAggregator _eventAggregator;
//properties
public ObservableCollection<Room> SelectedRooms { get; set; }
public ContactViewModel(IEventAggregator eventAggregator)
{
//Injection
_eventAggregator = eventAggregator;
//Subscripe to event
_eventAggregator.GetEvent<RoomsSelectedEvent>()
.Subscribe((data) => { SelectedRooms = data; });
}
public ObservableCollection<Room> Rooms
{
get { return SelectedRooms; }
set { SelectedRooms = value; NotifyPropertyChanged(); }
}
}
I have read it's maybe because of the IEventAggregator is not the same in both ViewModels. I'm using Unity to inject it like this code:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
//view & viewModels
_container = new UnityContainer();
_container.RegisterType<IViewMainWindowViewModel, MainWindow>();
_container.RegisterType<IViewMainWindowViewModel, MenuViewModel>();
_container.RegisterType<IViewBookingViewModel, BookingView>();
_container.RegisterType<IViewBookingViewModel, BookingViewModel>();
_container.RegisterType<IViewContactViewModel, ContactDetailsView>();
_container.RegisterType<IViewContactViewModel, ContactViewModel>();
_container.RegisterType<IGetRoomsService, GetRoomsService>();
_container.RegisterType<IPostReservationService, PostReservationService>();
_container.RegisterType<IGetReservationsListService, GetReservationsListService>();
//types
_container.RegisterType<IEventAggregator, EventAggregator>(new ContainerControlledLifetimeManager());
_container.RegisterType(typeof(IDialogService<>), typeof(DialogService<>));
_container.Resolve<MainWindow>().Show();
}
Found that I need to add the ContainerControlledLifetimeManager, but it still wont work.
When I debug in the subscriber viewModel, I can see that there is an event inside the instance, like on the image:
It wont catch it , thats the problem :(
Today I tried delete all packages I installed (prism 5.0) and go for the Prism.Core (6.1 version)
That resulted in the same, I can see when I debug that the event in published, but when I subscribe, it's still null.
This is my UnityResolver Class to create the instance of IUnityContainer
public sealed class UnityResolver
{
private static IUnityContainer _unityContainer;
private static volatile UnityResolver _unityresolverinstance;
private static object syncRoot = new Object();
public static IUnityContainer UnityContainerInitiation
{
get
{
if (_unityContainer == null)
{
if (_unityresolverinstance == null)
{
lock (syncRoot)
{
if (_unityresolverinstance == null)
_unityresolverinstance = new UnityResolver();
}
}
}
return UnityResolver._unityContainer;
}
}
public UnityResolver()
{
_unityContainer = new UnityContainer();
_unityContainer.RegisterType<MaintainRouteViewModel>();
}
}
Below is my Base View and Its ViewModelCode
public partial class MaintainRouteView : UserControl
{
public MaintainRouteViewModel maintainRouteViewModel = null;
IUnityContainer container;
public MaintainRouteView()
{
InitializeComponent();
container = UnityResolver.UnityContainerInitiation;
maintainRouteViewModel = container.Resolve<MaintainRouteViewModel>();
this.DataContext = maintainRouteViewModel;
}
///This button will navigate to the child view.
private void AddRoute_Click(object sender, RoutedEventArgs e)
{
pageAnimationControl.ShowPage(new AddNewRouteView());
}
}
Its ViewModel..
public class MaintainRouteViewModel : viewModelbase
{
private string _statusSuccessMessage = null;
private string _statusFailMessage =null;
private ObservableCollection<RouteDetailsModel> _routeDetailsCollection;
public ObservableCollection<RouteDetailsModel> routeDetailsCollection
{
get
{
return this._routeDetailsCollection;
}
set
{
this._routeDetailsCollection = value;
RaisePropertyChanged("routeDetailsCollection");
}
}
public string StatusSuccessMessage
{
get
{
return _statusSuccessMessage;
}
set
{
_statusSuccessMessage = value;
this.RaisePropertyChanged("StatusSuccessMessage");
}
}
public string StatusFailMessage
{
get { return _statusFailMessage; }
set
{
_statusFailMessage = value;
this.RaisePropertyChanged("StatusFailMessage");
}
}
public MaintainRouteViewModel()
{
///it will load some data to the Observablecollection
getAllCurrentRouteData();
}
}
Now Below is my Child View and its ViewModel....
public partial class AddNewRouteView : UserControl
{
public AddNewRouteView()
{
InitializeComponent();
IUnityContainer container = UnityResolver.UnityContainerInitiation;
this.DataContext = container.Resolve<AddNewRouteViewModel>();
}
}
Its ViewModel....
public class AddNewRouteViewModel : viewModelbase
{
private MaintainRouteViewModel maintainRouteViewModel;
public ICommand SaveCommand
{
get;
set;
}
[InjectionConstructor]
public AddNewRouteViewModel(MaintainRouteViewModel maintainRouteViewModel)
{
this.maintainRouteViewModel = maintainRouteViewModel;
SaveCommand = new DelegateCommand<object>((a) => ValidateNewRoute());
}
private void ValidateNewRoute()
{
bool flag = saveAndValidate();
if(flag)
{
updateRouteStatus();
}
}
public void updateRouteStatus()
{
maintainRouteViewModel.StatusSuccessMessage = "New Route successfully Added..";
}
}
}
Can Anyone Tell me how to use this way to get the same object of MaintainRouteViewModel in my Child VM Constructor So that i will show the Updated Status Message in my Base view MaintainRouteView???
*It will Work Fine If i replace my MaintainRouteView with below code :
this Is an another approach to use IOC .i previously using this in my project. it Works Fine for me but now i want to implement the same thing using Unity Container. Please Help.
public partial class MaintainRouteView : UserControl
{
public MaintainRouteViewModel maintainRouteViewModel = null;
public MaintainRouteView()
{
InitializeComponent();
maintainRouteViewModel = new MaintainRouteViewModel();
this.DataContext = maintainRouteViewModel;
}
private void AddRoute_Click(object sender, RoutedEventArgs e)
{
pageTransitionControl.ShowPage(
new AddNewRouteView
{
DataContext = new AddNewRouteViewModel(maintainRouteViewModel)
});
}
}
I am able to solve this issue using the LifeTime Management of Unity Container Register Types.
it will work fine if i tell the container to create a singleton instance of the MaintainRouteViewModel Class.
using :
container.RegisterType<MaintainRouteViewModel>(
new ContainerControlledLifetimeManager());
But it's just a workaround to get the expected result. i want to achieve it using a proper dependency injection without any singleton instance principle. Can anyone please help to provide the solution.
I'm working through the following tutorial http://www.mindscapehq.com/blog/index.php/2012/2/1/caliburn-micro-part-4-the-event-aggregator/
and I'm currently stuck at the publish / subscribe part.
I have everything set up, so that it should actually publish the events but the subscribing viewmodel doesn't get the message.
I've done the following:
Publishing ViewModel:
[Export(typeof(ColorViewModel))]
public class ColorViewModel : PropertyChangedBase
{
private readonly IEventAggregator events;
[ImportingConstructor]
public ColorViewModel(IEventAggregator events)
{
this.events = events;
}
public void Red()
{
this.events.PublishOnUIThread(new ColorEvent(new SolidColorBrush(Colors.Red)));
}
public void Green()
{
this.events.PublishOnUIThread(new ColorEvent(new SolidColorBrush(Colors.Green)));
}
public void Blue()
{
this.events.PublishOnUIThread(new ColorEvent(new SolidColorBrush(Colors.Blue)));
}
}
Subscribing ViewModel:
[Export(typeof(AppViewModel))]
public class AppViewModel : PropertyChangedBase, IAppViewModel, IHandle<ColorEvent>
{
private IEventAggregator events;
[ImportingConstructor]
public AppViewModel(ColorViewModel colorViewModel, IEventAggregator events)
{
this.ColorViewModel = colorViewModel;
this.events = events;
this.events.Subscribe(this);
}
public ColorViewModel ColorViewModel { get; private set; }
private SolidColorBrush color;
public SolidColorBrush Color
{
get
{
return this.color;
}
set
{
this.color = value;
this.NotifyOfPropertyChange(() => this.Color);
}
}
public void Handle(ColorEvent message)
{
this.Color = message.Color;
}
}
There are 3 radio buttons on the ColorView which I can click and I do get into the Red(), Green(), Blue() methods so that the PublishOnUIThread is called.
But I never reach the Handle(ColorEvent) method of the AppViewModel.
Am I missing something or why doesn't my handle method gets called after publishing the ColorEvents?
Thanks in advance
Where is the event aggregator coming from? Is it the same instance shared between AppViewModel and ColorViewModel?
Make sure the event aggregator is registered as a singleton in the dependency injector.