How to show UserControl dynamically loaded from outer assembly in MVVM fashion - c#

I'm developing a WPF MVVM application.
MainWindow VM loads a target outer assembly containing a UserControl and its ViewModel.
I want to show this UserControl in my MainWindow View.
I think I should use DataTemplates, but I can't understand how to make them work with dynamically loaded Types. I've no code to show because I've no idea on how to proceed, any suggestion is appreciated.
EDIT: here below the code used to load UC and VM from assembly
Assembly assembly = Assembly.LoadFile(testProgramPath);
var publicTypes = assembly.GetTypes().Where(t => t.IsPublic).ToArray();
TestProgramUserControl = publicTypes.Single(t => t.BaseType.FullName == "System.Windows.Controls.UserControl");
TestProgramUserControlViewModel = publicTypes.Single(t => t.GetCustomAttribute<TestProgramUserControlViewModelAttribute>() != null);
I can't make any assumption about UC or its VM, I want to display it in my MainWindow whatever it contains or does. It will be then its duty to communicate via proper messaging with suitable recipients.

My suggestions are a bit long for a comment.
Since you have "no idea" how to go about this here are some suggestions to point you in the right direction.
Managed Extensibility Framework is designed for dynamic discovery for extending applications in the way you describe. They made it for you.
https://learn.microsoft.com/en-us/dotnet/framework/mef/
As well as putting your view and viewmodel in this assembly, I recommend also putting a datatemplating resourcedictionary in there. You can use this to associate the view type with viewmodel type.
You can use mef or just a naming convention to define what this resource dictionary is.
To make a resource dictionary discoverable by mef you need to add a code behind class for it. You can then apply the correct attribute to that eg:
[Export(typeof(ResourceDictionary))]
public partial class ExternalDataTemplateResourceDictionary : ResourceDictionary
{
public ExternalDataTemplateResourceDictionary ()
{
InitializeComponent();
}
}
To connect that class up to your resourcedictionary you use a similar mechanism to that you have probably seen in windows or usercontrols. You use x:Class in it's opening tag:
<ResourceDictionary
....
x:Class="YourProject.ExternalDataTemplateResourceDictionary "
When you discover a dll, you load it's contents and merge it's templating resourcedictionary.
The parent view then doesn't need to explicitly know that your specialFoo usercontrol is associated with a superFooViewModel in this dll it's loaded. That merged datatemplate does that using the "standard" viewmodel first datatype association.

Thanks to advices here, and on SO WPF chat, I solved my problem as follows.
I added a constraint: my outer assembly can contain only one UserControl, and this user control must define a DataTemplate as a resource with a fixed name.
My main VM gets from outer UC the only resource with the aforementioned fixed name.
My main View uses this DataTemplate as ContentTemplate for a ContentPresenter.
Some simplified code for Outer User Control:
<UserControl xmlns:local="clr-namespace:MyNamespace">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="FixedKeyTemplate"
DataType="{x:Type local:MyOuterViewModel}">
<StackPanel>
...
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
</UserControl>
Main View Model:
Assembly assembly = Assembly.LoadFile(testProgramPath);
var publicTypes = assembly.GetTypes().Where(t => t.IsPublic).ToArray();
Type userControlType = publicTypes.Single(t => t.BaseType.FullName == "System.Windows.Controls.UserControl");
UserControl userControlView = Activator.CreateInstance(userControlType) as UserControl;
DataTemplate userControlDataTemplate = userControlView.Resources["TestProgramGUIDataTemplate"] as DataTemplate;
Type userControlViewModelType = publicTypes.Single(t => t.GetCustomAttribute<UserControlViewModelCustomAttribute>() != null);
object userControlViewModel = Activator.CreateInstance(userControlViewModelType);
Main View:
<ContentPresenter Content="{Binding UserControlViewModel}"
ContentTemplate="{Binding Path=DataContext.UserControlTemplate,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"/>
#Andy suggestion is more "professional", but as far as I'm in control of the complete application, and I'm also the only user, I think I can be satisfied by this simpler solution.

Related

