ContentView绑定无法正常工作

时间:2019-06-06 11:22:00

标签: xaml xamarin xamarin.forms

我有一个可重用的控件,用于显示加载微调器:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="Framework.Controls.Loading" x:Name="LoadingControl" IsVisible="{Binding LoadingIndicator}"
             HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
  <ContentView.Content>
      <ActivityIndicator HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" Color="DarkBlue" 
                         IsVisible="{Binding LoadingIndicator}"
                         IsRunning="{Binding LoadingIndicator}">
      </ActivityIndicator>
    </ContentView.Content>
</ContentView>

我试图在这样的页面上使用它:

<controls:Loading LoadingIndicator="{Binding IsLoading}"></controls:Loading>

但是,加载微调器无法在屏幕上显示。

当我将LoadingIndicator属性设置为true时,它看起来很好:

<controls:Loading LoadingIndicator="true"></controls:Loading>

我的“ IsLoading”绑定肯定可以正常工作,因为如果将以下代码直接放在我的XAML页面中,它也可以正常工作:

<ActivityIndicator HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
                    Color="DarkBlue" IsVisible="{Binding IsLoading}" IsRunning="{Binding IsLoading}">
</ActivityIndicator>

因此,这是什么错误呢?

<controls:Loading LoadingIndicator="{Binding IsLoading}"></controls:Loading>

在我的视图模型中的每个页面上都设置了“ IsLoading”属性。这是视图模型的一个片段:

public ICommand OnSave => new Command(async () =>
{
    IsLoading = true;
    await CreateItem();
    IsLoading = false;
});

用于我的控件的代码如下:

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Loading : ContentView
{
    public static readonly BindableProperty LoadingIndicatorProperty =
        BindableProperty.Create(
            propertyName: nameof(LoadingIndicator), typeof(bool),
            typeof(Loading), default(string), BindingMode.OneWayToSource);

    public bool LoadingIndicator
    {
        get => (bool)GetValue(LoadingIndicatorProperty);
        set => SetValue(LoadingIndicatorProperty, value);
    }

    public Loading()
    {
        InitializeComponent();
        BindingContext = this;
    }
}

如果IsLoading绑定得到更新,我是否需要编写代码来处理更改?

这是我使用控件的页面的完整代码:

ItemCreatePage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage Title="{Binding PageTitle}"
             xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:userControls="clr-namespace:Framework.UserControls"
             xmlns:converters="clr-namespace:Framework.ValueConverters"
             xmlns:controls="clr-namespace:Framework.Controls;assembly=Framework.Android"
             x:Class="Framework.Views.Item.ItemCreatePage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <converters:DoubleConverter x:Key="DoubleConverter"></converters:DoubleConverter>
        </ResourceDictionary>
    </ContentPage.Resources>

    <ContentPage.Content>
        <Grid>
            <ScrollView>
                <Grid RowSpacing="0" VerticalOptions="Start">
                  <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="Auto" />
                  </Grid.RowDefinitions>

                  <StackLayout Grid.Row="1" Padding="20,20,20,0" VerticalOptions="Start">

                    <Label Text="Category" />
                    <userControls:BindablePicker
                        ItemsSource="{Binding Categories}"
                        SelectedItem="{Binding Path=Item.CategoryName, Mode=OneWay}"
                        DisplayMemberPath="Name"
                        SelectedValuePath="Id"
                        SelectedValue="{Binding Path=Item.CategoryId, Mode=TwoWay}"/>

                    <Label Text="Description" />
                    <Editor Text="{Binding Item.Description}" HeightRequest="100"/>

                    <Label Text="Area"/>
                    <Entry Text="{Binding Item.LineNumber}"/>

                    <Label Text="Identifier"/>
                    <Entry Text="{Binding Item.Identifier}"/>

                    <Label Text="Code"/>
                    <Entry Text="{Binding Item.Code}"/>

                    <Label Text="Priority" />
                    <userControls:BindablePicker
                        ItemsSource="{Binding Priorities}"
                        SelectedItem="{Binding Path=Item.ItemPriority, Mode=OneWay}"
                        DisplayMemberPath="Name"
                        SelectedValuePath="Id"
                        SelectedValue="{Binding Path=Item.ItemPriorityCode, Mode=TwoWay}"/>

                    <Label Text="Owner" />
                    <userControls:BindablePicker
                        ItemsSource="{Binding Users}"
                        SelectedItem="{Binding Path=Item.OwnerName, Mode=OneWay}"
                        DisplayMemberPath="Name"
                        SelectedValuePath="Id"
                        SelectedValue="{Binding Path=Item.OwnerId, Mode=TwoWay}"/>

                    <Label Text="Due Date" />
                    <DatePicker Date="{Binding Item.DateDue}" />

                    <Label Text="Date Identified" />
                    <DatePicker Date="{Binding Item.DateIdentified}" />

                    <Label Text="Status" />
                    <userControls:BindablePicker
                        ItemsSource="{Binding Statuses}"
                        SelectedItem="{Binding Path=Item.Status, Mode=OneWay}"
                        DisplayMemberPath="Name"
                        SelectedValuePath="Id"
                        SelectedValue="{Binding Path=Item.StatusCode, Mode=TwoWay}"/>


                    <Label Text="Comment" />
                    <Editor Text="{Binding Item.Comment}" HeightRequest="100"/>

                    <Label Text="IOM" />
                    <Entry Text="{Binding Item.OutcomeMeasurementInitial, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />

                    <Label Text="FOM" />
                    <Entry Text="{Binding Item.OutcomeMeasurementFinal, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />

                    <Label Text="Longitude" />
                    <Entry Text="{Binding Item.Longitude, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />

                    <Label Text="Latitude" />
                    <Entry Text="{Binding Item.Latitude, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />

                    <Button Margin="0,20,0,20" Command="{Binding OnSave}" BackgroundColor="{StaticResource Primary}"
                            BorderRadius="2" Text="Save" VerticalOptions="End" TextColor="White" ></Button>

                  </StackLayout>
                </Grid>
            </ScrollView>
            <controls:Loading LoadingIndicator="{Binding IsLoading}"></controls:Loading>
        </Grid>
      </ContentPage.Content>
