如何从ViewModel关闭MauiCommunityToolkit Popup并解决二次调用异常
解决方案
一、修复WeakReferenceMessenger的空引用问题
问题核心是Popup关闭后消息订阅未注销,第二次发送消息时会触发已释放内部资源的Popup实例执行Close操作,导致CleanUp方法中出现空引用。
1. 关闭时注销消息订阅
在Popup的后台代码中,重写OnClosed方法,在Popup关闭后立即注销消息订阅,避免失效实例接收消息:
public class CustomPopup : Popup { public CustomPopup() { // 注册关闭消息 WeakReferenceMessenger.Default.Register<ClosePopupMsg>(this, (_, __) => { MainThread.BeginInvokeOnMainThread(() => { if (IsOpen) // 先校验Popup状态,避免无效操作 { Close(); } }); }); } protected override void OnClosed(EventArgs e) { base.OnClosed(e); // 注销当前实例的消息订阅 WeakReferenceMessenger.Default.Unregister<ClosePopupMsg>(this); } } // 定义关闭消息类 public class ClosePopupMsg { }
如果存在多个Popup实例,可给消息类添加PopupId字段,接收时判断标识,避免误关其他弹窗。
2. 发送消息时指定目标(可选)
若需精准控制特定Popup关闭,可在消息中携带标识,接收时匹配:
// 带标识的消息类 public class ClosePopupMsg { public string PopupId { get; set; } } // Popup构造时传入标识 public CustomPopup(string popupId) { WeakReferenceMessenger.Default.Register<ClosePopupMsg>(this, (_, msg) => { if (msg.PopupId == popupId && IsOpen) { MainThread.BeginInvokeOnMainThread(Close); } }); } // ViewModel中发送指定消息 WeakReferenceMessenger.Default.Send(new ClosePopupMsg { PopupId = "MyTargetPopup" });
二、无需传递句柄的更优关闭方式
1. 绑定IsOpen属性(最贴合MVVM)
CommunityToolkit.Maui的Popup支持双向绑定IsOpen属性,直接在ViewModel中通过属性控制弹窗显隐:
ViewModel代码:
private bool _isPopupOpen; public bool IsPopupOpen { get => _isPopupOpen; set => SetProperty(ref _isPopupOpen, value); } // 打开弹窗命令 public IRelayCommand OpenPopupCmd => new RelayCommand(() => IsPopupOpen = true); // 关闭弹窗命令 public IRelayCommand ClosePopupCmd => new RelayCommand(() => IsPopupOpen = false);
Popup的XAML代码:
<toolkit:Popup IsOpen="{Binding IsPopupOpen, Mode=TwoWay}"> <!-- 弹窗内容 --> </toolkit:Popup>
2. 利用PopupService跟踪实例
通过IPopupService打开弹窗时,在ViewModel中持有当前弹窗实例,无需对外传递句柄即可主动关闭:
private readonly IPopupService _popupService; private Popup _currentPopup; public MyViewModel(IPopupService popupService) { _popupService = popupService; OpenPopupCmd = new AsyncRelayCommand(OpenPopupAsync); ClosePopupCmd = new RelayCommand(ClosePopup); } public IAsyncRelayCommand OpenPopupCmd { get; } public IRelayCommand ClosePopupCmd { get; } private async Task OpenPopupAsync() { _currentPopup = new CustomPopup(); await _popupService.ShowPopupAsync(_currentPopup); _currentPopup = null; // 关闭后清空引用,避免内存泄漏 } private void ClosePopup() { if (_currentPopup != null && _currentPopup.IsOpen) { MainThread.BeginInvokeOnMainThread(() => _currentPopup.Close()); } }
内容的提问来源于stack exchange,提问作者ss1969
相关产品推荐
相关产品推荐

