Create instance from attribute - c#

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?

Related

Prism: View First with multiple ViewModels

I'm using Prism with Unity IOC-Container in a WPF-Project. For all my other Views I'm using only one ViewModel per View. Because this View should be a Mask for both Input and Output of Data, I'd like to use two ViewModels.
For the current navigation to the View i use this Code:
_regionManager.RequestNavigate(RegionNames.ContentRegionName, typeof(Events).ToString());`
The Code Behind of my View:
public partial class Events : UserControl
{
public Events(EventsViewModel viewModel)
{
InitializeComponent();
}
}
One of the ViewModels:
public class EventsViewModel : BindableBase
{
public EventsViewModel()
{
// Some Code
}
// Some other Code
}
I heard about ViewModel Discovery, where you give the Constructor of the View an Interface instead of an actual ViewModel. But i could only find exacly this much information.
// Example of such a Method
public Events(IViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel
}
public Interface IViewModel
{
}
My question is now: How do I navigate to the View and tell it wich ViewModel it should get as DataContext? I'm relatively new to programming and the MVVM-Pattern and english is not my native language so maybe I missed some Information. I would be glad if someone had an answer for this. Thanks in advance.
Edit: Workaround
I came up with a workaround wich works for me. I used the method SetDefaultViewTypeToViewModelTypeResolver() from the ViewModelLocationProvider and customized it.
// Bootstrapper.cs
protected override void InitializeShell()
{
var window = (MainWindow)this.Shell;
Application.Current.MainWindow = window;
// Calling the method
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(ResolveViewModel);
var regionManager = Container.Resolve<IRegionManager>();
window._regionManager = regionManager;
globalRegionManager = regionManager;
regionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(StartScreen));
regionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(Stock));
window.Show();
}
// Property for handing over the desired ViewModel
public static Type DynamicViewModel { private get; set; }
private Type ResolveViewModel (Type viewType)
{
string _viewModel = null;
var name = viewType.FullName.Replace(".Views.", ".ViewModels.");
if (DynamicViewModel != null)
_viewModel = DynamicViewModel.ToString();
else
_viewModel = $"{name}ViewModel";
var fullName = IntrospectionExtensions.GetTypeInfo(viewType).Assembly.FullName;
var typeString = string.Format(CultureInfo.InvariantCulture, $"{_viewModel}, {fullName}");
DynamicViewModel = null;
return Type.GetType(typeString);
}
Then when I want to navigate, I hand over the ViewModel beforehand.
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
Bootstrapper.DynamicViewModel = typeof(EventsViewModel);
_regionManager.RequestNavigate(RegionNames.ContentRegionName, typeof(Events).ToString());
}
A little tricky but it seems to work without any Exceptions.
If there is a cleaner way I'm alway happy to here it. :)
Here are some techniques for getting the view model for a view (view first).
View discovery in views code behind constructor
public EventsView(EventsViewModel view_model)
{
InitializeComponent();
DataContext = view_model;
}
Explicitly newing up the view model in code behind constructor
public EventsView()
{
InitializeComponent();
DataContext = new EventsViewModel();
}
View model locator in the XAML for the view
<UserControl x:Class="EventsModule.Views.EventsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<TextBlock Text="{Binding EventName}"></TextBlock>
</Grid>
</UserControl>
As long as you register your view with the region manager using any one of the techniques below, any one of the above will work. Then navigate like you are doing in your question.
RegionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(EventsView));
UnityContainer.RegisterType(typeof(object), typeof(EventsView), typeof(EventsView).FullName);
UnityContainer.RegisterTypeForNavigation<EventsView>(typeof(EventsView).FullName);
The first will activate the view in the region and is usually seen in the module Initialize method. Those last 2 are for registering a view for later navigation. The last one requires the Prism.Unity namespace.
I don't believe that a view can have more the one view model since the view object only has one DataContext property on it. You may have to extend one view model to include everything you need. Someone may come along and prove me wrong on this. I have seen where a view model is shared with more than one view, but never a view having more than one view model.

Correct way of getting unique ViewModels from MVVM Light ViewModelLocator

In my UWP app im using MVVM Light and its ViewModelLocator to get ViewModels.
My ViewModelLocator looks like this, im passing guid to GetInstance to get unique VM.
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MyViewModel>();
}
public MyViewModel MyVM => ServiceLocator.Current.GetInstance<MyViewModel>(Guid.NewGuid().ToString());
}
I have usercontrol which needs to have unique VM, as I can have multiple instances of this user control in my app. Here is how im getting the ViewModel:
<UserControl x:Class="My.App.Controls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding Path=MyVM, Source={StaticResource ViewModelLocator}}">
...
</UserControl>
Is this the correct way of getting unique VM's? Or are the VM's still cached, not disposed, even when my user control is not used any more?
UPDATE
So it seems that my code works ok, I get unique MyVM instance every time.
Now the question is, what is the correct way to unregistered/dispose view model.
I can do it with SimpleIoc.Default.Unregister(guid) but with my current implementation it is not very straight forward to deliver Guid (which was used in creating VM) to my user control, so I can unregister the VM.
Overall if I just create my VM runtime im ViewModelLocator with out SimpleIoc, is there any other drawbacks than losing dependency injection?
public class ViewModelLocator
{
public MyViewModel MyVM => new MyViewModel();
}
I assume your UserControl must be used by some View (window).
The ViewModel controlling that View could spawn the required ViewModels for the UserControl. You could bind the UserControl to this 'SubViewModel' property and also dispose of them however you then wished.
You can add an additional property to your view model and call a custom removal method in the view disposing your view model.
The modified getter for the view model looks like this:
public MyViewModel MyVM
{
get
{
String id = Guid.NewGuid().ToString();
var instance = SimpleIoc.Default.GetInstance<MyViewModel>(id);
instance.ID = id;
return instance;
}
}
The method for disposing the view model locator looks like this:
public static void UnregisterMyVM(String id)
{
SimpleIoc.Default.Unregister<MyViewModel>(id);
}
In your view you got to listen for closing events and call the unregistration method there:
public MyView()
{
InitializeComponent();
this.Closed += ((sender, arguments) =>
{
var viewModel = ((MyViewModel)this.DataContext);
viewModel.Dispose();
ViewModelLocator.UnregisterSourceCodeViewer(viewModel.ID);
this.DataContext = null;
});
}

Prism PopupWindowAction with viewmodel created using dependency injection

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>

Pass a parameter to a ViewModel and show its data

I have created an application which uses WPF and MVVM following this article from CodeProject.
I have a view, TVSeriesView, which has a TVSeriesViewModel. These two are connected using a DataTemplate which is done following the article.
<DataTemplate DataType="{x:Type Implementation:TVSeriesViewModel}">
<TVSeriesLibrary:TVSeriesView />
</DataTemplate>
The idea is to pass my model, the TVSeries, to this ViewModel as I have a property named TVSeries in the ViewModel. When this property is set, I will populate other properties such as Title, Cover and so on. These properties are meant to be binded to controls in the view.
public class TVSeriesViewModel : ViewModelBase, ITVSeriesViewModel
{
private TVSeries _tvSeries;
private string _title;
private ImageSource _cover;
public TVSeries TVSeries
{
get
{
return this._tvSeries;
}
set
{
this._tvSeries = value;
}
}
public string Title
{
get
{
return this._title;
}
set
{
this._title = value;
OnPropertyChanged("Title");
}
}
public ImageSource Cover
{
get
{
return this._cover;
}
set
{
this._cover = value;
OnPropertyChanged("Cover");
}
}
}
First and foremost, does this sound like the right way to do it?
Next, does anyone know how to pass a parameter (a TVSeries object) to the ViewModel when the TVSeriesView is shown?
And lastly, does anyone know how I can directly access resources in the view? For example if I don't want to use data binding but instead want to set the image directly like this:
myImage.ImageSource = myImageSource
The View and ViewModel together are one of the possible representations of the Model.
You can pass a repository handle which would be eventually responsible for data access or
Concrete/abstract object of Model through Dependency Injection via Constructor or
Dependency Injection, via property/method or
In more crude way you can write a DB access code in your VM (obviously it's not suggested.)
I would prefer as the order given here. Your code is doing the third option.

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.

Categories