WPF中MVVM模式下同View不同VM的Frame导航实现问询
爬虫程序的浏览器式导航解决方案
核心问题梳理
- 固定View,仅切换ViewModel实现前进/后退,自定义
NavigationService时,QueryRequest等引用类型无法维持独立状态,深克隆无法复制事件订阅 - 若用WPF的
Frame控件,担心点击Query按钮时导航到新页面会触发新ViewModel的Query,陷入无限循环 - 需实现类似浏览器的状态保留:搜索不同内容后返回,搜索框保留原搜索词
自定义NavigationService的状态修复方案
你的NavigationService目前存储的是QuerySession引用,导致状态共享。要解决引用类型状态污染,不需要深克隆事件,而是保存状态快照的副本:
1. 改造QuerySession为不可变类型
把QuerySession设计成不可变类,每次查询生成新实例,避免后续修改影响历史状态:
public record QuerySession( int PageIndex, int PageSize, QueryRequest QueryRequest, // 若QueryRequest是引用类型,同样改为不可变类型 QueryResponse? QueryResponse, double VerticalScrollOffset);
2. 优化状态赋值逻辑
在GoBack/GoForward时,对引用类型属性创建新实例,而非直接引用赋值:
// QueryViewModel的GoBack方法优化 [RelayCommand] private void GoBack() { _isNavigating = true; var session = _navigationService.GoBack(); if (session is not null) { PageIndex = session.PageIndex; PageSize = session.PageSize; QueryRequest = new QueryRequest(session.QueryRequest); // 用拷贝构造函数创建新实例 QueryResponse = session.QueryResponse; VerticalScrollOffset = session.VerticalScrollOffset; } _isNavigating = false; }
Frame控件的无限循环问题解决
用Frame控件时,避免无限循环的关键是导航逻辑与查询逻辑解耦:
1. 取消ViewModel自动查询
不要在ViewModel构造函数中自动执行QueryAsync,仅在首次加载或用户主动点击时触发。
2. 导航时传递已完成的查询结果
点击Query按钮完成查询后,再创建新ViewModel实例并传递结果,而非让新ViewModel重新触发查询:
// Query按钮命令调整 [RelayCommand(IncludeCancelCommand = true)] private async Task QueryAsync(CancellationToken cancellationToken = default) { try { using var scope = _serviceScopeFactory.CreateScope(); var webService = scope.ServiceProvider.GetRequiredService<IWebService>(); var response = await webService.QueryAsync(QueryRequest, cancellationToken); // 创建新ViewModel并传递已完成的查询状态 var newVm = new QueryViewModel( _navigationService, _serviceScopeFactory, PageIndex, PageSize, QueryRequest, response, VerticalScrollOffset); // 直接切换Frame的DataContext(View固定时无需导航新Page) _frame.Content = new QueryView { DataContext = newVm }; } catch (TaskCanceledException) { Growl.Info("Cancelled."); } catch { throw; } }
浏览器式搜索状态保留实现
核心是每个历史状态独立存储完整搜索参数:
- 在
QuerySession中完整保存QueryRequest的所有属性(包括搜索关键词)。 - 前进/后退时,从快照中读取搜索参数并赋值给ViewModel的绑定属性:
// QueryViewModel中添加搜索关键词绑定属性 public string SearchKeyword { get => _searchKeyword; set => SetProperty(ref _searchKeyword, value); } // GoBack时同步搜索状态 if (session is not null) { // ...其他属性赋值 SearchKeyword = session.QueryRequest.SearchKeyword; }
代码优化补充
1. 依赖注入调整
将NavigationService注册为单例,确保导航栈全局共享:
services.AddSingleton<INavigationService<QuerySession>, NavigationService<QuerySession>>(); services.AddTransient<QueryViewModel>();
2. NavigationService的跳转逻辑修复
修正GoBackTo和GoForwardTo的循环跳转逻辑:
public void GoBackTo(T content) { while (BackStack.Count > 1 && !EqualityComparer<T>.Default.Equals(BackStack.Peek(), content)) { GoBack(); } } public void GoForwardTo(T content) { while (ForwardStack.Count > 0 && !EqualityComparer<T>.Default.Equals(ForwardStack.Peek(), content)) { GoForward(); } }
内容的提问来源于stack exchange,提问作者CodingNinja
相关产品推荐
相关产品推荐

