自定义视图中的BindableProperty不会取消订阅PropertyChanged

时间:2016-06-28 08:29:55

标签: c# mvvm xamarin xamarin.forms

背景信息

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

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

问题

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

每次我返回搜索页面并导航回搜索结果时,都会重建视图并订阅PropertyChanged的{​​{1}}。这些BindableProperties事件永远不会取消订阅,因此每次导航到搜索结果视图并更改绑定的ViewModel属性时,事件都会多次触发。

在以下代码中,PropertyChanged会根据我从搜索视图导航到搜索结果视图的次数多次触发:

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) { ... } } 不应单独取消订阅BindablePropertyPropertyChanged吗?
  • 是否因为我将View与ViewModel相关联和/或浏览页面的方式而发生?
  • 我应该自己处理取消订阅这些事件,以及如何处理?

修改;其他导航信息

我有-Changing MainView,其TabbedPageSearchView

NavigationPage

public MainView() { InitializeComponent(); Children.Add(new NavigationPage(new SearchView()) { Title = AppResources.Tab_Search, Icon = "tab_search" }); } 在创建时,使用MVVMLight的SearchView容器,在本主题开头提到的ViewModelLocator分配的单实例ViewModel。

SimpleIoc中的搜索命令被触发时,我向API返回搜索结果的请求。这些结果显示在另一个页面上,我从SearchView的ViewModel导航到该页面:

SearchView

哪种功能看起来像这样:

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

BaseContentPage是:

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

ViewModel的基本情况如下:

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

2 个答案:

答案 0 :(得分:2)

  • BindableProperty是否应该取消订阅PropertyChanged并自行更改?
    • 是的 - 它应该。如果不是,那肯定是一个错误
  • 这是否因为我将View与ViewModels相关联和/或导航到页面的方式而发生?
    • 这很可能也是一种选择,因为我还没有体验过你所描述的行为。您需要分享更多周围的设置代码。
  • 我应该自己处理取消订阅这些活动,以及如何处理?
    • 你很难总是控制取消订阅,因为大部分时间都是控制订阅事件(除非你自己做,在这种情况下,你总是有责任再次取消订阅)

虽然它很丑陋,但有时需要快速解决方法,在您的情况下,它将浏览xamarin如何保存更改代表的列表,并在页面上手动取消订阅,例如。

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

更新

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

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

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

答案 1 :(得分:1)

  • BindableProperty不应该取消订阅PropertyChanged并自行更改吗?

没有。 BindableProperty班负责处理这个问题。不是BindingContext

  • 是否因为我将View与ViewModel相关联和/或浏览页面的方式而发生?

您正在看到这一点,因为您忘记了导航堆栈在内存中保留了一个页面列表。由于多个页面指向相同的BindingContext,因此有多个观察者可以进行更改。如果您没有重复使用View Models,则不会遇到此特定问题。

  • 我应该自己处理取消订阅这些事件,以及如何处理?

没有。如果确实存在问题,请在页面消失时将null设置为<group name="mygroup"> <groupFooter> <band height="123"> <rectangle> <reportElement mode="Opaque" x="1" y="1" width="558" height="23" backcolor="#00CC99" /> </rectangle> <textField isStretchWithOverflow="true" isBlankWhenNull="true"> <reportElement stretchType="RelativeToBandHeight" x="0" y="28" width="553" height="86" /> <textElement> <font fontName="Times New Roman" size="7" isBold="false"/> </textElement> <textFieldExpression><![CDATA[$P{Disclosure}]]></textFieldExpression> </textField> <textField evaluationTime="Report" pattern="$ #,##0" isBlankWhenNull="true"> <reportElement mode="Transparent" x="49" y="2" width="80" height="14" forecolor="#000000" /> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="Times New Roman" size="10" isBold="true"/> </textElement> <textFieldExpression><![CDATA[$V{sumFaceAmount}]]></textFieldExpression> </textField> <textField evaluationTime="Report" pattern="" isBlankWhenNull="true"> <reportElement mode="Transparent" x="0" y="2" width="48" height="14" forecolor="#000000" /> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="Times New Roman" size="10" isBold="true"/> </textElement> <textFieldExpression><![CDATA[$V{sumCounts}]]></textFieldExpression> </textField> <textField evaluationTime="Report" pattern="$ #,##0.00" isBlankWhenNull="true"> <reportElement mode="Transparent" x="385" y="2" width="80" height="14" forecolor="#000000" /> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="Times New Roman" size="10" isBold="true"/> </textElement> <textFieldExpression><![CDATA[$V{avgPremium}]]></textFieldExpression> </textField> <textField evaluationTime="Report" pattern="$ #,##0.00" isBlankWhenNull="true"> <reportElement mode="Transparent" x="130" y="2" width="85" height="14" forecolor="#000000" /> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="Times New Roman" size="10" isBold="true"/> </textElement> <textFieldExpression><![CDATA[$V{sumPremium}]]></textFieldExpression> </textField> <textField evaluationTime="Report" pattern="$ #,##0" isBlankWhenNull="true"> <reportElement mode="Transparent" x="304" y="2" width="80" height="14" forecolor="#000000" /> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="Times New Roman" size="10" isBold="true"/> </textElement> <textFieldExpression><![CDATA[$V{avgFaceAmount}]]></textFieldExpression> </textField> <textField evaluationTime="Report" isBlankWhenNull="true"> <reportElement mode="Transparent" x="533" y="2" width="25" height="14" forecolor="#000000" /> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="Times New Roman" size="10" isBold="true"/> </textElement> <textFieldExpression><![CDATA[$V{Avg_Tage}]]></textFieldExpression> </textField> <textField evaluationTime="Report" isBlankWhenNull="true"> <reportElement mode="Transparent" x="507" y="2" width="26" height="14" forecolor="#000000" /> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="Times New Roman" size="10" isBold="true"/> </textElement> <textFieldExpression><![CDATA[$V{Avg_Fage}]]></textFieldExpression> </textField> <textField evaluationTime="Report" isBlankWhenNull="true"> <reportElement mode="Transparent" x="478" y="2" width="28" height="14" forecolor="#000000" /> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="Times New Roman" size="10" isBold="true"/> </textElement> <textFieldExpression><![CDATA[$V{Avg_Mage}]]></textFieldExpression> </textField> </band> </groupFooter> </group> ,然后在重新显示时将其恢复。请记住,虽然这仍然有成本,特别是如果你的用户界面真的很忙并且有很多由数据绑定控制的动态内容。