Event Subscription does not connect with the event published - wpf

I have two ViewModels. In one of them I publish this event
_eventAggregator.GetEvent<AddUbicacionEvent>().Publish(sensorUbicado.Sensor.CodigoInterno);
And the in the other one I have this in the constructor
_eventAggregator.GetEvent<AddUbicacionEvent>().Subscribe(OnReceiveUbicacion);
And this method
private void OnReceiveUbicacion(string obj)
{
MessageBox.Show("Event Captured");
}
But the Code never stops in this method
I'm Using AutoFac to inject the Event Aggergator in the constructor of the view models
public IContainer Bootstrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<EventAggregator>().As<IEventAggregator>().SingleInstance();
Then I have this
public PrincipalViewModel(IEventAggregator eventAggregator, IUbicacionRepository ubicacionRepository, ComponenteEscucha componenteEscucha)
{
_eventAggregator = eventAggregator;
And this
public GestionUbicacionesViewModel(IEventAggregator eventAggregator, ):base(eventAggregator)
{
_eventAggregator = eventAggregator;
Any idea please?
Regards

Related

ViewModel won't handle a EventAggregator event

I only seem to be able to handle EventAggregator events from the ShellViewModel, but I want to handle it from LoginViewModel.
The ShellViewModel constructs LoginViewModel as it's Active Item. I've also set it up to inherit from IHandle<AuthenticatedMessage> as a test that event publishing is working. It is able to handle that event. I haven't shown any Unsubscribe events in my code for brevity.
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<AuthenticatedMessage>
{
public ShellViewModel(IEventAggregator eventAggregator, LoginViewModel loginViewModel)
{
_eventAggregator = eventAggregator;
_loginViewModel = loginViewModel;
}
protected override async Task OnActivateAsync(CancellationToken cancellationToken)
{
await base.OnActivateAsync(cancellationToken);
_eventAggregator.SubscribeOnPublishedThread(this);
await ActivateItemAsync(_loginViewModel);
}
public async Task HandleAsync(AuthenticatedMessage authCode, CancellationToken cancellationToken)
{
// this is reached! So the event is publishing successfully.
await Task.CompletedTask;
}
}
LoginViewModel also subscribes to this event, but it's Handle method is not invoked.
The Login method is responsible for creating the LoginWindowViewModel Window (shown underneath) which publishes the event.
public class LoginViewModel : Screen, IHandle<AuthenticatedMessage>
{
private IEventAggregator _eventAggregator;
private readonly IWindowManager _windowManager;
private readonly ILoginWindowViewModelFactory _LoginWindowViewModelFactory;
public LoginViewModel(IEventAggregator eventAggregator,
IWindowManager windowManager,
ILoginWindowViewModelFactory loginWindowViewModelFactory)
{
_eventAggregator = eventAggregator;
_windowManager = windowManager;
_LoginWindowViewModelFactory = loginWindowViewModelFactory;
}
protected override async Task OnActivateAsync(CancellationToken cancellationToken)
{
await base.OnActivateAsync(cancellationToken);
_eventAggregator.SubscribeOnPublishedThread(this);
}
// This is bound to a button click event. It creates a window.
public async void Login()
{
Uri loginUri = new Uri(_api.BaseLoginUrl);
await _windowManager.ShowWindowAsync(
_ndLoginWindowViewModelFactory.Create(loginUri, _eventAggregator));
}
public async Task HandleAsync(AuthenticatedMessage authCode, CancellationToken cancellationToken)
{
// why is this is never reached?
await Task.CompletedTask;
}
}
The LoginWindowViewModel that publishes a AuthenticatedMessage event:
public class LoginWindowViewModel : Screen
{
private readonly IEventAggregator _eventAggregator;
private readonly Uri _initialUri;
public NDLoginWindowViewModel(Uri initialUri, IEventAggregator eventAggregator)
{
_initialUri = initialUri;
_eventAggregator = eventAggregator;
}
// bound to the WebView2 (browser control) event
public async void NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
string authCode = HttpUtility.ParseQueryString(new Uri(e.Uri).Query).Get("code");
// Publish event here. LoginViewModel should handle this, but currently only ShellViewModel can.
await _eventAggregator.PublishOnUIThreadAsync(new AuthenticatedMessage(authCode));
}
}
}
I resolved the issue after moving eventAggregator.SubscribeOnPublishedThread(this); to the LoginViewModel constructor, instead of the OnActivateAsync() method.
From here:
protected override async Task OnActivateAsync(CancellationToken cancellationToken)
{
await base.OnActivateAsync(cancellationToken);
_eventAggregator.SubscribeOnPublishedThread(this);
}
To here:
public LoginViewModel(IEventAggregator eventAggregator,
IWindowManager windowManager,
ILoginWindowViewModelFactory loginWindowViewModelFactory)
{
_eventAggregator = eventAggregator;
_windowManager = windowManager;
_LoginWindowViewModelFactory = loginWindowViewModelFactory;
_eventAggregator.SubscribeOnPublishedThread(this);
}
EDIT:
The OnActivateAsync method isn't being called when the View is first created in the ShellViewModel because it is my root Screen and Conductor. So the Subscription was never taking place.
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
...
protected override async Task OnActivateAsync(CancellationToken cancellationToken)
{
await base.OnActivateAsync(cancellationToken);
await ActivateItemAsync(_loginViewModel);
// IsActive = false here, therefore the child Screen `_loginViewModel`
// is also not active. Result is that OnActivateAsync
// in this view model does not get called.
}
}
It is directly related to this problem and answer.
That explains why moving it to the constructor solved the problem.
The final solution was to add _eventAggregator.SubscribeOnPublishedThread(this); in both the Constructor AND OnActivateAsync method. This allows me to resubscribe to the event after I navigate away from this viewmodel and come back to it.

