Xamarin中子视图的绑定问题

时间:2019-10-16 21:32:12

标签: c# xamarin xamarin.forms

我在“添加和详细信息”页面上有一个共享视图。由于详细信息页面中的某些原因,视图模型不会绑定到该子视图(页面显示为空白,因为api服务中没有填充值)。有什么想法吗?

对此进行调试,并且来自Web api的数据同时包含 CategoryList _activity

如何调试此绑定过程?

ActivityView.xaml

<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="AthlosifyMobileApp.Views.ActivityView">
    <StackLayout Spacing="12">
        <Entry x:Name="txtName" Text="{Binding Name}" HeightRequest="40" BackgroundColor="White" Placeholder="Name" HorizontalOptions="FillAndExpand"/>
        <Entry  x:Name="txtNoOfMinutes" Keyboard="Numeric"  Text="{Binding NoOfMinutes}" BackgroundColor="White" Placeholder="NoOfMinutes" HorizontalOptions="FillAndExpand"/>
        <Entry x:Name="txtDescription" Text="{Binding Description}" HeightRequest="40" BackgroundColor="White" Placeholder="Description" HorizontalOptions="FillAndExpand"/>
        <Picker ItemsSource="{Binding CategoryList}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding SelectedCategory}"></Picker>
    </StackLayout>
</ContentView>

ActivityView.xaml.cs

namespace AthlosifyMobileApp.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class ActivityView : ContentView
    {
        public ActivityView ()
        {
            InitializeComponent ();
        }
    }
}

ActivityDetailViewModel.cs

namespace AthlosifyMobileApp.ViewModels
{
    public class ActivityDetailViewModel : ActivityBaseViewModel
    {
        public ICommand DeleteCommand { get; private set; }
        public ICommand UpdateCommand { get; private set; }

        public ActivityDetailViewModel(INavigation navigation, int selectedActivityId)
        {
            _navigation = navigation;
            _activityValidator = new ActivityValidator();
            _activity = new Activity();
            _activity.Id = selectedActivityId;
            _apiService = new ApiService();

            //DeleteCommand = new Command(async () => await HandleDeleteActivity());
            UpdateCommand = new Command(async () => await UpdateActivity());

            FetchActivityDetail();
            FetchCategories();
        }


        async void FetchActivityDetail()
        {
            _activity = await _apiService.GetActivity(_activity.Id);
        }

        async void FetchCategories()
        {
            CategoryResult categoryResult = await _apiService.GetCategories();
            CategoryList = categoryResult.Results;
        }


        async Task UpdateActivity()
        {
            _activity.OwnerId = Preferences.Get(Constant.Setting_UserId, "");
            _activity.CategoryId = SelectedCategory.Id;
            _activity.CategoryName = SelectedCategory.Name;


            var validationResults = _activityValidator.Validate(_activity);

            if (validationResults.IsValid)
            {
                bool isUserAccept = await Application.Current.MainPage.DisplayAlert("Contact Details", "Update Contact Details", "OK", "Cancel");
                if (isUserAccept)
                {
                    var response = await _apiService.UpdateActivity(_activity.Id,_activity);
                    if (!response)
                    {
                        await Application.Current.MainPage.DisplayAlert("Add Activity", "Error", "Alright");
                    }
                    else
                    {
                        await _navigation.PushAsync(new ActivityListPage());
                    }
                    await _navigation.PopAsync();
                }
            }
            else
            {
                await Application.Current.MainPage.DisplayAlert("Add Contact", validationResults.Errors[0].ErrorMessage, "Ok");
            }
        }

        public async Task HandleDeleteActivity(int id)
        {
            var alert = await Application.Current.MainPage.DisplayAlert("Warning", "Do you want to delete this item?", "Yes", "Cancel");
            if (alert)
            {
                var response = await _apiService.DeleteActivity(id);
                if (!response)
                {
                    await Application.Current.MainPage.DisplayAlert("Error", "Something wrong", "Alright");
                }
                else
                {
                    await _navigation.PushAsync(new ActivityListPage());
                }
            }
        }


    }
}

