The code below worked on shared instance of the view. Now what I'm trying to achieve is each time I navigate to ViewB I want a new instance of the view and its backing view model. I have tried various combinations of the below but they all seem to ultimately end with the RequestNavigate failing silently the second time I try to navigate to ViewB...
I have also tried setting IsNaviagtionTarget to false after the view has been navigated to once.
Bootstrapper:
public void Initialize()
{
_regionManager.RegisterViewWithRegion(RegionNameConstants.MainRegion, typeof(ViewA));
_regionManager.RegisterViewWithRegion(RegionNameConstants.MainRegion, typeof(ViewB));
}
ViewB (class):
[RegionMemberLifetime(KeepAlive = false)]
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
internal partial class ViewB
{
[ImportingConstructor]
public ViewB(ViewBViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
ViewBViewModel:
[Export(typeof(ViewBViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
internal class ViewBViewModel : BindableBase, INavigationAware
{
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}
ViewA simply has a button with a command that calls:
ViewA Navigation command:
public override void Execute(object parameter)
{
_regionManager.RequestNavigate(RegionNameConstants.MainRegion, new Uri(nameof(ViewB), UriKind.Relative));
}
Don't register typeof(ViewB) with the region manager in the bootstrapper:
public void Initialize()
{
_regionManager.RegisterViewWithRegion(RegionNameConstants.MainRegion, typeof(ViewA));
}
And since you are navigating to the Uri of nameof(ViewB), you should also export the view with a contract name of nameof(ViewB):
[Export(nameof(ViewB))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[RegionMemberLifetime(KeepAlive = false)]
internal partial class ViewB
{
[ImportingConstructor]
public ViewB(ViewBViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
Then you should get a new instance of ViewB each time you navigate to it using:
_regionManager.RequestNavigate(RegionNameConstants.MainRegion, new Uri(nameof(ViewB), UriKind.Relative));
Related
I'm using caliburn micro for this project.
I have my ShellView with my contentcontrol:
<ContentControl x:Name="ActiveItem"
Grid.Row="0" Grid.Column="0" />
In ShellViewModel i got it to show my usercontrol LoginView with:
public class ShellViewModel : Conductor<object>
{
public ShellViewModel()
{
ActivateItem(new LoginViewModel());
}
public void ShowSignUp()
{
ActivateItem(new SignUpViewModel());
}
}
However, i can't navigate to SignUpView from LoginView with my button:
<!-- Row 4 -->
<Button x:Name="ShowSignUp"
Content="Sign Up Now!"
Grid.Row="3" Grid.Column="1"
Style="{StaticResource LoginBtnsStyle}" />
LoginViewModel deriving from ShellViewModel:
public class LoginViewModel : ShellViewModel
{
}
How do i navigate from LoginView to SignUpView with a button that is on the LoginView?
I'm getting no errors, it just isn't changing view.
I also tried putting ShowSignUp() on the LoginViewModel but no success.
Update 1 ShellViewModel:
public class ShellViewModel : Conductor<object>, IHandle<ActionInvokedMessage>
{
DispatcherTimer dt = new DispatcherTimer();
private SplashScreenViewModel _splashVM;
private LoginViewModel _loginVM;
private SignUpViewModel _signUpVM;
private IEventAggregator _eventAggregator;
public ShellViewModel(SplashScreenViewModel splashVM, LoginViewModel loginVM, SignUpViewModel signUpVM)
{
_loginVM = loginVM;
_signUpVM = signUpVM;
_splashVM = splashVM;
ActivateItem(_splashVM);
dt.Tick += new EventHandler(Dt_Tick);
dt.Interval = new TimeSpan(0, 0, 2);
dt.Start();
}
private void Dt_Tick(object sender, EventArgs e)
{
dt.Stop();
ActivateItem(_loginVM);
}
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
ActivateItem(new LoginViewModel(_eventAggregator));
}
public void Handle(ActionInvokedMessage message)
{
ActivateItem(message.Page);
}
public void ShowSignUp()
{
ActivateItem(new SignUpViewModel());
}
}
You could achieve this using EventAggregator to publish indicative messages from LoginViewModel to ShellViewModel to update the UI.
To begin with, you need to define an message class, which would tells the ShellViewModel which ViewModel needs to be changed. For example,
public class ActionInvokedMessage
{
public Screen Page { get; set; }
}
The Page property would indicate which Screen needs to be loaded. Now, you could change your LoginViewModel as the following.
public class LoginViewModel: Screen
{
private IEventAggregator _eventAggregator;
public LoginViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
public void ShowSignUp()
{
_eventAggregator.PublishOnUIThread(new ActionInvokedMessage { Page = new SignupViewModel() }); ;
}
}
The PublishOnUIThread method would broadcast a message to all the listeners of the Message Type ActionInvokedMessage for the change. Next step would be to ensure the ShellViewModel would be listening to the change.
public class ShellViewModel : Conductor<object>, IHandle<ActionInvokedMessage>
{
private IEventAggregator _eventAggregator;
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
ActivateItem(new LoginViewModel(_eventAggregator));
}
public void Handle(ActionInvokedMessage message)
{
ActivateItem(message.Page);
}
public void ShowSignUp()
{
ActivateItem(new SignupViewModel());
}
}
The Implementation of IHandle interface allows us to handle the action that would be required to be executed when the ShellViewModel recieves the ActionInvokedMessage. As seen in the code, this would be an appropriate place to use the ActivateItem method to load the Signup Page.
You can create an interface for navigation and use it in view models to navigate around app.
interface INavigation {
void NavigateTo(System.Type typeId);
}
class ShellViewModel: Conductor<object>, INavigation {
private List<object> pages = new List<Object>();
public ShellViewModel() {
pages.Add(new SignupViewModel(this));
pages.Add(new LoginViewModel(this));
}
void NavigateTo(System.Type typeId) {
var page = pages.Where(x => x.GetType() == typeId).FirstOrDefault()
ActivateItem(page);
}
}
class SignupViewModel {
public SignupViewModel(INavigation navigation) {
this.ShowLoginCommand= new NavigateCommand<LoginViewModel>(navigation);
}
}
class LoginViewModel {
public LoginViewModel (INavigation navigation) {
this.ShowSignUpCommand = new NavigateCommand<SignupViewModel>(navigation);
}
}
Navigation command may be implemented like follows:
public class NavigateCommand<T> : ICommand
{
public event EventHandler CanExecuteChanged;
private readonly INavigation navigation;
public NavigateCommand(INavigation navigation)
{
this.navigation = navigation;
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => this.navigation.NavigateTo(typeof(T));
}
Here I pass System.Type but you can design type that better describes navigation request so that you may pass additinal paramters.
Recently I am going through some old code and found the below code
public class ProfileModule : IModule
{
private readonly IRegionManager regionManager;
private readonly IUnityContainer container;
private IEventAggregator eventAggregator;
public ProfileModule(IUnityContainer c, IRegionManager r, IEventAggregator e)
{
container = c;
regionManager = r;
eventAggregator = e;
}
public void Initialize()
{
// Create and add profiles as new Tab items
container.RegisterType<IProfileViewModel, Profile1ViewModel>(new ContainerControlledLifetimeManager());
regionManager.Regions[RegionNames.HomeRegion].Add(container.Resolve<ProfileView>());// HomeRegion is of type TabControl
container.RegisterType<IProfileViewModel, Profile2ViewModel>(new ContainerControlledLifetimeManager());
regionManager.Regions[RegionNames.HomeRegion].Add(container.Resolve<ProfileView>());
container.RegisterType<IProfileViewModel, Profile3ViewModel>(new ContainerControlledLifetimeManager());
regionManager.Regions[RegionNames.HomeRegion].Add(container.Resolve<ProfileView>());
}
}
Below is the ProfileView.xaml.cs
public partial class ProfileView : INotifyPropertyChanged
{
[InjectionConstructor]
public ProfileView(IProfileViewModel vm)
{
DataContext = vm;
InitializeComponent();
}
}
Below are the viewModels
public abstract class ProfileViewModelBase : IProfileViewModel, IDataErrorInfo, INotifyPropertyChanged
{
public ProfileViewModelBase(IEventAggregator eventAggregator, IRegionManager regionManager)
{
}
}
public class Profile1ViewModel : ProfileViewModelBase
{
[InjectionConstructor]
public Profile1ViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
: base (eventAggregator, regionManager)
{
}
}
public class Profile2ViewModel : ProfileViewModelBase
{
[InjectionConstructor]
public Profile2ViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
: base (eventAggregator, regionManager)
{
}
}
public class Profile3ViewModel : ProfileViewModelBase
{
[InjectionConstructor]
public Profile3ViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
: base (eventAggregator, regionManager)
{
}
}
The part of the code that is not clear for me is the ProfileModule.Initialise().
Everytime when the region manager is adding a view a new new instance of ProfileView is getting created and the viewModel is the one that is registered last.
First time ProfileView is created with Profile1ViewModel as a Datacontext.
Second time ProfileView is created with Profile2ViewModel as a Datacontext.
Third time ProfileView is created with Profile3ViewModel as a Datacontext.
How the container knows exactly which viewmodel to create when creating the view.
Also I understand , container.Resolve will return the view if it already got one, first time view is created and returned, second time I except same view will be returned, but a new view is created. same with third.
Can anyone explain what is happening?
Here goes:
What you can see inside the Initialize method is that after registering the IProfileViewModel the code is then immediately calling Resolve<ProfileView> which on the first Resolve is providing Profile1ViewModel to the ProfileView constructor. Then the second Register replaces the first registration with Profile2ViewModel. Therefore subsequent calls to Resolve will never give you an instance (or the singleton instance) of Profile1ViewModel.
If for some reason you really want to resolve the same instance of ProfileView then you need to Register this with the Unity container as a singleton like the below.
container.RegisterType(new ContainerControlledLifetimeManager());
This is obviously assuming you have an interface defined called IProfileView
I have a UdpClient, firing off a DataRecevied event on my MainWindow:
public partial class MainWindow : Window
{
public static YakUdpClient ClientConnection = new YakUdpClient();
public ClientData;
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
ClientData = new ClientData();
ClientConnection.OnDataReceived += ClientConnectionOnDataReceived;
}
private void ClientConnectionOnDataReceived(object sender, MessageEventArgs messageEventArgs)
{
ClientData.Users = messageEvenArgs.ConnectedUsers;
}
}
My ClientData and User classes look as follow:
public class ClientData
{
public List<User> Users {get;set;)
}
public class User
{
public string Name {get;set;}
}
On my MainWindow, I have a UserControl called UserListView which has a ViewModel called UserListViewModel
The ViewModel looks as follow:
public class UserListViewModel: BindableBase
{
public UserListViewModel()
{
//I am sure there are better ways of doing this :(
Users = new ObservableCollection<User>((MainWindow)Application.Current.MainWindow).ClientData.Users
});
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get{ return _users;}
set { this.SetProperty(ref this._users, value); }
}
}
The difficulty I have here, is when the ClientConnectionOnDataReceived event on the MainWindow gets fired, I would like to update my ClientData class, My Viewmodel should then somehow be notified that the list changed, and subsequently update my UI.
Can anyone give me a solid example of how to achieve this using MVVM (Prism) in WPF?
I am new to MVVM, so i am still trying to figure this out.
First of all, there's no obvious reason why the main window should do the subscription.
I'd go for something like this:
create a service that encapsulates the subscription (and subscribes in its constructor)
register that as a singleton
have it implement INotifyPropertyChanged (to notify consumers of a change to Users)
inject the service into UserListViewModel and observe the Users property (see PropertyObserver)
when Users in the service changes, update Users in the user list view model
and best of all, no need for ObservableCollection here :-)
EDIT: example:
interface IUserService : INotifyPropertyChanged
{
IReadOnlyCollection<User> Users
{
get;
}
}
class YakUdpService : BindableBase, IUserService
{
private readonly YakUdpClient _yakUdpClient;
private IReadOnlyCollection<User> _users;
public YakUdpService()
{
_yakUdpClient = new YakUdpClient();
_yakUdpClient.OnDataReceived += ( s, e ) => Users = e.ConnectedUsers;
}
public IReadOnlyCollection<User> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
class UserListViewModel : BindableBase
{
private IReadOnlyCollection<UserViewModel> _users;
private readonly IUserService _userService;
private readonly PropertyObserver<IUserService> _userServiceObserver;
public UserListViewModel( IUserService userService )
{
_userService = userService;
_userServiceObserver = new PropertyObserver<IUserService>( userService );
_userServiceObserver.RegisterHandler( x => x.Users, () => Users = _userService.Users.Select( x => new UserViewModel( x ) ).ToList() );
// ^^^ should use factory in real code
}
public IReadOnlyCollection<UserViewModel> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
and then register the service
Container.RegisterType<IUserService, YakUdpService>( new ContainerControlledLifetimeManager() );
in your bootstrapper or your module's initialization.
I have a need to switch the view being displayed based on a certain condition.
I have implemented the switching logic in the constructor of the ViewModel
I am implementing IRegionMemberLifetime on the View and setting KeepAlive to false so that I always get a new instance of the View and the ViewModel.
But for some reason, when I click on the Navigation Button, my breakpoint at KeepAlive never reaches and I get the MainView instead of the WelcomeView.
Here is the code for your reference:
Navigation Button:
<Controls:SignedButton VerticalAlignment="Top" Width="275" Height="45"
Foreground="#FFFFFF"
LeftSign="<" Text="Back to Accounts"
TextSize="20" ButtonBackground="#666666"
HoverBackground="#0FBDAC" HoverOpacity="1" Margin="0,25,0,0"
Command="{x:Static Infrastructure:ApplicationCommands.NavigateCommand}"
CommandParameter="{x:Type Views:MainView}"/>
View Model:
[RegionMemberLifetime(KeepAlive = false)]
public class MainViewModel : ViewModel, IMainViewModel
{
private readonly IUnityContainer _container;
private readonly IRegionManager _regionManager;
public MainViewModel(IUnityContainer container, IRegionManager regionManager)
{
_container = container;
_regionManager = regionManager;
Accounts = new List<Account>();
if (Accounts.Any()) return;
IRegion region = _regionManager.Regions[Regions.Main];
var views = region.Views;
foreach (var view in views)
{
region.Remove(view);
}
region.Add(_container.Resolve<IWelcomeView>());
}
public IList<Account> Accounts { get; private set; }
}
View Model Base:
public abstract class ViewModel : IViewModel
{
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
View:
[RegionMemberLifetime(KeepAlive = false)]
public partial class MainView : UserControl, IMainView
{
public MainView(IMainViewModel mainViewModel)
{
InitializeComponent();
ViewModel = mainViewModel;
}
public IViewModel ViewModel
{
get { return (IViewModel) DataContext; }
set { DataContext = value; }
}
}
Shell View Model:
public class ShellViewModel : ViewModel, IShellViewModel
{
private readonly IRegionManager _regionManager;
public ShellViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<object>(Navigate);
ApplicationCommands.NavigateCommand.RegisterCommand(NavigateCommand);
}
private void Navigate(object navigatePath)
{
if (navigatePath != null)
{
_regionManager.RequestNavigate(Regions.Main, navigatePath.ToString());
}
}
public DelegateCommand<object> NavigateCommand { get; private set; }
}
It would be useful for you to look into the RegionMemberLifetimeBehavior class in the Prism Library (for me, it is at C:\Prism4\PrismLibrary\Desktop\Prism\Regions\Behaviors)
Both the IRegionMemberLifetime interface and the RegionMemberLifetimeAttribute accomplish the same thing and can be defined on either your View or your ViewModel (provided the viewmodel is set to DataContext).
Here's the code that is relevant:
private void OnActiveViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// We only pay attention to items removed from the ActiveViews list.
// Thus, we expect that any ICollectionView implementation would
// always raise a remove and we don't handle any resets
// unless we wanted to start tracking views that used to be active.
if (e.Action != NotifyCollectionChangedAction.Remove) return;
var inactiveViews = e.OldItems;
foreach (var inactiveView in inactiveViews)
{
if (!ShouldKeepAlive(inactiveView))
{
this.Region.Remove(inactiveView);
}
}
}
private static bool ShouldKeepAlive(object inactiveView)
{
IRegionMemberLifetime lifetime = GetItemOrContextLifetime(inactiveView);
if (lifetime != null)
{
return lifetime.KeepAlive;
}
RegionMemberLifetimeAttribute lifetimeAttribute = GetItemOrContextLifetimeAttribute(inactiveView);
if (lifetimeAttribute != null)
{
return lifetimeAttribute.KeepAlive;
}
return true;
}
I was able to even step this code to see how it interacts with my application. To answer your question, if your KeepAlive property is not getting hit, then it is not being removed from the ActiveViews. Also make sure that if you are resolving your view from a container through IoC that you have not registered it as a singleton type (that you get a new instance each time it is resolved). The attribute/interface will only remove it completely from the region, not guarantee you get a fresh instance.
I haveViewModel1 and View1 associated with it. I start dialog window from ViewModel2 (some another viewmodel) using IWindowManager object. The code from ViewModel2 class:
windowManager.ShowDialog(new ViewModel());
So, I have Dialog Window with View1 user control.
My answer is next - I can close that dialog window using red close button, but how to close it using my specific button (contained in View1 user control), something like "Cancel" button with close command (Command={Binding CancelCommand}), CancelCommand of course is contained in ViewModel1 class.
It's even easier if your view model extends Caliburn.Micro.Screen:
TryClose();
You can get the current view (in your case the dialog window) with implementing the IViewAware interface on your ViewModel. Then you can call Close on the the view (the Window created as the dialog) when your command is executed.
The easiest why is to derive from ViewAware:
public class DialogViewModel : ViewAware
{
public void ExecuteCancelCommand()
{
(GetView() as Window).Close();
}
}
If you are not allowed to derive you can implement it yourself:
public class DialogViewModel : IViewAware
{
public void ExecuteCancelCommand()
{
dialogWindow.Close();
}
private Window dialogWindow;
public void AttachView(object view, object context = null)
{
dialogWindow = view as Window;
if (ViewAttached != null)
ViewAttached(this,
new ViewAttachedEventArgs(){Context = context, View = view});
}
public object GetView(object context = null)
{
return dialogWindow;
}
public event EventHandler<ViewAttachedEventArgs> ViewAttached;
}
Note: I've used Caliburn.Micro 1.3.1 for my sample.
A cleaner way (Subject of personal taste) that I use alot is to use the IResult pattern, this way you abstract the Window implemenation
Viewmodel
public IEnumerable<IResult> CloseMe()
{
yield return new CloseResult();
}
Result code
public class CloseResult : Result
{
public override void Execute(ActionExecutionContext context)
{
var window = Window.GetWindow(context.View);
window.Close();
base.Execute(context);
}
}
public abstract class Result : IResult
{
public virtual void Execute(ActionExecutionContext context)
{
OnCompleted(this, new ResultCompletionEventArgs());
}
protected virtual void OnCompleted(object sender, ResultCompletionEventArgs e)
{
if (Completed != null)
Completed(sender, e);
}
public event EventHandler<ResultCompletionEventArgs> Completed;
}
edit (Only needed for IoC): If you wanna take it a step further you do a base class for all screens
public abstract class ShellPresentationModel : Screen
{
public ShellPresentationModel(IResultFactory resultFactory)
{
Result = resultFactory;
}
public IResultFactory Result { get; private set; }
}
This way you can inject dependencies with a IoC much easier, then your VIewmodel close method will look like this
public IEnumerable<IResult> CloseMe()
{
yield return Result.Close();
}
An example on a IResult that uses dependency can be
public class ShowDialogResult<TModel> : Result
{
private readonly IWindowManager windowManager;
private readonly TModel model;
private Action<TModel> configure;
public ShowDialogResult(IWindowManager windowManager, TModel model)
{
this.windowManager = windowManager;
this.model = model;
}
public IResult Configure(Action<TModel> configure)
{
this.configure = configure;
return this;
}
public override void Execute(ActionExecutionContext context)
{
if(configure != null)
configure(model);
windowManager.ShowDialog(model);
base.Execute(context);
}
}
edit Just noticed that i forgot to add an example of the above IoC exmaple, here goes
With a child IoC container pattern it would look like this
public IEnumerable<IResult> ShowDialog()
{
yield return Result.ShowDialog<MyViewModel>();
}
Without a child container pattern you would need to inject parent dependeync into the child manually
yield return Result.ShowDialog<MyViewModel>().Configure(m => m.SomeData = this.SomeData);