Prism PopupWindowAction with viewmodel created using dependency injection - c#

I have created a "popup" window which is being displayed using a PopupWindowAction as per the Prism documentation. The view is loading just fine, but the ViewModel is not. All the examples I can find just have a simple ViewModel being created in the code behind for the view. My ViewModel needs to be constructed by unity so that dependencies can be injected, but this is being bypassed because the view is being declared in the xaml:
<prism:InteractionRequestTrigger SourceObject="{Binding CustomViewRequest, Mode=OneWay}">
<prism:PopupWindowAction>
<prism:PopupWindowAction.WindowContent>
<views:CustomView />
</prism:PopupWindowAction.WindowContent>
</prism:PopupWindowAction>
</prism:InteractionRequestTrigger>
I have a partial workaround which is to embed a ContentControl (with a region) within the PopupWindowAction.WindowContent. That works in that when I load the view into the region, the ViewModel is created for me. However, each time the Window appears, it is the same size as the total desktop space across all displays.
I was thinking I could implement some code to set the starting position and dimensions of the popup, but I don't have access to the Window because that is being created for me in the PopupWindowAction. I don't want to restrict the size of the underlying ContentControl or View, otherwise the user won't be able to adjust the window size. Plus, that just feels like a workaround!
So how can I get the PopupWindowAction to load the ViewModel using dependency injection? Or if that is not straightforward, how can can access to the Window dimensions and bind them to the viewmodel associated with the view in the ContentControl?

I ran into the same need and was able to get it working with a custom derived PopupWindowAction, to allow its WindowContent to be composed. According to the Prism doc Unity doesn't support the TryResolve extension method, but if you are more familiar with Unity there may be an alternative way to do the TryResolve part that would work in Unity.
So I defined a ComposablePopupWindowAction that adds a WindowContentType dependency property. Then I overrode the Invoke method, to use the service locator to get an instance of the WindowContentType if it is defined.
// a popupWindowAction that allows the DI to compose its view
public class ComposablePopupWindowAction : PopupWindowAction
{
public static readonly DependencyProperty WindowContentTypeProperty =
DependencyProperty.Register(
"WindowContentType",
typeof(Type),
typeof(ComposablePopupWindowAction),
new PropertyMetadata(null),
v => {
Type type = v as Type;
Type frameworkElementType = typeof(FrameworkElement);
// either this is not specified, or if it is then it needs to be a FrameworkElement
return (v == null) || type.IsSubclassOf(frameworkElementType) || (type == frameworkElementType);
}
);
public Type WindowContentType
{
get { return (Type)GetValue(WindowContentTypeProperty); }
set { SetValue(WindowContentTypeProperty, value); }
}
protected override void Invoke(object parameter)
{
ConfigureWindowContent();
base.Invoke(parameter);
}
protected void ConfigureWindowContent()
{
// configure the windowContent if not specified, but a type was
if ((this.WindowContentType != null) && (this.WindowContent == null))
{
// this doesn't appear to be supported in Unity so might need slightly different logic here?
var view = ServiceLocator.Current.TryResolve(this.WindowContentType);
// if can't get thedesired type then base will use the notification
if ((view != null) && (view.GetType() == this.WindowContentType))
{
this.WindowContent = view as FrameworkElement;
}
}
}
}
Then do the interactionRequestTrigger as normal, and specify the view type for the WindowContentType property:
<prism:InteractionRequestTrigger SourceObject="{Binding ConfigurationPopupRequest, Mode=OneWay}">
<inf:ComposablePopupWindowAction IsModal="True" CenterOverAssociatedObject="True" WindowContentType="{x:Type analysis:ConfigurationPopupView}" />
</prism:InteractionRequestTrigger>

Related

"IoC is not initialized." error when using DesignTime support in Caliburn Micro for cascaded ViewModels

