Can't navigate using Prism - c#

I can't get the navigation in Prism to work. When I click on the buttons to go to respective views, nothing happens.
This is the Man View (Shell) XAML:
<Window x:Class="MVVMPractice2.MainWindow"
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"
xmlns:Views="clr-namespace:MVVMPractice2.Views"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Margin="108,130,331.4,152.8" Content="View A" Command="{Binding NavigateCommand}" CommandParameter="ViewA"/>
<Button Margin="254,130,185.4,152.8" Content="View B" Command="{Binding NavigateCommand}" CommandParameter="ViewB"/>
<ContentControl prism:RegionManager.RegionName="ContentRegion"/> <!--PRISM POWER-->
</Grid>
</Window>
and its ViewModel:
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager regionManager; //PRISM POWER
public DelegateCommand<string> NavigateCommand { get; set; }
public MainWindowViewModel(IRegionManager regionManager)
{
this.regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
}
private void Navigate(string uri)
{
regionManager.RequestNavigate("ContentRegion", uri);
}
}
and Bootstrapper:
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType(typeof(object), typeof(ViewA), "ViewA");
Container.RegisterType(typeof(object), typeof(ViewB), "ViewB");
Container.RegisterType<ICustomer, Customer>();
}
}
I would appreciate some help.

I got mine to work by using the prism:ViewModelLocator.AutoWireViewModel="True" on UserControls only and not the window. I am assuming that you are using prism 6.
So what I did is, I first created the MainWindow that is going to house all my UserControls, I then created a MainUserControl that would house all the other UserControls. All this I achieved following this blog post (http://brianlagunas.com/getting-started-prisms-new-viewmodellocator/). Remember to create your MVVM folders (View and ViewModel) folders with their respective contents as the blog highlights.
Hope this helps.

First of all you should expose ICommand to button's command property , not delegate command which is concrete implementation of ICommand.
You can get rid of conventions of view model locator by implementing
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) in application class startup overriden method.
For more info please search Brian Lagunas viewmodellocator blog

Related

Using Ninject as the DI Container with Calburn.Micro and MVVM in WPF

All I have some experience with Caliburn.Micro using System.ComponentModel.Composition as an IoC container. This time I want to have some fun and use Niject. To setup the Calburn.Micro bootstrapper, I have the following class
public class Bootstrapper : BootstrapperBase
{
private IKernel _kernel;
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_kernel = new StandardKernel();
_kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
_kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
_kernel.Bind<IMainWindowViewModel>().To<MainWindowViewModel>().InSingletonScope();
}
protected override object GetInstance(Type service, string key)
{
return _kernel.Get(service);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _kernel.GetAll(service);
}
protected override void OnStartup(object sender, StartupEventArgs suea)
{
base.OnStartup(sender, suea);
DisplayRootViewFor<IMainWindowViewModel>();
}
protected override void OnExit(object sender, EventArgs e)
{
_kernel.Dispose();
base.OnExit(sender, e);
}
}
This seem to be called fine, but when the line
DisplayRootViewFor<IMainWindowViewModel>();
Is hit, it seems to launch the view IMainWindowView okay, but
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
}
}
public interface IMainWindowViewModel { }
and MainWindowViewModel as
public class MainWindowViewModel : Conductor<IMainWindowViewModel>, IMainWindowViewModel { }
with the XAML of IMainWindowView as
<Window x:Class="Mole.Replay.Framework.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ViewModels="clr-namespace:Mole.Replay.Framework"
xmlns:local="clr-namespace:Mole.Replay.Framework"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Window.DataContext>
<ViewModels:MainWindowView/>
</Window.DataContext>
<Grid>
</Grid>
</Window>
The ctor is called over and over again and results in a StackOverflow exception, there is no clear cause for this what-so-ever. This type is bound in singleton scope. Why is this happening am I missing something?
<Window.DataContext>
<ViewModels:MainWindowView/>
</Window.DataContext>
it doesn't make sense to set DataContext of MainWindowView to another instance of MainWindowView which will also try to set DataContext etc, until you get StackOverflow exception.
it should be a view model in DataContext. I don't know if caliburn.micro creates view models for view based on conventions, but at least remove current <Window.DataContext> assignment.
it is not clear to me why view namespace is aliased as ViewModels. If real view model is in the same namespace and it is not resolved automatically, assign view model
<Window.DataContext>
<ViewModels:MainWindowViewModel/>
</Window.DataContext>
DI container really should provide constructor arguments.Use them to assign DataContext:
public MainWindowView(IMainWindowViewModel vm)
{
InitializeComponent();
DataContext = vm;
}