Open new window on click in WPF, Ninject and Caliburn.Micro

I'm trying to set up a WPF app to call the new window on a menu click with the data provider interface injected into the new viewmodel.
Followed many tutorials and created the Bootstrapper for Caliburn, a service locator and module for ninject. So far the main view doesn't need the IDataProvider but I'd like to open a new window on click event.
The Bootstrapper:
public class Bootstrapper : BootstrapperBase
{
public Bootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<MainScreenViewModel>();
}
}
The Service Locator and Module:
public class ServiceLocator
{
private readonly IKernel _kernel;
public ServiceLocator()
{
_kernel = new StandardKernel(new ServiceModule());
}
public MainScreenViewModel MainScreenViewModel => _kernel.Get<MainScreenViewModel>();
public NewLayoutViewModel NewLayoutViewModel => _kernel.Get<NewLayoutViewModel>();
}
public class ServiceModule : NinjectModule
{
public override void Load()
{
Bind<ISqlite>().To<Sqlite>();
Bind<IDataProvider>().To<DataProvider>();
}
}
And this is where I got stuck:
public class MainScreenViewModel : Conductor<object>
{
private IWindowManager _windowManager;
public MainScreenViewModel()
{
_windowManager = new WindowManager();
}
public void NewLayout()
{
_windowManager.ShowWindow(new NewLayoutViewModel());
}
}
since the NewLayoutViewModel requires the IDataProvider.
Not sure, what am I missing, but in my understanding Ninject should take care of this di for NewLayoutViewModel.
Found a good solution from Tim Corey on YouTube.
Basically the answer is, if you not insist Ninjet, use Caliburn.Micro's build-in DI solution "SimpleContainer".

How the Unity container will resolve the registered type

