.Net MAUI data binding not carrying through to custom component - maui

I am having trouble getting data binding to work with custom components.
I have created an IncrementValue property that gets incremented with every button click.
The changes are reflected when binded to a Label.
However they do not work when I bind it to a Bindable property in a custom component.
In the example, I have built a custom component called Card which has two bindable properties CardTitle and CardIncrement
Is there something I'm missing as I'm new to MAUI and even Xamarin.
Github link of code snippets below: https://github.com/814k31/DataBindingExample
Card.xaml.cs
namespace DataBindingExample;
public partial class Card : VerticalStackLayout
{
public static readonly BindableProperty CardTitleProperty = BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(Card), string.Empty);
public static readonly BindableProperty CardIncrementProperty = BindableProperty.Create(nameof(CardIncrement), typeof(int), typeof(Card), 0);
public string CardTitle
{
get => (string)GetValue(CardTitleProperty);
set => SetValue(CardTitleProperty, value);
}
public int CardIncrement
{
get => (int)GetValue(CardIncrementProperty);
set => SetValue(CardIncrementProperty, value);
}
public Card()
{
InitializeComponent();
BindingContext = this;
}
}
Card.xaml
<?xml version="1.0" encoding="utf-8" ?>
<VerticalStackLayout
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:databindingexample="clr-namespace:DataBindingExample"
x:DataType="databindingexample:Card"
x:Class="DataBindingExample.Card"
Spacing="25"
Padding="30,0"
VerticalOptions="Center"
BackgroundColor="red"
>
<Label
Text="{Binding CardTitle}"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center"
/>
<Label
Text="{Binding CardIncrement}"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center"
/>
</VerticalStackLayout>
MainPage.xml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBindingExample.MainPage"
xmlns:DataBindingExample="clr-namespace:DataBindingExample"
xmlns:ViewModels="clr-namespace:DataBindingExample.ViewModels"
x:DataType="ViewModels:MainPageViewModel"
>
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center"
>
<Label
Text="{Binding IncrementedValue}"
SemanticProperties.HeadingLevel="Level2"
FontSize="18"
HorizontalOptions="Center"
/>
<!-- Why doesnt this work? -->
<DataBindingExample:Card CardIncrement="{Binding IncrementedValue}" />
<Button
x:Name="CounterBtn"
Text="Click Me"
SemanticProperties.Hint="Counts the number of times you click"
Command="{Binding IncrementValueCommand}"
HorizontalOptions="Center"
/>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

