D
D
Dmitry2021-07-03 22:44:19
Mobile development
Dmitry, 2021-07-03 22:44:19

The ListView does not display the data from the ObservableCollection and the XAML refuses to bind. How to fix?

Good afternoon! I study and write a course project gradually.
Began to comprehend Xamarin.Forms using MVVM pattern and SQLite. And I've run into a problem that I can't find the answer to anywhere. The main page in the ListView should display data received from the database, loaded into the ObservableCollection. And it seems that the data comes to the collection, and there is a Binding with ItemsSource, but there are either empty lines or errors on the screen. As a result, I stalled with this for a couple of days.

ViewModel to Model

namespace Pillbox.ViewModels
{
    public class MedicineViewModel:BaseViewModel
    {  
        public MedicineViewModel() { }
        public MedicineViewModel(Medicine medicine)
        {
            Id = medicine.Id;
            Title = medicine.Title;
            Format = medicine.Format;
            Method = medicine.Method;
            StartMedicationTime = medicine.StartMedicationTime;
            FinishMedicationTime = medicine.FinishMedicationTime;
            Dosage = medicine.Dosage;
            Number = medicine.Number;
            Start = medicine.Start;
            DurationDays = medicine.DurationDays;
            Finish = medicine.Finish;
            EveryDay = medicine.EveryDay;
            InDays = medicine.InDays;
            NonStop = medicine.NonStop;
        }
        public int Id { get; set; }
        private string _title; 
        public string Title
        {
            get => _title;
            set => Set(ref _title, value);
        }
        private string _format;
        public string Format
        {
            get => _format;
            set => Set(ref _format, value);
        }
        private string _method;
        public string Method
        {
            get => _method;
            set => Set(ref _method, value);
        }
        private TimeSpan _startMedicationTime;
        public TimeSpan StartMedicationTime
        {
            get => _startMedicationTime;
            set => Set(ref _startMedicationTime, value);
        }
        private TimeSpan _finishMedicationTime;
        public TimeSpan FinishMedicationTime
        {
            get => _finishMedicationTime;
            set => Set(ref _finishMedicationTime, value);
        }
        private float _dosage;
        public float Dosage
        {
            get => _dosage;
            set => Set(ref _dosage, value);
        }
        private int _number;
        public int Number 
        { 
            get=>_number; 
            set=>Set(ref _number, value); 
        }
        private DateTime _start;
        public DateTime Start { get=>_start; set=>Set(ref _start, value); }
        private int _durationDays;
        public int DurationDays { get=> _durationDays; set=>Set(ref _durationDays, value); }
        private DateTime _finish;
        public DateTime Finish { get=> _finish; set=>Set(ref _finish, value); }
        private bool _everyDay;
        public bool EveryDay { get=> _everyDay; set=>Set(ref _everyDay, value); }
        private int _inDays;
        public int InDays { get=> _inDays; set=>Set(ref _inDays, value); }
        private bool _nonStop;
        public bool NonStop { get=> _nonStop; set=>Set(ref _nonStop, value); }        
    }
}

ViewModel for the main page
namespace Pillbox.ViewModels
{
    public class MedPageViewModel:BaseViewModel
    {
        public ICommand AddMedicineCommand { get; protected set; }
        public ICommand DeleteMedicineCommand { get; protected set; }
        public ICommand SelectMedicineCommand { get; protected set; }
        public ICommand LoadMedicinesCommand { get; protected set; }

        private bool _isDataLoaded;

        private IMedicineDatabase _medicineDB;

        private IPageSevices _pageService;

        private MedicineViewModel _selectedMedicine;
        public MedicineViewModel SelectedMedicine
        {
            get => _selectedMedicine;
            set
            {
                Set(ref _selectedMedicine, value);
            }
        }

        public ObservableCollection<MedicineViewModel> Medicines { get; set; }
            = new ObservableCollection<MedicineViewModel>();
        
        public MedPageViewModel(IPageSevices pageSevices, IMedicineDatabase medicineDatabase)
        {
            _pageService = pageSevices;
            _medicineDB = medicineDatabase;
                                 
            LoadMedicinesCommand = new Command(async () => await Load());
            AddMedicineCommand = new Command(async () => await AddMedicine());
            DeleteMedicineCommand = new Command<MedicineViewModel>(async c => await DeleteMedicine(c));
            SelectMedicineCommand = new Command<MedicineViewModel>(async c => await SelectMedicine(c));

            MessagingCenter.Subscribe<AdditionViewModel, Medicine>
                (this, Events.MedicineAdded, OnMedicineAdded);
            MessagingCenter.Subscribe<AdditionViewModel, Medicine>
                (this, Events.MedicineUpdate, OnMedicineUpdated);            
        }

        private async Task SelectMedicine(MedicineViewModel medicine)
        {
            if (medicine == null) 
                return;
            SelectedMedicine = null;
            await _pageService.PushAsync(new AdditionView(medicine));
        }

