You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UWP应用中Page-Frame与ContentPresenter选型及MVVM导航方案咨询

UWP MVVM视图导航与项目结构指南

一、MainPage替代MainWindow的设计逻辑

UWP从设计之初就面向跨设备场景(手机、平板、PC等),没有WPF中独立的MainWindow概念,而是用Page作为核心容器,MainPage是默认的启动入口,相当于WPF里的主窗口,但它能更好地适配不同设备的窗口尺寸、旋转状态。所有视图切换都基于Page或嵌套的UserControl完成,这是UWP的标准设计,按此逻辑实现即可。

二、MVVM模式下的视图导航最佳实践

UWP中推荐用ContentControl + ViewModel驱动 + DataTemplate匹配的方式,和WPF的ContentPresenter思路一致,核心逻辑:

  • 用ContentControl作为主视图容器,绑定到ViewModel中的CurrentViewModel属性
  • 为每个子ViewModel定义对应的DataTemplate,让UWP自动根据ViewModel类型渲染对应的UserControl
  • 通过导航服务切换CurrentViewModel,视图仅绑定数据,不处理导航逻辑,完全符合MVVM

核心组件实现

1. 导航服务(INavigationService)

封装导航逻辑,避免ViewModel依赖视图:

public interface INavigationService
{
    void NavigateTo<TViewModel>() where TViewModel : ViewModelBase;
    void NavigateTo<TViewModel>(object parameter) where TViewModel : ViewModelBase;
}

public class NavigationService : INavigationService
{
    private readonly MainViewModel _mainViewModel;
    private readonly Func<Type, ViewModelBase> _viewModelFactory;

    public NavigationService(MainViewModel mainViewModel, Func<Type, ViewModelBase> viewModelFactory)
    {
        _mainViewModel = mainViewModel;
        _viewModelFactory = viewModelFactory;
    }

    public void NavigateTo<TViewModel>() => NavigateTo<TViewModel>(null);

    public void NavigateTo<TViewModel>(object parameter)
    {
        var viewModel = _viewModelFactory(typeof(TViewModel)) as ViewModelBase;
        viewModel?.SetParameter(parameter);
        _mainViewModel.CurrentViewModel = viewModel;
    }
}

2. ViewModel基类

实现INotifyPropertyChanged,所有ViewModel继承此类:

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    // 接收导航参数的虚方法
    public virtual void SetParameter(object parameter) { }
}

3. MainViewModel(主视图ViewModel)

维护当前激活的ViewModel:

public class MainViewModel : ViewModelBase
{
    private ViewModelBase _currentViewModel;
    public ViewModelBase CurrentViewModel
    {
        get => _currentViewModel;
        set
        {
            _currentViewModel = value;
            OnPropertyChanged();
        }
    }

    public INavigationService NavigationService { get; }

    public MainViewModel(INavigationService navigationService)
    {
        NavigationService = navigationService;
        // 初始导航到首页ViewModel
        NavigationService.NavigateTo<HomeViewModel>();
    }
}

三、重写App.xaml.cs的OnLaunched方法

初始化MainPage,注入ViewModel和导航服务:

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
    Frame rootFrame = Window.Current.Content as Frame;

    if (rootFrame == null)
    {
        rootFrame = new Frame();
        rootFrame.NavigationFailed += OnNavigationFailed;

        if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            // 此处添加应用挂起后的状态恢复逻辑
        }

        Window.Current.Content = rootFrame;
    }

    if (e.PrelaunchActivated == false)
    {
        if (rootFrame.Content == null)
        {
            // 手动实例化ViewModel和导航服务(实际项目可改用依赖注入框架)
            var mainViewModel = new MainViewModel(new NavigationService(
                null,
                type => 
                {
                    return type switch
                    {
                        Type t when t == typeof(HomeViewModel) => new HomeViewModel(),
                        Type t when t == typeof(UserManageViewModel) => new UserManageViewModel(),
                        _ => null
                    };
                }));
            
            rootFrame.Navigate(typeof(MainPage), mainViewModel);
        }
        Window.Current.Activate();
    }
}

private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
    throw new Exception($"加载页面失败:{e.SourcePageType.FullName}");
}

四、视图层实现(XAML)

MainPage.xaml

用ContentControl绑定CurrentViewModel,定义ViewModel与UserControl的匹配模板:

<Page
    x:Class="UwpMvvmDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UwpMvvmDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.DataContext>
        <local:MainViewModel/>
    </Page.DataContext>

    <Grid>
        <!-- 侧边导航按钮 -->
        <StackPanel Orientation="Horizontal" Margin="10">
            <Button Content="首页" Command="{Binding NavigationService, Converter={local:NavigationCommandConverter {x:Type local:HomeViewModel}}}" Margin="0,0,10,0"/>
            <Button Content="用户管理" Command="{Binding NavigationService, Converter={local:NavigationCommandConverter {x:Type local:UserManageViewModel}}}"/>
        </StackPanel>

        <!-- 主视图容器 -->
        <ContentControl Content="{Binding CurrentViewModel}" Margin="0,50,0,0">
            <ContentControl.Resources>
                <DataTemplate DataType="{x:Type local:HomeViewModel}">
                    <local:HomeUserControl/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type local:UserManageViewModel}">
                    <local:UserManageUserControl/>
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
    </Grid>
</Page>

辅助转换器(NavigationCommandConverter)

将导航服务转为ICommand,实现按钮点击导航:

public class NavigationCommandConverter : IValueConverter
{
    public Type ViewModelType { get; set; }

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var navigationService = value as INavigationService;
        return new RelayCommand(() => navigationService?.NavigateTo(ViewModelType));
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

// 标准RelayCommand实现
public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action execute, Func<bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;

    public void Execute(object parameter) => _execute();

    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

五、CRUD示例(用户管理)

1. UserManageViewModel

public class UserManageViewModel : ViewModelBase
{
    private ObservableCollection<User> _users;
    public ObservableCollection<User> Users
    {
        get => _users;
        set
        {
            _users = value;
            OnPropertyChanged();
        }
    }

    private User _selectedUser;
    public User SelectedUser
    {
        get => _selectedUser;
        set
        {
            _selectedUser = value;
            OnPropertyChanged();
            if (value != null)
            {
                EditName = value.Name;
                EditAge = value.Age;
            }
        }
    }

    private string _editName;
    public string EditName
    {
        get => _editName;
        set
        {
            _editName = value;
            OnPropertyChanged();
        }
    }

    private int _editAge;
    public int EditAge
    {
        get => _editAge;
        set
        {
            _editAge = value;
            OnPropertyChanged();
        }
    }

    public ICommand AddCommand { get; }
    public ICommand UpdateCommand { get; }
    public ICommand DeleteCommand { get; }

    public UserManageViewModel()
    {
        // 模拟初始化数据
        Users = new ObservableCollection<User>
        {
            new User { Id = 1, Name = "张三", Age = 25 },
            new User { Id = 2, Name = "李四", Age = 30 }
        };

        AddCommand = new RelayCommand(AddUser);
        UpdateCommand = new RelayCommand(UpdateUser, () => SelectedUser != null);
        DeleteCommand = new RelayCommand(DeleteUser, () => SelectedUser != null);
    }

    private void AddUser()
    {
        var newUser = new User
        {
            Id = Users.Max(u => u.Id) + 1,
            Name = EditName,
            Age = EditAge
        };
        Users.Add(newUser);
        EditName = string.Empty;
        EditAge = 0;
    }

    private void UpdateUser()
    {
        SelectedUser.Name = EditName;
        SelectedUser.Age = EditAge;
    }

    private void DeleteUser()
    {
        if (SelectedUser != null)
        {
            Users.Remove(SelectedUser);
            SelectedUser = null;
        }
    }
}

// User实体类
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

2. UserManageUserControl.xaml

<UserControl
    x:Class="UwpMvvmDemo.UserManageUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UwpMvvmDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- 编辑操作区 -->
        <StackPanel Orientation="Horizontal" Margin="10">
            <TextBox PlaceholderText="姓名" Text="{Binding EditName, Mode=TwoWay}" Width="150" Margin="0,0,10,0"/>
            <TextBox PlaceholderText="年龄" Text="{Binding EditAge, Mode=TwoWay}" Width="100" Margin="0,0,10,0"/>
            <Button Content="添加" Command="{Binding AddCommand}"/>
            <Button Content="更新" Command="{Binding UpdateCommand}" Margin="10,0,0,0"/>
            <Button Content="删除" Command="{Binding DeleteCommand}" Margin="10,0,0,0"/>
        </StackPanel>

        <!-- 用户列表区 -->
        <ListView ItemsSource="{Binding Users}" SelectedItem="{Binding SelectedUser, Mode=TwoWay}" Grid.Row="1" Margin="10">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Id}" Width="50"/>
                        <TextBlock Text="{Binding Name}" Width="150"/>
                        <TextBlock Text="{Binding Age}" Width="100"/>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</UserControl>

内容的提问来源于stack exchange,提问作者JoJo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 22:47:05