316 lines
9.2 KiB
Markdown
316 lines
9.2 KiB
Markdown
# 04 - 导航系统示例
|
||
|
||
演示如何使用 Sharp.Wpf 的导航服务实现多页面 WPF 应用,包括页面跳转、参数传递、前进/后退等功能。
|
||
|
||
## 🎯 学习目标
|
||
|
||
- 使用 `INavigationService` 实现页面导航
|
||
- 理解导航历史栈(前进/后退)
|
||
- 页面间参数传递
|
||
- 导航生命周期钩子(INavigationAware)
|
||
- 弹出式导航并获取结果
|
||
|
||
## 📦 使用的模块
|
||
|
||
- **Sharp.Wpf** - INavigationService, NavigationService
|
||
|
||
## 🚀 快速运行
|
||
|
||
```bash
|
||
cd examples/04-Navigation
|
||
dotnet run
|
||
```
|
||
|
||
## 📝 核心代码讲解
|
||
|
||
### 1. MainViewModel.cs - 主导航容器
|
||
|
||
```csharp
|
||
using Wpf.Navigation;
|
||
|
||
public class MainViewModel : ViewModelBase
|
||
{
|
||
private readonly INavigationService _navigationService;
|
||
private ViewModelBase? _currentPage;
|
||
|
||
public ViewModelBase? CurrentPage
|
||
{
|
||
get => _currentPage;
|
||
set => SetProperty(ref _currentPage, value);
|
||
}
|
||
|
||
public bool CanGoBack => _navigationService.CanGoBack;
|
||
public bool CanGoForward => _navigationService.CanGoForward;
|
||
|
||
public ICommand NavigateToHomeCommand { get; }
|
||
public ICommand NavigateToProductsCommand { get; }
|
||
public ICommand NavigateToSettingsCommand { get; }
|
||
public ICommand GoBackCommand { get; }
|
||
public ICommand GoForwardCommand { get; }
|
||
|
||
public MainViewModel(INavigationService navigationService)
|
||
{
|
||
_navigationService = navigationService;
|
||
|
||
// 监听导航事件
|
||
_navigationService.Navigated += (s, vm) =>
|
||
{
|
||
CurrentPage = vm;
|
||
OnPropertyChanged(nameof(CanGoBack));
|
||
OnPropertyChanged(nameof(CanGoForward));
|
||
};
|
||
|
||
// 导航命令
|
||
NavigateToHomeCommand = new AsyncCommand(async (ct) =>
|
||
{
|
||
await _navigationService.NavigateAsync<HomeViewModel>();
|
||
});
|
||
|
||
NavigateToProductsCommand = new AsyncCommand(async (ct) =>
|
||
{
|
||
await _navigationService.NavigateAsync<ProductsViewModel>();
|
||
});
|
||
|
||
NavigateToSettingsCommand = new AsyncCommand(async (ct) =>
|
||
{
|
||
await _navigationService.NavigateAsync<SettingsViewModel>();
|
||
});
|
||
|
||
GoBackCommand = new AsyncCommand(async (ct) =>
|
||
{
|
||
await _navigationService.GoBackAsync();
|
||
}, () => CanGoBack);
|
||
|
||
GoForwardCommand = new AsyncCommand(async (ct) =>
|
||
{
|
||
await _navigationService.GoForwardAsync();
|
||
}, () => CanGoForward);
|
||
|
||
// 初始导航到首页
|
||
Task.Run(async () => await _navigationService.NavigateAsync<HomeViewModel>());
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. ProductsViewModel.cs - 带参数的页面
|
||
|
||
```csharp
|
||
public class ProductsViewModel : ViewModelBase, INavigationAware
|
||
{
|
||
private readonly INavigationService _navigationService;
|
||
private readonly IDialogService _dialogService;
|
||
private string _category = "全部";
|
||
|
||
public string Category
|
||
{
|
||
get => _category;
|
||
set => SetProperty(ref _category, value);
|
||
}
|
||
|
||
public ICommand ViewProductDetailsCommand { get; }
|
||
|
||
public ProductsViewModel(
|
||
INavigationService navigationService,
|
||
IDialogService dialogService)
|
||
{
|
||
_navigationService = navigationService;
|
||
_dialogService = dialogService;
|
||
|
||
ViewProductDetailsCommand = new AsyncCommand<int>(async (productId, ct) =>
|
||
{
|
||
// 导航到产品详情页,传递产品ID
|
||
await _navigationService.NavigateAsync<ProductDetailViewModel>(productId);
|
||
});
|
||
}
|
||
|
||
// 导航生命周期:进入页面时
|
||
public async Task OnNavigatedToAsync(object? parameter)
|
||
{
|
||
if (parameter is string category)
|
||
{
|
||
Category = category;
|
||
_dialogService.ShowMessage($"显示分类: {category}", "提示");
|
||
}
|
||
|
||
// 可以在这里加载数据
|
||
await LoadProductsAsync();
|
||
}
|
||
|
||
// 导航生命周期:离开页面时
|
||
public async Task OnNavigatedFromAsync()
|
||
{
|
||
// 清理资源、保存状态等
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
private async Task LoadProductsAsync()
|
||
{
|
||
IsBusy = true;
|
||
await Task.Delay(500); // 模拟加载
|
||
IsBusy = false;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. ProductDetailViewModel.cs - 详情页(带结果返回)
|
||
|
||
```csharp
|
||
public class ProductDetailViewModel : ViewModelBase,
|
||
INavigationAware,
|
||
INavigationResult<bool> // 返回 bool(是否购买)
|
||
{
|
||
private readonly TaskCompletionSource<bool> _resultSource = new();
|
||
private int _productId;
|
||
private string _productName = "";
|
||
|
||
public int ProductId
|
||
{
|
||
get => _productId;
|
||
set => SetProperty(ref _productId, value);
|
||
}
|
||
|
||
public string ProductName
|
||
{
|
||
get => _productName;
|
||
set => SetProperty(ref _productName, value);
|
||
}
|
||
|
||
public ICommand BuyCommand { get; }
|
||
public ICommand CancelCommand { get; }
|
||
|
||
public ProductDetailViewModel()
|
||
{
|
||
BuyCommand = new RelayCommand(() =>
|
||
{
|
||
// 返回结果:已购买
|
||
_resultSource.SetResult(true);
|
||
});
|
||
|
||
CancelCommand = new RelayCommand(() =>
|
||
{
|
||
// 返回结果:取消
|
||
_resultSource.SetResult(false);
|
||
});
|
||
}
|
||
|
||
public async Task OnNavigatedToAsync(object? parameter)
|
||
{
|
||
if (parameter is int productId)
|
||
{
|
||
ProductId = productId;
|
||
// 加载产品详情
|
||
ProductName = $"产品 #{productId}";
|
||
}
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
public Task OnNavigatedFromAsync() => Task.CompletedTask;
|
||
|
||
// 实现 INavigationResult<bool>
|
||
public Task<bool> GetResultAsync()
|
||
{
|
||
return _resultSource.Task;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. App.xaml.cs - 注册导航服务
|
||
|
||
```csharp
|
||
protected override void OnStartup(StartupEventArgs e)
|
||
{
|
||
var services = new ServiceCollection();
|
||
|
||
services.AddSharpWpf(); // 自动注册 INavigationService
|
||
|
||
// 注册所有页面 ViewModel
|
||
services.AddViewModel<MainViewModel>();
|
||
services.AddViewModel<HomeViewModel>();
|
||
services.AddViewModel<ProductsViewModel>();
|
||
services.AddViewModel<ProductDetailViewModel>();
|
||
services.AddViewModel<SettingsViewModel>();
|
||
|
||
var provider = services.BuildServiceProvider();
|
||
var mainWindow = provider.GetRequiredService<MainWindow>();
|
||
mainWindow.Show();
|
||
}
|
||
```
|
||
|
||
### 5. MainWindow.xaml - 导航容器
|
||
|
||
```xml
|
||
<Window x:Class="NavigationExample.MainWindow"
|
||
Title="导航系统示例" Height="600" Width="800">
|
||
<Grid>
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<!-- 导航栏 -->
|
||
<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#f0f0f0" Padding="10">
|
||
<Button Content="首页" Command="{Binding NavigateToHomeCommand}" Margin="0,0,10,0"/>
|
||
<Button Content="产品列表" Command="{Binding NavigateToProductsCommand}" Margin="0,0,10,0"/>
|
||
<Button Content="设置" Command="{Binding NavigateToSettingsCommand}" Margin="0,0,20,0"/>
|
||
|
||
<Button Content="← 后退" Command="{Binding GoBackCommand}" Margin="0,0,10,0"/>
|
||
<Button Content="前进 →" Command="{Binding GoForwardCommand}"/>
|
||
</StackPanel>
|
||
|
||
<!-- 内容区域 - 显示当前页面 -->
|
||
<ContentControl Grid.Row="1" Content="{Binding CurrentPage}">
|
||
<ContentControl.Resources>
|
||
<!-- 为每个 ViewModel 指定对应的 View -->
|
||
<DataTemplate DataType="{x:Type local:HomeViewModel}">
|
||
<local:HomeView/>
|
||
</DataTemplate>
|
||
<DataTemplate DataType="{x:Type local:ProductsViewModel}">
|
||
<local:ProductsView/>
|
||
</DataTemplate>
|
||
<DataTemplate DataType="{x:Type local:SettingsViewModel}">
|
||
<local:SettingsView/>
|
||
</DataTemplate>
|
||
</ContentControl.Resources>
|
||
</ContentControl>
|
||
</Grid>
|
||
</Window>
|
||
```
|
||
|
||
## 🎓 练习建议
|
||
|
||
### 练习1:面包屑导航
|
||
添加面包屑显示当前导航路径(如:首页 > 产品列表 > 产品详情)。
|
||
|
||
### 练习2:导航守卫
|
||
实现 `INavigationGuard` 接口,在离开页面前检查是否有未保存的更改。
|
||
|
||
### 练习3:嵌套导航
|
||
在产品列表页内部实现子导航(如:全部/热销/新品标签页切换)。
|
||
|
||
### 练习4:导航缓存
|
||
实现页面缓存,避免重复创建 ViewModel(保持页面状态)。
|
||
|
||
## 💡 常见问题
|
||
|
||
**Q: 导航后如何保持页面状态?**
|
||
A: 不要后退时,CurrentViewModel 会保留在前进栈中,前进时会恢复状态。如果需要完全保持状态,可以使用 Singleton ViewModel。
|
||
|
||
**Q: 如何传递复杂对象作为参数?**
|
||
A: 使用 `NavigateAsync(object parameter)`,parameter 可以是任何对象。
|
||
|
||
**Q: 导航历史栈何时清空?**
|
||
A: 调用 `_navigationService.ClearHistory()` 手动清空。
|
||
|
||
**Q: 能否使用动画过渡?**
|
||
A: 可以,在 ContentControl 上添加 Storyboard 实现页面切换动画。
|
||
|
||
## 📚 延伸阅读
|
||
|
||
- [WPF 数据模板](https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/data/data-templating-overview)
|
||
- [MVVM 导航模式](https://prismlibrary.com/docs/navigation.html)
|
||
- 下一个示例:[05-Localization](../05-Localization/README.md)
|
||
|
||
---
|
||
|
||
**掌握导航系统,构建流畅的多页面应用!** 🧭
|