When making a custom component (that includes XAML), DO NOT set BindingContext = this;.
REASON: You want the component to use the SAME BindingContext as the page it is placed in. This happens automatically, if you do NOT set a BindingContext in the custom component.
HOWEVER, removing this line breaks all your component's xaml Bindings; you'll need to add something to the xaml, to fix this.
Or to put it another way: How refer to the card's Properties from its XAML? See the next section.
ACCESS COMPONENT PROPERTIES VIA x:Name
Solution: Give the card an x:Name, and make that the "Source" of those bindings:
<VerticalStackLayout
...
x:Name="me" <-- IMPORTANT! Change name as desired.
x:Class="DataBindingExample.Card"
>
...
<Label Text={Binding CardIncrement, Source={x:Reference me}}"
...
Notice the two parts to this solution:
In component's xaml header, define x:Name="mynamehere".
In each Binding, say that the component is the source:
, Source={x:Reference mynamehere}.
OPTIONAL: If custom component has a "ViewModel":
To have a custom component be "data-driven", pass in a parameter that controls its behavior.
This parameter could be considered a "ViewModel", but above I have specified:
DO NOT set a BindingContext (so that component has easy access to the page's BindingContext).
So unlike other uses of ViewModel, in this technique, we don't set the ViewModel as the BindingContext.
How access this ViewModel?
By saving it as a property of the component; e.g.:
public partial class MyComponent : ContentView
{
private MyViewModel VM;
public void MyComponent(MyViewModel vm)
{
InitializeComponent();
VM = vm;
}
public class MyViewModel : ObservableObject
{
[ObservableProperty]
SomeType someProperty; // This is field. Property "SomeProperty" is generated.
}
Then in xaml, we access properties of VM, using . notation:
<Label Text={Binding VM.SomeProperty, Source={x:Reference me}}"

Related

How can I make a list view have alternating background colors? NET MAUI

I am trying to create a list view where the background color alternates for each entry in the list. Is there a way to do this in MAUI?
This can be done in multiple ways.And the method often recommended is through a DataTemplateSelector.
1.Create a DataTemplateSelector that holds two templates and selects them based on the index of the item:
public class AlternateColorDataTemplateSelector: DataTemplateSelector
{
public DataTemplate EvenTemplate { get; set; }
public DataTemplate UnevenTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
// TODO: cast `Monkey` to your Item
return ((List<Monkey>)((ListView)container).ItemsSource).IndexOf(item as Monkey) % 2 == 0 ? EvenTemplate : UnevenTemplate;
}
}
2.In XAML, we can define two templates, one with the alternate color and one with the normal color.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp0606.MainPage"
xmlns:local="clr-namespace:MauiApp0606"
>
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="evenTemplate">
<ViewCell>
<Grid BackgroundColor="White">
<Label Text="{Binding Name}" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="unevenTemplate">
<ViewCell>
<Grid BackgroundColor="LightGray">
<Label Text="{Binding Name}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</ViewCell>
</DataTemplate>
<local:AlternateColorDataTemplateSelector x:Key="alternateColorDataTemplateSelector"
EvenTemplate="{StaticResource evenTemplate}"
UnevenTemplate="{StaticResource unevenTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
<VerticalStackLayout Spacing="25" Padding="30">
<ListView ItemsSource="{Binding Monkeys}" ItemTemplate="{StaticResource alternateColorDataTemplateSelector}">
</ListView>
</VerticalStackLayout>
</ContentPage>
To solve this problem I added a LineNumber property to my view model and used a value converter to set the color.
The value converter:
public class NumberToAlternatingColorValueConverter : IValueConverter
{
public Color EvenNumberColor { get; init; } = Colors.Green;
public Color OddNumberColor { get; init; } = Colors.Red;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is not int intValue
? Colors.Transparent
: (intValue % 2) == 0
? EvenNumberColor
: OddNumberColor;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("NumberToAlternatingColorValueConverter.ConvertBack");
}
}
Register the value converter
I registered the value converter in my main resource dictionary like so:
<converter:NumberToAlternatingColourValueConverter
x:Key="MagentaCyanRowColorConverter"
EvenNumberColour="Magenta"
OddNumberColour="Cyan" />
You can register more than one value converter with a different key for different colour combinations.
Use the value converter
And I used the bound value converter in my XAML like so:
<StackLayout BindableLayout.ItemsSource="{Binding Summary.Lines}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="vm:SummaryLineViewModel">
<Grid BackgroundColor="{Binding LineNumber, Converter={StaticResource MagentaCyanRowColorConverter}}">
<Label Grid.Column="0" Text="{Binding Name}" />
<Label Grid.Column="1" Text="{Binding Value}" />
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
As in this answer we can used DataTemplateSelector to resolve your problem.
Alternative solutions,
You can use a property in the model and straight a way bind it to the row background. (In your case Grid's Background color)
You can use triggers make the color change by considering a logic.
( Xamarin Forms Triggers )

Xamarin Forms - Set BindingContext of a controltemplate

I have a xamarin forms application. It has a tabbedpage within it multiple tabs. The tabbedpage and te tabs, each of them has their own viewmodel as a bindingcontext.
In the app.xaml I defined a controltemplate. I use this control template in each tab, because I want each of those tabs to have a button at the bottom of the page.
At this moment: the button in the controltemplate binds with a property defined in each tab. But I want the button to bind at one place. Isn't it possible to create a viewmodel special for the controltemplate and bind the button defined in the controltemplate with that viewmodel?
Current code:
<ControlTemplate x:Key="ActivityStatusButton">
<StackLayout>
<ContentPresenter>
</ContentPresenter>
<StackLayout VerticalOptions="EndAndExpand" HorizontalOptions="Fill" Padding="15">
<Button Style="{StaticResource RedBackGroundWithWhiteTextButtonStyle}" Command="{TemplateBinding BindingContext.ClickOnStatusButton, Mode=TwoWay}" Text="{TemplateBinding BindingContext.ok, Mode=TwoWay}"></Button>
</StackLayout>
</StackLayout>
</ControlTemplate>
A typical tab:
<ContentPage ...>
<ContentPage.Content>
<Label Text="hello"></Label>
</ContentPage.Content>
<!--The control template is placed here (the button) -->
You could create a Custom Control (a subclass of ContentView) like
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="template"
x:Class="App24.MyControlTemplate">
<ContentView.Content>
<StackLayout VerticalOptions="EndAndExpand" HorizontalOptions="Fill" Padding="15">
<Button Clicked="Button_Clicked" Command="{Binding Source={x:Reference template},Path=ButtonCommand}" Text="{Binding Source={x:Reference template},Path=ButtonText}" CommandParameter="{Binding Source={x:Reference template},Path=CommandParameter}" />
</StackLayout>
</ContentView.Content>
</ContentView>
using System;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace App24
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyControlTemplate : ContentView
{
public event EventHandler ButtonClick;
public static readonly BindableProperty ButtonTextProperty =
BindableProperty.Create("ButtonText", typeof(string), typeof(MyControlTemplate), default(string));
public string ButtonText
{
get => ((string)GetValue(ButtonTextProperty));
set => SetValue(ButtonTextProperty, value);
}
public static readonly BindableProperty ButtonCommandProperty =
BindableProperty.Create("ButtonCommand", typeof(ICommand), typeof(MyControlTemplate), null, BindingMode.Default, null);
public ICommand ButtonCommand
{
get => (ICommand)GetValue(ButtonCommandProperty);
set
{
SetValue(ButtonCommandProperty, value);
}
}
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create("CommandParameter", typeof(object), typeof(MyControlTemplate), null);
public object CommandParameter
{
get => (object)GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public MyControlTemplate()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
ButtonClick?.Invoke(sender, e);
}
}
}
Now you could add it to any page and binding Text , Command or CommandParameter in code behind .
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<local:MyControlTemplate ButtonText="{Binding ButtonText}" ButtonCommand="{Binding ClickCommand}" CommandParameter="test" />
</StackLayout>

