I'm currently trying out some MVP patterns sample and I have been told not create concrete Presenter objects in the View. Is there any way to have Presenter objects created dynamically ?
public partial class View: Window, IView
{
private Presenter _presenter;
public View()
{
InitializeComponent();
_presenter = new Presenter(this); //Asked to avoid this
}
}
You're thinking it wrong. You don't create the presenter in the view. You create it elsewhere (application startup, other presenters) and it passes itself to the view, either as a constructor parameter, or by setting a property.
Like this:
class FooView : IFooView
{
private readonly IFooPresenter presenter;
public FooView(IFooPresenter presenter)
{
this.presenter = presenter;
}
}
class FooPresenter1 : IFooPresenter
{
private readonly IFooView view;
public FooPresenter1()
{
view = new FooView(this);
}
}
// or
class FooPresenter2 : IFooPresenter
{
private readonly IFooView view;
public FooPresenter2(IFooView view)
{
this.view = view;
view.Presenter = this;
}
}
And by the way, you seem to be using WPF. If that's the case you may want to have a look at the Model-View-ViewModel pattern instead.
With view first creation you can use an IoC container to create your Presenter:
public View(IMyPresenter presenter)
{
InitializeComponent();
_presenter = presenter;
}
Alternatively, you can use model (presenter) first where the View is passed to the Presenter in much the same way. See Which came first, the View or the Model? for discussion on this topic.
Or you could use a third object to bind the View and Presenter together, like the IBinder service in Caliburn.
Related
I have my MainView and an associated MainViewViewModel which are linked by ViewModelLocator.
Within MainViewViewModel there is a command which should trigger a new Window to open which has it's own View and ViewModel (NewView and NewViewViewModel).
In a lot of the examples I've seen it is suggested to use Mvvmlight's Messenger to do something like this:
public class MainViewViewModel
{
private void OpenNewWindow()
{
Messenger.Default.Send(new NotificationMessage("NewView"));
}
}
And then register the NewViewViewModel and handle the message like this:
public class NewViewViewModel
{
public NewViewViewModel()
{
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage obj)
{
if (obj.Notification == "NewView")
{
NewView view = new NewView();
view.Show();
}
}
}
However, this doesn't work because the NewViewViewModel isn't yet instantiated (so isn't registered with Messenger). Additionally, this doesn't fit with MVVM because NewViewViewModel is responsible for creating NewView.
What is the correct way to achieve a simple command which instantiates and opens a new View and ViewModel pair which are linked via ViewModelLocator and setting of DataContext="{Binding NewView, Source={StaticResource Locator}}" in NewView.xml?
Use a window service:
MVVM show new window from VM when seperated projects
You may either inject the view model to with an IWindowService implementation or use a static WindowService class:
public static class WindowService
{
public static void OpenWindow()
{
NewView view = new NewView();
view.Show();
}
}
Dependency injection is obviously preferable for being able to unit test the view model(s) and switch implementations of IWindowService at runtime.
This question already has an answer here:
Why my Subscribe method is not called when using Prism EventAggregator?
(1 answer)
Closed 3 years ago.
In my solution I am using Autofac, and Prism as well. Below is a simplified project that explains what happens.
I am registering my views, view models and EventAggregator in Autofac's container class like that:
public class BootStrapper
{
public IContainer BootStrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<EventAggregator>()
.As<IEventAggregator>().SingleInstance();
builder.RegisterType<MainWindow>().AsSelf();
builder.RegisterType<ChildView1>().AsSelf();
builder.RegisterType<MainViewModel>().AsSelf();
builder.RegisterType<Child1ViewModel>().AsSelf();
return builder.Build();
}
}
Note, that when registering view models as a singletons, effect was the same. I am injecting EventAggregator into my VM like that:
public class MainViewModel
{
private IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
UpdateName("Name1");
}
public void UpdateName(string name)
{
ChildView1 win1 = new ChildView1(); //in the backend Child1ViewModel is assigend to its DataContext
win1.Show();
_eventAggregator.GetEvent<UpdateNameEvent>().Publish(name); //this does not work
}
}
Code above does not work. Because of some reason (I hope that you will tell me why), when executing UpdateName method, this dependency does not work, and inside of Child1ViewModel class UpdateName method is not executed:
public class Child1ViewModel : ViewModelBase
{
private IEventAggregator _eventAggregator;
public Child1ViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
private void UpdateName(string name)
{
this.Name = name; //debug does not hit this code line
}
}
Constructor of Child1ViewModel is hiten during debug, just UpdateName is not executed. BUT, if I use direct call of the EventAggregator, like this:
Utility.EventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
or this:
Utility.EventAggregator.GetEvent<UpdateNameEvent>().Publish(name);
it works! When assuming, that Utility class looks like:
public class Utility
{
public static EventAggregator EventAggregator { get; set; }
static Utility()
{
EventAggregator = new EventAggregator();
}
}
I suspect, that there is some problem with registering the aggregator in Autofac, but I have no idea what is the problem, I just used it as per odl exaples I found.
Resolving Child1ViewModel and MainViewModel:
public partial class ChildView1 : Window
{
public ChildView1()
{
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
Child1ViewModel vm = container.Resolve<Child1ViewModel>();
InitializeComponent();
DataContext = vm;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
MainViewModel vm = container.Resolve<MainViewModel>();
InitializeComponent();
DataContext = vm;
}
}
I'm assuming you are using Prism's ViewModelLocator mechanism. My assumption is based on that you are manually instantiating ChildView1 rather than resolving it with dependency injection container and state in the comment that viewModel is resolved in the background.
You must change the container of Prism ViewModelLocator used for resolving the ViewModel instances as shown below.
ViewModelLocationProvider.SetDefaultViewModelFactory(viewModelType) =>
{
return YourAutofacContainer.Resolve<viewModelType>();
});
For further information see Control how ViewModels are Resolved.
Keep in mind that for keeping single instance lifetime scope of object instances consistent throughout your whole application, you must use the same dependency injection container instance for resolving objects and this objects' parent objects all the way up to the root object.
Could I get help with a little issue I am encountering regarding splitting projects into different tiers. In my ViewModel logic I have code where I create a new instance of a window when a button is clicked (I use ICommand interface for that)
The problem is however, that this requires my View folder which is in the presentation layer, I can’t reach it as my presentation layer is dependent on my ViewModel in the logic layer.
I would just move the code that deals with the creation of the pages to the view code behind but I also pass the current instance of a viewmodel as a parameter for that new window being created (for eventhandling purposes).
Any help is much appreciated! Thanks.
A view model shouldn't create instances of windows. What you could do is to inject your view model with a service that is responsible for creating windows, e.g.:
public class MainWindowViewModel
{
private readonly IWindowService _windowService;
public MainWindowViewModel(IWindowService windowService)
{
_windowService = windowService;
CreateWindowCommand = new DelegateCommand(() =>
{
_windowService.CreateWindow(new SomeViewModel());
});
}
public ICommand CreateWindowCommand { get; }
}
Define the IWindowService interface in the view model project and the concrete implementation of it in the view/presentation project:
public class WindowService : IWindowService
{
public void CreateWindow(SomeViewModel vm)
{
Window win = new Window();
win.DataContext = vm;
win.Show();
}
}
After I log something in my program, I stored the Logging information in a public field called LogItems in the class CustomLogger.
EDIT: My CustomLogger class now implements IMyLogger
public class CustomLogger : IMyLogger
{
// LogItems are stored in this field. Assume that it is not Null.
public ObservableCollection<LogItem> LogItems = new ObservableCollection<LogItem>
public CustomLogger()
{
}
// Other methods that populate the LogItems field
}
IMyLogger is the following:
public interface IMyLogger : ILoggerFacade
{
ObservableCollection<LogItem> LogItems { get; set; }
}
EDIT: Can anyone tell me how to retrieve the LogItems object from the CustomLogger class? I would need the LogItems in order to display it in my viewModel. My ViewModel class is the following:
[Export]
[PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.Shared)]
public class LoggerViewModel : ViewModelBase
{
public ObservableCollection<LogItem> LogItems { get; set; }
protected static readonly ILog log = LogManager.GetLogger(typeof(LoggerViewModel));
[ImportingConstructor]
public LoggerViewModel(CustomLogger cLogger)
{
// Can anyone tell me why I cannot call LogItems from cLogger here? An activation error is being thrown here.
LogItems = cLogger.LogItems;
}
}
After running the code, I get an activation error when trying to get instance of type LoggerView, key "".
If I change the LoggerViewModel constructor to
public LoggerViewModel()
{
}
No error is thrown but nothing is displayed in my view model.
How would I be able to display logging information in my view model?
How do you create your LoggerViewModel? Do you use any IoC framework or you create the instance yourself?
It looks like you are trying to use ViewModelLocator in LoggerView.xaml. Since you put an argument to your constructor you need to resolve it. You have 2 options:
If you use IoC like SimpleIoC from MVVM light, register CustomLogger first like this.
SimpleIoc.Default.Register<CustomLogger>();
Create your LoggerViewModel in the code and then assign DataContext to LoggerView manually.
LoggerViewModel vm = new LoggerViewModel(new CustomLogger());
this.loggerView.DataContext = vm;
I hope this helps.
=========================================================================
More details with regards to solution 1.
If you decide to use SimpleIoC then:
You need to register your CustomLogger and LoggerViewModel in constructor of ViewModelLocator
SimpleIoc.Default.Register< CustomLogger>();
SimpleIoc.Default.Register< LoggerViewModel>();
LoggerViewModel constructor should have constructor with CustomLogger argument, IoC will take of this.
In ViewModelLocator you should expose a property
public LoggerViewModel LoggerViewModel
{
get { return ServiceLocator.Current.GetInstance< LoggerViewModel>(); }
}
In LoggerView.xaml you should have DataContext of our View set to ViewModelLocator with path LoggerViewModel
< UserControls ....
DataContext="{Binding Source={StaticResource Locator}, Path=LoggerViewModel}">
I've following architecture:
desktop application, .Net 4.5, C#, WPF, MVVM Light, Messenger, IoC - ViewModel locator, so ViewModels doen't know anyhing about Views.
I have main view with data grid of some elements, and I want to display details of each individual element in new/child windows after double click on data grid.
I've bind event double click on main view to main view model. From this event handler in main view model, message is sent via Messanger.
New view (new/child window) is created in main view via delegate of also double click.
New/child window is a view which locate his view model and this view model register to the specific message in his constructor.
The problem is that new/child window (new view, and view model so on) is created too late, because message is already sent when new view model register for it.
Do you know maybe some patterns for such architecture. Any ideas will be appreciated.
It would help to know exactly what you try to do.
If your problem is just to display a detailed Window when double click on a row, I would say: create only one childWindow at start, and play with its visbility when required.
If you really need a new window each time, you could create it from your viewModel with an injected service for example.
In any case, you never has to create your window from main view! Either you create one window at start, either you dynamically create it from view model.
You cannot hope to create it from view and send the message in your view model.
Edit about the injected service, you could use something like that:
public interface IWindowService
{
void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window;
}
public class WindowService : IWindowService
{
private readonly IUIDispatcher _dispatcher;
public WindowService(IUIDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window
{
_dispatcher.Run(() => OpenThreadSafe<TWindow>(viewModel));
}
private static void OpenThreadSafe<TWindow>(ViewModelBase viewModel) where TWindow : Window
{
var view = (TWindow) Activator.CreateInstance(typeof(TWindow), viewModel);
view.Show();
}
}
public class UIDispatcher : IUIDispatcher
{
public void Run(Action action)
{
var dispatcher = DispatcherHelper.UIDispatcher;
if (dispatcher == null)
{
action();
return;
}
DispatcherHelper.CheckBeginInvokeOnUI(action);
}
Note this DispatcherHelper come from MVVMlight, but you could erplace it easily.
Hope it helps.
The problem is that the ViewModel Locator creates the viewmodel instance only when it is needed (lazy loading).
just configure the ViewModelLocator to instantiate the viewmodel eager instead of lazy. This is done by passing the parameter "true" to the IoC Container.
Sample:
namespace Administration.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//Eager Loading
SimpleIoc.Default.Register<UserManagementViewModel>(true);
//Lazy Loading
SimpleIoc.Default.Register<InformationManagementViewModel>();
}
public UserManagementViewModel UserManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<UserManagementViewModel>();
}
}
public InformationManagementViewModel InformationManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<InformationManagementViewModel>();
}
}
public static void Cleanup()
{
SimpleIoc.Default.Unregister<UserManagementViewModel>();
SimpleIoc.Default.Unregister<InformationManagementViewModel>();
}
}
}