Custom XAML Binding SOURCE Class

I am trying to find a better way to declare the ViewModel a UWP XAML Page uses.
At this moment,
I declare a ViewModel class ViewModelClass that contains my data properties.
Then I declare an instance of that class as StaticResource of the Page. I like calling those VIEW for consistency across all my Page designs.
Finally, I declare the Page's DataContext as a Binding to the StaticResource VIEW.
This yields a page that understands what data structure is in use and allows AutoComplete when working Bindings. Nice, though lots of lines of same-old-same-old code.
Only, it is not really suitable to ViewModels as the declared resource is a static resource. It is instantiated when the page is instantiated. Most pages will receive a ViewModel parameter upon NavigatedTo, which cannot be used to replace the static resource, because it is, well. static.
So I end up changing the Page's DataContext upon navigation from the initial reference to VIEW to the ViewModel instance I actually want to use.
Big caveat is to declare the back-reference to the page's DataContext when deep in the bowels of a Master-Detail situation is rather horrible. Imagine a collection whose display is in part depending on a Master's property.
How do you tie back robustly to the DataContext of the page from anywhere inside the page?
I have tried giving the page a Name (PAGE for simplicity) and then using ElementName=PAGE, Path=DataContext.someProperty. Ugly, plus you lose all information of the class represented by DataContext.
Another approach is to create a Wrapper around the actual ViewModel called StaticViewModel that has only one property: public ViewModel viewModel. Now I can declare the wrapper as a StaticResource, and tell the page's top-level FrameworkElement to use VIEW.viewModel as its DataContext. Works, and reliably, but sooooo ackward and cumbersome.
I would LOVE to implement a SOURCE class for bindings called PageDataContext that would do nothing else but to loop into the page and get the DataContext from there.
Imagine: {Binding someProperty, Source={PageDataContext}
How would I go about declaring said Source class for a UWP app???
I would LOVE to implement a SOURCE class for bindings called PageDataContext that would do nothing else but to loop into the page and get the DataContext from there. Imagine: {Binding someProperty, Source={PageDataContext}
For your requriment, you could implement your viewmodel in the page Resources and give it x:key. When you bind the property of viewmodel you could access this viewmodel with x:key Source={StaticResource ViewModel} for more please refer the following code.
ViewModel
public class ViewModel
{
public ViewModel()
{
Visibility = false;
}
public bool Visibility { get; set; }
}
Xaml
<Page.Resources>
<local:ViewModel x:Key="ViewModel" />
</Page.Resources>
<TextBlock
Width="100"
Height="44"
Text="{x:Bind Name}"
Visibility="{Binding Visibility, Source={StaticResource ViewModel}}" />
Well, no dice so far.
Working with all those options I found that the best one is a Wrapper class that can be assigned to a static resource which in turn is the basis for the page's DataContext. The wrapped instance of the actual ViewModel is then assigned during OnNavigatedTo() and used as the DataContext of the basic FrameworkElement of the page.
Brr, so much verbose code.
STILL WONDERING how to implement a different version of a source directive. Is there no way to declare one's own Source for a Data Binding? Is this stuff really hard-coded into the framework???

Approach to using resources in a user control without relying on App.xaml

I converted an application to a DLL library where my main window became a user control. I was using the method of creating a System.Windows.Application object manually to store my resources but I want move away from that and have my user control be self sufficient, so I can simply do something like:
CustomUserControl control = new CustomUserControl ( object_to_pass);
It will then take care of everything else internally. The basic layout of the control is a frame that hosts multiple pages, like a wizard style app.
I am having two main issues:
Setting up references to the view models
I thought that instead of using System.Windows.Application.FindResource which I extensively used, I will use a similar function on the user control class and pass a reference to my user control around via a singleton.
To do this I use mvvm-light's SimpleIoc container in a class called 'ViewModelLocator' to keep track of all the view models. Problem is, this was a resource in App.xaml loaded from the datacontext binding of the user control.
This cannot be done anymore as the user control itself has to instantiate the resources containing it further along in its own xaml:
<UserControl x:Class="WUP.Views.WarmUpPluginUserControl"
mc:Ignorable="d"
...
...
<!--This will not work-->
DataContext="{Binding Source={StaticResource Locator}, Path=MainWindowLogic}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Path/To/ViewModelLocator/Resource.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
Now I have to instantiate the ViewModel locator in the user control code behind and set it up as a resource with Resources.Add. This forces me to switch to dynamic resource for all references to the ViewModel locator from all other views (Pages). Not only does this cause issues described further on, its ugly as I can no longer access member function with the path like I used to:
DataContext="{Binding Source={StaticResource Locator}, Path=MainWindowLogic}">
Referencing resources from xaml, Dynamic vs Static
The resources I use are brushes, colours, templates and converters, each in their own resource dictionary, and I add them in the right order to avoid dependency issues.
The method in the first part works ok for accessing resources from the ViewModel via the reference to the user control in the singleton. The problem now is how to have the resources loaded in each view of the app. I tried the brute force method of sticking them all in Page.Resources or UseControl.Resources but that gave me resource not found errors in some pages despite them existing there. I am looking into why this happens but I am not sure
I then tried Dr.WPF's method of creating a singleton class that you can use to create a single instance of resources and expose them as a dependent property. This forces me to use dynamic resources again for all my views.
This is fine for all my resources except the converters, and I get errors for all converters originally referenced in this way:
Visibility="{Binding Functions.DictatesActions, Converter={StaticResource BooleanToVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}"
So I don't know how to deal with this is the dynamic scenario.
I am seriously thinking to abandon this approach and just use System.Windows.Application to store all my resources, despite it potentially causing issues with other user controls in the hosting application (winforms). Please let me know if there is a better way!
I finally managed to fix my issues:
Setting up references to the view models
Here I just had to do it all from the code behind. As I mentioned I used a ViewModelLocator to keep track of all my VMs, so I set up the references as resources in the actual user control constructor:
Resources["Start"] = view_model_locator.Start;
Resources["SelectUnit"] = view_model_locator.SelectUnit;
Resources["HardwareChecks"] = view_model_locator.HardwareChecks;
Resources["ConfigurationChecks"] = view_model_locator.ConfigurationChecks;
...
I then included the reference to the user control in the ViewModel locator as static property:
ViewModelLocator.WarmUpPluginUserControl = this;
Then I could access it from the other views in their code behind like this:
DataContext = ViewModelLocator.WarmUpPluginUserControl.FindResource("Start");
I could also use it in the VMs in the same way that I used the Application.Current.FindResource(). It's not the most elegant solution, but it worked
Referencing resources from xaml, Dynamic vs Static
Here I stuck with the brute force method of including all the resources at the top of every page:
<WUP:WUPPage.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/WarmUpPlugin;component/Resources/Colors.xaml"/>
<ResourceDictionary Source="pack://application:,,,/WarmUpPlugin;component/Resources/Styles.xaml"/>
...
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
This did cause issues for me in the beginning where I had resource not found errors, but this was due to some of my dictionaries having dependencies other ones so I had to add them in to the relevant dictionaries via MergedDictionaries.
I did not notice this dependency issue when the application was standalone as all the resources required were already loaded in the application scope.
I intend to replace this with the Dr WPF method, but I would still have to change all my XAML references to dynamic from static then deal with the converters not being able be accessed via dynamic resource references.

WPF: Cannot get View to change dynamically using DataTemplate

I have a problem designing my WPF application. I cannot get the Views to change dynamically. The code to call another View is contained in the Views themselves. (I am trying to implement the MVVM pattern. I do not want any code behind in the View xaml files other than assigning the DataContext. An exception is made in the xaml file of the MainWindow).
Basically, I have a Window that contains a UserControl. The UserControl is my View and it is connected to another class serving as ViewModel through Datacontext.
What I want to do is to dynamically change this View/ViewModel pairs contained in the Window.
My idea was define a static property in the ViewModel of the MainWindow and store the ViewModel of the current View in it. Then I planned to use DataTemplates to automatically load a new View whenever a new ViewModel is stored in the static property.
I decided to use a static property because the code to load another ViewModel is contained in the ViewModels itself and I needed a central point with access from everywhere.
So far so good. My initial View loads and displays correctly.
However, pressing a button in that View to load the next View fails although the new ViewModel is correctly assigned to the static property.
I tried several things.
I defined DataTriggers within the ContentControl to react to changes in the static property. No help.
Implementing INotifyProperty and DependencyProperty failed in the end because of the static nature of the property (or I did something wrong).
I just can’t get it to work.
Do you have any ideas why this would be?
Do you have an idea how I could solve my general problem of dynamically displaying Views without using a static property in my MainWindow. I believe this is causing problems and I have a notion that I am not using the most elegant method. (I do want to maintain the concept of each View holding the code to load any other View)
This is a code fragment from the MainWindow:
<UserControl>
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:StartViewModel}">
<v:StartView></v:StartView>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:OverviewViewModel}">
<v:OverviewView></v:OverviewView>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl Content="{Binding ActiveViewModel}">/ContentControl>
</Grid>
</UserControl>
Code behind:
DataContext = new MainViewModel();
MainViewModel contains the definition for the property ActiveViewModel. The constructor for the class is static. All ViewModels inherit from BaseViewModel class:
private static BaseViewModel activeViewModel;
static public BaseViewModel ActiveViewModel
{
get { return activeViewModel; }
set { activeViewModel = value; }
}
Thanks a lot for your help.
Bye,
Eskender