Using compiled bindings with Prism

I want to use compiled bindings in my Xamarin Forms app in combination with Prism.
I created a small xamarin forms app with a simple view, viewmodel and prism (prism:ViewModelLocator.AutowireViewModel="True"). Classic binding works as expected.
How should I implemented compiled binding without creating the binding context twice?
Classic binding with prism: HomePage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="CompiledBinding.Views.HomePage">
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
HomePageViewModel.cs:
using Prism.Mvvm;
using Prism.Navigation;
using System;
using Xamarin.Forms;
namespace CompiledBinding.ViewModels
{
public class HomePageViewModel : BindableBase
{
string _name = "Compiled binding test";
public HomePageViewModel(INavigationService navigationService)
{
var nav = navigationService;
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
// Do something
Name = DateTime.Now.ToString("yyyy MMMM dd hh:mm:ss");
return true; // True = Repeat again, False = Stop the timer
});
}
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
}
}
Adding the binding context to the xaml page again, is not an option:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:viewModels="clr-namespace:CompiledBinding.ViewModels"
x:Class="CompiledBinding.Views.HomePage"
x:DataType="viewModels:HomePageViewModel">
<ContentPage.BindingContext>
<viewModels:HomePageViewModel />
</ContentPage.BindingContext>
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Besides of defining the binding context again, it also results in the error: no public parameterless constructor.
Do I oversee something? Does anyone know how to work with compiled bindings together with prism?

Content view binding not working

