Prism VM binding with View within Page code behind - c#

Using Xamarin Forms & PCL
I saw a lot of examples and snippets About binding VM with View in the Page.Xaml
using this block
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
And what if I want to bind the view model within the page code behind (Page.cs).

You can get access to ViewMode from code behind is simply by typecasting your binding context
var pageViewModel = (PageViewModel)this.BindingContext;
It works for me.

That case you have to pass the both parameter on class instantiated because you are have required two parameter in constructor.Try the below code.
public Page()
{
InitializeComponent();
this.BindingContext = new PageViewModel(Navigation,PageDialogService);
}

In my case
I removed from page.xaml
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
and inside the code Behind (page.cs)
i added
public Page()
{
InitializeComponent();
this.BindingContext = new pageViewModel(null,null);
}
and it worked for me

You just can new the viewmodel and set it to the BindingContext.
public Page()
{
InitializeComponent();
this.BindingContext = new MyViewModel();
}
==== EDITED ====
If your viewmodel is with parameter that need to dependency inject and you want to resolve it correctly.
App.xaml.cs
protected override void OnInitialized()
{
...
Microsoft.Practices.Unity.UnityContainerExtensions.RegisterType<IMyViewModel, MyViewModel);
...
}
Page.xaml.cs
public Page()
{
InitializeComponent();
var viewModel = Microsoft.Practices.Unity.UnityContainerExtensions.Resolve<IMyViewModel>(((App)Application.Current).Container);
this.BindingContext = viewModel;
}

Related

Using MvvmLight.Messaging.Messenger to instantiate new View + ViewModel (Window)