ViewModel instatiated twice with MEF

I'm trying to create a simple modular MVVM application with MEF. I have a ViewModel class and a UserControl as the View. I connect the two through a DataTemplate, like this:
<DataTemplate DataType="{x:Type local:MyViewModel}">
<local:MyView />
</DataTemplate>
In the View, I define the ViewModel as a StaticResource, to make binding simple:
<UserControl.Resources>
<local:MyViewModel x:Key="ViewModel" />
</UserControl.Resources>
Then I bind like this:
<Grid DataContext="{StaticResource ResourceKey=ViewModel}">
<TextBlock Text="{Binding Text}" />
</Grid>
This all works as intended without MEF. However, as I am aiming for modularity, I use MEF to discover my ViewModel classes. I have an Export attribute on my ViewModel class:
[Export(typeof(MyViewModel))]
public class MyViewModel
{
// ...
}
and I use MEF to dynamically load the ViewModel into my shell in App.xaml.cs:
private void Application_Startup(object sender, StartupEventArgs e)
{
var shell = new MainWindow();
var catalog = new AssemblyCatalog(this.GetType().Assembly);
var container = new CompositionContainer(catalog);
shell.Contents.ViewModel = container.GetExportedValues<MyViewModel>().First();
shell.Show();
}
Now, at this point, MEF creates an instance of my ViewModel when it loads the vm, and my View creates another instance when it declares the vm as a resource. (This is easily checked by setting a breakpoint in the constructor.)
The question is, how should I pass the instance created by MEF to my resource declaration? Can I declare that specific instance as resource?
DropBox link with full code:
https://www.dropbox.com/sh/pbdl029d26sx7gl/AAA6po50dLjbJSoNPBhCyWZ3a?dl=0
The creation of the MyViewModel is purely based on the sequence of how your program execution but you can set the CreationPolicy to make your instance become a singleton so that both your code and the resources both referring to the same instance.
[Export(typeof(MyViewModel)), PartCreationPolicy(CreationPolicy.Shared)]
Side note: To use MEF, Microsoft hide the implementation of CompositionInitializer and CompositionHost from .Net Framework for some reason. Try to google up and import the 2 classes from Microsoft instead of using the CompositionContainer directly. You'll have better experience in using MEF with those.
Okay, so, what I had to do was this.
I had two instances because once MEF instantiated the ViewModels when it imported them, and then another time WPF created them, when it created the ViewModel resource. I figured that the solution would be not directly creating the resource, but had no idea how I could manage to do that. Then along came Resource Injection and then DataContextSpy, from this question here:
https://stackoverflow.com/a/5402653/5219911
And here's the direct link to the topic:
http://www.codeproject.com/Articles/27432/Artificial-Inheritance-Contexts-in-WPF
I am now using a resource that is a DataContextSpy, through which I can reach out to the ViewModel instance that is used when creating the DataTemplate.
In my View's resources, I define
and then I just set the root element's DataContext to this resource:
DataContext="{Binding Source={StaticResource ResourceKey=ViewModel},
Path=DataContext}"
Now, unfortunately, I don't get Intellisense support with this alone, as DataContextSpy is sort of a proxy to the real DataContext, so I do have to manually set up the design time DataContext type by using:
d:DataContext="{d:DesignInstance Type=viewModel:MyViewModel}"