I have ContentView which need ViewModel binding
Test.xaml
<ContentView.Content>
<Frame x:Name="HelpBaseFrame" BackgroundColor="White" CornerRadius="16" HorizontalOptions="Fill" VerticalOptions="Center">
<StackLayout>
<ListView HasUnevenRows="True" x:Name="lstview" SeparatorColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell IsEnabled="False">
<ScrollView x:Name="ScrollView" Orientation="Vertical" Padding="0, 1, 0, 0" BackgroundColor="Transparent" HorizontalOptions="Center">
<StackLayout>
<Label x:Name="LabelHeader" FontAttributes="Bold" Font="HiraginoSans-W6, 16"
HorizontalOptions="Center" Margin="0,20,0,0">
<Label.Text>
<Binding Path="HeaderData"></Binding>
</Label.Text>
</Label>
<local:LineSpacingLabel x:Name="LabelHeaderDesceiption" LineSpacing="6"
Font="HiraginoSans-W3, 16" FontAttributes="None" Margin="0,20,0,0">
<Label.Text>
<Binding Path="DescriptionData"></Binding>
</Label.Text>
</local:LineSpacingLabel>
</StackLayout>
</ScrollView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</Frame>
</ContentView.Content>
BindingClass
public void SetData(Dictionary<string, string> dictionary)
{
............
lstview.ItemsSource = HelpDataList; // HelpDataList is observable collection of HElp Data
}
Model class :
public class HelpData : BaseViewModel
{
private string Header = string.Empty;
private string Description = string.Empty;
public string HeaderData
{
get { return Header; }
set
{
Header = value;
OnPropertyChanged("HeaderData");
}
}
public string DescriptionData { get; set; }
}
This view model for above view.
This binding is not working.
Is anything wrong?
This view model for above view.
This binding is not working.
Is anything wrong?
This view model for above view.
This binding is not working.
Is anything wrong?
You have to set bindingContext on the target control by using x:Reference markup extension.
BindingContext="{x:Reference Name=ViewModelField}"
Or
BindingContext="{x:Reference ViewModelField}"
Label.Text should be binding with string not viewModel.
Text="{Binding Path=Value}"
Or
Text="{Binding Value}" ( “Path=” part of the markup extension can be omitted if the path is the first item in the Binding markup extension)
Refer to Bindings
Update
<Binding> is not a valid tag.
Modify the label :
<Label Text="{Binding Path = HeaderData}">

Collapse/Visible UserControls on ButtonClick with MVVM - no swap mechanism -

