U:调整调用方式和兼容性
This commit is contained in:
1 parent
433e206d45
commit
4ec76b7855
50 files changed
+5592
-47
No files matched your search
@@ -0,0 +1,315 @@
|
||||
# 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)
|
||||
|
||||
---
|
||||
|
||||
**掌握导航系统,构建流畅的多页面应用!** 🧭
|
||||
Reference in new issue
Block a user