Привязка ContentView не работает должным образом - PullRequest
0 голосов
/ 06 июня 2019

У меня есть такой элемент управления, который может отображать загрузочный счетчик:

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

1 голос
/ 18 июня 2019

Вам не нужно устанавливать 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;
    }
}
1 голос
/ 06 июня 2019

Вы не вызываете событие PropertyChanged при изменении свойства IsLoading.Если вы хотите обновить пользовательский интерфейс, вам нужно вызвать это событие для выбранного свойства.

Измените реализацию свойства IsLoading на:

private bool _isLoading;
public bool IsLoading
{ 
   get=> _isLoading;
   set
   {
      _isLoading=value;
      OnPropertyChanged(nameof(IsLoading));
   }
}
and it should work
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...