In my scenario I have a MainView + MainViewModel, UserControl1 + UserControl 2.
In the MainView I have 2 buttons labeled: Button_ShowUserControl1 + Button_ShowUserControl2.
At the lower part of the MainView I have a "ContentGrid" which takes/should_take... every UserControl.
My goal:
When Button_ShowUserControl1 is clicked UserControl1 is Visible and UserControl2 OR any other UserControl must be set to Collapsed. Same is valid for Button_ShowUserControl2.
My problem:
1.) As the UserControls shall be loaded at application start how can I put them all together in the one "ContentGrid"? Thats actually not possible... so how can I make one UserControl visible while the other is in the same place/"ContentGrid" just collapsed ?
2.) As 1.) seems not possible how can I instantiate all UserControls at start of the application and make them only Visible/Collapsed when respective Button is clicked?
3.) As a UserControl has a property Visibility = Visible/Hidden/Collapsed, how can I bind to a property in a ViewModel return such a value like Collapsed? I only could get a boolean value like Visibility = false/true ?
My testcode:
<Grid x:Name="LayoutRoot" Background="#FFBDF5BD" ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="96*" />
<RowDefinition Height="289*" />
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Stretch" Name="MenuGrid" VerticalAlignment="Stretch" Background="#FFCECEFF">
<StackPanel Name="stackPanel1" Background="#FFEDFF00" Orientation="Horizontal">
<Button Content="User Data 1" Height="35" Name="button1" Command="{Binding Path=ShowUserControl1Command}" Width="150" Margin="100,0,0,0" />
<Button Content="User Data 2" Height="35" Name="button2" Width="150" Margin="100,0,0,0" />
</StackPanel>
</Grid>
<Grid Grid.Row="1" HorizontalAlignment="Stretch" Name="ContentGrid" VerticalAlignment="Stretch" Background="#FFB15454" />
</Grid>
<UserControl x:Class="SwapUserControls.MVVM.UserControl2"
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:SwapUserControls.MVVM.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Visibility="{Binding IsUserControl1Collapsed, Path=Value}">
<UserControl.Resources>
<vm:MainViewModel x:Key="MainViewModelID" />
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource MainViewModelID}" />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="228*" />
<RowDefinition Height="72*" />
</Grid.RowDefinitions>
<Button Content="UserControl2" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="112,27,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
<DataGrid HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" Background="#FFC046F8" />
</Grid>
public class MainViewModel : ViewModelBase
{
RelayCommand _ShowUserControl1Command;
private bool _IsUserControl1Collapsed;
public RelayCommand ShowUserControl1Command
{
get
{
if (_ShowUserControl1Command == null)
{
_ShowUserControl1Command = new RelayCommand( () => ShowUserControl1() );
}
return _ShowUserControl1Command;
}
}
public void ShowUserControl1()
{
_IsUserControl1Collapsed = true;
}
public bool IsUserControl1Collapsed
{
get
{
return _IsUserControl1Collapsed;
}
}
}
Yes the code is wrong, therefore I ask here :)
You only have 2 things wrong with this code.
1) You can't set the visibility of a usercontrol directly... you have to set it on a container:
<Grid Visibility="Collapsed">
<myControls:MyUserControl />
</Grid>
2) Visibility is not a boolean value, it is an enum. As such, you will need to use a converter to convert from boolean to Visibility. Observe:
<Window ...>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>
<Grid Visibility="{Binding ShouldShowUsercontrol1, Converter={StaticResource BoolToVis}}">
<myControls:MyUserControl />
</Grid>
</Window>
That should be it. Hope this helps.
There are other things that you are leaving clues about that might affect the ability of this to work. For example, you don't show the biggest container element... are you wrapping everything in a StackPanel? If you are wrapping everything in a Grid, for example, the controls will overlay everything in layers.
Try these changes I suggest... it should get you closer.
Edit: Another idea using data templates
Another thing you could do is make sure you have a unique ViewModel for each of these views you want to show and hide:
public class MyFirstViewModel : ViewModel
{
}
public class MySecondViewModel : ViewModel
{
}
Then from your "parent" or "main" ViewModel, you show or hide the views you want by virtue of having them in a collection of ViewModels:
public MyMainViewModel : ViewModel
{
public ObservableCollection<ViewModel> ViewsToShow
{
...
}
public void ShowFirstViewModel()
{
ViewsToShow.Add(new MyFirstViewModel());
}
}
To wire everything up in your view, you would then datatemplate these types with their user controls (but this would not cause those views to be instantiated unless they were needed:
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type myViewModels:MyFirstViewModel}">
<myViews:MyFirstView />
</DataTemplate>
<DataTemplate DataType="{x:Type myViewModels:MySecondViewModel}">
<myViews:MySecondView />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding ViewsToShow}" />
</Window>
And for any ViewModels you put in "ViewsToShow", the view will automatically see that and template in the appropriate view. Again, without instantiating it before it's needed.
This is probably a little cleaner than putting everything single thing in the View and setting visibility, but it would be dependent on you have a unique view model type for every view, which might not be the case.
The question of saving state comes up when using the DataTemplated approach. The solution here is to tread your ViewModel as the state of the control and design both your ViewModels and your Views accordingly. Here is an example that allows you to swap out your Views using DataTemplating, but switching back and forth saves state.
Assume you have the setup from the last section w/ 2 viewmodels that have datatemplates defined. Let's change up the MainViewModel a little:
public MyMainViewModel : ViewModel
{
public RelayCommand SwapViewsCommand
{
...
}
public ViewModel View
{
...
}
private ViewModel _hiddenView;
public MyMainViewModel()
{
View = new MyFirstViewModel();
_hiddenView = new MySecondViewModel();
SwapViewsCommand = new RelayCommand(SwapViewModels);
}
public void SwapViewModels()
{
var hidden = _hiddenView;
_hiddenView = View;
View = hidden;
}
}
And a few changes to the main view. I've omitted the DataTemplates for brevity.
<Window ...>
<!-- DataTemplates Here -->
<Button Command="{Binding SwapViewsCommand}">Swap!</Button>
<ContentControl Content="{Binding View}" />
</Window>
That's it. The secret here is I'm saving the reference to the original view model. This way, let's say there is a string property in a viewmodel and an associated textbox in the DataTemplated usercontrol with a two-way binding then the state will essentially be saved.