How to navigate through windows with MVVM Light for WPF? - wpf

I've just started a new project in which the presentation layer will be done by WPF and MVVM Light by GalaSoft.
I need a lot of views and it's not clear to me how to manage navigation through windows.
First of all, the templates offered in MVVM Light for creating a new "WPF MVVM View" create a new Window that is not possible to use for navigation by frame (I mean, by putting a frame in mainView and changing the source path to navigate).
Do I simply have to change Window to Page for all the views I create using templates?
Or is there a different way to perform navigation in WPF with the MVVM Light toolkit?

I usually use a ContentControl to display dynamic content. It's Content property is usually bound to a CurrentViewModel property in the parent ViewModel, and DataTemplates are used to tell WPF how to draw the child ViewModels.
To change views, simply change the CurrentViewModel property in the parent ViewModel
You can find an example at this article of mine

Eventually I did it this way.
Following the idea of o_q, I created NavigationWindow as MainWindow and changed all the the views to page.
Then, I created an inteface and a class which using Navigation:
public interface INavigationService
{
event NavigatingCancelEventHandler Navigating;
void NavigateTo(Uri pageUri);
void GoBack();
}
public class NavigationService : INavigationService
{
private NavigationWindow _mainFrame;
#region Implementation of INavigationService
public event NavigatingCancelEventHandler Navigating;
public void NavigateTo(Uri pageUri)
{
if (EnsureMainFrame())
{
_mainFrame.Navigate(pageUri);
}
}
public void GoBack()
{
if (EnsureMainFrame()
&& _mainFrame.CanGoBack)
{
_mainFrame.GoBack();
}
}
#endregion
private bool EnsureMainFrame()
{
if (_mainFrame != null)
{
return true;
}
_mainFrame = System.Windows.Application.Current.MainWindow as NavigationWindow;
if (_mainFrame != null)
{
// Could be null if the app runs inside a design tool
_mainFrame.Navigating += (s, e) =>
{
if (Navigating != null)
{
Navigating(s, e);
}
};
return true;
}
return false;
}
}
Then, in viewModelLocator I created all the const string nedded to store the paths to my views:
public class ViewModelLocator
{
#region Views Paths
public const string FrontendViewPath = "../Views/FrontendView.xaml";
public const string BackendViewPath = "../Views/BackendView.xaml";
public const string StartUpViewPath = "../Views/StartUpView.xaml";
public const string LoginViewPath = "../Views/LoginView.xaml";
public const string OutOfOrderViewPath = "../Views/OutOfOrderView.xaml";
public const string OperativeViewPath = "../Views/SubViews/OperativeView.xaml";
public const string ConfigurationViewPath = "../Views/SubViews/ConfigurationView.xaml";
#endregion
}
In App.cs, in the Application_Startup event handler, with the help of Unity IoC I registered a singleton of NavigationService:
public partial class App : System.Windows.Application
{
private static IUnityContainer _ambientContainer;
public static IServiceLocator AmbientLocator { get; private set; }
...
private void Application_Startup(object sender, System.Windows.StartupEventArgs e)
{
_ambientContainer =
new UnityContainer();
_ambientContainer.RegisterType<INavigationService, NavigationService>(new ContainerControlledLifetimeManager());
AmbientLocator = new UnityServiceLocator(_ambientContainer);
ServiceLocator.SetLocatorProvider(() => AmbientLocator);
Now, in my ViewModelLocator, I can register a "Galasoft" message to catch all the events and navigate to a page; in the constructor I have:
public ViewModelLocator()
{
CreateMain();
CreateFrontend();
CreateBackend();
CreateStartUp();
CreateOperative();
CreateLogin();
CreateConfiguration();
CreateOutOfOrder();
// Set Startup Page...
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath, UriKind.Relative));
Messenger.Default.Register<MoveToViewMessage>(this, message =>
{
switch (message.StateInfo.StateType)
{
case StateType.StartUpState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath,UriKind.Relative));
break;
case StateType.LoginState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(LoginViewPath, UriKind.Relative));
break;
case StateType.OperativeState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(OperativeViewPath, UriKind.Relative));
break;
case StateType.ConfigurationState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(ConfigurationViewPath, UriKind.Relative));
break;
case StateType.ClosedState:
case StateType.OutOfOrderState:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(OutOfOrderViewPath, UriKind.Relative));
break;
default:
ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath, UriKind.Relative));
break;
}
});
}
In this way I keep all the viewModels "ignorant"... they don't know anything about navigation, plus I don't have code behind.
If I need to navigate by using a button from a view I can resolve NavigationService from the connected viewModel and navigate to the Page I need.
And, most important thing, it works!

