ContentView 绑定无法正常工作 [英] ContentView binding doesn't work properly

查看:27
本文介绍了ContentView 绑定无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个像这样的可重用控件来显示加载微调器:

I have a resuable control like this to display a loading spinner:

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

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

I am trying to consume it on a page like this:

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

但是,加载微调器没有出现在屏幕上.

However, the loading spinner fails to appear on-screen.

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

When I set the LoadingIndicator property to true, it appears just fine:

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

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

My 'IsLoading' binding is definitely working properly, because if I place the following code directly in my XAML page it also works fine:

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

因此,这有什么问题?

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

IsLoading"属性从我的视图模型中设置在我的每个页面上.这是视图模型的一个片段:

The 'IsLoading' property gets set on each of my pages from my view model. Here is a snippet from the view model:

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

我的控件的代码隐藏如下:

The code-behind for my control looks like this:

[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 绑定更新,我是否需要编写代码来处理更改?

Do I need to write code to handle the change if the IsLoading binding gets updated?

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

This is the full code for the page where I am using the control:

ItemCreatePage.xaml

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

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
    }

表单视图模型:

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

基础视图模型:

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

推荐答案

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

You don't need to set your custom control's BindingContext here:

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

这是一种实现您想要的方法:

Here is a way to achieve what you 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;
    }
}

这篇关于ContentView 绑定无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