ActivityBaseViewModel.cs

namespace AthlosifyMobileApp.ViewModels
{
    public class ActivityBaseViewModel : INotifyPropertyChanged
    {
        public Activity _activity;

        public INavigation _navigation;
        public IValidator _activityValidator;
        public ApiService _apiService;

        public string Name
        {
            get
            {
                return _activity.Name;
            }
            set
            {
                _activity.Name = value;
                NotifyPropertyChanged("Name");
            }
        }

        public string Description
        {
            get { return _activity.Description; }
            set
            {
                _activity.Description = value;
                NotifyPropertyChanged("Description");
            }
        }

        public int NoOfMinutes
        {
            get { return _activity.NoOfMinutes; }
            set
            {
                _activity.NoOfMinutes = value;
                NotifyPropertyChanged("NoOfMinutes");
            }
        }

        public int CategoryId
        {
            get { return _activity.CategoryId; }
            set
            {
                _activity.CategoryId = value;
                NotifyPropertyChanged("CategoryId");
            }
        }

        public string CategoryName
        {
            get { return _activity.CategoryName; }
            set
            {
                _activity.CategoryName = value;
                NotifyPropertyChanged("CategoryName");
            }
        }

        //List<Activity> _activityList;
        InfiniteScrollCollection<Activity> _activityList;

        //public List<Activity> ActivityList
        public InfiniteScrollCollection<Activity> ActivityList
        {
            get => _activityList;
            set
            {
                _activityList = value;
                NotifyPropertyChanged("ActivityList");
            }
        }

        List<Category> _categoryList;

        public List<Category> CategoryList
        {
            get { return _categoryList; }
            set
            {
                _categoryList = value;
                NotifyPropertyChanged("CategoryList");
            }
        }

        public Category SelectedCategory
        {
            get
            {
                return _activity.SelectedCategory;
            }
            set
            {
                _activity.SelectedCategory = value;
                NotifyPropertyChanged("SelectedCategory");
            }
        }

        #region INotifyPropertyChanged       
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

ActivityDetailPage.xaml

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:AthlosifyMobileApp.Views"
             x:Class="AthlosifyMobileApp.Views.ActivityDetailPage"   
             Title="Detail Activity">
    <ContentPage.ToolbarItems>
        <ToolbarItem Command="">
            <ToolbarItem.IconImageSource>
                <FontImageSource  Glyph="&#xf1c0;" FontFamily="{StaticResource MaterialFontFamily}"/>
            </ToolbarItem.IconImageSource>
        </ToolbarItem>
        <ToolbarItem Command="{Binding UpdateCommand}">
            <ToolbarItem.IconImageSource>
                <FontImageSource Size="30" Glyph="&#xf193;" FontFamily="{StaticResource MaterialFontFamily}"/>
            </ToolbarItem.IconImageSource>
        </ToolbarItem>
    </ContentPage.ToolbarItems>
    <ContentPage.Content>
        <StackLayout  Padding="20" Spacing="12">
            <local:ActivityView />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

ActivityDetailPage.xaml.cs

namespace AthlosifyMobileApp.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class ActivityDetailPage : ContentPage
    {
        public ActivityDetailPage(int activityId)
        {
            InitializeComponent ();
            BindingContext = new ActivityDetailViewModel(Navigation, activityId);
        }
    }
}

3 个答案:

答案 0 :(得分:0)

'我不确定,但是显然页面正在更改视图的绑定上下文。

在视图的OnBindingContextChanged重写方法内设置一个断点并进行调试。如果已确认,请从页面实例化视图模型。

干杯

答案 1 :(得分:0)

基于共享的代码,我认为您可能没有在活动详细信息页面上看到任何数据,因为您正在通过未等待的异步方法(FetchActivityData)来获取数据。顺便说一句,如果可能,应避免使用异步无效方法。无法捕获/处理从它们引发的异常。

由于您正在从viewmodel的构造函数调用,因此您似乎没有等待。实际发生的是构造函数立即返回,而FetchActivityDetail()和FetchCategories()继续在后台运行。显示页面,但是尚无数据,因此您看不到任何显示。然后,当FetchActivityDetail完成时,它将设置_activity,但这是一个字段,因此不会触发PropertyChanged事件,因此页面不知道需要更新。

以下是一些建议:

  1. 不要在构造函数中执行长时间运行的过程(如获取数据)。通常可以传入现有数据(例如您的activityid),尽管如果最终想要这样做,则使用依赖注入会更困难。

  2. 导航到需要获取数据的视图模型时,通常建议等到显示视图/ vm后再进行api调用。为此,我在视图模型中将所有视图都调用了OnAppearing方法。可以轻松地将其移至所有继承的BasePage和BaseViewModel中。然后,您可以执行诸如设置IsBusy状态(以触发微调框之类的UI)之类的操作,并填充数据。看起来可能像这样:

    public override async Task OnAppearing()
    {
        await base.OnAppearing();
    
        try
        {
            IsBusy = true;
            await FetchActivityDetail(); 
            await FetchCategories(); 
        }
        catch (Exception ex)
        {
            //handle/display error
        }
        finally 
        {
            IsBusy = false;
        }
    }
    

另一个选择是使它成为导航之前被调用的方法,但这将需要首先创建viewmodel,这与您在此处使用的导航模式不同。有一些很好的例子,其中有viewmodel-first导航,但是这里我不再赘述。

  1. 确保在提取数据时,它会设置导致引发PropertyChanged事件的属性,以便更新视图绑定。您不能只设置一个备用字段。

答案 2 :(得分:0)

根据您的描述,您想在Xamarin.Forms中绑定自定义视图,建议您不要在自定义控件内部分配绑定,请使用以下方法:

<ContentView
x:Class="demo2.simplecontrol.View1"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<ContentView.Content>
    <StackLayout>
        <Entry x:Name="label1" />
        <Entry x:Name="label2" />
    </StackLayout>
</ContentView.Content>

public partial class View1 : ContentView
{
    public View1 ()
    {
        InitializeComponent ();          
    }

    public static readonly BindableProperty Label1Property= BindableProperty.Create(
                    nameof(Label1),
        typeof(string),
        typeof(View1),
        "",
        BindingMode.TwoWay,
        propertyChanged: (bindable, oldValue, newValue) =>
        {
            if (newValue != null && bindable is View1 control)
            {
                var actualNewValue = (string)newValue;
                control.label1.Text = actualNewValue;
            }
        });

    public string Label1 { get; set; }

    public static readonly BindableProperty Label2Property = BindableProperty.Create(
                    nameof(Label2),
        typeof(string),
        typeof(View1),
        "",
        BindingMode.TwoWay,
        propertyChanged: (bindable, oldValue, newValue) =>
        {
            if (newValue != null && bindable is View1 control)
            {
                var actualNewValue = (string)newValue;
                control.label2.Text = actualNewValue;
            }
        });

    public string Label2 { get; set; }
}

然后您可以在ContentPage中使用此自定义视图。

<ContentPage
x:Class="demo2.simplecontrol.Page10"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:demo2.simplecontrol">
<ContentPage.Content>
    <StackLayout>
        <Label
            HorizontalOptions="CenterAndExpand"
            Text="Welcome to Xamarin.Forms!"
            VerticalOptions="CenterAndExpand" />
        <local:View1 Label1="{Binding text1}" Label2="{Binding text2}" />
    </StackLayout>
</ContentPage.Content>

public partial class Page10 : ContentPage, INotifyPropertyChanged
{
    private string _text1;
    public string text1
    {
        get { return _text1; }
        set
        {
            _text1 = value;
            RaisePropertyChanged("text1");
        }
    }

    private string _text2;
    public string text2
    {
        get { return _text2; }
        set
        {
            _text2 = value;
            RaisePropertyChanged("text2");
        }
    }
    public Page10 ()
    {
        InitializeComponent ();
        text1 = "test1";
        text2 = "test2";
        this.BindingContext = this;
    }


    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

最后,您从Web API获得CategoryList的数据,因此可以添加断点以检查是否有数据。