I have my MainView and an associated MainViewViewModel which are linked by ViewModelLocator.
Within MainViewViewModel there is a command which should trigger a new Window to open which has it's own View and ViewModel (NewView and NewViewViewModel).
In a lot of the examples I've seen it is suggested to use Mvvmlight's Messenger to do something like this:
public class MainViewViewModel
{
private void OpenNewWindow()
{
Messenger.Default.Send(new NotificationMessage("NewView"));
}
}
And then register the NewViewViewModel and handle the message like this:
public class NewViewViewModel
{
public NewViewViewModel()
{
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage obj)
{
if (obj.Notification == "NewView")
{
NewView view = new NewView();
view.Show();
}
}
}
However, this doesn't work because the NewViewViewModel isn't yet instantiated (so isn't registered with Messenger). Additionally, this doesn't fit with MVVM because NewViewViewModel is responsible for creating NewView.
What is the correct way to achieve a simple command which instantiates and opens a new View and ViewModel pair which are linked via ViewModelLocator and setting of DataContext="{Binding NewView, Source={StaticResource Locator}}" in NewView.xml?
Use a window service:
MVVM show new window from VM when seperated projects
You may either inject the view model to with an IWindowService implementation or use a static WindowService class:
public static class WindowService
{
public static void OpenWindow()
{
NewView view = new NewView();
view.Show();
}
}
Dependency injection is obviously preferable for being able to unit test the view model(s) and switch implementations of IWindowService at runtime.

WPF MVVM Dependency Injection Constructors exception

I'm developing my own application to learn something new. It's WPF .net Core 3.1 app using MVVM pattern.
Recently I've decided to include Microsoft DependencyInjection library.
I've removed StartupUri and modifiec app.xaml.cs:
public IServiceProvider ServiceProvider { get; private set; }
public IConfiguration Configuration { get; private set; }
protected override void OnStartup(StartupEventArgs e)
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
InitializeCefSharp();
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build();
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
ServiceProvider = serviceCollection.BuildServiceProvider();
var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
mainWindow.Show();
}
private void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>
(Configuration.GetSection(nameof(AppSettings)));
// Views
services.AddSingleton<MainWindow>();
services.AddSingleton<SettingsView>();
services.AddSingleton<WwwView>();
services.AddSingleton<BuildingView>();
services.AddSingleton<TroopsMovementsView>();
// ViewModels
services.AddSingleton<MainWindowViewModel>();
services.AddSingleton<SettingsViewModel>();
services.AddSingleton<WwwViewModel>();
services.AddSingleton<DomainViewModel>();
services.AddSingleton<WorldViewModel>();
services.AddSingleton<RegisterViewModel>();
}
I'm setting DataContext inside Views' constructors. All views except MainWindow are UserControl type.
private readonly MainWindowViewModel _mainWindowViewModel;
public MainWindow(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
DataContext = _mainWindowViewModel;
InitializeComponent();
}
private readonly SettingsViewModel _settingsViewModel;
public SettingsView(SettingsViewModel settingsViewModel)
{
_settingsViewModel = settingsViewModel;
DataContext = settingsViewModel;
InitializeComponent();
}
All Views are embedded in MainWindow like this:
<dz:DockControl Grid.Row="3" Loaded="FrameworkElement_OnLoaded">
<dz:DockItem x:Name="Settings" TabText="Settings" ShowAction="{dz:ShowAsDockPositionAction DockPosition=RightAutoHide}">
<views:SettingsView/>
</dz:DockItem>
<dz:DockItem x:Name="WWW" TabText="WWW" ShowAction="{dz:ShowAsDockPositionAction DockPosition=Document}" DefaultDockPosition="Document" >
<views:WwwView/>
</dz:DockItem>
</dz:DockControl>
It's visual studio-like docking library.
The problem is that I got exception during startup, that there's no parameterless constructor. But I can not have any parameterless constructors as I need Views to get ViewModels injected. ViewModels to get repositories injected.
When I've created 2nd ocnstructor which is parameterless, there's strange things happening - Views doesn't load inside MainWindow or load, but doesn't use ViewModels.
If I'm setting DataContext in xaml file, Views got parameterless constructors, but then ViewModels must have parameterless constructors...
<UserControl.DataContext>
<vm:SettingsViewModel/>
</UserControl.DataContext>
How to correctly use Dependency injection in my case?
WPF requires objects which are instantiated in XAML to define a parameterless constructor.
There are many ways to accomplish Dependency Injection in WPF using MVVM. When searching the internet the most wide spread solution seems to be the ViewModelLocator, another Service Locator implementation which is considered widely an anti-pattern (like the infamous static Singleton IoC container).
A simple solution is to use composition. You create a main view model which is composed of other view models where each is dedicated to a certain view.
MainViewModel.cs
class MainViewModel
{
public MainViewModel(IFirstControlViewModel firstControlViewModel ,
ISecondControlViewModel secondControlViewModel)
{
this.FirstControlViewModel = firstControlViewModel;
this.SecondControlViewModel = secondControlViewModel;
}
public IFirstControlViewModel FirstControlViewModel { get; }
public ISecondControlViewModel SecondViewModel { get; }
}
FirstViewModel.cs
class FirstViewModel : IFirstViewModel
{
}
SecondViewModel.cs
class SecondViewModel : ISecondViewModel
{
public SecondVieModel(IThirdViewModel thirdViewModel) => this.ThirdViewModel = thirdViewModel;
public IThirdViewModel ThirdViewModel { get; }
}
MainWindow.xaml
<Window>
<StackPanel>
<FirstUserControl DataContext="{Binding FirstViewModel}" />
<SecondUserControl DataCOntext="{Binding SecondViewModel}" />
</StackPanel>
</Window>
SecondUserControlxaml
<UserControl>
<Grid>
<ThirdUserControl DataContext="{Binding ThirdViewModel}" />
</Grid>
</UserControl>
App.xaml.cs
private void Run(StartupEventArgs e)
{
IMainViewModel viewModel = container.GetExportedValue<IMainViewModel>();
var mainWindow = new MainWindow { DataContext = viewModel };
mainWindow.Show();
}
Or use only top-level composition:
MainViewModel.cs
class MainViewModel
{
public MainViewModel(IFirstControlViewModel firstControlViewModel ,
ISecondControlViewModel secondControlViewModel,
IThirdViewModel thirdViewModel)
{
this.FirstControlViewModel = firstControlViewModel;
this.SecondControlViewModel = secondControlViewModel;
this.ThirdViewModel = thirdViewModel;
}
public IFirstControlViewModel FirstControlViewModel { get; }
public ISecondControlViewModel SecondViewModel { get; }
public IThirdViewModel ThirdViewModel { get; }
}
App.xaml.cs
private void Run(StartupEventArgs e)
{
IMainViewModel viewModel = container.GetExportedValue<IMainViewModel>();
// For simplicity, you can add the view model to the globally accessible App.xaml ResourceDictionary
this.Resources.Add("MainViewModel", viewModel);
var mainWindow = new MainWindow { DataContext = viewModel };
mainWindow.Show();
}
SecondUserControlxaml
<UserControl>
<Grid>
<ThirdUserControl DataContext="{Binding Source="{StaticResource MainViewModel}", Path=ThirdViewModel}" />
</Grid>
</UserControl>
Composition is a very simple solution to use Dependency Injection with views. If performance e.g. classes with a big dependency tree is an issue many DI frameworks like MEF support Lazy<T> exports.

Navigate to a Default View when Application Loaded using Prism 7 in WPF

