I am trying to understand how to switch views and its viewmodels in a wpf mvvm application which uses prism and unity. I put something together from a tutorial but have additional question bec a few things dont seem right. What I have so far is a WPF application with a shell.xaml window that has section placeholders using prism regions. In addition, I have a bootstrapper class that register modules which will fill in the different regions in the shell.xaml window. In the modules which are class libraries I have the initialize function setting the views and viewmodels. I have only two regions in this application the navigation and the workspace. I have 2 buttons on the navigation and they change the view in the workspace. The workspace views are in their own modules. So at this point each workspace view has its own class library module. In a big application this seems unreasonable of having each view have its own class library. I wanted to know how can I have multiple views and viewmodels in one class library and swap them in and out. If you have a good step by step tutorial that would be great.
I know this is 4 years late, but I ll give an answer anyway.
First thing you do is add the Views you need to the Views folder and add their corresponding ViewModels in the ViewModels folder so that you have a structure as described below :
ViewModels
ViewModelA
ViewModelB
Views
ViewA
ViewB
The ViewModels and their corresponding view could look something like this :
ViewModelA
using System;
namespace SomeApp.DemoModule.ViewModels
{
public class ViewModelA : INavigationAware
{
public ViewModelA(IUnityContainer container, IRegionManager regionManager, IEventAggregator eventAggregator)
{
this.container = container;
this.regionManager = regionManager;
this.eventAggregator = eventAggregator;
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
//Do stuff here
//For navigation back
navigationService = navigationContext.NavigationService;
}
#region Executes
/// <summary>
/// Command when ViewB button clicked
/// </summary>
public void Execute_ViewBCommand()
{
regionManager.RequestNavigate("REGIONNAME_HERE", new Uri("ViewB", UriKind.Relative));
}
ViewA
<UserControl x:Class="DemoModule.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:local="clr-namespace:ViewInjection.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Content="VIEWB" FontSize="38" Command="{Binding ViewBCommand}"></Button>
</Grid>
ViewA.Xaml.cs
namespace SomeApp.DemoModule.Views
{
/// <summary>
/// Interaction logic for ViewA.xaml
/// </summary>
public partial class ViewA : UserControl
{
public ViewA(ViewModelA model)
{
InitializeComponent();
this.DataContext = model;
}
}
}
ViewModelB
using System;
namespace SomeApp.DemoModule.ViewModels
{
public class ViewModelB : INavigationAware
{
public ViewModelB(IUnityContainer container, IRegionManager regionManager, IEventAggregator eventAggregator)
{
this.container = container;
this.regionManager = regionManager;
this.eventAggregator = eventAggregator;
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
//Do stuff here
//For navigation back
navigationService = navigationContext.NavigationService;
}
#region Executes
/// <summary>
/// Command when ViewA button clicked
/// </summary>
public void Execute_ViewACommand()
{
regionManager.RequestNavigate("REGIONNAME_HERE", new Uri("ViewA", UriKind.Relative));
}
ViewB
<UserControl x:Class="DemoModule.Views.ViewB"
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="300" d:DesignWidth="300">
<Grid>
<Button Content="VIEWA" FontSize="38" Command="{Binding ViewACommand}"></Button>
</Grid>
ViewB.Xaml.cs
namespace SomeApp.DemoModule.Views
{
/// <summary>
/// Interaction logic for ViewB.xaml
/// </summary>
public partial class ViewB : UserControl
{
public ViewB(ViewModelB model)
{
InitializeComponent();
this.DataContext = model;
}
}
}
You can register your views in several ways, we use a DemoModuleInit class in the project root that registers the view :
DemoModuleInit
public class DemoModuleInit : IModule
{
private IRegionManager regionManager;
/// <summary>
/// Bind your interfaces, subscribe to events, do stuff that needs to be done on intialization of module
/// </summary>
public void OnInitialized(IContainerProvider containerProvider)
{
// Setup Event listeners etc...
regionManager = containerProvider.Resolve<IRegionManager>();
}
/// <summary>
/// Register your views for this Module
/// </summary>
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>();
containerRegistry.RegisterForNavigation<ViewB>();
}
If implemented correctly you should be able to navigate from ViewA to ViewB and back within the same module
For more information on Prism check out :
Prsim on GitHub
you can have as many views in your module as you need. You just need to navigate through them. To do so, you need to register them and then from each of the viewmodel you can request navigate to another view from your regionmanager. Have a look here prism navigation
Related
So I have a TabControl that has an instance of ViewModel1 as each tab. ViewModel1's View has a custom UserControl I built, that basically exposes a DependencyProperty "Images" that stores the list of images the control has. This property has been bound (OneWayToSource) to ViewModel1's property "Images".
The problem I'm having is that for some reason, all instances of ViewModel1 (all tabs) are sharing this property. So if Tab1 has 1 image in the control, and Tab2 has 3 images in the control, the "Images" property of each ViewModel1 instance has a collection of 4 images.
I don't know how anything like this could happen - anyone have any ideas?
Note that I'm using Caliburn.Micro as a MVVM framework.
EDIT: The property inside the control is defined like this:
public List<ImageData> Images
{
get { return (List<ImageData>)GetValue(ImagesProperty); }
set { return; }
}
public static readonly DependencyProperty ImagesProperty =
DependencyProperty.Register("Images", typeof(List<ImageData>), typeof(WebImageAlbum),
new UIPropertyMetadata(new List<ImageData>()));
New items are just added to that with Images.Add() and inside the View's XAML, this property is bound to the ViewModel's "Images" property with Mode=OneWayToSource.
EDIT: This is what the Tab view with the UserControl looks like:
<UserControl
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:local="clr-namespace:NET_MD3.Views"
xmlns:cal="http://www.caliburnproject.org"
xmlns:CustomControls="clr-namespace:NET_MD3.CustomControls" x:Class="NET_MD3.Views.AlbumTabView"
mc:Ignorable="d"
d:DesignHeight="267.789" d:DesignWidth="473.684">
<Grid>
<CustomControls:WebImageAlbum x:Name="Album" Margin="10,10,10,50" Width="Auto" Height="Auto" ImageWidthHeight="95"
cal:Message.Attach="[Event ImageClicked] = [Action ImageClicked($eventArgs)]"
Images="{Binding Images, Mode=OneWayToSource}"/>
<Button x:Name="CloseTab" Content="Close tab and delete album" HorizontalAlignment="Left" Margin="10,0,0,10" VerticalAlignment="Bottom" Width="172" Height="30"/>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="342,243,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
</Grid>
This is what the ViewModel looks like:
public class AlbumTabViewModel : Screen, IAlbumTabItem
{
#region Constructor
public AlbumTabViewModel(int id)
{
this.TabID = id;
}
#endregion
#region Properties
/// <summary>
/// Get the Images loaded in this tab's Album (do not use the setter - it's for the Binding only)
/// </summary>
public List<ImageData> Images
{
get; set;
}
/// <summary>
/// The Display name of this Screen (Tab)
/// </summary>
public override string DisplayName
{
get
{
return $"Album #{this.TabID}";
}
set { base.DisplayName = value; }
}
/// <summary>
/// The ID of this tab
/// </summary>
public int TabID { get; private set; }
#endregion
#region Actions
/// <summary>
/// Delete this album tab
/// </summary>
public void CloseTab()
{
this.TryClose();
}
/// <summary>
/// An image has been clicked within the album
/// </summary>
/// <param name="e"></param>
public void ImageClicked(ImageData e)
{
//ttt
}
#endregion
}
Turns out the problem was in the declaration of the DependencyProperty. In the UIPropertyMetadata() that I passed to the static declaration of the property, I made it use a 'new List()' as the default value and turns out that you can't really do that, because it will create that List as a static object, which explains why every instance of the UserControl had the same list. This probably happens because the DependencyProperty itself is static and so when it tries to create that default value, it will be static as well.
I guess the lesson is don't put reference types as default values when declaring a new DependencyProperty, and if you must, set it elsewhere (within the CLR property that accesses the value, or something).
I created a WPF MVVM app using MEF and Prism 5.0. I have MEF loading a module at startup called MobileRecruiterModule (below). I need to read some settings from App.config (or any config file really) and update the ViewModel properties with those config values so the View can pick them up.
Where is the appropriate place to load settings here? Do I do it in the MEF module (the thing that implements Microsoft.Practices.Prism.Modularity.IModule or in my View?
MobileRecruiterModule.cs
[ModuleExport(typeof (MobileRecruiterModule))]
public class MobileRecruiterModule : IModule
{
/// <summary>
/// The region manager
/// </summary>
[Import] public IRegionManager Region;
/// <summary>
/// Notifies the module that it has be initialized.
/// </summary>
public void Initialize()
{
Region.RegisterViewWithRegion(RegionNames.MainContentRegion, typeof (MobileRecruiterView));
}
...
}
MobileRecruiterView.xaml.cs
[Export("MobileRecruiterView")]
[PartCreationPolicy(CreationPolicy.Shared)]
[RegionMemberLifetime(KeepAlive = false)]
[Export]
public partial class MobileRecruiterView : UserControl
{
[Import]
public MobileRecruiterViewModel ViewModel
{
get { return (MobileRecruiterViewModel)DataContext; }
set { DataContext = value; }
}
[ImportingConstructor]
public MobileRecruiterView(MobileRecruiterViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
}
MobileRecruiterViewModel.cs
[Export]
public class MobileRecruiterViewModel : BindableBase
{
public string DatabaseServer { get; set; }
... and a few other properties that the XAML view binds to ...
}
I would suggest that you should load your settings in ViewModel constructor. Because your ViewModel is the DataContext for the View, you have to initialize it before you show it. I hope that you do not store any BLOB in it, so the time for *.config loading will be small enough to do it on UI thread.
Here is my issue, I created a UserControl as follows:
XAML:
<UserControl x:Class="ProcessVisualizationBar.UserControl1"
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" xmlns:lb="clr-namespace:ProcessVisualizationBar"
Name="ProcessVisualizationBar">
<Border BorderBrush="Silver" BorderThickness="1,1,1,1" Margin="0,5,5,5" CornerRadius="5" Padding="2">
<ListBox Name="ProcessVisualizationRibbon" Grid.Column="1" Height="40" ItemsSource="{Binding ElementName=ProcessVisualizationBar, Path=ItemsSource}"/>
</Border>
</UserControl>
Code Behind(C#):
using System.Windows;
using System.Windows.Controls;
namespace ProcessVisualizationBar
{
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(System.Collections.IEnumerable), typeof(UserControl));
public System.Collections.IEnumerable ItemsSource
{
get { return ProcessVisualizationRibbon.ItemsSource; }
set { ProcessVisualizationRibbon.ItemsSource = value; }
}
public UserControl1()
{
InitializeComponent();
}
}
}
I build my Usercontrol and add the .dll to the reference of another project. I add the reference at the top of my XAML as such:
xmlns:uc="clr-namespace:ProcessVisualizationBar;assembly=ProcessVisualizationBar"
Then I go to use the control.
<uc:UserControl1 Grid.Row="2" x:Name="ProcessVisualizationContent" />
It finds the control okay, but when I try and find the ItemsSource Property I added to it, I'm not finding it. I'm not sure what I missed, and I'm not sure what debug tools are really available to figure this out.
Anyone have some experience with this that can share their wisdom?
What is the actual data being passed? That is what you should be creating and not a pass through situation which you are attempting.
Create a dependency property targetting the actual data to be passed with a property changed handler. On the change event, then call internal code to bind it to the ProcessVisualazation ItemsSource. That way you can debug when the data comes through by placing a breakpoint in the event.
Here is an example where the consumer will see StringData in the Xaml and needs to pas a list of strings into the custom control:
#region public List<string> StringData
/// <summary>
/// This data is to be bound to the ribbon control
/// </summary>
public List<string> StringData
{
get { return GetValue( StringDataProperty ) as List<string>; }
set { SetValue( StringDataProperty, value ); }
}
/// <summary>
/// Identifies the StringData dependency property.
/// </summary>
public static readonly System.Windows.DependencyProperty StringDataProperty =
System.Windows.DependencyProperty.Register(
"StringData",
typeof( List<string> ),
typeof( UserControl ),
new System.Windows.PropertyMetadata( null, OnStringDataPropertyChanged ) );
/// <summary>
/// StringDataProperty property changed handler.
/// </summary>
/// <param name="d">DASTreeBinder that changed its StringData.</param>
/// <param name="e">Event arguments.</param>
private static void OnStringDataPropertyChanged( System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e )
{
UserControl source = d as UserControl;
List<string> value = e.NewValue as List<string>;
BindDataToRibbon( value );
}
#endregion public List<string> StringData
Now just create a BindDataToRibbon method which will do the dirty work. Note that I use Jeff Wilcox's Silverlight dependency snippets in Visual Studio to generate the above dependency. I have used it for WPF and Silverlight projects.
Im trying to use MVVM on a PRISM Module. I have a ViewModel in my module with a parameterized constructor which accepts an IOutputService object which will be injected using Ninject.
namespace HelloWorld.ViewModels
{
public class HelloWorldViewModel : ViewModelBase
{
private IOutputService outputService;
public HelloWorldViewModel(IOutputService outputService)
{
this.outputService = outputService;
}
}
}
In the HelloWorldModule.cs file, I register IOutputService with a class that implements it.
public class HelloWorldModule : IModule
{
private IKernel kernel;
private IRegionManager regionManager;
public HelloWorldModule(IKernel kernel, IRegionManager regionManager)
{
this.kernel = kernel;
this.regionManager = regionManager;
}
public void Initialize()
{
kernel.Bind<IOutputService>().To<MessageBoxOutputService>();
regionManager.RegisterViewWithRegion("Region1", typeof(HelloWorldView));
}
}
You can also notice that I am registering the HelloWorldView to Region1. HelloWorldView uses HelloWorldViewModel. The problem now is I can't initialize the HelloWorldViewModel in XAML of View because my ViewModel doesn't have a parameterless constructor.
<UserControl x:Class="HelloWorld.Views.HelloWorldView"
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:vm="clr-namespace:HelloWorld.ViewModels"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:HelloWorldViewModel />
</UserControl.DataContext>
<Grid>
</Grid>
</UserControl>
When I run this, the InitializeComponent() method of the View throws an NullReferenceException. Any proper way to make this work? Thanks.
In the codebehind of the view, inject the viewmodel in the constructor.
public partial class HelloWorldView : UserControl
{
public HelloWorldView(HelloWorldViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
}
Well You don't have to set View's DataContext in xaml. It can be simply done in CodeBehind. Add following property to HelloWorldView.xaml.cs
/// <summary>
/// Gets or sets ViewModel.
/// </summary>
[Inject]
public HelloWorldViewModel ViewModel
{
get { return this.DataContext as HelloWorldViewModel; }
set { this.DataContext = value; }
}
When you call RegisterViewWithRegion an instance of that view will be resolved and thanks to InjectAttribute Ninject will provide that View with DataContext. So you don't need to worry about that. Constructor injection should work as well.
Try using ObjectDataProvider.
Example (From MSDN):
<ObjectDataProvider x:Key="myDataSource" ObjectType="{x:Type src:Person}">
<ObjectDataProvider.ConstructorParameters>
<system:String>Joe</system:String>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
Here is a good example by Bea Stollnitz:
Why should I use ObjectDataProvider?
I'm just playing around with WPF and MVVM, and I have made a simple app that displays a Rectangle that changes color whenever Network availability changes.
But when that happens, I get this error: Cannot use a DependencyObject that belongs to a different thread than its parent Freezable.
Code
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="600">
<DockPanel LastChildFill="True">
<Rectangle x:Name="networkStatusRectangle" Width="200" Height="200" Fill="{Binding NetworkStatusColor}" />
</DockPanel>
</Window>
Code-behind
using System.Windows;
using WpfApplication1.ViewModels;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new NetworkViewModel();
}
}
}
ViewModel
using System.ComponentModel;
using System.Net.NetworkInformation;
using System.Windows.Media;
namespace WpfApplication1.ViewModels
{
public class NetworkViewModel : INotifyPropertyChanged
{
private Brush _NetworkStatusColor;
public Brush NetworkStatusColor
{
get { return _NetworkStatusColor; }
set
{
_NetworkStatusColor = value;
NotifyOfPropertyChange("NetworkStatusColor");
}
}
public NetworkViewModel()
{
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
}
protected void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
if (e.IsAvailable)
{
this.NetworkStatusColor = new SolidColorBrush(Colors.Green);
}
else
{
this.NetworkStatusColor = new SolidColorBrush(Colors.Red);
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void NotifyOfPropertyChange(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I assume that I should change the NetworkStatusColor property by invoking something?
You assume correctly. It's the Dispatcher class and the .Invoke method you want to take a look at.
Something a bit like this:
if (this.Dispatcher.Thread != Thread.CurrentThread)
{
this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(...your method...), any, params, here);
return
}
There's an MSDN article here with some more info.
With MVVM you have a couple of options when dealing with dispatching. Either you can send some kind of message to your view to have it invoke the operation for you, or you can create some kind of abstract dispatcher service that you are able to easily mock.
Take a look at the MVVM Light toolkit, as it includes a simple dispatcher-service you can use/copy.