MVVM Switching Between Views

I'm new to WPF so bear with me. I have a WinForms Application that I am trying to redo in WPF. In my current WinForms Application, I stick all my Controls into one Form, and hide/show them based on what buttons are hit, as well as making use of a second form.
My goal: Create different views to switch between smoothly based on what button is hit, instead of hiding Controls or making separate Forms and then hiding those.
I currently have a MainWindow view (My initial launch window), where with a button, I switch to my CreateAccount view. What I am having issues with is, how can I make my button in my CreateAccount go "back" to my MainWindow?
My end goal is to be able to switch between 4 views based off Button clicks.
Here is my MainWindow.xaml
<Window x:Class="MusicPlayer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MusicPlayer"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Name="CreateAccountTemplate" DataType="{x:Type ViewModels:CreateAccountViewModel}">
<Views:CreateAccountView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="164,182,0,0" VerticalAlignment="Top" Height="61" Width="68" Click="CreateAccountView_Clicked"/>
<PasswordBox HorizontalAlignment="Left" Margin="164,284,0,0" VerticalAlignment="Top" Width="120"/>
<ContentPresenter Content="{Binding}"/>
</Grid>
</Window>
My MainWindow.xaml.cs
using System;
using System.Windows;
using MusicPlayer.ViewModels;
namespace MusicPlayer {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
protected override void OnClosed(EventArgs e) {
base.OnClosed(e);
Application.Current.Shutdown();
} //end of onClosed
private void CreateAccountView_Clicked(object sender, RoutedEventArgs e) {
DataContext = new CreateAccountViewModel();
} //end of CreateAccountView_Clicked
}
}
And here is my CreateAccount.xaml
<UserControl x:Class="MusicPlayer.Views.CreateAccountView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
</UserControl.Resources>
<Grid Background="White">
<Button Content="Button" HorizontalAlignment="Left" Margin="276,279,0,0" VerticalAlignment="Top" Height="60" Width="59" Click="Button_Click"/>
</Grid>
</UserControl>
And my CreateAccountView.xaml.cs
using System.Windows;
using System.Windows.Controls;
using MusicPlayer.ViewModels;
namespace MusicPlayer.Views {
public partial class CreateAccountView : UserControl {
//public static readonly DependencyProperty TestMeDependency = DependencyProperty.Register("MyProperty", typeof(string), typeof(CreateAccountView));
public CreateAccountView() {
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
}
}
It seems to me that your current attempt is on the right track. The main issue with the code you posted is that the CreateAccountView.Button_Click() handler doesn't have access to the DataContext property it should be setting:
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
This DataContext property belongs to the CreateAccountView user control. However, this is not the controlling context for what's being displayed. So changing the value of that DataContext property doesn't have any useful effect. (Indeed, a user control should not set its own DataContext property at all, because doing so discards whatever context the client code using that user control had set.)
There's not enough context to know exactly what the best way for you to do this would be. I don't think it would be possible to provide enough context here on Stack Overflow. The overall architecture will depend on too many little details about your program. But, one way to approach this which I think is a good one would be this:
Create a "main" view model that governs the overall behavior of the app
Create individual view models that relate to different states of the UI
Have the main view model configure the individual view models to switch the current view model as appropriate, given the user input (e.g. clicking buttons)
Translating that into code, looks something like this…
First, the view models:
class MainViewModel : NotifyPropertyChangedBase
{
private object _currentViewModel;
public object CurrentViewModel
{
get => _currentViewModel;
set => _UpdateField(ref _currentViewModel, value);
}
private readonly HomeViewModel _homeViewModel;
private readonly Sub1ViewModel _sub1ViewModel;
private readonly Sub2ViewModel _sub2ViewModel;
public MainViewModel()
{
_sub1ViewModel = new Sub1ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_sub2ViewModel = new Sub2ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_homeViewModel = new HomeViewModel
{
ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),
ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)
};
CurrentViewModel = _homeViewModel;
}
}
class HomeViewModel : NotifyPropertyChangedBase
{
private ICommand _showSub1Command;
public ICommand ShowSub1Command
{
get => _showSub1Command;
set => _UpdateField(ref _showSub1Command, value);
}
private ICommand _showSub2Command;
public ICommand ShowSub2Command
{
get => _showSub2Command;
set => _UpdateField(ref _showSub2Command, value);
}
}
class Sub1ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
class Sub2ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
Of course, these view models contain only the implementation details needed to handle the UI switching. In your program, each would also include the stuff specific to each view state that you need.
In my little sample, the "home" view contains a couple of buttons, used to select the individual sub-views available:
<UserControl x:Class="WpfApp1.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Home: "/>
<Button Content="Sub1" Command="{Binding ShowSub1Command}"/>
<Button Content="Sub2" Command="{Binding ShowSub2Command}"/>
</StackPanel>
</UserControl>
The sub views just contain the button required to go back to the home view:
<UserControl x:Class="WpfApp1.Sub1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub1 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
<UserControl x:Class="WpfApp1.Sub2View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub2 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
Finally, the main window sets the main view model, and declares templates to use for each of the specific sub views:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:HomeViewModel}">
<l:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub1ViewModel}">
<l:Sub1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub2ViewModel}">
<l:Sub2View/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
</Window>
Importantly, you'll see that none of the view objects include any code-behind. It's not necessary when you approach the problem this way, at least not for the purpose of controlling basic behaviors in the code. (You may still wind up with code-behind for view objects, but this will usually only be for the purpose of implementing specific user-interface behaviors unique to that view object, not for dealing with the view model state.)
Using this approach, you let WPF do as much of the heavy-lifting as possible. It also decouples all of the view model objects from each other. There's a clear hierarchy: only the top-level "main" view model even knows about the other view models. This allows the sub-view models ("home", "sub1", and "sub2") to be reused as necessary in other scenarios without any modification or special-case handling within them.
Here are the helper classes I used above:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
public DelegateCommand(Action execute)
{
_execute = execute;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute();
}

