Caliburn Micro - Share data between ViewModels - c#

Starting from the Caliburn Micro "Simple MDI" example. I would like to achieve the following:
I would like to share the reference of a class between the ViewModels. The shareme.count should be passed as reference to all the ViewModels. This would allow me to change it from within each ViewModel.
How can I achieve this close to Caliburn Micro convention?
ShellViewModel.cs
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive {
SharedClass _shareme;
public SharedClass shareme {
get { return _shareme; }
set {
_shareme = value;
NotifyOfPropertyChange(() => shareme);
}
}
public ShellViewModel() {
shareme.count = 1;
}
public void OpenTab() {
ActivateItem(new TabViewModel {
DisplayName = "Tab " + shareme.count++
});
}
TabViewModel.cs
public class TabViewModel : Screen {}
AppBootstrapper.cs
public class AppBootstrapper : BootstrapperBase {
SimpleContainer container;
public AppBootstrapper() {
Initialize();
}
protected override void Configure() {
container = new SimpleContainer();
container.Singleton<IWindowManager, WindowManager>();
container.Singleton<IEventAggregator, EventAggregator>();
container.PerRequest<IShell, ShellViewModel>();
}
protected override object GetInstance(Type service, string key) {
var instance = container.GetInstance(service, key);
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
protected override IEnumerable<object> GetAllInstances(Type service) {
return container.GetAllInstances(service);
}
protected override void BuildUp(object instance) {
container.BuildUp(instance);
}
protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
DisplayRootViewFor<IShell>();
}
}

The will also serve as answer to your duplicate question.
EventAggregator isn't just for "events" you can message pass data to any or all viewmodels that are listening for message or event signature in question.
public class ViewModelA : Screen, IHandle<ShareMeMessageA>
{
private readonly IEventAggregator _events;
private int _sharemecount;
public class ViewModelA(IEventAggregator events){
_events = events;
_events.Subscribe(this);
}
//... other bits out for brevity
//-- EDIT --
public void SomeEventClick(){
_event.PublishOnUiThread(new ShareMeMessageB(){ ... etc ... });
}
protected override void Deactivated(bool close){
_events.Unsubscribe(this);
}
private void Handle(ShareMeMessageA msg)
{
if(msg != null)
sharemecount = msg.Count;
}
}
as this is just an example you don't have to pass the class object at all you can pass any type you want bool, int, float, etc..

Related

Trigger EventHandler of one ViewModel in another ViewModel

I have achieved desired result with MessagingCenter, but I have got an information from reading Xamarin articles that MessagingCenter is not the preferred way to trigger 30+ events. Additional to that I have to unsubscribe from MessagingCenter after action has been done. I want to have Settings page where I would have 30+ settings that have to be changed across whole application in different views. How I can inject SettingsViewModel into other ViewModels in Xamarin.Forms application?
SettingsViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class SettingsViewModel : BaseViewModel, ISettingsViewModel
{
public ICommand ChangeCommand { get; set; }
public SettingsViewModel()
{
Title = "Settings";
this.BoxColor = Color.Red;
this.ChangeCommand = new Command(this.ChangeColor);
}
private void ChangeColor()
{
this.BoxColor = Color.FromHex(this.BoxColorS);
MessagingCenter.Send<Object, Color>(this, "boxColor", this.BoxColor);
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
this.OnPropertyChanged();
}
}
private string _boxColorS;
public string BoxColorS
{
get => Preferences.Get("BoxColor", "#17805d");
set
{
Preferences.Set("BoxColor", value);
this.ChangeColor();
this.OnSettingsChanged();
this.OnPropertyChanged();
}
}
public event EventHandler<SettingsChangedEventArgs> SettingsChanged;
private void OnSettingsChanged() => this.SettingsChanged?.Invoke(this, new SettingsChangedEventArgs(this.Settings));
public Settings Settings { get; private set; }
}
}
HomeViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class HomeViewModel : BaseViewModel
{
public HomeViewModel()
{
this.Title = "Home";
MessagingCenter.Subscribe<Object, Color>(this, "boxColor", (sender, arg) =>
{
System.Diagnostics.Debug.WriteLine("received color = " + arg);
this.BoxColor = arg;
});
this.BoxColor = Color.Red;
this.SettingsViewModel = new SettingsViewModel();
this.SettingsViewModel.SettingsChanged += OnSettingsChanged;
}
private void OnSettingsChanged(object sender, SettingsChangedEventArgs e)
{
throw new NotImplementedException();
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
OnPropertyChanged();
}
}
private ISettingsViewModel SettingsViewModel { get; }
}
}
Should I somehow do all in MainViewModel? I mean:
namespace MessagingCenterApp.ViewModels
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
this.SettingsViewModel = new SettingsViewModel();
this.HomeViewModel = new HomeViewModel(this.SettingsViewModel);
}
public SettingsViewModel SettingsViewModel { get; set; }
public HomeViewModel HomeViewModel { get; }
}
}
Then initialized it in AppShell? I could not get this approach working.
Important! I don't want to use any MVVM framework! Only native behaviour.
mvvmcross' Messenger is alleged to be "lighter weight" than X-Form's built-in Messaging Center.
I use mvvmcross Messenger by defining some helper methods in a "BasePage". Then each page inherits from "BasePage" rather than "ContentPage".
This automatically handles "unsubscribe" of each method. And makes it easier to manage mvvmcross' "subscription tokens".
BasePage.xaml.cs:
// If not using mvvmcross, this could inherit from ContentPage instead.
public class BasePage : MvxContentPage
{
protected readonly IMvxMessenger Messenger;
public BasePage()
{
this.Messenger = Mvx.IoCProvider.Resolve<IMvxMessenger>();
}
protected override void OnAppearing()
{
base.OnAppearing();
// Examples of subscribing to messages. Your subclasses of BasePage can also do this.
this.Subscribe<MyMessage1>(OnMyMessage1);
this.SubscribeOnMainThread<MyMessage2>(OnMyMessage2);
}
protected override void OnDisappearing()
{
UnsubscribeAll();
base.OnDisappearing();
}
#region Messenger Subscriptions
protected List<MvxSubscriptionToken> _subscriptions = new List<MvxSubscriptionToken>();
/// <summary>
/// Create subscription and add to "_subscriptions".
/// Call this from subclass' OnAppearing, once per subscription.
/// Automatically unsubscribed in OnDisappearing.
/// </summary>
/// <param name="token"></param>
/// <param name="msgType"></param>
protected void Subscribe<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.Subscribe<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
protected void SubscribeOnMainThread<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.SubscribeOnMainThread<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
/// <summary>
/// OnDisappearing calls this.
/// </summary>
private void UnsubscribeAll()
{
if (_subscriptions.Count > 0)
{
foreach (MvxSubscriptionToken token in _subscriptions)
{
// Per "https://www.mvvmcross.com/documentation/plugins/messenger", this is sufficient to Unsubscribe:
// "Subscriptions can be cancelled at any time using the Unsubscribe method on the IMvxMessenger or by calling Dispose() on the subscription token."
token.Dispose();
}
_subscriptions.Clear();
}
}
#endregion
}
For view models, class would be "BaseViewModel", that your view models inherit from. Contents similar to above, but different method names for Appearing/Disappearing.
BaseViewModel.cs:
public class BaseViewModel : MvxViewModel
{
...
// mvvmcross' MvxViewModel provides these.
protected override void ViewAppearing()
{
...
}
protected override void ViewDisappearing()
{
...
}
... Messenger Subscriptions methods ...
}