Recently I am going through some old code and found the below code
public class ProfileModule : IModule
{
private readonly IRegionManager regionManager;
private readonly IUnityContainer container;
private IEventAggregator eventAggregator;
public ProfileModule(IUnityContainer c, IRegionManager r, IEventAggregator e)
{
container = c;
regionManager = r;
eventAggregator = e;
}
public void Initialize()
{
// Create and add profiles as new Tab items
container.RegisterType<IProfileViewModel, Profile1ViewModel>(new ContainerControlledLifetimeManager());
regionManager.Regions[RegionNames.HomeRegion].Add(container.Resolve<ProfileView>());// HomeRegion is of type TabControl
container.RegisterType<IProfileViewModel, Profile2ViewModel>(new ContainerControlledLifetimeManager());
regionManager.Regions[RegionNames.HomeRegion].Add(container.Resolve<ProfileView>());
container.RegisterType<IProfileViewModel, Profile3ViewModel>(new ContainerControlledLifetimeManager());
regionManager.Regions[RegionNames.HomeRegion].Add(container.Resolve<ProfileView>());
}
}
Below is the ProfileView.xaml.cs
public partial class ProfileView : INotifyPropertyChanged
{
[InjectionConstructor]
public ProfileView(IProfileViewModel vm)
{
DataContext = vm;
InitializeComponent();
}
}
Below are the viewModels
public abstract class ProfileViewModelBase : IProfileViewModel, IDataErrorInfo, INotifyPropertyChanged
{
public ProfileViewModelBase(IEventAggregator eventAggregator, IRegionManager regionManager)
{
}
}
public class Profile1ViewModel : ProfileViewModelBase
{
[InjectionConstructor]
public Profile1ViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
: base (eventAggregator, regionManager)
{
}
}
public class Profile2ViewModel : ProfileViewModelBase
{
[InjectionConstructor]
public Profile2ViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
: base (eventAggregator, regionManager)
{
}
}
public class Profile3ViewModel : ProfileViewModelBase
{
[InjectionConstructor]
public Profile3ViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
: base (eventAggregator, regionManager)
{
}
}
The part of the code that is not clear for me is the ProfileModule.Initialise().
Everytime when the region manager is adding a view a new new instance of ProfileView is getting created and the viewModel is the one that is registered last.
First time ProfileView is created with Profile1ViewModel as a Datacontext.
Second time ProfileView is created with Profile2ViewModel as a Datacontext.
Third time ProfileView is created with Profile3ViewModel as a Datacontext.
How the container knows exactly which viewmodel to create when creating the view.
Also I understand , container.Resolve will return the view if it already got one, first time view is created and returned, second time I except same view will be returned, but a new view is created. same with third.
Can anyone explain what is happening?
Here goes:
What you can see inside the Initialize method is that after registering the IProfileViewModel the code is then immediately calling Resolve<ProfileView> which on the first Resolve is providing Profile1ViewModel to the ProfileView constructor. Then the second Register replaces the first registration with Profile2ViewModel. Therefore subsequent calls to Resolve will never give you an instance (or the singleton instance) of Profile1ViewModel.
If for some reason you really want to resolve the same instance of ProfileView then you need to Register this with the Unity container as a singleton like the below.
container.RegisterType(new ContainerControlledLifetimeManager());
This is obviously assuming you have an interface defined called IProfileView

Update viewmodel based on MainWindow event

