I think I might be doing something totally wrong here, so hopefully you guys can correct me.
I want to switch Views (switch UserControls inside a Window) using a button, then have the other View be able to have a button that switches back to the first View (like a back button). I've found how to do this using the Google, but the problem I'm running into is that I have my first View's DataContext bound to one ViewModel and the code for switching views is in another, so I can't seem to bind the button in the first View to Bind its Command to that other ViewModel. Here's what I mean (simplified):
FirstView.xaml.cs:
private readonly FirstViewModel viewModel = new FirstViewModel();
public AirplanesStatusView()
{
InitializeComponent();
DataContext = viewModel; //Bound to this viewModel for other reasons
}
FirstView.xaml:
<UserControl>
<Grid>
<Button Command="{Binding GotoSecondViewCommand}}" //This Command is in other MainWindowViewModel
</Grid>
</UserControl>
MainWindow.xaml.cs:
private readonly MainWindowViewModel mainWindowViewModel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = mainWindowViewModel;
}
So then from what I found on Google, this is how it said to approach switching Views:
MainWindow.xaml:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:FirstViewModel}">
<local:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SecondViewModel}">
<local:SecondView/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentView}" />
</Window>
MainWindowViewModel.cs:
private ICommand _gotoFirstViewCommand;
private ICommand _gotoSecondViewCommand;
private object _currentView;
public ICommand GotoFirstViewCommand{ //stuff }
public ICommand GotoSecondViewCommand{ //stuff }
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
The problem is when I run the program, in the output it says "System.Windows.Data Error: 40 : BindingExpression path error: 'GotoSecondViewCommand' property not found on 'object' ''FirstViewModel', so the Button can't even find the command. I'm pretty sure I'm doing something totally wrong as I'm pretty new to WPF and the MVVM patter. Help and correction would be greatly appreciated.
Try this:
<Button Command="{Binding DataContext.GotoSecondViewCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
It binds to the DataContext of the parent window, i.e. the MainWindowViewModel where the GotoSecondViewCommand is defined.
You are binding wrong ViewModel as DataContext. Based on you structure, you need to do...
Take Command for the Button in FirstViewModel for moving to SecondView.
Take Command for the Button in SecoondViewModel for moving to FirstView.
Remove command from MainViewModel because it is of no use.
Put relative business logic in command execution to transfer the views.
Your CurrentView is nothing but the current user control (either FirstView / Second View)
Another way is if you want to take command in MainViewModel
Take only one ICommand as "ChangeViewsCommand"
Detect the current View either FirstView / Second View) from CurrentView
Transfer to another view which is not active.
Related
I have a WPF application with multiple views. I want to switch from view 1 to view 2 and from there I can switch to multiple views. So I want a button on view 1 that loads view2 in the same window.
I tried those things, but can't get it to work.
How to navigate through windows with MVVM Light for WPF?
https://galasoft.ch/posts/2011/01/navigation-in-a-wp7-application-with-mvvm-light
From the first link, the problem is that I don't understand the ViewModelLocator code. They call the CreateMain(); function but where is this defined, and how can I switch to another view from inside a view.
Firstly, you don't need any of those toolkits/frameworks to implement MVVM. It can be as simple as this... let's assume that we have a MainViewModel, and PersonViewModel and a CompanyViewModel, each with their own related view and each extending an abstract base class BaseViewModel.
In BaseViewModel, we can add common properties and/or ICommand instances and implement the INotifyPropertyChanged interface. As they all extend the BaseViewModel class, we can have this property in the MainViewModel class that can be set to any of our view models:
public BaseViewModel ViewModel { get; set; }
Of course, you'd be implementing the INotifyPropertyChanged interface correctly on your properties unlike this quick example. Now in App.xaml, we declare some simple DataTemplates to connect the views with the view models:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use one of our BaseViewModel instances in our application, these DataTemplates will tell the framework to display the related view instead. We can display them like this:
<ContentControl Content="{Binding ViewModel}" />
So all we need to do now to switch to a new view is to set the ViewModel property from the MainViewModel class:
ViewModel = new PersonViewModel();
Finally, how do we change the views from other views? Well there are several possible ways to do this, but the easiest way is to add a Binding from the child view directly to an ICommand in the MainViewModel. I use a custom version of the RelayComand, but you can use any type you like and I'm guessing that you'll get the picture:
public ICommand DisplayPersonView
{
get { return new ActionCommand(action => ViewModel = new PersonViewModel(),
canExecute => !IsViewModelOfType<Person>()); }
}
In the child view XAML:
<Button Command="{Binding DataContext.DisplayPersonView, RelativeSource=
{RelativeSource AncestorType={x:Type MainView}}, Mode=OneWay}" />
That's it! Enjoy.
When i first started wiht MVVM I also struggled with the different MVVM-frameworks and especially the navigation part. Therefore I use this little tutorial i found, that Rachel Lim has created. It's very nice and well explained.
Have a look at it on the following link:
http://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Hope it helped you :)
Maybe this link will help you. Just set the NavigateTo property to the view which you need to display on the window.
As an example you can do something like
<Window x:Class="MainWindowView" 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:meffed="http:\\www.codeplex.com\MEFedMVVM"
meffed:ViewModelLocator.NonSharedViewModel="YourViewModel"
WindowStartupLocation="CenterScreen">
<Button meffed:NavigationExtensions.NavigateTo="firstview"
meffed:NavigationExtensions.NavigationHost="{Binding ElementName=_viewContainer}"
meffed:NavigationExtensions.NavigateOnceLoaded="False"
Visibility="Visible" />
<ContentControl x:Name="_viewContainer" Margin="0,0,0,10" />
<Window>
Then the class file would be
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
}
public ContentControl ViewContainer { get { return _viewContainer; } }
}
Then you can define each view as UserControl and then using the link I gave above bind the button's meffed:NavigationExtensions.NavigateTo="secondView". To target the ContentControl of the Window just use a RelativeSource binding. For e.g
meffed:NavigationExtensions.NavigationHost="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}},Path=ViewContainer}"
In each of the view just see that you annotate the code behind class definition with the [NavigationView("firstview")] and so on.
It is complicated for first time but it will be very easy once you understand the idea.
<ContentControl x:Name="K.I.S.S" Content="{Binding ViewModel, Converter={StaticResource ViewLocator}}"/>
EDIT:
The problem was that my View Models were not public, so the bindings were not found. For some reason they need to be public in Silverlight, even though private view models work for WPF. More detail in my answer below.
I am trying to implement an MVVM WPF app in Silverlight, but I have never used Silverlight before. I am struggling to get the bound content to display. I am implementing it almost exactly how I did with WPF, except that the Main bit is a UserControl, not a Window, the app is hosted by a Web Site, and I had to remove the x:Type in DataTemplate DataType
My Website-to-MainPage is working properly, because I can display a text box or something in that page, however the Binding is just not displayed. Any suggestions?
My MainPage.xaml.cs:
public partial class MainPage : UserControl
{
private MainPageViewModel _viewModel;
public MainPage()
{
InitializeComponent();
_viewModel = new MainPageViewModel();
this.DataContext = _viewModel;
}
}
My MainPage.xaml:
<UserControl x:Class="TransformationServices.Silverlight.MainPage"
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" d:DesignHeight="300" d:DesignWidth="400"
xmlns:view="clr-namespace:TransformationServices.Silverlight.View"
xmlns:viewModel="clr-namespace:TransformationServices.Silverlight.ViewModel"
xmlns:local="clr-namespace:TransformationServices.Silverlight"
Background="#FF2D2D30">
<UserControl.Resources>
<local:ViewConverter x:Key="viewConverter"/>
<DataTemplate DataType="viewModel:InputSelectViewModel">
<TextBlock Text="Hello World!"/>
<!-- This textblock is just for testing. It doesn't work, and neither does the following line-->
<view:InputSelect/>
</DataTemplate>
</UserControl.Resources>
<ContentControl Content="{Binding CurrentView}" />
</UserControl>
My MainPageViewModel.cs
class MainPageViewModel : ViewModelBase, INotifyPropertyChanged
{
private ViewModelBase _currentView;
private List<ViewModelBase> _viewModels;
private int _viewIndex;
public ViewModelBase CurrentView
{
get { return _currentView; }
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
public MainPageViewModel()
{
_viewModels = new List<ViewModelBase>();
// Add view models here
_viewModels.Add(new InputSelectViewModel());
_viewIndex = 0;
CurrentView = _viewModels[_viewIndex];
}
}
It would appear as though the problem is that my ViewModels were not public. I noticed in the Output the following type of message:
System.Windows.Data Error: Cannot get '<Whatever property of View Model>' value ...
This message was coming up for every property that I was binding to, including CurrentView. After making the View Models public, the bindings worked perfectly. It seems odd to me that Silverlight would need the classes to be public when they worked fine as private for WPF, but this is my first time working with Silverlight, so this intricacy tripped me up. Hopefully this answer can help someone in the future. Anyway, this is the main reason for why the bindings were not working, and thus the CurrentView was not being displayed.
Another reason that it was hard for me to debug this issue is that my page was accidentally caching everything, so some of my changes would not display. This link helped me with that:
http://www.codeproject.com/Articles/143414/Prevent-your-Silverlight-XAP-File-from-Caching-in
I have a Window with a ContentControl inside. I want to show multiple view-filling UserControls like a wizard with multiple steps. Those UserControls need their own ViewModel and the possibility to replace themselves with another UserControl in the Window's ContentControl.
I want to work with the MVVM pattern and am currently struggling how to access the Window's ViewModel from the ViewModel of the UserControl.
Here is the simplified code I have so far. The content changing works without any problem when I change it inside the main ViewModel:
Window XAML:
<Grid>
<ContentControl Content="{Binding CurrentView}" />
</Grid>
Window ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private object currentView;
public object CurrentView
{
get { return currentView; }
private set
{
currentView = value;
OnPropertyChanged(); // <- Property name is set automatically, so no parameter needed
}
}
public MainWindowViewModel()
{
this.CurrentView = new UserControl1(); // Initial view to show within the ContentControl
}
}
UserControl1 XAML:
<UserControl>
<Grid>
<Button Command="{Binding SwitchToUserControl2}">Switch content</Button>
</Grid>
</UserControl>
Now I have the following "thinking problems":
If I set the DataContext of the UserControl to its ViewModel, I cannot access the MainWindowViewModel to change the CurrentView Property to UserControl2.
If I don't set the DataContext of the UserControl, I automatically inherit the correct ViewModel for Binding the Command to change the Content but haven't instantiated the ViewModel of the UserControl. I need this because many actions of the UserControl should be handled within it's own ViewModel.
In my understanding it is neccessary to have access to both ViewModels from the view but have no clue how to achieve this.
I would not have the MainWindowViewModel create a view, but rather create your first ViewModel. The ViewModel could then use events or any other mechanism to notify that it should transition to the next step.
The View portion can be handled easily in that case via DataTemplates that map the ViewModel to the appropriate View. The advantage here is that the ViewModel never knows about the View used to render it, which stays "pure" in an MVVM perspective. Right now, your ViewModel is manipulating the View layer, which is an MVVM violation.
Reed's answer is correct, and is one way to solve your problem, create the ViewModel of the control in the MainWindow, hook up the events and bind the ViewModel to the user control via a DependencyProperty.
To allow the binding of the ViewModel to work, make sure you do not set the DataContext in the Constructor of the UserControl or on the Root element of the Xaml of the UserControl. Instead, set the DataContext on the first content element of the UserControl. This will allow external bindings to the UserControl to continue working while the DataContext of the UserControl is what you want.
<UserControl x:Class="StackOverflow._20914503.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"
xmlns:this="clr-namespace:StackOverflow._20914503"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
</Grid>
</UserControl>
With regards to swapping controls in and out, Reed is again correct and DataTemplates are the way to go.
Another way to solve your communications problems is to use RoutedEvents. Create a RoutedEvent with in the application and since the event will have no real association to a ui element, lets create a class to publish the routed event.
public static class EventManagement
{
public static readonly RoutedEvent ChangeViewEvent = EventManager.RegisterRoutedEvent("ChangeView", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl));
}
Now, in each of the UserControls (and it must be done within the code behind of a UserControl), you can call RaiseEvent which is implemented in the UIElement class. In the following code, I am picking up an event from the ViewModel of my UserControl and firing the RoutedEvent
private void ViewModel_ChangeEvent(object sender, EventArgs e)
{
RaiseEvent(new RoutedEventArgs(EventManagement.ChangeViewEvent));
}
In my main window, without know where the RoutedEvent is going to be fired from, I can add a handler to the Routed event like so
public MainWindow()
{
InitializeComponent();
this.AddHandler(EventManagement.ChangeViewEvent, new RoutedEventHandler(SomeControl_ChangeView));
}
private void SomeControl_ChangeView(object sender, RoutedEventArgs routedEventArgs)
{
}
.Net will handle the routing of the event for you based on the RoutedEvent registration.
The advantage of this approach is the separation the functionality. Everything works without knowing anything else. You can use triggers to insert UserControl into the MainWindow, they can all Raise the same RoutedEvent, and the MainWindow will handle them all.
To Summarise the flow of control. The ViewModel of the UserControl raises a standard CLR event that the UserControl handles. The UserControl Raises the RoutedEvent. .Net Bubbles the event up to the main window. The main window receives the event via its handler.
A couple of points to note.
1. The default routing strategy for RoutedEvents is Bubbling (from lowest element, say a button, to the highest, say MainWindow).
1. An event will stop once a handler has flagged the event as Handled.
1. Routing is mostly done via the Visual Tree.
If necessary, I can post the component parts of my example.
I hope this helps.
I have a WPF application with multiple views. I want to switch from view 1 to view 2 and from there I can switch to multiple views. So I want a button on view 1 that loads view2 in the same window.
I tried those things, but can't get it to work.
How to navigate through windows with MVVM Light for WPF?
https://galasoft.ch/posts/2011/01/navigation-in-a-wp7-application-with-mvvm-light
From the first link, the problem is that I don't understand the ViewModelLocator code. They call the CreateMain(); function but where is this defined, and how can I switch to another view from inside a view.
Firstly, you don't need any of those toolkits/frameworks to implement MVVM. It can be as simple as this... let's assume that we have a MainViewModel, and PersonViewModel and a CompanyViewModel, each with their own related view and each extending an abstract base class BaseViewModel.
In BaseViewModel, we can add common properties and/or ICommand instances and implement the INotifyPropertyChanged interface. As they all extend the BaseViewModel class, we can have this property in the MainViewModel class that can be set to any of our view models:
public BaseViewModel ViewModel { get; set; }
Of course, you'd be implementing the INotifyPropertyChanged interface correctly on your properties unlike this quick example. Now in App.xaml, we declare some simple DataTemplates to connect the views with the view models:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use one of our BaseViewModel instances in our application, these DataTemplates will tell the framework to display the related view instead. We can display them like this:
<ContentControl Content="{Binding ViewModel}" />
So all we need to do now to switch to a new view is to set the ViewModel property from the MainViewModel class:
ViewModel = new PersonViewModel();
Finally, how do we change the views from other views? Well there are several possible ways to do this, but the easiest way is to add a Binding from the child view directly to an ICommand in the MainViewModel. I use a custom version of the RelayComand, but you can use any type you like and I'm guessing that you'll get the picture:
public ICommand DisplayPersonView
{
get { return new ActionCommand(action => ViewModel = new PersonViewModel(),
canExecute => !IsViewModelOfType<Person>()); }
}
In the child view XAML:
<Button Command="{Binding DataContext.DisplayPersonView, RelativeSource=
{RelativeSource AncestorType={x:Type MainView}}, Mode=OneWay}" />
That's it! Enjoy.
When i first started wiht MVVM I also struggled with the different MVVM-frameworks and especially the navigation part. Therefore I use this little tutorial i found, that Rachel Lim has created. It's very nice and well explained.
Have a look at it on the following link:
http://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Hope it helped you :)
Maybe this link will help you. Just set the NavigateTo property to the view which you need to display on the window.
As an example you can do something like
<Window x:Class="MainWindowView" 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:meffed="http:\\www.codeplex.com\MEFedMVVM"
meffed:ViewModelLocator.NonSharedViewModel="YourViewModel"
WindowStartupLocation="CenterScreen">
<Button meffed:NavigationExtensions.NavigateTo="firstview"
meffed:NavigationExtensions.NavigationHost="{Binding ElementName=_viewContainer}"
meffed:NavigationExtensions.NavigateOnceLoaded="False"
Visibility="Visible" />
<ContentControl x:Name="_viewContainer" Margin="0,0,0,10" />
<Window>
Then the class file would be
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
}
public ContentControl ViewContainer { get { return _viewContainer; } }
}
Then you can define each view as UserControl and then using the link I gave above bind the button's meffed:NavigationExtensions.NavigateTo="secondView". To target the ContentControl of the Window just use a RelativeSource binding. For e.g
meffed:NavigationExtensions.NavigationHost="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}},Path=ViewContainer}"
In each of the view just see that you annotate the code behind class definition with the [NavigationView("firstview")] and so on.
It is complicated for first time but it will be very easy once you understand the idea.
<ContentControl x:Name="K.I.S.S" Content="{Binding ViewModel, Converter={StaticResource ViewLocator}}"/>
I have a WPF Window which contains few UserControls, those controls contain another. And now, what is the most principal way how to create ViewModel for this Window and where to bind it.
I do expect that one firstly needs to create ViewModel for each of sub-controls.
There are a few ways to do this.
Inject the VM
I would recommend this method.
If your window is created in the App class like
var window = new MyWindow();
window.Show();
I would assign the VM before showing the window:
var window = new MyWindow();
window.DataContext = GetDataContextForWindow();
window.Show();
If one of your controls needs an own view model assign the VM wile creating the control instance.
DataBind
If you want to set the VM of a control you can bind the DataContext property to an VM instance provided by the surrounding VM.
<Controls:MyControl DataContext={Binding MyControlsVm} />
Code Behind
You may set the VM using the init method in code behind like
public MyWindow()
{
InitializeComponent();
DataContext = CreateViewModel;
}
You may use a trick if you don't want to create a VM for your main page:
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
and just use the code behind class as VM.
I see the view as a visual representation of the ViewModel so I like WPF picking the view based on the instance of the ViewModel it wants to render.
I call this the View Locator pattern, I use this method to instantiate my view because I have found it to be very simple to implement.
It basically puts an entry in the ResourceDictionary of your app that tells WPF to use an IValueConverter to look up and instantiate the View when it comes across a ViewModel.
So a working example would be:
In your app.xaml:
<Application x:Class="MyApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml" >
<Application.Resources>
<ResourceDictionary Source="Resources.xaml"/>
</Application.Resources>
</Application>
In resources.xaml:
<DataTemplate DataType="{x:Type vm:ViewModelBase}">
<ContentControl Content="{Binding Converter={StaticResource ViewModelConverter}}"/>
</DataTemplate>
Set the DataContext of your startup Window Control e.g.
public MainWindow : Window
{
InitializeComponent();
DataContext = new MainViewModel();
}
And you're pretty much done. So if you have a MainViewModel like so:
public class MainViewModel : ViewModelBase
{
public ChildViewModel1 Child1 {get;set;}
public ChildViewModel2 Child2 {get;set;}
}
and you have a UserControl that resolves to your MainViewModel like so:
<UserControl x:Class="MainView">
<StackPanel>
<ContentPresenter Content="{Binding Child1}"/>
<ContentPresenter Content="{Binding Child2}"/>
</StackPanel>
</UserControl>
So your ViewModelConverter will return an instance of the appropriate View without any extra effort on your part.
On the child controls issue, why wouldn't one of the properties of the root view model be an instance of the child view model that you would pass onto the child control? The other option would be a converter that converts the non-view model based property into an instance of the child view model (like an adapter pattern).
You might be interested in the sample applications of the WPF Application Framework (WAF). They show how composite Views and ViewModels can be instantiated and how they interact which each other.