I have put the Bootstrapper in a separate assembly than ViewModels and Views.
However the DesignTime support seems to work for simple *ViewModel->*View visualizations when there is no other Screen embedded that needs to resolve the corresponding view. But I can't get it running for child ViewModels. I get an Exception instead...
Container with Child ViewModel:
<UserControl ...
d:DataContext="{d:DesignInstance Type=vm:DesignSomeContainerViewModel, IsDesignTimeCreatable=True}"
cal:Bind.AtDesignTime="True ... >
<ContentControl cal:View.Model={Binding PropertyWithChildModel} />
// where PropertyWithChildModel is another Screen
The Screen within the property is working correctly when displaying on it's own at DesignTime but not inside the ContentControl of another View... then the DesignTime support gets broken. I followed the guidance as suggested here Caliburn Micro Documentation. But there is nothing said about specialties when using nested resolving.
My project structure:
[Application.exe]
- MyNameSpace.ApplicationName
- Bootstrapper (knows assemblies with views)
[Satellite.dll]
- MyNameSpace.SomeWhere
- SomeContainerViewModel
- [DesignTime]/DesignSomeContainerViewModel (Folder is Namespaceprovider)
- SomeContainerView (SomeContainerView is set to look at DesignSomeContainerViewModel at DesignTime)
- [MyChilds]/SomeChildViewModel
- [DesignTime]/DesignSomeChildViewModel
- [MyChilds]/SomeChildView (SomeChildView is set to look at DesignSomeChildViewModel)
Class model:
public interface ISomeChildViewModel
{
string SomeValue {get;}
}
// should not be used during design time
public class SomeChildViewModel : Screen, ISomeChildViewModel
{
// real implementation
}
// this is used at design time
public class DesignSomeChildViewModel : Screen, ISomeChildViewModel
{
public DesignSomeChildViewModel()
{
this.SomeValue = "Hello World!";
// showing up correctly in the Xaml View at DesignTime
}
string SomeValue {get;set;}
}
public interface ISomeContainerViewModel
{
IScreen PropertyWithChildModel{get;}
}
// should not be used during design time
public class SomeContainerViewModel : Screen, ISomeContainerViewModel
{
// real implementation
}
// used during design time
public class DesignSomeContainerViewModel : Screen, ISomeContainerViewModel
{
public DesignSomeContainerViewModel ()
{
// this assignment leads to an exception that the IoC is not ready
// "IoC is not initialized."
this.PropertyWithChildModel = new DesignSomeChildViewModel();
}
IScreen PropertyWithChildModel
{
get { return propertyWithChildModel; }
set
{
this.propertyWithChildModel=value;
this.NotifyOfPropertyChange();
}
}
}
What I already tried/checked:
Bootstrapper: list of assemblies from SelectAssembly contains assembly with Views
ViewModelLocator: was set to match against DesignTime ViewModel instead of ViewModel (even if not, there should be a message that the View can't be resolved for the ViewModel)
used Conductor with ActiveItem instead of simple property
the container DesignSomeContainerViewModel and SomeContainerViewModel share same interface with IScreen PropertyWithChild {get;} and binding points to the right property

Is there any way to remove a view (by name) from a Prism region when the view was added using the RegionManager.RequestNavigate method?

I am using Prism for navigation in my WPF MVVM application. I register my view as follows.
// MyView is the data type of the view I want to register and "MyView"
// is the name by which I want the data type to be identified within
// the IoC container.
_container.RegisterType<object, MyView>("MyView");
I display this view as follows.
_regionManager.RequestNavigate(
"MyRegion", // This is the name of the Region where the view should be displayed.
"MyView" // This is the registered name of the view in the IoC container.
);
Elsewhere in the application, I need to remove this view in an event handler; however, the following code returns an ArgumentNullException.
_regionManager.Regions["MyRegion"].Remove(
_regionManager.Regions["MyRegion"].GetView("MyView")
);
This indicates that the RequestNavigate method does not add MyView to MyRegion using the name "MyView". I know that if I were to use the _regionManager.Add(MyView, "MyView") method, the GetView method would not return null. Unfortunately, RequestNavigate does not seem to handle the view name in the same way. Is there any way to remove a view (by name) from a region when the view was added using the RequestNavigate method?
It stems from how you add your view, not with your removal. Previously asked answered by adding the view fully, aka including the name.
_regionManager.Regions["MyRegion"].Add(myView, "MyView");
So now you can do your retrieval and removal:
var theView = _regionManager.Regions["MyRegion"].GetView("MyView");
_regionManager.Regions["MyRegion"].Remove(theView);
Without defining name during Regions.Add()
In your View, define a property that is accessible (public if multi-project, internal if all in one project). Use this property in everything, one example would be a public string ViewTitle { get { return "XYZ"; } }. Then retrieve from the Views the item that has the desired ViewTitle. The Views collection is the collection of views in that region, so you can use dynamic in .NET 4.0+ to ignore the type and get the property/function you specify, assuming it is there. Another option is to make your imported ViewModel in the View have a getter rather than just setting the DataContext, then you'd check the property "is" to the ViewModel you're looking for. Removes the string search but exposes the view's datacontext. So probably make an enum like I would do with the region.
I included everything in my View's .cs file so you can see how it works without complicating it or really breaking MVVM.
[ViewSortHint("050")]
[ViewExport(RegionName = RegionNames.WorkspaceTabRegion)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class AView : UserControl
{
public AView()
{
InitializeComponent();
}
[Import]
[SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly", Justification = "MEF requires property; never retrieved")]
PrintingViewModel ViewModel { set { this.DataContext = value; } }
public string ViewTitle { get { return "AView"; } }
}
Now in the ViewModel at some point:
var viewToRemove = RegionManager.Regions[RegionNames.WorkspaceTabRegion].Views.FirstOrDefault<dynamic>(v => v.ViewTitle == "AView");
RegionManager.Regions[RegionNames.WorkspaceTabRegion].Remove(viewToRemove);
We recently found ourselves with the same problem; thanks #odysseus.section9 for pointing its root in your comment, it really helped.
We considered making all views implement an interface having a Name property but didn't feel quite right. Then we explored #bland solution but felt uncomfortable about using dynamic so we went for a very similar approach using reflection.
Since we are also already using the ViewExportAttribute to export our views and it contains the desired ViewName property, what we do is querying for each view in a region for its attributes, looking for the ViewExportAttribute and checking the value of the ViewName property. Although in our design all views are annotated with it, the query tolerates views that don't - it simply ignores them.
For convenience we created an extension method for IRegion which searches for the views with the desired name within a region. Also, we added two extension methods to IRegionManager for two common scenarios in our application: re-using an existing view or navigating and removing all existing views (matching a name) and navigating. I think the latter solves your need just by getting rid of the call to
public static IEnumerable<object> FindViews(this IRegion region, string viewName)
{
return from view in region.Views
from attr in Attribute.GetCustomAttributes(view.GetType())
where attr is ViewExportAttribute && ((ViewExportAttribute)attr).ViewName == viewName
select view;
}
public static void ActivateOrRequestNavigate(this IRegionManager regionManager, string regionName, string viewName, UriQuery navigationParams)
{
IRegion region = regionManager.Regions[regionName];
object view = region.FindViews(viewName).FirstOrDefault();
if (view != null)
region.Activate(view);
else
regionManager.RequestNavigate(regionName,
new System.Uri(navigationParams != null ? viewName + navigationParams.ToString() : viewName, UriKind.Relative));
}
public static void RemoveAndRequestNavigate(this IRegionManager regionManager, string regionName, string viewName, UriQuery navigationParams)
{
IRegion region = regionManager.Regions[regionName];
foreach (object view in region.FindViews(viewName))
region.Remove(view);
regionManager.RequestNavigate(regionName,
new System.Uri(navigationParams != null ? viewName + navigationParams.ToString() : viewName, UriKind.Relative));
}

How does a View know what ViewModel to use in WPF?

Can someone explain how the View and ViewModel are connected? I can't find anywhere the xaml or the xaml.cs for the View that references the ViewModel, nor anything in the ViewModel.cs file that references the View, yet they are somehow connected, and binding members from the ViewModel to the View work.
Also, in the constructor of each, there is only the InitializeComponent for the View and a basic constructor for the ViewModel (no declaration/definition of the View).
Thanks!
There are various options here.
Something has to set the View's DataContext to be an instance of the ViewModel. There are lots of options here:
This can be done directly in xaml (the View just instances the ViewModel directly).
This can be done in the View's constructor (this.DataContext = new MyViewModel();)
This can be handled via a DataTemplate
A "coordinating" class can wire these together (ie: a separate "presenter" class can construct both and set the DataContext appropriately)
The most common are to either have the View define the VM in the xaml (View-first), or to have everything based from a ViewModel-centric point of view, and have WPF automatically create the View based on the bound VM (ViewModel-first).
The former approach is what's used by a lot of toolkits, such as MVVM Light. The latter approach is what I used in my MVVM blog series, and used by some other toolkits.
A "clean" way for connecting the views to the view-models would be...
When you create the views, for each view, set its DataSource to its view-model:
E.g.
public class App
{
private void OnAppStart()
{
var model = new MainModel();
var vm = new MainVM();
var view = new MainWindow();
vm.Model = model;
view.DataSource = vm;
view.Show();
}
}
When the model you are viewing changes, update the VM:
public class MainVM
{
private void OnSelectedModelItemChanged()
{
this.SelectedItem = new ItemVM();
this.SelectedItem.Model = this.SelectedModelItem;
}
}
And use data templates to make view select the correct sub views for each VM.
The view contains an object of the view model class in the xaml.
The InitializeComponent function creates all the controls on the page, sets styles, etc.
As others have already shown, there are multiple options. Of course, whenever you hear of multiple options you have to wonder what are the advantages and disadvantages of each. Well, it just so turns out that all of them have major disadvantages except one.
The following approach involves no external libraries, no additional housekeeping classes and interfaces, almost no magic, and is very flexible because you can have viewmodels that contain other viewmodels, and you get to instantiate each one of them, so you can pass constructor parameters to them.
For the viewmodel of the main window:
using Wpf = System.Windows;
public partial class TestApp : Wpf.Application
{
protected override void OnStartup( Wpf.StartupEventArgs e )
{
base.OnStartup( e );
MainWindow = new MainView();
MainWindow.DataContext = new MainViewModel( e.Args );
MainWindow.Show();
}
}
For all other viewmodels:
This is in MainViewModel.cs:
using Collections = System.Collections.Generic;
public class MainViewModel
{
public SomeViewModel SomeViewModel { get; }
public OtherViewModel OtherViewModel { get; }
public Collections.IReadOnlyList<string> Arguments { get; }
public MainViewModel( Collections.IReadOnlyList<string> arguments )
{
Arguments = arguments;
SomeViewModel = new SomeViewModel( this );
OtherViewModel = new OtherViewModel( this );
}
}
This in MainView.xaml:
[...]
xmlns:local="clr-namespace:the-namespace-of-my-wpf-stuff"
[...]
<local:SomeView DataContext="{Binding SomeViewModel}" />
<local:OtherView DataContext="{Binding OtherViewModel}" />
[...]
As you can see, a viewmodel can simply be a member (child) of another viewmodel; in this case SomeViewModel and OtherViewModel are children of MainViewModel. Then, in the XAML file of MainView, you can just instantiate each of the child views and specify their DataContext by Binding to the corresponding child viewmodels.

Create instance from attribute

I'm using MVVM viewmodel-first pattern in a WPF application and I have some definitions like these:
public interface IMyView { }
[Export(typeof(IMyView))]
public class MyView : UserControl, IMyView { }
[ViewTypeAttribute(typeof(IMyView))]
public interface IMyViewModel { }
[Export(typeof(IMyViewModel))]
public class MyViewModel : ViewModelBase, IMyViewModel { }
That the ViewTypeAttribute is a custom attribute to retrive which View should be used as DataTemplate for the given ViewModel. Really here I have a Type! But I don't know how to create an instance from that Type via MEF? can anybody help me please?
use:
var container = new CompositionContainer(/* your container .ctor here */);
var type = typeof (IYourType); // read the type from attribute
var export = container.GetExports(type, null, null).FirstOrDefault();
var obj = export.Value as YourCostingHere;
why you dont use the built in wpf stuff to get the right view for your viewmodel? if you simply create a datatemmplate for your viewmodel and set the right view/usercontrol. then all its done.
<DataTemplate DataType={vm:IMyViewModel}>
<local:MyIViewUserControl />
</DataTemplate>
any where in your app where you bind your viewmodel to a contentcontrol its rendered as the MyIViewUserControl.
EDIT: maybe i'm wrong, but i thought you use your ViewTypeAttribute (MEF Export Attribute) to get the linkin between view and viewmodel. And then you wanna create a datatemplate with this information?
so why not export the DataTemplate directly and add it to the app.resources?

Prism MVVM - Add child View to parent View without using Regions and Injection, just XAML

I'm fairly new to the Silverlight and the MVVM / Prism pattern so this may be a stupid question.
I have a View which has custom controls within it. These custom controls are actually Views too and have ViewModels to drive them.
Currently to add these 'child' Views to the View I'm using (see Fig.1) and then in the ViewModel I have an Initialise() method which resolves the child View and injects it (see Fig.2).
Fig.1
<UserControl
x:Class="ProjectA.Module.Screens.Views.PlatformScreenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Regions="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation"
>
<Grid x:Name="LayoutRoot">
<ContentControl
Regions:RegionManager.RegionName="FeaturesSelectionControl"
/>
</Grid>
Fig.2
public void Initialise()
{
IRegion featuresRegion = _regionManager.Regions["FeaturesSelectionControl"];
featuresRegion.Add(_container.Resolve<IFeatureSelectionControlViewModel>().View);
}
My question is do I have to do this for every control I want to add? I understand why it works this way but it seems like quite a bit of code and also I need to keep track of all the region names and ensure I don't have any clashes etc. Is there a simpler way of doing this without regions and just in XAML?
I've seen a snippet of XAML on StackOverflow here but not sure how it works and if it's what I want -
<ContentControl Content="{Binding SmartFormViewModel}"/>
Any help is much appreciated.
James
Edit after clarification:
It appears you don't want to use RegionManager at all, which is fine. What I would suggest then is this:
Create an interface for your Modules to use to register view creation methods:
public interface IViewRegistry
{
void RegisterMainView(Func<object> ViewCreationDelegate);
}
Your modules will use this like this:
public MyModule : IModule
{
IViewRegistry _registry;
public MyModule(IViewRegistry registry)
{
_registry = registry;
}
public void Initialize()
{
_registry.RegisterMainView(() =>
{
var vm = new MyViewModel();
var view = new MyView();
var view.DataContext = vm;
return view;
});
}
}
Then in your shell, you first implement the view registry (this is a simplification... you'd probably want something more robust)
public ViewRegistry : IViewRegistry
{
public static List<Func<object>> ViewFactories
{
get { return _viewFactories; }
}
static List<Func<object>> _viewFactories = new List<Func<object>>();
public void RegisterMainView(Func<object> viewFactory)
{
_viewFactories.Add(viewFactory);
}
}
And lastly, here's how your shell would show that stuff. Here's its ViewModel first:
public ShellViewModel : ViewModel
{
public ObservableCollection<object> MainViews
{
...
}
public ShellViewModel()
{
MainViews = new ObservableCollection<object>(ViewRegistry.Select(factory => factory()));
}
}
And here's your View (look ma, no RegionManager!):
<UserControl ...>
<Grid>
<ItemsControl ItemsSource="{Binding MainViews}" />
</Grid>
</UserControl>
The region manager sort of attempts to give you everything I've written here, plus a lot of extensibility points, but if you don't like RegionManager or you find it doesn't fit your needs for some reason, this is how you would do this in Silverlight without it.
Further Edits:
After some more commentary from the OP, I think I understand that the OP just wants to show a view within another view without having to use RegionManager. It appears the OP is using RegionManager to show every view on the screen, which is probably overkill.
The scenario I was given included an Address View and associated ViewModel being used from a different parent control. This is what I do (whether right or wrong):
<UserControl x:Class="Views.MyParentView" ...>
<StackPanel>
<TextBlock>Blah blah blah some other stuff... blah blah</TextBlock>
<myViews:AddressView DataContext="{Binding AddressVM}" />
</StackPanel>
</UserControl>
And here's the parent view's viewModel:
public ParentViewModel : ViewModel
{
public AddressViewModel AddressVM
{
...
}
public ParentViewModel()
{
AddressVM = new AddressViewModel();
}
}
That's it. This prevents you from having to work too hard to show these views.
RegionManager is really appropriate for decoupling the parent container view from the subview, but if both of these live in the same place (same module / assembly) there is no reason to use RegionManager to show these views.

Categories