I have a UdpClient, firing off a DataRecevied event on my MainWindow:
public partial class MainWindow : Window
{
public static YakUdpClient ClientConnection = new YakUdpClient();
public ClientData;
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
ClientData = new ClientData();
ClientConnection.OnDataReceived += ClientConnectionOnDataReceived;
}
private void ClientConnectionOnDataReceived(object sender, MessageEventArgs messageEventArgs)
{
ClientData.Users = messageEvenArgs.ConnectedUsers;
}
}
My ClientData and User classes look as follow:
public class ClientData
{
public List<User> Users {get;set;)
}
public class User
{
public string Name {get;set;}
}
On my MainWindow, I have a UserControl called UserListView which has a ViewModel called UserListViewModel
The ViewModel looks as follow:
public class UserListViewModel: BindableBase
{
public UserListViewModel()
{
//I am sure there are better ways of doing this :(
Users = new ObservableCollection<User>((MainWindow)Application.Current.MainWindow).ClientData.Users
});
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get{ return _users;}
set { this.SetProperty(ref this._users, value); }
}
}
The difficulty I have here, is when the ClientConnectionOnDataReceived event on the MainWindow gets fired, I would like to update my ClientData class, My Viewmodel should then somehow be notified that the list changed, and subsequently update my UI.
Can anyone give me a solid example of how to achieve this using MVVM (Prism) in WPF?
I am new to MVVM, so i am still trying to figure this out.
First of all, there's no obvious reason why the main window should do the subscription.
I'd go for something like this:
create a service that encapsulates the subscription (and subscribes in its constructor)
register that as a singleton
have it implement INotifyPropertyChanged (to notify consumers of a change to Users)
inject the service into UserListViewModel and observe the Users property (see PropertyObserver)
when Users in the service changes, update Users in the user list view model
and best of all, no need for ObservableCollection here :-)
EDIT: example:
interface IUserService : INotifyPropertyChanged
{
IReadOnlyCollection<User> Users
{
get;
}
}
class YakUdpService : BindableBase, IUserService
{
private readonly YakUdpClient _yakUdpClient;
private IReadOnlyCollection<User> _users;
public YakUdpService()
{
_yakUdpClient = new YakUdpClient();
_yakUdpClient.OnDataReceived += ( s, e ) => Users = e.ConnectedUsers;
}
public IReadOnlyCollection<User> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
class UserListViewModel : BindableBase
{
private IReadOnlyCollection<UserViewModel> _users;
private readonly IUserService _userService;
private readonly PropertyObserver<IUserService> _userServiceObserver;
public UserListViewModel( IUserService userService )
{
_userService = userService;
_userServiceObserver = new PropertyObserver<IUserService>( userService );
_userServiceObserver.RegisterHandler( x => x.Users, () => Users = _userService.Users.Select( x => new UserViewModel( x ) ).ToList() );
// ^^^ should use factory in real code
}
public IReadOnlyCollection<UserViewModel> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
and then register the service
Container.RegisterType<IUserService, YakUdpService>( new ContainerControlledLifetimeManager() );
in your bootstrapper or your module's initialization.

How to get the popup to go full screen?

I am currently experimenting with the ChildWindowDialog and with prism I have created a controller class. I would like my popup to be displayed on all the screen (a bit like fullscreen mode). I have HtmlPage.Window.Eval() below but I am not sure if this is the correct thing to do. One of the reasons it feels wrong is I have no idea how to test this class in the future. Also, I have coupled the controller to the Browser class which will mean I could not reuse it in a WPF app.
public class GalleryCoverFlowChildWindowController
{
private readonly IEventAggregator _eventAggregator;
private readonly IUnityContainer _container;
public GalleryCoverFlowChildWindowController(IEventAggregator eventAggregator, IUnityContainer container)
{
_eventAggregator = eventAggregator;
_container = container;
_eventAggregator.GetEvent<GalleryCoverViewPopupEvent>().Subscribe(PopupShow, ThreadOption.UIThread, true, Filter);
}
private bool Filter(string obj)
{
return true;
}
private void PopupShow(string obj)
{
var galleryPopup = _container.Resolve<GalleryCoverFlowChildWindow>();
galleryPopup.Width = (double)System.Windows.Browser.HtmlPage.Window.Eval("screen.availWidth");
galleryPopup.Height = (double)System.Windows.Browser.HtmlPage.Window.Eval("screen.availHeight");
galleryPopup.Show();
}
}
JD.
To resolve the issue with coupling, I created a ScreenService and injected it in via Unity. That way I do not have a dependency on DOM. This will make testing the code easier.
Any thoughts?

Resources