How to re-wire View data context to XAML from C#? - c#

I've set my View's data context to it's associated ViewModel in the View's code behind. But after reading a question on the MVVM pattern, it's suggested to move this glue code to the View's XAML mark up.
Googling has shown me examples of setting the context below in XAML, by setting a namespace to the VM and setting data context.
Although in my case, the MainViewModel takes a parameter of a CustomerRepository instance, which I'm not sure how to set up in XAML, base on the previous example.
Does anyone know how to move the data context to the View's XAML?
This is how I've currently set the View's code behind in C#:
public partial class MainView : Window
{
private MainViewModel ViewModel { get; set; }
public MainView()
{
InitializeComponent();
ViewModel = new MainViewModel(CustomerRepository.Instance);
this.DataContext = ViewModel;
}
}

You can instantiate your view model in your xaml like this:
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
</Window>
You explained that your view model constructor has a parameter for your repository. Do you need this constructor for unit testing purposes?
Usually you can just new up your repository instance in a parameterless constructor of the view model.
public class MainViewModel : ObservableObject, INotifyPropertyChanged
{
private static CustomerRepository _customerRepository;
// existing constructor
public MainViewModel(CustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
// new parameterless constructor
public MainViewModel(CustomerRepository customerRepository)
{
if (DesignerProperties.GetIsInDesignMode(this))
{
_customerRepository = new CustomerRepository();
}
}
}
Check if is in design mode
There is one more thing you need to think of when creating a view model in xaml: The view models constructor is called at design time when you open the view.
So you need to wrap any code in the constructor which does not make any sense at design time into a "only when not in design time" condition.
If you do not do this, your view will open with an error in the designer.
This is explained in this response: Check if in design mode

Related

Binding a DataContext in a UserControl that has a default value [duplicate]

I'm want to remove from my view model's creating of view's
I wrote WinodwsService class to creating a new window:
public class WindowService : IWindowService
{
public void ShowWindow(object viewModel)
{
//var win = new DXWindowCloasable(viewModel);
var win = new DXWindow();
win.Content = viewModel;
win.DataContext = viewModel;
win.ShowDialog();
}
}
In view model I call method:
var vm = new PolaPrzewoznikowViewModel(konf);
IWindowService wnf = new WindowService(); // this is only for test
wnf.ShowWindow(vm);
In UserControl I have defined view model type:
<UserControl.DataContext>
<local:PolaPrzewoznikowViewModel />
</UserControl.DataContext>
When I have this, I can drill down (CTRL + B) on commands, and user an code completition when I'm projecting a View - this is very helpful.
But... when I use
win.ShowDialog(); the new view model is created. And displayed view has view model without parameters (default constructor).
How can I use window service and keep defined UserControl.DataContext in view?
instead of initializing DataContext in xaml
<UserControl.DataContext>
<local:PolaPrzewoznikowViewModel />
</UserControl.DataContext>
I suggest to use DesignInstance:
<UserControl d:DataContext="{d:DesignInstance Type=local:PolaPrzewoznikowViewModel,
IsDesignTimeCreatable=True}" ...>
It will give IntelliSense and designer enough information in design-time, but a new instance won't be created in run-time (there will only DataContext from WindowService)
Why are you setting both the content and the datacontext of the window?
Regarding intellisense you should do as ASh suggests, the data context by its nature will be available to all view descendants.
If you don't want to implement the window service yourself you can always use my framework https://github.com/FantasticFiasco/mvvm-dialogs.

Control the view in MVVM

I created a project with the MVVM model, and done so with the view-first approach.
I have a TextBox in my XAML code, along with a Button to pass the data from the TextBox:
<!-- View - XAML code -->
<TextBox
MinWidth="30"
Name="TagId"/>
<Button
Command="{Binding AddTagCommand}"
CommandParameter="{Binding Text, ElementName=TagId}"
Content="Add"/>
When I click the button, I want the TextBox cleared.
According to the Prism manual:
In some cases, the code-behind may contain UI logic code that implements visual behavior that is difficult or inefficient to express in Extensible Application Markup Language (XAML), such as complex animations, or when the code needs to directly manipulate visual elements that are part of the view.
Here's the code behind, and the viewmodel.
//View - code behind
public partial class ApplicationStarterView : UserControl
{
public ApplicationStarterView()
{
}
public ApplicationStarterView(ApplicationStarterViewModel viewModel) : this()
{
DataContext = viewModel;
InitializeComponent();
}
}
//View model
public class ApplicationStarterViewModel : BindableBase
{
private readonly IUnityContainer _container;
public ApplicationStarterViewModel(IUnityContainer container)
{
_container = container;
AddTagCommand = new DelegateCommand<object>(AddTag);
}
public ICommand AddTagCommand { get; private set; }
private void AddTag(object input)
{
//Forward stuff
//Clear TextBox
}
}
Can I in any way squeeze in some code to do a TagId.Clear()?
I'd bind the text to another property on the view model.
That way, you can skip the command parameter and the AddTagCommand can directly read the new Text property, do his adding stuff and then clear it, thus updating TagId.
Completely unrelated piece of advice: it is almost never a good idea to inject the IUnityContainer... if you need to create stuff, use factories.

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;
});
}

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