c# - 自定义 View 中的 BindableProperty 不会取消订阅 PropertyChanged

标签 c# mvvm xamarin xamarin.forms

背景信息

我正在 XAML 中开发 Xamarin Forms(v4.1.1.3,在 iOS 上测试)应用程序,使用 MVVM 和先查看方法;我正在使用 MVVMLight 的 ViewModelLocator 服务将单实例 ViewModel 分配给 View :

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);

问题

我已经实现了一个自定义控件( View ?),它应该以类似图 block 的布局显示搜索结果。当从搜索 NavigationPage 导航到搜索结果 ContentPage 时创建此控件。

每次我返回搜索页面并导航回搜索结果时,都会重建 View 并订阅 BindablePropertiesPropertyChanged。这些 PropertyChanged 事件永远不会取消订阅,因此每次我导航到搜索结果 View 并更改绑定(bind)的 ViewModel 属性时,该事件都会被触发多次。

在下面的代码中,根据我从搜索 View 导航到搜索结果 View 的次数,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 吗?
  • 出现这种情况是否是因为我将 View 与 View 模型相关联和/或浏览页面的方式?
  • 我应该自己处理取消订阅这些事件吗?如何处理?

编辑;额外的导航信息

我有一个 MainView TabbedPage,它将 SearchView 创建为 NavigationPage:

public MainView()
{
    InitializeComponent();

    Children.Add(new NavigationPage(new SearchView())
    {
        Title = AppResources.Tab_Search,
        Icon = "tab_search"
    });
}

SearchView 在创建时有一个由本主题开头提到的 ViewModelLocator 分配的单实例 ViewModel,使用 MVVMLight 的 SimpleIoc容器。

当触发 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 吗?
    • 是的 - 它应该。如果不是,那肯定是一个错误
  • 发生这种情况是否是因为我将 View 与 View 模型相关联和/或浏览页面的方式?
    • 这很可能也是一种选择,因为我还没有经历过您描述的行为。您将需要共享更多周边设置代码。
  • 我应该自己处理取消订阅这些事件吗?如何处理?
    • 您很难始终控制取消订阅,因为大多数时候它将控制订阅事件(除非您自己动手,在这种情况下,您始终有责任再次取消订阅)

虽然它很丑陋,但有时需要快速解决方法,在您的情况下,这将是浏览 xamarin 如何保存更改委托(delegate)列表并在出现的页面上手动取消订阅它们。

我希望这能回答您的问题。如果没有,请随时发表评论。

更新

在你的情况下,我会调试你的页面基础,并验证是否

  • OnDisappearing 被正确调用
  • 您的处理程序在取消订阅后消失了
  • (这很懒惰,但我通常在取消订阅之前取消订阅事件,只是为了确保不会发生此类错误,因为如果您尝试取消订阅未注册的处理程序,大多数 EventManagement 服务都不会抛出。 )

至少这是您遇到问题的最可能原因。

关于c# - 自定义 View 中的 BindableProperty 不会取消订阅 PropertyChanged,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38070967/

相关文章:

c# - Asp .Net Identity - 自定义 IUserStore FindByNameAsync 方法

ios - 从 firebase 获取列表数据,或如何构建数据

c# - 将 Android View 作为自定义控件集成到 Xamarin.Forms 中

c# - Web 服务不再适用于单元测试/客户端代理创建

c# - 从 azure blob 存储获取文件名

c# - MVVM 轻量级 SimpleIoc

mvvm - 编程更改不会反射(reflect)在 knockout View 模型中

c# - Xamarin.Forms ios 编辑日历事件

xamarin - Microsoft.AppCenter.Crashes.TrackError 已过时且在 UWP for Xamarin Forms 上不可用

c# - 使用具有流畅验证的自定义验证响应