WinUI 3中如何将嵌入页面绑定到主窗口ViewModel
如何让Frame导航的页面共享主窗口ViewModel(避免为每个页面单独创建VM)
我正在开发一个欢迎界面,主窗口通过Frame控件导航展示5个页面。希望将页面中的部分控件通过x:Bind绑定到主窗口的ViewModel,不想为这5个页面分别创建ViewModel,请问该如何实现?或者如何将主窗口的ViewModel对象传递给这5个页面?
现有代码参考
主窗口 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(); } }
页面 WelcomeScreenPage1.xaml
<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(); } }
解决方案
方案一:通过导航参数传递ViewModel
在主窗口导航页面时,将主ViewModel作为参数传递给页面,页面接收后作为绑定源使用。
- 修改主窗口的导航代码,传递ViewModel:
// 替换原Navigate中的null为ViewModel if (isForward) { NavigationFrame.Navigate(pagetype, ViewModel, // 传递主窗口ViewModel new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight }); } else { NavigationFrame.Navigate(pagetype, ViewModel, // 传递主窗口ViewModel new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromLeft }); }
- 在页面中接收并保存ViewModel:
修改WelcomeScreenPage1.xaml.cs,重写OnNavigatedTo方法获取参数:
public sealed partial class WelcomeScreenPage1 : Page { public WelcomeScreenPageViewModel ViewModel { get; set; } public WelcomeScreenPage1() { this.InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); // 接收传递的ViewModel if (e.Parameter is WelcomeScreenPageViewModel vm) { ViewModel = vm; this.Bindings.Update(); // 通知UI更新绑定 } } }
- 修改页面XAML的绑定:
<TextBlock x:Name="PageText" Text="{x:Bind ViewModel.xxx, Mode=OneWay}"/>
方案二:直接绑定到主窗口的ViewModel
通过Window.Current.Content获取主窗口实例,直接在页面XAML中绑定到主窗口的ViewModel属性,无需修改页面后台代码。
修改页面XAML的绑定表达式:
<TextBlock x:Name="PageText" Text="{x:Bind ((local:WelcomeScreen)Window.Current.Content).ViewModel.xxx, Mode=OneWay}"/>
两种方案对比
- 方案一:耦合度低,页面可自主处理ViewModel,适合需要对ViewModel做额外逻辑处理的场景。
- 方案二:代码更简洁,无需修改页面后台,但依赖主窗口实例的存在,耦合度稍高,适合简单绑定场景。
内容的提问来源于stack exchange,提问作者Ming
相关产品推荐
相关产品推荐

