自定义视图中的BindableProperty未取消订阅PropertyChanged
本文关键字:取消 PropertyChanged BindableProperty 视图 自定义 | 更新日期: 2023-09-27 17:51:24
背景信息
我正在XAML中开发Xamarin Forms(v4.1.3,在iOS上测试(应用程序,使用MVVM和View first方法;我使用MVVMLight的ViewModelLocator服务将单个实例ViewModels分配给视图:
BindingContext="{Binding [SearchViewModel], Source={StaticResource ViewModelLocator}}"
当导航到另一个页面时,我正在构建该页面的一个新实例,它每次都会收到相同的ViewModel实例。
var page = new SearchView();
var tabbedPage = Application.Current.MainPage as TabbedPage;
if (tabbedPage != null)
await tabbedPage.CurrentPage.Navigation.PushAsync(page);
问题
我已经实现了一个自定义控件(视图?(,它应该以类似平铺的布局显示搜索结果。该控件是在从搜索NavigationPage
导航到搜索结果ContentPage
时创建的。
每次我返回搜索页面并导航回搜索结果时,都会重建视图,并订阅BindableProperties
的PropertyChanged
。这些PropertyChanged
事件从未取消订阅,因此每当我导航到搜索结果视图并更改绑定的ViewModel属性时,该事件就会被多次触发。
在下面的代码中,OnItemsPropertyChanged
被多次触发,这取决于我从搜索视图导航到搜索结果视图的次数:
public class WrapLayout : Grid
{
public static readonly BindableProperty ItemsProperty =
BindableProperty.Create("Items", typeof(IEnumerable), typeof(WrapLayout), null, propertyChanged: OnItemsPropertyChanged);
public IEnumerable Items
{
get { return (IEnumerable)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public WrapLayout()
{
...
}
private static void OnItemsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
...
}
}
我的问题:
BindableProperty
不应该自己取消订阅PropertyChanged
和-Changing
吗- 发生这种情况是因为我将视图与ViewModels关联和/或浏览页面的方式吗
- 我应该自己处理取消订阅这些活动吗?如何处理
编辑;附加导航信息
我有一个MainView
TabbedPage
,它将SearchView
创建为NavigationPage
:
public MainView()
{
InitializeComponent();
Children.Add(new NavigationPage(new SearchView())
{
Title = AppResources.Tab_Search,
Icon = "tab_search"
});
}
SearchView
在创建时,使用MVVMLight的SimpleIoc
容器,具有由本主题开头提到的ViewModelLocator
分配的单个实例ViewModel。
当SearchView
中的搜索命令被触发时,我向API发送一个请求,返回搜索结果。这些结果显示在另一个页面上,我从SearchView
的ViewModel:导航到该页面
await _navigationService.NavigateTo(ViewModelLocator.PageKeyFileResults, searchResult);
哪个功能看起来有点像:
public async Task NavigateTo(string pagekey, object viewModelParameter)
{
var constructor = _pagesByKey[pagekey].Constructor; //Gets the Func<Page> that simple creates the requested page, without using reflection.
var page = constructor() as Page;
var viewModel = page.BindingContext as BaseViewModel;
if (viewModel != null)
viewModel.Initialize(viewModelParameter);
var tabbedPage = Application.Current.MainPage as TabbedPage;
if (tabbedPage != null)
await tabbedPage.CurrentPage.Navigation.PushAsync(page);
else
await Application.Current.MainPage.Navigation.PushAsync(page);
}
构建的页面看起来有点像:
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Views.FileResultsView"
xmlns:pages="clr-namespace:Views.Pages;assembly=Views"
xmlns:controls="clr-namespace:Views.Controls;assembly=Views"
BindingContext="{Binding [FileResultsViewModel], Source={StaticResource ViewModelLocator}}">
<ScrollView>
<controls:WrapLayout
Items="{Binding SearchResults}" />
</ScrollView>
</pages:BaseContentPage>
BaseContentPage所在位置:
public class BaseContentPage : ContentPage
{
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Subscribe<DialogMessage>(this, "ShowDialog", (dialogMessage) =>
{
if (string.IsNullOrWhiteSpace(dialogMessage.AcceptButton))
DisplayAlert(dialogMessage.Title, dialogMessage.Content, dialogMessage.CancelButton);
else
DisplayAlert(dialogMessage.Title, dialogMessage.Content, dialogMessage.AcceptButton, dialogMessage.CancelButton);
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Unsubscribe<DialogMessage>(this, "ShowDialog");
}
}
ViewModel基本上是这样的:
public class FileResultsViewModel : BaseViewModel
{
private IEnumerable<ASRow> _searchResults;
public IEnumerable<ASRow> SearchResults
{
get { return _searchResults; }
set { Set(ref _searchResults, value); }
}
internal override void Initialize(object parameter)
{
base.Initialize(parameter);
if (parameter is AdvancedSearchResponse)
{
var searchResults = parameter as AdvancedSearchResponse;
SearchResults = new List<ASRow>(searchResults.Rows);
}
}
}
- BindableProperty不应该自己取消订阅PropertyChanged和-Changing吗?
- 是的,应该。如果没有,那肯定是一个错误
- 发生这种情况是因为我将视图与ViewModels关联和/或浏览页面的方式吗?
- 这很可能也是一种选择,因为我还没有经历过你描述的行为。您需要共享更多的周围设置代码
- 我应该自己处理取消订阅这些活动吗?如何处理?
- 你很难总是控制取消订阅,因为大多数时候都是控制订阅事件(除非你自己做,在这种情况下,你总是有责任再次取消订阅(
虽然这很难看,但有时有必要找到一个快速的解决方法,在您的情况下,可以浏览xamarin如何保存更改委托的列表,并在出现的页面上手动取消订阅它们。
我希望这能回答你的问题。如果没有,欢迎发表评论。
更新
在你的情况下,我会调试你的页面库,并验证是否
- OnDisappearing调用正确
- 取消订阅后,您的处理程序不在了
- (这很懒惰,但我通常在对事件进行子脚本之前取消脚本,只是为了确保不会发生这样的错误,因为如果你试图取消脚本未注册的处理程序,大多数EventManagement服务都不会抛出。(
至少这是你的问题最可能的原因。
- BindableProperty不应该自己取消订阅PropertyChanged和-Changing吗
没有。Binding
类负责处理此问题。不是BindableProperty
。
- 发生这种情况是因为我将视图与ViewModels关联和/或浏览页面的方式吗
您之所以看到这种情况,是因为您忘记了导航堆栈会在内存中保存页面列表。由于多个页面指向同一BindingContext,因此存在多个更改的观察者。如果不重用视图模型,就不会有这个特殊的问题。
- 我应该自己处理取消订阅这些活动吗?如何处理
没有。如果确实存在问题,请在页面消失时将BindingContext
设置为null
,然后在再次出现时将其恢复。但请记住,这仍然有成本,尤其是当您的UI非常繁忙并且有大量由数据绑定控制的动态内容时。