Prism 7.2 How to inject an instance into ViewModel another Module

i'm a new in a development of WPF applications with PRISM and AKKA.NET frameworks.
In my code for Shell Window I made an instance of ActorSystem.
public partial class App
{
private ActorSystem appActorSystem;
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
appActorSystem = ActorSystem.Create(System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name);
containerRegistry.RegisterInstance(appActorSystem);
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<CommandBar.CommandBarModule>();
}
protected override void OnExit(ExitEventArgs e)
{
appActorSystem.Terminate();
appActorSystem.Dispose();
base.OnExit(e);
}
}
The instance will be registered in DI Container (Unity) after creation.
In my application i have also a module.
public class CommandBarModule : IModule
{
private IContainerProvider _containerProvider;
public void OnInitialized(IContainerProvider containerProvider)
{
_containerProvider = containerProvider;
var appActorSystem = _containerProvider.Resolve<ActorSystem>();
var regionManager = _containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion(regionNames.CommandBar, typeof(ViewA));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>();
}
}
I want to get the instance of the Actorsystem in the module. It works fine in CommandBarModule Class.
But I want also to get the instance of my Actorsystem in the ViewModel of the module...
public class ViewAViewModel : BindableBase
{
private string _message;
public string Message
{
get { return _message; }
set { SetProperty(ref _message, value); }
}
public ViewAViewModel()
{
Message = "test";
}
}
My First Idea was, that i can do injection of IContainerProvider into the constructor of the ViewModel, like this:
public class ViewAViewModel : BindableBase
{
private IContainerProvider _containerProvider;
private string _message;
public string Message
{
get { return _message; }
set { SetProperty(ref _message, value); }
}
public ViewAViewModel(IContainerProvider containerProvider)
{
_containerProvider = containerProvider;
var appActorSystem = _containerProvider.Resolve<ActorSystem>();
Message = "test";
}
}
But it doesn't work...
Could you please explain me how to do it right?
Inject the dependency, not the container:
public ViewAViewModel(ActorSystem appActorSystem)
{
Message = "test";
}
It does not matter in which module a service is registered and where it is resolved, as long as it's resolved after being registered.

