Search code examples
c#mvvmbindingwinui-3xbind

x:Bind a embeded page to the parent window viewmodel in WinUI 3


I'm working on a welcome screen and it can navigate 5 pages displayed using "Frame" control in the main window.

Now I need to x:Bind some controls to the viewmodel of main window cause I don't want to create 5 viewmodels for all 5 pages. So how to implement this? Or how to pass the view model object of main window to 5 pages

Main window: WelcomeScreen.xaml

<Window
    x:Class="WelcomeScreen.WelcomeScreen"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WelcomeScreen"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="">
    <Grid ColumnDefinitions="*,*,*" RowDefinitions="*" >
        <StackPanel Grid.Column="0" Grid.ColumnSpan="3" Orientation="Vertical" >
            <Frame x:Name="NavigationFrame" />
            <Line Stroke="LightGray" X1="0" Y1="0" X2="1200" Y2="0" StrokeThickness="2" Margin="12,0,12,0"/>
            <RelativePanel>
                <CheckBox x:Name="DoNotShowAaginCheckBox" Content="Don't show this again" FontFamily="{StaticResource VeneerFont}" Checked="{x:Bind ViewModel.OnChecked}" Unchecked="{x:Bind ViewModel.OnChecked}" Visibility="{x:Bind ViewModel.IsCheckBoxVisible,Mode=OneWay}" Margin="12,12,0,0" RelativePanel.AlignLeftWithPanel="True" />

            <Button x:Name="BackButton" Width="100" Style="{StaticResource AccentButtonStyle}" FontFamily="{StaticResource VeneerFont}" Click="{x:Bind ViewModel.BackButton_Click}" RelativePanel.AlignLeftWithPanel="True" Content="Back" Visibility="{x:Bind ViewModel.IsBackButtonVisible,Mode=OneWay}"  Margin="12,12,12,0"/>

            <PipsPager x:Name="PipsPager" Margin="0,15,0,0" NumberOfPages="{x:Bind ViewModel.WelcomeScreenPageList.Count}" SelectedPageIndex="{x:Bind ViewModel.CurrentPageIndex, Mode=TwoWay}" RelativePanel.AlignHorizontalCenterWithPanel="True" SelectedIndexChanged="Pager_SelectedIndexChanged" />

            <Button x:Name="NextButton" Width="100" Style="{StaticResource AccentButtonStyle}" FontFamily="{StaticResource VeneerFont}" Click="{x:Bind ViewModel.NextButton_Click}" Content="{x:Bind ViewModel.NextButtonText,Mode=OneWay}" RelativePanel.AlignRightWithPanel="True" Margin="0,12,12,0"/>
        </RelativePanel>
    </StackPanel>
</Grid>

WelcomeScreen.xaml.cs:

public sealed partial class WelcomeScreen : Window
{

    internal WelcomeScreenPageViewModel ViewModel { get; set; }

    public WelcomeScreen(object viewModel) : this()
    {
        if (viewModel == null || viewModel.GetType() != typeof(WelcomeScreenPageViewModel))
            return;

        ViewModel = viewModel as WelcomeScreenPageViewModel;
    }

    public WelcomeScreen()
    {
        this.InitializeComponent();
        this.InitializeControls();
    }

    public void Pager_SelectedIndexChanged(object sender,PipsPagerSelectedIndexChangedEventArgs e)
    {
        bool isForward = false;
        if (ViewModel.CurrentPageIndex > ViewModel.PreviousPageIndex)
           isForward = true;

        ViewModel.PreviousPageIndex = ViewModel.CurrentPageIndex;

        Type pagetype =   Type.GetType(ViewModel.WelcomeScreenPageList[ViewModel.CurrentPageIndex]);

        if (isForward)
        {
            NavigationFrame.Navigate(pagetype,
                       null,
                       new SlideNavigationTransitionInfo()
                       { Effect = SlideNavigationTransitionEffect.FromRight });
        }
        else
        {
            NavigationFrame.Navigate(pagetype,
                       null,
                       new SlideNavigationTransitionInfo()
                       { Effect = SlideNavigationTransitionEffect.FromLeft });
        }

        ViewModel.UpdateControls();

    }
}

Page1:

<Page
    x:Class="WelcomeScreen.WelcomeScreenPage1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WelcomeScreen"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource SystemControlBackgroundChromeWhiteBrush}">

    <Grid ColumnDefinitions="*" RowDefinitions="*,*" >
      HorizontalAlignment="Right" Grid.Row="0" Margin="0,5,5,0"/>
        <StackPanel Orientation="Horizontal" Grid.Row="1">
            <TextBlock x:Name="PageText" Text="{x:Bind WelcomeScreenPageViewModel.xxx? , Mode=OneWay}""/>            
        </StackPanel>
    </Grid>
</Page>

WelcomeScreenPage1.xaml.cs:

public sealed partial class WelcomeScreenPage1 : Page
{
    public WelcomeScreenPage1()
    {
        this.InitializeComponent();
    }
}

Solution

  • You can set the ViewModel right after the navigation:

    Let's say you have 2 pages:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0">
            <Button
                Click="Page1Button_Click"
                Content="Page 1" />
            <Button
                Click="Page2Button_Click"
                Content="Page 2" />
        </StackPanel>
        <Frame
            x:Name="ContentFrame"
            Grid.Row="1" />
    </Grid>
    
    public sealed partial class Page1 : Page
    {
        public Page1()
        {
            this.InitializeComponent();
        }
    
        public PageViewModel ViewModel { get; set; }
    }
    
    public sealed partial class Page2 : Page
    {
        public Page2()
        {
            this.InitializeComponent();
        }
    
        public PageViewModel ViewModel { get; set; }
    }
    

    then you can do:

    private void Page1Button_Click(object sender, RoutedEventArgs e)
    {
        this.ContentFrame.Navigate(typeof(Page1));
        (this.ContentFrame.Content as Page1).ViewModel = new PageViewModel { Title = "Page 1" };
    }
    
    private void Page2Button_Click(object sender, RoutedEventArgs e)
    {
        this.ContentFrame.Navigate(typeof(Page2));
        (this.ContentFrame.Content as Page2).ViewModel = new PageViewModel { Title = "Page 2" };
    }