For a navigable application, you'll want your start up view to be a NavigationWindow instead of a Window
<NavigationWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MainWindow"
Title="My Application Title"
Height="300"
Width="400" />
Code behind:
using System.Windows.Navigation;
public partial class MainWindow : NavigationWindow
{
public MainWindow()
{
InitializeComponent();
}
}
The MVVM Light view templates use Window, but as you have guessed, you can just change it. If you want to be able to navigate to and from this view, make it a Page.
This is how you navigate:
<Page
x:Class="Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1">
<Grid>
<!-- this button will navigate to another page -->
<Button
Content="Go to Page 2"
Click="Button_Click" />
</Grid>
</Page>
Code Behind:
using System.Windows;
using System.Windows.Controls;
public partial class Page1 : Page
{
public Page1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// the Page class has a property "NavigationService" which allows you to navigate.
// you can supply the "Navigate" method with a Uri or an object instance of the page
base.NavigationService.Navigate(new Page2());
}
}

Related

Data binding doesn't work on Custom controls inside collection

WPF Data binding doesnt work for custom controls that are defined inside a xaml collection tag. I just want to define a collection of custom widgets inside a custom control and bind some widgets properties against viewmodel properties. Like so.
<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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget ImportantToggle="{Binding SomeToggle}"/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</Grid>
</Window>
That is my custom control. I use an obseravblecollection for the widgets and call SetValue in the constructor to get propertychanged callback later (right now not used in example)
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
namespace WpfApp1
{
public class MyCustomControl : FrameworkElement
{
public ObservableCollection<MyCustomWidget> Widgets
{
get { return (ObservableCollection<MyCustomWidget>)this.GetValue(WidgetsProperty); }
set { this.SetValue(WidgetsProperty, value); }
}
public static DependencyProperty WidgetsProperty = DependencyProperty.Register("Widgets", typeof(ObservableCollection<MyCustomWidget>), typeof(MyCustomControl), new PropertyMetadata(null, (e, args) => ((MyCustomControl)e).WidgetsChanged(args)));
public void WidgetsChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("widgets collection object changed inside my custom control!");
}
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
}
}
}
and that is my custom widget:
namespace WpfApp1
{
public class MyCustomWidget : FrameworkContentElement
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty = DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget), new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
}
And finally my simplistic ViewModel:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApp1
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _someToggle;
public bool SomeToggle
{
get { return this._someToggle; }
set
{
this._someToggle = value;
this.NotifyPropertyChanged();
}
}
public MainViewModel()
{
this.SomeToggle = !this.SomeToggle;
}
}
}
Thats the output I get from Debug.Writeline: widgets collection object changed inside my custom control!
Observation: I cant bind against properties of MyCustomWidget. I understand that the binding might fail in this scenario because the observablecollection is created inside of the constructor of mycustomcontrol, but I dont know how to fix it to get the binding working inside mycustomwidget.
For that binding to work, your local:MyCustomWidget needs to have the same DataContext as the main window. WPF elements inherit their logical parent's DataContext. MyCustomWidget doesn't, because it's not in the logical tree. It's just sitting there. You're not adding it to any kind of normal child collection of its parent, just to a random ObservableCollection that the framework doesn't know about.
The code below is probably a crude hack. I haven't investigated this corner of WPF. I urge you with the utmost sincerity to find out the right way of doing this. But with this addition to your code, I hit the propertychanged event in MyCustomWidget when the binding is initialized.
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
Widgets.CollectionChanged += Widgets_CollectionChanged;
}
private void Widgets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems is System.Collections.IEnumerable)
{
foreach (MyCustomWidget widget in e.NewItems)
{
AddLogicalChild(widget);
}
}
}
By the way, you can save the trouble of toggling the toggle in the MainViewModel constructor. That happens long before the binding exists. I added a checkbox instead:
<StackPanel>
<CheckBox IsChecked="{Binding SomeToggle}">Test Toggle</CheckBox>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</StackPanel>
Update:
This omits your Widgets collection entirely, and the binding works without any effort on our part. The child widgets will be in MyCustomControl.Children. Importantly that we aren't limiting the child type to MyCustomWidget any more. That's a significant design change, and may not fit your requirements. You could examine the Panel class closely, and write a class that works the same way, but accepts only one type of child (that would mean writing an analog of UIElementCollection, which will be mostly a big pile of tedious boilerplate).
MyCustomControl.cs
[ContentProperty("Children")]
public class MyCustomControl : Panel
{
}
MyCustomWidget.cs
public class MyCustomWidget : Control
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty =
DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget),
new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
MainWindow.xaml
<local:MyCustomControl>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl>