</ContentPage>

ItemCreatePage.xaml.cs

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ItemCreatePage : ContentPage
{
    public ItemCreatePage ()
    {
        InitializeComponent ();
    }

    protected override async void OnAppearing()
    {
        var vm = BindingContext as ItemCreateViewModel;
        vm.Item = new Data.Entities.Item();
        await vm?.GetDeviceLocation();
        base.OnAppearing();
    }
}

视图模型代码:

public class ItemCreateViewModel : FormViewModel<Data.Entities.Item>
{
    public async Task GetDeviceLocation()
    {
        this.Item = await this.Item.AddDeviceLocation();
        OnPropertyChanged(nameof(this.Item));
    }

    public ILookupService LookupService { get; set; }

    public IItemService ItemService { get; set; }

    #region selectLists
    public List<EnumListItem<ItemPriority>> Priorities => EnumExtensions.ToEnumList<ItemPriority>();

    public List<EnumListItem<ItemStatus>> Statuses => EnumExtensions.ToEnumList<ItemStatus>();

    public string PageTitle => $"{PageTitles.ItemCreate}{this.OfflineStatus}";

    public List<Data.Entities.User> Users => UserService.GetAll(this.Offline);

    public List<Data.Entities.Lookup> Categories => LookupService.GetLookups(this.Offline, LookupTypeCode.ItemCategories);
    #endregion

    public Data.Entities.Item Item { get; set; }

    public ICommand OnSave => new Command(async () =>
    {
        await Loading(CreateItem);
    });

    private async Task CreateItem()
    {
        // ... Save logic is here
    }

FormViewModel:

public class FormViewModel<T> : BaseViewModel
{
    public IValidator<T> Validator => Resolve<IValidator<T>>();

    public bool IsLoading { get; set; }

    /// <summary>
    /// Render a loading spinner whilst we process a request
    /// </summary>
    /// <param name="method"></param>
    /// <returns></returns>
    public async Task Loading(Func<Task> method)
    {
        IsLoading = true;
        await method.Invoke();
        IsLoading = false;
    }
}

BaseViewModel:

public class BaseViewModel : IViewModelBase
{
    public BaseViewModel()
    {
        if (this.GetCurrentUserToken() != null && !UserService.IsActive())
        {
            SettingService.ClearToken();
            Bootstrapper.MasterDetailPage.IsPresented = false;
            Application.Current.MainPage = new LoginPage();
        }
    }

    public T Resolve<T>() => AutofacBootstrapper.Container.Resolve<T>();

    public IUserService UserService => Resolve<IUserService>();

    public INavigator Navigator => AutofacBootstrapper.Navigator;

    public IDisplayAlertFactory DisplayAlert { get; set; }

    public INavigation MasterNavigation => Bootstrapper.MasterDetailPage?.Detail?.Navigation;

    public bool Offline => SettingService.GetSetting<bool>(CacheProperties.Offline);

    public string OfflineStatus => this.Offline ? " - Offline" : string.Empty;

    public Token GetCurrentUserToken() => SettingService.GetToken() ?? null;

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyname = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
    }
}

2 个答案:

答案 0 :(得分:1)

更改IsLoading属性时,您不会调用PropertyChanged事件。如果要刷新UI,则需要为所选属性调用此事件。

IsLoading属性的实现更改为:

private bool _isLoading;
public bool IsLoading
{ 
   get=> _isLoading;
   set
   {
      _isLoading=value;
      OnPropertyChanged(nameof(IsLoading));
   }
}
and it should work

答案 1 :(得分:1)

您无需在此处设置自定义控件的BindingContext:

public Loading()
    {
        InitializeComponent();
        BindingContext = this;//It's wrong!
                              //because the custom control's BindingContext will
                              //automatically be set to the BindingContext of
                              //the page where it's used which is what we usually want.
    }

这里是实现您想要的方式:

您的自定义控件的XAML:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="Framework.Controls.Loading" x:Name="LoadingControl"
             HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
  <ContentView.Content>
      <ActivityIndicator x:Name="TheIndicator" HorizontalOptions="CenterAndExpand"
                         VerticalOptions="CenterAndExpand" Color="DarkBlue"/>
    </ContentView.Content>
</ContentView>

以下是其代码:

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Loading : ContentView
{
    public static readonly BindableProperty LoadingIndicatorProperty =
        BindableProperty.Create(propertyName:nameof(LoadingIndicator),
        returnType: typeof(bool), declaringType: typeof(Loading), defaultValue: default(bool),
        defaultBindingMode:BindingMode.Default, propertyChanged:LoadingBindingChanged);

    private static void LoadingBindingChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        var view = (Loading)(bindable);
        view.SetLoadingVisibility((bool)newvalue);
    }

    public Loading()
    {
        InitializeComponent();
        IsVisible = false; // we do this because by default a view' IsVisible property is true
    }

    public bool LoadingIndicator
    {
        get => (bool)GetValue(LoadingIndicatorProperty);
        set => SetValue(LoadingIndicatorProperty, value);
    }

    public void SetLoadingVisibility(bool show)
    {
        IsVisible = show;
        TheIndicator.IsVisible = show;
        TheIndicator.IsRunning = show;
    }
}