Design pattern in WPF

I am making my first WPF application, so this question may seem rather odd. I have been reading about MVVM and so far it has made sense to me. What I don't understand, though, is separating all the XAML.
What I mean is this: I assume you don't place everything in the MainWindow.xaml and just collapse controls based upon what is going to be used. I would think you would want a container that would contain xaml of other files. Is this correct?
How do you go about separating the XAML out so that it isn't just a mash of everything in one file?
How do you go about separating the XAML out so that it isn't just a mash of everything in one file?
There are many ways, including creating a seperate UserControl, CustomControl, Page or Window
For example, if you wanted to pull some XAML out of your MainWindow.xaml, you could create a UserControl (right-click project, Add, New Item..., User Control (WPF)) called MyUserControl.xaml like this:
<UserControl x:Class="WpfApplication1.MyUserControl"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Grid>
<TextBlock>This is from a different XAML file.</TextBlock>
</Grid>
</UserControl>
and then use this control in your MainWindow.xaml like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myControls="clr-namespace:WpfApplication1">
<Grid>
<myControls:MyUserControl/>
</Grid>
</Window>
Note that you need to add a reference to the namespace of your UserControl
xmlns:myControls="clr-namespace:WpfApplication1"
I agree with Kevin's answer about UserControl and Window, so I'll just address the follow up question:
So I should switch between various controls that are collapsed/visible in the content of my MainWindow when the user is interacting with my application?
That is a valid option. It might get messy if you are working with a large application.
The other options that I've used are
switching Views in the code behind; i.e. on the click events you can add and remove elements from the page as you would have done in WinForms. This is not pure MVVM and some people will jump down your throat, but I believe MVVM is a tool, not a religion.
provide Views as properties in your ViewModel, and bind to them from your parent View. I don't know if this is pure MVVM, it's nice when you need to dynamically create Views depending on complex conditions but it can get complicated
use DataTemplates, which are essentially rules to determine the View to use based on the type of data that is provided. So if the data is an Address (or AddressViewModel), use the AddressView. If the data is a CustomerViewModel, use the CustomerView. And so on.
DataTemplates are the preferred pattern in my opinion - clean, easy to maintain, and a nice standard. It's trivial to go to the DataTemplate to see how the binding works, whereas the other two options I've given may lead to spaghetti code in the wrong hands.
MSDN has a nice page on DataTemplates in WPF.
When using Caliburn framework you could compose your application using smaller Views and ViewModels and have a shell which binds all those smaller views together. The shell would display one or many views at the same time, depending on how you want your application to behave. The good thing about this - unlike the pattern mentioned above where you hardcode the name of the View/UserControl in other places - is that you just create a ContentControl and bind it to the correct ViewModel property and Caliburn will find the correct View for you by convention.
Let's say you have a ShellViewModel and a ShellView which is just an empty window, and another View/ViewModel where you want to display in your shell at one point. You no longer need to instantiate your views anywhere and just work your way using POCO ViewModels objects:
public class ShellViewModel : Screen
{
public ChildViewModel Child { get; set; }
public void SomeAction()
{
Child = new ChildViewModel(); //Or inject using factory, IoC, etc.
}
}
public class ShellView : Window
{
}
<Window x:Class="WpfApplication1.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<Grid>
<ContentControl cal:View.Model="{Binding Child}" />
</Grid>
</Window>

Categories

Resources