INavigationAware can't navigate away

I'm using Prism Unity, I have an abstract RecordViewModel : BindableBase, RecordListViewModel : RecordViewModel, and RecordUpdateViewModel: RecordViewModel, INavigationAware. There is also a separate Navigation Module and my MainWindow has 2 regions, NavigationRegion and ContentRegion. All RecordViews reside in ContentRegion. For whatever reason, whether I make a GoBack button or click on a button in the NavigationRegion, I cannot leave the Update view. I have narrowed down that the problem is in the ViewModel and that I'm missing something for INavigationAware. Please tell me what I'm missing or did wrong, thank you.
public class RecordUpdateViewModel : RecordViewModel, INavigationAware
{
private IRegionNavigationJournal navigationJournal;
public RecordUpdateViewModel(IRecordService context) : base(context)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return false;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
throw new NotImplementedException();
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
//irrelevant to problem logic to bring in Record.Id
navigationJournal = navigationContext.NavigationService.Journal;
}
}
Edit
Just incase I screwed up elsewhere here's my registrations in the module.cs
container.RegisterType<IRecordService, RecordService>();
container.RegisterTypeForNavigation<RecordListView>();
container.RegisterTypeForNavigation<RecordUpdateView>();
regionManager.RegisterViewWithRegion("ContentRegion", typeof(RecordListView));
regionManager.RegisterViewWithRegion("ContentRegion", typeof(RecordUpdateView));
I navigate to the views with regionManager.RequestNavigate("ContentRegion", "RecordUpdateView", parameter) and if I don't use INavigationAware on the UpdateView, all buttons work, but when I put it back on I can't navigate away.
Edit2
Here is the XAML for the ListView that navigates to the UpdateView and the bound command
<UserControl x:Class="App.Record.Views.RecordListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:prism="http://prismlibrary.com/"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
prism:ViewModelLocator.AutoWireViewModel="True">
<DockPanel>
<WrapPanel>
<Button Content="Edit"
Command="{Binding EditCommand}"/>
</WrapPanel>
<DataGrid>
<--Irrelevant-->
</DataGrid>
</DockPanel>
</UserControl>
Command
private DelegateCommand editCommand;
public DelegateCommand EditCommand => editCommand ?? (editCommand = new DelegateCommand(EditRecord));
private const string RecordID = "RecordID";
void EditCommand()
{
var parameter = new NavigationParameters();
parameter.Add("RecordID", SelectedRecord.ID);
regionManager.RequestNavigate("ContentRegion", "RecordUpdateView", parameter);
}
Commands for the Navigation menu buttons work the same, and any view not using INavigationAware can be navigated away from.
INavigationAware has nothing to do with navigating from view to view. It simply give you an easy way to pass parameters between the view. I think what you're looking for is something called IRegionManager then you can do somehting like regionManager.NavigateTo(regionName,viewName) you will have to register the view with your container. Something like container.RegisterType<object,Views.View>(ViewNames.ViewName); and of course ViewNames.ViewName is a constant string with the name of the view.
I shall hang my head in shame... look to the OnNavigatedFrom... and all 3 INavigationAware Members for anyone else that looks to this... and remove the NotImplementedExceptions that are auto generated. If you don't need that void, it has to be there so INavigationAware doesn't yell at you for not fully implementing it, but there doesn't have to be any logic in those voids if you don't need them.

