Обязательный вопрос о детском взгляде в Xamarin - PullRequest
3 голосов
/ 17 октября 2019

У меня есть общий вид для страницы добавления и детализации. По какой-то причине на странице сведений модель представления не будет привязана к этому дочернему представлению (страница отображается пустым, как в случае НЕ заполняемого значения из службы API). Любые идеи?

Отладка этого и были данные, поступающие из веб-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 голосов
/ 17 октября 2019

Исходя из кода, которым вы поделились, я думаю, что вы, вероятно, не видите никаких данных на странице сведений об активности, потому что вы извлекаете данные с помощью асинхронного метода, который не ожидается (FetchActivityData). Кстати, по возможности следует избегать использования асинхронного пустого метода. Нет никакого способа поймать / обработать исключения, выданные из них.

Похоже, вы не ожидаете, потому что вы звоните из конструктора вашей модели представления. Здесь на самом деле происходит то, что конструктор сразу возвращается, а FetchActivityDetail () и FetchCategories () продолжают работать в фоновом режиме. Страница отображается, но данных пока нет, поэтому ничего не отображается. Затем, когда FetchActivityDetail завершает работу, он устанавливает _activity, но это поле, поэтому события PropertyChanged не запускаются, поэтому страница не знает, что ее нужно обновить.

Вот несколько предложений:

  1. Не выполняйте длительные процессы (например, выборку данных) в конструкторах. Передача существующих данных (например, ваш код активности), как правило, в порядке, хотя это может затруднить использование внедрения зависимостей, если вы в конечном итоге захотите это сделать.

  2. При переходе к модели представления, которая требует выборки данных, я обычно рекомендую подождать, пока представление / vm не отобразится, прежде чем выполнять вызов API. Для этого у меня все мои представления вызывают метод OnAppearing в моих моделях представления. Это легко перемещается в BasePage и BaseViewModel, от которых все наследуется. Затем вы можете делать такие вещи, как установка статуса IsBusy (для запуска некоторого пользовательского интерфейса, например, счетчика), и заполнять ваши данные. Это может выглядеть примерно так:

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

Другой вариант - сделать этот метод вызываемым до навигации, но для этого сначала потребуется создать модель представления, которая отличаетсяшаблон навигации, чем вы используете здесь. Есть несколько хороших примеров навигации, ориентированной на модель, но я не буду вдаваться в подробности.

Убедитесь, что при получении данных он устанавливает свойства, которые вызывают события PropertyChanged, чтобы привязки представления обновлялись. Вы не можете просто установить поле поддержки.
0 голосов
/ 17 октября 2019

Согласно вашему описанию, вы хотите связать пользовательский вид в 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));
        }
    }
}

Наконец, вы получаете данные, поступающие из веб-API для CategoryList, поэтому вы можете добавить точку останова впроверьте наличие данных.

0 голосов
/ 17 октября 2019

'Я не уверен, но, очевидно, страница изменяет контекст привязки вашего представления.

Установите точку останова внутри вашего переопределенного метода OnBindingContextChanged и отладьте его. Если это подтверждается, создайте экземпляр вашей модели представления со своей страницы.

Приветствия,

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...