I follows samples code provided in https://github.com/PrismLibrary/Prism-Samples-Wpf/blob/master/17-BasicRegionNavigation
I want to achieve the following result when I run the application (without explicitly clicking Navigate to View A). Does anyone know how to do it?
I have tried adding Navigate("ViewA"); after this line. However, I cannot get the desired outcome. Is it because the module hasn't been initialized?
Thanks.
did you add your module to the modulecatalog using override method ConfigureModuleCatalog? take a look at here
Eventually I solve by adding the following code in MainWindow.xaml.cs
public partial class MainWindow
{
IRegionManager _regionManager;
public MainWindow()
{
InitializeComponent();
_regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
RegionManager.SetRegionManager(ContentRegion, _regionManager);
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_regionManager.RequestNavigate("ContentRegion", "ViewA");
}
}
Get idea from: https://github.com/MahApps/MahApps.Metro/issues/1020#issuecomment-44779574
I'm kinda late to the party here, but I also stumbled over the question of how to navigate to a default view during the applications startup.
I found two ways:
1. App decides the default view
This can be solved in the CreateShell()-override in the App-Class.
This is my CreateShell-Method:
/// <inheritdoc />
protected override Window CreateShell()
{
var window = this.Container.Resolve<MainWindow>();
window.Loaded += (sender, args) =>
{
var manager = this.Container.Resolve<IRegionManager>();
manager.RequestNavigate("ContentRegion", "ViewA");
};
return window;
}
2. ViewModel decides the default view
Add a constructor to MainWindowViewModel that looks like this:
public MainWindowViewModel(IRegionManager regionManager)
{
regionManager.RegisterViewWithRegion("ContentRegion", "ViewA");
}

Calling a method present in Viewmodel from OnNavigate of Page using MVVM

I have to do some operations in OnNavigation of page. But I want to do all those operations in ViewModel.cs. For that I just want to call that method in ViewModel.cs from OnNavigation of Page.
I am not getting how to do that. I am creating Viewmodel.cs object in particular View,
<Page.DataContext>
<obj:ViewModel/>
</Page.DataContext>
How to do this using Events and Delegates? and if there is some other way, then what is it.
In the code behind you can get the reference of the viewmodel:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var vm = this.DataContext as ViewModel;
vm.NameOfYourMethod();
base.OnNavigatedTo(e);
}

Dependency Injection with MVVM and Child Windows

I am using MVVM Light and I'm currently using SimpleIoC that comes with the package. I'm getting a bit stuck with the dependency injection. I have a bunch of services that I want to use in my view models, however most windows are a List-Edit paradigm, i.e. one screen lists all of type Person and then you can Add or Edit a Person via a new screen.
When I was doing all code in the code behind my code for adding and editing a record was as follows:
View
private void btnEdit_Click(object sender, RoutedEventArgs e)
{
_viewModel.Edit();
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
_viewModel.Add();
}
View Model
public void Add()
{
var f = new TypeDetails();
f.Show();
}
public void Edit()
{
if (SelectedItem == null)
return;
var f = new TypeDetails(SelectedItem.Id);
f.Show();
}
The constructor of TypeDetails is as follows:
public TypeDetails(int id = 0)
{
InitializeComponent();
_viewModel = new TypeDetailsViewModel(id);
DataContext = _viewModel;
}
What would the best be to implement this type functionality with MVVM Light? I have been using the ViewModelLocator class for the List screens, however I cannot see a way to do this using the SimpleIoC. My way round so far has been to keep the constructor the same, which works fine until I need to inject dependencies into the TypeDetailsViewModel such as a service. With a service the constructor of TypeDetailsViewModel would be:
public TypeDetailsViewModel(ISomeService someService, int id = 0)
{
...
}
But that means in my view constructor I have to build these dependencies one at a time and manually inject them...
public TypeDetails(int id = 0)
{
InitializeComponent();
_viewModel = new TypeDetailsViewModel(SimpleIoC.Current.GetInstance<ISomeService>(),id);
DataContext = _viewModel;
}
Is there a better way to do this?
First off I would look into the "RelayCommand" class which is part of MVVM Light. It will remove the need for events in your code behind. Start with that.
You should always favor "Constructor Injection" instead of the ServiceLocator (ex: SimpleIoC.Current.GetInstance())
Your ViewModel constructor should only be injecting services and not primitive types like "int". In your example "int id" should be the parameter of a method and not injected.
Ex: Instead, your TypeDetailsViewModel should look more like:
public TypeDetailsViewModel(ISomeService someService)
{
TypeDetail GetDetailsCommand(int id)
{
...
}
}
Lastly, your Models should never have any reference to your ViewModels.
For your DataContext, you can use a ViewModelLocator (ViewModels in ViewModelLocator MVVM Light)
To hook up your View and ViewModel to use the GetDetailsCommand, you can use the EventToCommand behavior (http://msdn.microsoft.com/en-us/magazine/dn237302.aspx). Ex: The OnLoaded event on the View calls the GetDetailsCommand on your ViewModel.

Categories

Resources