Opening second window from main window in WPF/MVVM app

Could you tell me how to in pure MVVM way call (I mean open/show) child window from parent window. Let's say I have two Views:
MainWindow.cs (MainWindow.xaml) - parent window (DataContext = new MainWindowViewModel())
Window.cs (Window.xaml) - child window (DataContext = new WindowViewModel())
And corresponding ViewModel classes:
MainWindowViewModel.cs
WindowViewModel.cs
I would like my window to be opened after button click (button that is on the MainWindow view). Because of that I have defined command binding in MainWindow.xaml:
<Button x:Name="buttonOpenWindow" Content="Open window..." Width="100" Height="20" Command="{Binding OpenWindowCmd}"/>
And MainWindowViewModel.cs piece:
public ICommand OpenWindowCmd { get; set; }
public MainWindowViewModel()
{
OpenWindowCmd = new RelayCommand(o => OpenWindow());
}
private void OpenWindow()
{
// What to put here?
}
In Window.xaml I added something like that:
<Window x:Class="Namespace.View.Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:vm="clr-namespace:Namespace.ViewModel"
Title="Title" Height="300" Width="325" Visibility="{Binding IsWindowVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
(...)
And the WindowViewModel.cs:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Namespace.Annotations;
namespace Namespace.ViewModel
{
public class WindowViewModel : INotifyPropertyChanged
{
private bool _isWindowVisible;
public bool IsWindowVisible
{
get { return _isWindowVisible; }
set
{
_isWindowVisible = value;
OnPropertyChanged(nameof(IsWindowVisible));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I am not sure what to do next and if that approach is correct. I found some services implementations in the forum, but I thought of using just Visibility property instead (but not sure if it is possible). I need to somehow change the IsWindowVisible in one of the view models I suppose. Could anyone suggest how to gently handle such sub window opening?
If I understood well, you need something like this:
private void OpenWindow()
{
WindowViewModel wvm = new WindowViewModel();
Window win = new Window()
{
DataContext = wvm;
};
win.Show();
}
If you don't like this solution then try the one from the comments with IWindowService.
In any case it makes no sense to use a Visibility property.

Prism 6 with Unity - resolving view models for views without naming convention

I am trying to have view models resolved using DI with Prism 6 and Unity in my WPF app and this works. However I don't know how to tell the framework which view should be merged with which view model.
If I use the convention, i.e. have ViewModels, and Views namespaces, and classes ViewA and ViewAViewModel everything works, however I would like to have more flexibility to name and organize my classes and this is why I want to somehow tell the framework explicitly which view goes with which view model. I tried many things, but nothing really works. Current "solution" makes app run but view model is not set.
Here is the code:
ViewA.xaml
<UserControl x:Class="WPFDITest.Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<TextBlock Text="{Binding ViewAMessage}"/>
<TextBox Text="{Binding ViewAMessage, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</UserControl>
MainWindow.xaml
<UserControl x:Class="WPFDITest.Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<TextBlock Text="{Binding ViewAMessage}"/>
<TextBox Text="{Binding ViewAMessage, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</UserControl>
ViewAVM.cs
public class ViewAVM : BindableBase
{
private string viewAMessage;
public ViewAVM(IModelA model)
{
viewAMessage = model.HelloMsgA();
}
public string ViewAMessage
{
get { return viewAMessage; }
set { SetProperty(ref viewAMessage, value); }
}
}
Model.cs
public interface IModelA
{
string HelloMsgA();
}
public class ModelA : IModelA
{
public string HelloMsgA()
{
return "Hello from A!";
}
}
App.xaml.cs
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var bootstraper = new Bootstrapper();
bootstraper.Run();
}
}
Bootstrapper.cs
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType<IModelA, ModelA>(new ContainerControlledLifetimeManager());
Container.RegisterType<object, ViewAVM>("ViewA");
}
protected override void ConfigureViewModelLocator()
{
ViewModelLocationProvider.SetDefaultViewModelFactory(type => Container.Resolve(type));
}
}
After some digging through Prism sources I found out how to do what I want. I can register each view with ViewModelLocationProvider.Register passing in a factory method for the view model. I created method that does just that with convenient syntax and uses container to resolve view model for given type:
public void BindViewModelToView<TViewModel, TView>()
{
ViewModelLocationProvider.Register(typeof(TView).ToString(), () => Container.Resolve<TViewModel>());
}
And there is how I use it to bind ViewAVM to ViewA and ViewB both using same singleton instance.
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType<IModelA, ModelA>(new ContainerControlledLifetimeManager());
Container.RegisterType<ViewAVM>(new ContainerControlledLifetimeManager());
}
protected override void ConfigureViewModelLocator()
{
BindViewModelToView<ViewAVM, ViewA>();
BindViewModelToView<ViewAVM, ViewB>();
}
}
By the way, as far as I can tell by the sources, it is only possible to associate view to view model through with ViewModelLocator by registering factories or by using their or custom convention, don't look for some DI magic.
Here is a link to Brian's blog on the ViewModelLocator, and it includes a section (Change those Nasty Conventions) on how to override the conventions if you would like.
Getting Started with Prism’s new ViewModelLocator
Personally, I set my DataContext in the code behind of the UserControl, in the constructor, after the View gets registered with the container in the Module. Conventions be damned!! :)
public ProductView(ProductViewModel view_model)
{
InitializeComponent();
DataContext = view_model;
}

Categories

Resources