        private async Task Load()
        {
            Medicines.Clear();
            try
            {
                if (_isDataLoaded)
                    return;
                _isDataLoaded = true;
                var medicines = await _medicineDB.UpdateMedicineList();
                foreach (var medicine in medicines)
                    Medicines.Add(new MedicineViewModel(medicine));
            }
            catch (Exception)
            { throw; }

        }

        async Task AddMedicine()
        {
            await _pageService.PushAsync(new AdditionView(new MedicineViewModel()));
        }

        async Task DeleteMedicine(MedicineViewModel deleteMedicine)
        {
            if (await _pageService.DisplayAlert("Внимание", $"Вы действительно хотите удалить {deleteMedicine.Title}?", "Да", "Нет"))
            {
                Medicines.Remove(deleteMedicine);
                var medicine = await _medicineDB.GetMedicine(deleteMedicine.Id); 
                await _medicineDB.DeleteMedicine(medicine);
            }
        }           
    }
}


Main page view
namespace Pillbox.Views.MainViews
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MedPage : ContentPage
    {     
        public MedPageViewModel ViewModelMP
        {
            get => BindingContext as MedPageViewModel;
            set => BindingContext = value;
        }
        public MedPage()
        {        
            var medicineDB = new MedicineDatabase(DependencyService.Get<ISQLiteDb>());
            var pageService = new PageService();
            ViewModelMP = new MedPageViewModel(pageService, medicineDB);
            InitializeComponent();                      
        }
        protected override void OnAppearing()
        {
           
            ViewModelMP.LoadMedicinesCommand.Execute(null);
            base.OnAppearing();            
        }

        void OnMedicineSelected(object sender, SelectedItemChangedEventArgs e)
        {
            ViewModelMP.SelectMedicineCommand.Execute(e.SelectedItem);
        }          
    }
}

AND XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:Pillbox.ViewModels"
             x:DataType="vm:MedPageViewModel"
             x:Class="Pillbox.Views.MainViews.MedPage"
             Title="Мои лекарства" x:Name="medPage">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <ScrollView Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
                    <ListView x:Name="listView" ItemsSource="{Binding Medicines}" SelectedItem="{Binding SelectedMedicine, Mode=TwoWay}" 
                          HasUnevenRows="True" SeparatorColor="#005400" ItemSelected="OnMedicineSelected" IsVisible="True" IsTabStop="False" IsEnabled="True" IsRefreshing="False">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                        <ViewCell>
                            <Grid IsVisible="True" IsEnabled="True">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="auto"/>
                                </Grid.RowDefinitions>
                                <Label Text="{Binding Source=Medicines, Path=Title}" FontSize="Large"/>
                                <Label Text="{Binding Source=Medicines, Path=Format}"/>
                                <Label Text="{Binding Source=Medicines, Path=Method}"/>


                            </Grid>                            
                        </ViewCell>
                    </DataTemplate>
                        </ListView.ItemTemplate>

                    </ListView>
            </ScrollView>
            <Button Grid.Row="1" Grid.Column="1" Text="+ Добавить" FontSize="Medium" FontAttributes="None" TextColor="White"
                        BackgroundColor="#13bd13" BorderRadius="75" HorizontalOptions="End" VerticalOptions="End" 
                Command="{Binding AddMedicineCommand}" Margin="0,0,30,30"/>
        </Grid>

</ContentPage>

Answer the question

In order to leave comments, you need to log in

1 answer(s)
F
Foggy Finder, 2021-07-04
@Kytukyla

You are using compiled bindings, which means you need to specify the type explicitly for child elements as well. At least until it's fixed in XF
Adding x:DataType to a ContentPage breaks nested
o
x:DataType="vm:MedicineViewModel"
...

<ListView.ItemTemplate>
    <DataTemplate x:DataType="vm:MedicineViewModel">
        <ViewCell>
            <ViewCell.View>
                <StackLayout>
                    <Label FontSize="Large" Text="{Binding Title}" />
                    <Label FontSize="Small" Text="{Binding Format}" />
                    <Label FontSize="Small" Text="{Binding Method}" />
                </StackLayout>
            </ViewCell.View>
        </ViewCell>
    </DataTemplate>
</ListView.ItemTemplate>

And let me give you a couple of tips that don't directly relate to answer
1. While working on projects, request a CodeReview from time to time, so the code will be cleaner and you can "grow" faster.
2. Don't send private keys to git. While the project is educational, this is not so scary, but it is better to exercise caution as early as possible.
3. When you encounter a problem (and this will certainly happen), do not drag the code from your project into the question, it is better to create a small test project where there will be nothing superfluous. At first glance, this is a waste of time, but in many cases you will get to the bottom of the problem during the preparation of such an MCVE.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question