How can I move the keyboard focus to the view after ReactiveUI routing

Hello I'm using ReactiveUI for my Wpf application and I run into a problem. My mainscreen uses a RouterViewHost to display the view that is currently active.
After my mainscreen routes to a new view the keyboard focus moves to the Mainform and I would like to have it moved to the first focusable control inside the view that is currently displayed in the RoutedViewHost.
What is the best way to achieve this. I found that the following code works, but is that the best/desired way to do this?
RoutedViewHost.TransitionCompleted += (s, e) => RoutedViewHost.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
Thanks Niek
I don't use the built in routing, but could you use this.WhenActivated in the view's code behind?
public partial class FooView : UserControl, IViewFor<FooViewModel>
{
public FooView()
{
InitializeComponent();
this.WhenActivated(d =>
{
SomeTextField.Focus();
});
}
public FooViewModel ViewModel
{
get { return (FooViewModel) GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(FooViewModel), typeof(FooView), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (FooViewModel) value; }
}
}

ICommand with MVVM in WPF

I am new to MVVM and WPF, trying to use ICommand in WPF and MVVM. Below is the code.
Can someone please help to know why the below code is not working, means nothing happens on button click.
Appreciate your help.
View
<Grid>
<Button Height="40" Width="200" Name="button1" Command="{Binding Path=Click}">Click Me</Button>
</Grid>
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow mainWindow = new MainWindow();
MainWindowViewModel vm = new MainWindowViewModel();
mainWindow.DataContext = vm;
}
}
MainWindowViewModel.cs
namespace TestWPFApplication.ViewModel
{
public class MainWindowViewModel
{
private ICommand _click;
public ICommand Click
{
get
{
if (_click == null)
{
_click = new CommandTest();
}
return _click;
}
set
{
_click = value;
}
}
private class CommandTest : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Hi! Test");
}
}
}
}
It looks like your OnStartup method is instantiating a MainWindow and never showing it. You probably have the StartupUri set in XAML which is creating a different MainWindow with the data context not set.
You could remove the StartupUri and call mainWindow.Show(). Alternatively, you could get rid of the OnStartup method and set up the data context in the main window's constructor.
You don't need to initialize this Window in OnStartup.
In MainWindow constructor after Initialize create instance of ViewModel and it should work.

Loading Views into ContentControl and changing their properties by clicking buttons

