Closed AnderssonPeter closed 8 years ago
Your attempt to use a Func(Of ...) should work. I just tried, but this registrations pass verification.
Public Class BusyViewMode
End Class
Public Class ShellViewModel
Private ReadOnly busyViewModeFactory As Func(Of BusyViewMode)
Public Sub New(busyViewModeFactory As Func(Of BusyViewMode))
Me.busyViewModeFactory = busyViewModeFactory
End Sub
Private ReadOnly Property busyViewMode As BusyViewMode
Get
Return Me.busyViewModeFactory.Invoke()
End Get
End Property
End Class
Public Module CompositionRoot
Public Sub Initialize()
Dim container = New Container()
container.RegisterSingleton(Of ShellViewModel)()
container.Register(Of BusyViewMode)()
container.RegisterSingleton(Of Func(Of BusyViewMode)) _
(Function() container.GetInstance(Of BusyViewMode)())
container.Verify()
End Sub
End Module
Notice that however this will pass verification and will return a new BusyViewMode
every time you make a call to the private property, you will have to use a background task (using the old threading model or the async-await) in order to successfully display the BusyIndicator
. If you will do the long running task in the UI thread, WPF will not update the UI until the thread is free, which is at the end of the method in most cases.
After rereading your question I saw I missed a part. You want also to inject the same type twice in the constructor. But the question is, will each type have a separate view?
If that is the case, how would you think this will work in practice? I would recommend to inject a single factory which binds to a single view in which you can differentiate by passing some object to a method in 'BusyViewMode'. 'BusyViewMode' can use this object to change the text, or other parts of the UI by using default MVVM bindings
Yes each view model should get their own view i haven't managed to test it yet but that's the idea.
But using a single one might also work I just have to redesign it so it can have multiplie texts.
Yes each view model should get their own view i haven't managed to test it yet but that's the idea.
This would result in making decisions in your ShellViewModel
which injected BusyViewMode
should bind to which view. With that your violating almost all 5 SOLID principles.
Besides that how do you think your composition root (if done by a container, like Simple Injector, but also using Pure DI) should decide which BusyViewMode should be injected in which parameter, beside looking at the parameter naming...? If these have distinct functionality they should be different abstractions..
@TheBigRic every parameter should get its own instance, so if we have ShellViewModel
with Parameter a
, and FileSaveViewModel
with parameter a
those should not be the same instance.
But it seems like I'm missing the whole point..
But one simple question
Is it normal to mark the ShellViewModel
as Singelton
and if your root element is a Singelton
how would you ever get a Transient
.
Are there any example projects that follow best practices that I could learn from?
Sorry, I totally forgot about this question. I hope an answer is still usefull.
In most of my applications I also use an application root viewmodel like ShellViewModel
or MainViewModel
. And whether this is registered as Singleton or not, it will behave as Singleton because when this will close, the application closes.
So how would you then show other views which need to be newed every time they open, in other words transients?
Your root object, the ShellViewModel
in this case should be part of the composition root and should have no other functionalities than opening other view(model)s. In the best scenario this root viewmodel doesn't have a view at all, other than an empty window which only acts as a container for the views of the transient viewmodels which contain the actual application interface.
The ShellViewModel
itself can then callback in the container to get a fresh copy of transient viewmodel and bind it to a corresponding view or it delegates this to some dispatch class with a single method which takes the viewmodel type as a parameter and has the single responsibility to create the viewmodel (by calling again back into the container) and find the corresponding view of that viewmodel, normally by using some MVVM tool like Caliburn Micro.
So this could look like:
public class ShellViewModel
{
private readonly IViewModelHandler viewModelHandler;
public ShellViewModel(IViewModelHandler viewModelHandler)
{
this.viewModelHandler = viewModelHandler;
}
public void ShowCustomers()
{
this.viewModelHandler.HandleViewModel<CustomerViewModel>();
}
}
public interface IViewModelHandler
{
void HandleViewModel<TViewModel>() where TViewModel : class;
}
public class MvvmViewModelHandler : IViewModelHandler
{
private readonly Container container;
private readonly IWindowManager windowManager;
public MvvmViewModelHandler(Container container, IWindowManager windowManager)
{
this.container = container;
this.windowManager = windowManager;
}
public void HandleViewModel<TViewModel>() where TViewModel : class
{
var viewModel = this.container.GetInstance<TViewModel>();
// windowManager is defined in Caliburn, caliburn will find the
// correct view, create and initialize a window class from it
// and bind the viewmodel to the datacontext of this window
// amongst several other things
this.windowManager.ShowDialog(viewModel);
}
}
If don't use a Mvvm toolkit (which I can recommend!) you can also implement your own implementation of a very simple ConventionViewHandler
like this very simplied example:
public class CustomByConventionViewModelHandler : IViewModelHandler
{
private readonly Container container;
public CustomByConventionViewModelHandler(Container container)
{
this.container = container;
}
public void HandleViewModel<TViewModel>() where TViewModel : class
{
var viewType = this.FindViewType<TViewModel>();
var view = (Window) this.container.GetInstance(viewType);
var viewModel = this.container.GetInstance<TViewModel>();
view.DataContext = viewModel;
view.ShowDialog();
}
public Type FindViewType<TViewModel>()
{
var viewModelName = typeof(TViewModel).FullName;
var viewName = this.GetViewName(viewModelName);
var viewType = this.GetViewType(viewName);
if (viewType == null)
{
throw new InvalidOperationException(
$"A view for {viewModelName} could not be located!");
}
return viewType;
}
private Type GetViewType(string viewName)
{
return (
from type in Assembly.GetExecutingAssembly().GetExportedTypes()
where type.FullName == viewName
select type).SingleOrDefault();
}
private string GetViewName(string viewModelName)
{
if (!viewModelName.EndsWith("viewmodel", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
"By convention viewmodels should be named xxxViewModel");
}
return viewModelName.Substring(0, viewModelName.Length - 5);
}
}
I'm using WPF and the MVVM pattern.
When I'm executing anything that's long running i want to display a loading indicator so i created a
BusyViewModel
for that. Now i want to get a new instance of this for each long running task, so lets say I have 2 one forSaving
and one forLoading
.And the constructor for
ShellViewModel
has the following parametersBusyViewMode savingBusyViewMode, BusyViewMode loadingBusyViewMode
.When I call verify I get a exception that tells me my
Lifestyle
s are mismatching, so my second attempt was to take aFunc<BusyViewMode>
as parameter to the constructor but that didn't work either.So I my idea totally stupid or how would I solve this?