Historically in WPF when using MVVM (typically done with MVVMLight) I use the approach where I bind a control to something in its view-model. To wit:
XAML:
xmlns:vm="clr-namespace:My.Namespace.ViewModels"
And then declare the data context in XAML using
DataContext="{Binding Main, Source={StaticResource Locator}}"
ViewModel:
public class MainViewModel : BaseViewModel
{
// Properties, methods, commands etc. here
}
MVVMLight kicked in and provides the ViewModel locator which allowed me to do the following in the App.xaml
<local:ViewModelLocator x:Key="locator"/>
local
is defined earlier in the XAML similar to how
My questions are:
What's the way to follow this kind of pattern now with MVVM Toolkit? The Migrating from MvvmLight article glossed over it. All the examples provided are far too complex for what I'm looking for.
Setting a whole Window's Data Context now appears impossible. Do I need to use the x:Bind
syntax for each control?
As an extension of 2. above the examples I've seen are all setting the Data Context within the code-behind. Is this now the recommended approach?
I got this approach years ago mainly from reading Josh Smith's website and some of the Reed Copsey's excellent answers on this site like: What is the preferred way to connect viewmodels to their views?
x:Bind does not use DataContext
as its source. Let me show you a simple sample code using the CommunitToolkit.Mvvm.
MainPage.xaml
<Page
x:Class="CommunityToolkitMvvmDemo.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:local="using:CommunityToolkitMvvmDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<StackPanel>
<TextBox
PlaceholderText="Enter..."
Text="{x:Bind ViewModel.SomeText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<!-- x:Bind is `OneTime` mode by default. -->
<TextBlock Text="{x:Bind ViewModel.SomeText, Mode=OneWay}" />
<Button
Command="{x:Bind ViewModel.ClearSomeTextCommand}"
Content="Clear" />
</StackPanel>
</Page>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
}
public MainPageViewModel ViewModel { get; } = new();
}
MainPageViewModel.cs
// This class needs to be `partial` for the source generator.
public partial class MainPageViewModel : ObservableObject
{
// The source generator will create a `SomeText` property for you.
[ObservableProperty]
private string _someText = string.Empty;
// The source generator will create a `ClearSomeTextCommand` for you.
[RelayCommand]
private void ClearSomeText() => SomeText = string.Empty;
}
As you can see, instead of using the DataContext
, I have a MainPageViewModel
property initialized in code-behind.
If you want to inject the ViewModel, you can just:
public MainPage(MainPageViewModel viewModel)
{
ViewModel = viewModel;
InitializeComponent();
}
Just so you know, you can still use Binding just like WPF:
<StackPanel>
<TextBox
PlaceholderText="Enter..."
Text="{Binding SomeText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding SomeText}" />
<Button
Command="{Binding ClearSomeTextCommand}"
Content="Clear" />
</StackPanel>
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
DataContext = new MainPageViewModel();
}
but you should use x:Bind whenever you can for performance and compile-time validation reasons.
UPDATE
If you want to set the DataContext
in XAML, you can:
<Page...>
<Page.DataContext>
<local:MainPageViewModel x:Name="ViewModel" />
</Page.DataContext>
<StackPanel>
<TextBox
PlaceholderText="Enter..."
Text="{x:Bind ViewModel.SomeText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{x:Bind ViewModel.SomeText, Mode=OneWay}" />
<Button
Command="{x:Bind ViewModel.ClearSomeTextCommand}"
Content="Clear" />
</StackPanel>
</Page>