Is there any problems with my MEF Exports?

I get an error:
An exception has occurred while trying to add a view to region 'MenubarRegion'.
- The most likely causing exception was was:
'Microsoft.Practices.ServiceLocation.ActivationException: Activation error occured
while trying to get instance of type MenuView, key "" --->
My MenuView sets its datacontext through MenuViewModel using MEF, which inturn imports an instance of IServiceFactory. I am sure that error is occured due to IServiceFactory and MEF........ I mean Exports on it or Imports. I guess that because when I remove ImportingConstructor and IServiceFactory declarations in MenuViewModel, my program works well.
I have checked for errors on MEF using MefX. Here are the results:
And here is my code:
MenuView.xaml.cs
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class MenuView : UserControlViewBase
{
[ImportingConstructor]
public MenuView(MenuViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
}
MenuViewModel.cs
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class MenuViewModel : ViewModelBase
{
IServiceFactory _ServiceFactory;
[ImportingConstructor]
public MenuViewModel(IServiceFactory serviceFactory)
{
_ServiceFactory = serviceFactory;
}
protected override void OnViewLoaded()
{
_MenuItems = new ObservableCollection<MenuItem>();
WithClient<IMenuItemService>(_ServiceFactory.CreateClient<IMenuItemService>(), menuItemClient =>
{
MenuItem[] menuItems = menuItemClient.GetAllParentMenuItemsWithChildren();
if (menuItems != null)
{
foreach (MenuItem menuItem in menuItems)
{
_MenuItems.Add(menuItem);
}
_SelectedMenuItem = _MenuItems[2];
}
});
}
private ObservableCollection<MenuItem> _MenuItems;
public ObservableCollection<MenuItem> MenuItems
{
get
{
return _MenuItems;
}
set
{
if (_MenuItems != value)
{
_MenuItems = value;
OnPropertyChanged(() => MenuItems, false);
}
}
}
private MenuItem _SelectedMenuItem;
public MenuItem SelectedMenuItem
{
get
{
return _SelectedMenuItem;
}
set
{
if (_SelectedMenuItem != value)
{
_SelectedMenuItem = value;
OnPropertyChanged(() => SelectedMenuItem);
}
}
}
}
IServiceFactory.cs
public interface IServiceFactory
{
T CreateClient<T>() where T : IServiceContract;
}
ServiceFactory.cs
[Export(typeof(IServiceFactory))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ServiceFactory : IServiceFactory
{
public T CreateClient<T>() where T : IServiceContract
{
return ObjectBase.Container.GetExportedValue<T>();
}
}
Bootstrapper (Client side):
public static class MEFLoader
{
public static CompositionContainer Init()
{
return Init(null);
}
public static CompositionContainer Init(ICollection<ComposablePartCatalog> catalogParts)
{
AggregateCatalog catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(MenuItemClient).Assembly));
catalog.Catalogs.Add(new AssemblyCatalog(typeof(MEFLoader).Assembly));
if (catalogParts != null)
foreach (var part in catalogParts)
catalog.Catalogs.Add(part);
CompositionContainer container = new CompositionContainer(catalog);
return container;
}
}
Bootstrapper (Business side)
public static class MEFLoader
{
public static CompositionContainer Init()
{
AggregateCatalog catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(MunimPlusEngine).Assembly));
catalog.Catalogs.Add(new AssemblyCatalog(typeof(MenuItemManager).Assembly));
catalog.Catalogs.Add(new AssemblyCatalog(typeof(MenuItemRepository).Assembly));
catalog.Catalogs.Add(new AssemblyCatalog(typeof(MEFLoader).Assembly));
CompositionContainer container = new CompositionContainer(catalog);
return container;
}
}
Bootstrapper (WPF Main Application)
public class BootStrapper : MefBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.GetExportedValue<Shell>();
}
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)Shell;
App.Current.MainWindow.Show();
}
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(BootStrapper).Assembly));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(RegionNames).Assembly));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(ModuleMenu.Module).Assembly));
}
}
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ObjectBase.Container = MEFLoader.Init(new List<ComposablePartCatalog>()
{
new AssemblyCatalog(Assembly.GetExecutingAssembly())
});
BootStrapper bootstrapper = new BootStrapper();
bootstrapper.Run();
}
}
Project
Here is my Project if anybody would like to have a look at it:
Download Project
The problem is that your ServiceFactory implementation is not added to the MEF catalog. When you add:
public class BootStrapper : MefBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.GetExportedValue<Shell>();
}
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)Shell;
App.Current.MainWindow.Show();
}
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(BootStrapper).Assembly));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(RegionNames).Assembly));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(ModuleMenu.Module).Assembly));
//Added catalog
//-->
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(ServiceFactory).Assembly));
}
}
to your bootstrapper configuration, the application stops throwing exception on startup. (however it still doesn't show anything)
In order to add that type I needed to add a reference to an additional project in the main application.

Interface events pattern

public interface IConnector
{
void Connect();
event EventHandler<EventArgs> Received;
// and more
}
public class ConnectorA: IConnector
{
public event EventHandler<EventArgs> Received;
public void Connect(){
...
}
}
public class ConnectorB: IConnector
{
public event EventHandler<EventArgs> Received;
public void Connect(){
...
}
}
Now let's say I have a factory like this:
public interface IConnectorFactory
{
void Create(string type);
}
public class ConnectorFactory: IConnectorFactory
{
public void Create(string type)
{
switch(type)
{
case "A":
return new ConnectorA();
case "B":
return new ConnectorB();
}
}
}
I inject this factory via constructor to Manager class:
public class Manager
{
IConnectorFactory _factory;
IConnector _actualConnector;
string _type;
public string Type
{
get
{
return _type;
}
set
{
_type = value;
if (_actualConnector != null)
{
_actualConnector.Disconnect();
_actualConnector.Received -= ReceivedFunc;
}
_actualConnector = _factory.Create(Type);
_actualConnector.Received += ReceivedFunc;
}
}
public Manager(IConnectorFactory factory)
{
_factory = factory;
}
public void Connect()
{
_actualConnector.Connect();
}
public void DoSomethingElse()
{
_actualConnector.DoSomethingElse();
_actualConnector.Received += ReceivedFunc;
}
public void ReceivedFunc()
{
}
}
Type is property that is set external (for example binded to UI ComboBox). Problem is that every time it is changed I have to unsubscribe old event and subscribe to new event. Isn't there some pattern for subscribing to interface event?
When you assign a value to _actualConnector:
_actualConnector = _factory.Create(Type);
your're just changing a field and the old object has still its Received event handled by ReceivedFunc. This has nothing to do with interfaces, this is the way objects and references work in .NET

Unity + EntityFramework + PRISM - "Resolution of the dependency failed"

Im new on Unity/EF world and im getting a Error when i execute my test new project.
I have a Prism bootstrap first:
class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return new Shell();
}
protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.MainWindow = (Window)this.Shell;
Application.Current.MainWindow.Show();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
this.Container
.RegisterType<IDataContextAsync, SistemaContext>(new ContainerControlledLifetimeManager())
.RegisterType<IUnitOfWorkAsync, UnitOfWork>(new ContainerControlledLifetimeManager())
.RegisterType<IRepositoryAsync<Cliente>, Repository<Cliente>>()
.RegisterType<IClienteService, ClienteService>();
}
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
ModuleCatalog moduleCatalog = (ModuleCatalog)this.ModuleCatalog;
moduleCatalog.AddModule(typeof(CadastroModule.CadastroModule));
}
}
And other project "CadastroModule" with the CadastroModule:
public class CadastroModule : IModule
{
private readonly IRegionManager regionManager;
public CadastroModule(IRegionManager regionManager)
{
this.regionManager = regionManager;
}
public void Initialize()
{
this.regionManager.RegisterViewWithRegion("MainRegion", typeof(Views.CadastroCliente));
}
}
Thats my EF Context:
public class SistemaContext : Sistema.Common.Repository.DataContext
{
static SistemaContext()
{//I dont know what to do here!
//Database.SetInitializer<SistemaContext>(new MigrateDatabaseToLatestVersion<SistemaContext, Sistema.DataAccess.Migrations.Configuration>());
}
public SistemaContext()
: base("Name=ContactsDb")
{
}
public DbSet<Cliente> Cliente { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new Sistema.DataAccess.Mapping.ClienteConfig());
modelBuilder.Configurations.Add(new Sistema.DataAccess.Mapping.EnderecoConfig());
}
}
I need the "MigrateDatabaseToLatestVersion" for automatic migrations.
The View:
public partial class CadastroCliente : UserControl
{
private readonly IClienteService _clienteService;
private readonly IUnitOfWork _unitOfWork;
public CadastroCliente()
{
InitializeComponent();
}
public CadastroCliente(IClienteService clienteService, IUnitOfWork unitOfWork)
{
_clienteService = clienteService;
_unitOfWork = unitOfWork;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
dgv.DataContext = _clienteService.ClienteOrdenadoNome();
}
}
UPDATE 1 IF i take out the IUnitOfWork it runs!:
public partial class CadastroCliente : UserControl
{
private readonly IClienteService _clienteService;
private readonly IUnitOfWork _unitOfWork;
public CadastroCliente()
{
InitializeComponent();
}
public CadastroCliente(IClienteService clienteService)
{
_clienteService = clienteService;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
dgv.DataContext = _clienteService.ClienteOrdenadoNome();
}
}
Im using a Generic unit of Work repository located here: Link
And the Error is this:
At the time of the exception, the container was:
Resolving CadastroModule.Views.CadastroCliente,(none)
Resolving parameter \"unitOfWork\" of constructor CadastroModule.Views.CadastroCliente(Sistema.Service.IClienteService clienteService, Sistema.Common.Repository.UoW.IUnitOfWork unitOfWork)
Resolving Sistema.Common.Repository.UoW.IUnitOfWork,(none)
I detected when the UnitOfWork is taking the context
public class UnitOfWork : UnitOfWorkAsync
public UnitOfWork(IDataContextAsync dataContext) { _dataContext = dataContext; }
The dataContext return a internal exception exception:
The context cannot be used while the model is being created.
This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently.
Note that instance members of DbContext and related classes are not guaranteed to be thread safe
What im doing wrong?
Problem resolved after many days...
Internaly of the RepositoryFramework that im using, the EntityClasses need to inherit from a class named Entity:
public abstract class Entity : IObjectState
{
[NotMapped]
public ObjectState ObjectState { get; set; }
}
A child "Complex"(Entity framework Mapping ComplexTypeConfiguration<*>) class, was not inherited.
The error was not the UNIT/PRISM, was the repository itself that im using.
To resolve that i created a new project using no Modularity PRISM to be more simple to find where the error is comming.
So... my problem was resolved by putting the ": Entity" on my Child class:
public class Endereco : Entity //HERE
{
public string Rua { get; set; }
public int Numero { get; set; }
}

Categories