I have a mvvm(model view viewmodel) silverlight application that has several views that need to be loaded into ContentControls (i made it all in expression blend). What i dont know how to do is, for example, to load one view (user control) in one content control by clicking a button from another view that is in another content control. To make it easier to understand the problem, i need to do something similar to this:
http://www.codeproject.com/KB/silverlight/BlendableVMCom.aspx
with that difference that child1 and child2 are supposed to be loaded into theirown content controls by clicking Call child1 or call child2 buttons.
and example would be appreciated. Thanks in advance!
This example is very simplified, but I think you now how to adjust it to your application.
The main view:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border x:Name="commandsView">
<Button Content="Call view 1" Command="{Binding CallView1Command}" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="5" />
</Border>
<Border x:Name="displayedView" Grid.Column="1">
<ContentControl Content="{Binding CurrentView}" />
</Border>
</Grid>
I haven't created separated views as user controls, here are just borders, which can be replaced by real views.
Different view models for different views in code behind:
this.commandsView.DataContext = new CommandsViewModel();
this.displayedView.DataContext = new DisplayedViewModel();
First view model conains the command which sends the message to another view model:
public class CommandsViewModel
{
public CommandsViewModel()
{
this.CallView1Command = new RelayCommand(() =>
Messenger.Default.Send<View1Message>(new View1Message()));
}
public RelayCommand CallView1Command { get; set; }
}
public class View1Message : MessageBase
{
}
To make this example work, download the MVVM Light library.
The second view model receive the message and creates a view for its property:
public class DisplayedViewModel : ViewModelBase
{
public DisplayedViewModel()
{
Messenger.Default.Register<View1Message>(this, obj =>
this.CurrentView = new TextBlock { Text = "Pressed the button 1 and now here is the view 1" });
}
private object currentView;
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
RaisePropertyChanged("CurrentView");
}
}
}
Again, it is possible to use clr object instead of controls and apply data templates in xaml, but there will not be enough space to provide all the resulting code.
So that is all, the main idea is a some kind of event aggregator, which is the Messenger class in this particular case.
Without the MVVM Light it will require more code:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var events = new GlobalEvents();
this.commandsView.DataContext = new CommandsViewModel(events);
this.displayedView.DataContext = new DisplayedViewModel(events);
}
}
public class GlobalEvents
{
public event EventHandler View1Event = delegate { };
public void RaiseView1Event()
{
View1Event(this, EventArgs.Empty);
}
}
/// <summary>
/// Commands which call different views
/// </summary>
public class CommandsViewModel
{
public CommandsViewModel(GlobalEvents globalEvents)
{
this.CallView1Command = new DelegateCommand(globalEvents.RaiseView1Event);
}
public DelegateCommand CallView1Command { get; set; }
}
/// <summary>
/// Model where views are changed and then displayed
/// </summary>
public class DisplayedViewModel : INotifyPropertyChanged
{
public DisplayedViewModel(GlobalEvents globalEvents)
{
globalEvents.View1Event += (s,e) =>
this.CurrentView = new TextBlock { Text = "Pressed the button 1 and now here is the view 1" };
}
private object currentView;
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
RaisePropertyChanged("CurrentView");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In this example you must change the DelegateCommand class for something different. Other code will work for everyone.
It sounds like you might be trying to do some sort of navigation. If that's true, check out the Silverlight navigation framework.

How to close a ChildWindow with Cancel button using MVVM Light Toolkit

I'm new to MVVM and trying to figure out how to close a ChildWindow with the traditional Cancel button using MVVM Light Toolkit.
In my ChildWindow (StoreDetail.xaml), I have :
<Button x:Name="CancelButton" Content="Cancel" Command="{Binding CancelCommand}" />
In my ViewModel (ViewModelStoreDetail.cs), I have :
public ICommand CancelCommand { get; private set; }
public ViewModelStoreDetail()
{
CancelCommand = new RelayCommand(CancelEval);
}
private void CancelEval()
{
//Not sure if Messenger is the way to go here...
//Messenger.Default.Send<string>("ClosePostEventChildWindow", "ClosePostEventChildWindow");
}
private DelegateCommand _cancelCommand;
public ICommand CancelCommand
{
get
{
if (_cancelCommand == null)
_cancelCommand = new DelegateCommand(CloseWindow);
return _cancelCommand;
}
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
If you displayed your child window by calling ShowDialog(), then you can simply set the IsCancel property of your button control to "True".
<Button Content="Cancel" IsCancel="True" />
It becomes the same as clicking the X button on the window, or pressing ESC on the keyboard.
Have a look at this articleon MSDN. About half way down there is an approach on how to do this. Basically it uses either uses a WorkspaceViewModel or you implements an interface that exposes and event RequestClose
You then inside the Window's DataContext (if you are setting the ViewModel to it) you can attach to the event.
This is an excerpt from the article (Figure 7). You can adjust it to suit your needs.
// In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
// Create the ViewModel to which
// the main window binds.
string path = "Data/customers.xml";
var viewModel = new MainWindowViewModel(path);
// When the ViewModel asks to be closed,
// close the window.
viewModel.RequestClose += delegate
{
window.Close();
};
// Allow all controls in the window to
// bind to the ViewModel by setting the
// DataContext, which propagates down
// the element tree.
window.DataContext = viewModel;
window.Show();
}
It's been a while since I've used WPF and MVVMLight but yes I think I'd use the messanger to send the cancel event.
In MVVM Light Toolkit the best what you can do is to use Messenger to interact with the View.
Simply register close method in the View (typically in the code behind file) and then send request to close a window when you need it.
We have implemented a NO-CODE BEHIND functionality. See if it helps.
EDIT: Here is there Stackoverflow discussion
Here are some ways to accomplish it.
Send message to your childwindow and set DialogueResult to false on childwindow code-behind.
Make property of DialogueResult and Bind it with childwindow Dialoue CLR property, set it on CancelEval method of CancelCommand.
Create object of Childwindow and set DialogueResult false on CancelEval.
Kind of late to the party but I thought I'd add my input. Borrowing from user841960's answer:
public RelayCommand CancelCommand
{
get;
private set;
}
Then:
SaveSettings = new RelayCommand(() => CloseWindow());
Then:
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
It's a bit cleaner than using an ICommand and works just as well.
So, to sum it all up, the example class would look like so:
public class ChildViewModel
{
public RelayCommand CancelCommand
{
get;
private set;
}
public ChildViewModel()
{
SaveSettings = new RelayCommand(() => CloseWindow());
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
}

Resources