无论 .NET MAUI 中绑定的 ObservableRangeCollection 如何更改,列表视图都不会更新

问题描述 投票:0回答:1

正如标题所说。如果我尝试刷新绑定到

ObservableRangeCollection
中的
RestroomDetailPage.xaml
评论的列表视图,它只会卡在活动指示器上,并且不会更新任何内容。

我已经检查过

ObservableRangeCollection
,它工作得很好。尝试刷新列表视图时不会引发任何错误。

这是仓库:https://github.com/K-Ketchup/Dook

评论.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SQLite;

namespace Dook.Shared.Models
{
    public class Review : INotifyPropertyChanged
    {
        private int id;
        private string username;
        private double stars;
        private string text;
        private int restroomid;

        protected void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                handler(this, e);
        }

        protected void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [SQLite.PrimaryKey, SQLite.AutoIncrement]
        [Column("Id")]
        public int Id
        {
            get { return id; } 
            set
            {
                if (value != id)
                {
                    id = value;
                    OnPropertyChanged("Id");
                }
            }
        }
        [Column("Username")]
        public string Username
        {
            get { return username; }
            set
            {
                if (value != username)
                {
                    username = value;
                    OnPropertyChanged("Username");
                }
            }
        }
        [Column("Stars")]
        public double Stars
        {
            get { return stars; }
            set
            {
                if (value != stars)
                {
                    stars = value;
                    OnPropertyChanged("Stars");
                }
            }
        }
        [Column("Text")]
        public string Text
        {
            get { return text; }
            set
            {
                if (value != text)
                {
                    text = value;
                    OnPropertyChanged("Text");
                }
            }
        }
        [Column("RestroomId")]
        public int RestroomId
        {
            get { return restroomid; }
            set
            {
                if (value != restroomid)
                {
                    restroomid = value;
                    OnPropertyChanged("RestroomId");
                }
            }
        }
        //[Column("Tags")]
        //public string Tags { get; set; }
    }
}

InternetReviewService.cs

using Dook.Shared.Models;
using Newtonsoft.Json;
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Dook.Services
{
    public static class InternetReviewService
    {
        //static string Baseurl = DeviceInfo.Platform == DevicePlatform.Android ?
        //                                    "http://10.0.2.2:5000" : "http://localhost:5000";
        static string BaseUrl = "https://dookwebapp.azurewebsites.net/";
        static HttpClient client;

        static InternetReviewService()
        {
            client = new HttpClient()
            {
                BaseAddress = new Uri(BaseUrl)
            };
        }

        static Random random = new Random();

        public static async Task AddReviewAsync(string username, double stars, string text, int restroomid)
        {
            int idNum = random.Next(0, 100000);

            try
            {
                //Check to see if ID is a duplicate
                var randomReview = await client.GetStringAsync($"api/Review/Individual/{idNum}");
                await AddReviewAsync(username, stars, text, restroomid);
            }
            catch(HttpRequestException ex)
            {
                var review = new Review()
                {
                    Username = username,
                    Stars = stars,
                    Text = text,
                    Id = idNum,
                    RestroomId = restroomid
                };

                var json = JsonConvert.SerializeObject(review);
                var content =
                    new StringContent(json, Encoding.UTF8, "application/json");

                var response = await client.PostAsync("api/Review", content);

                if (!response.IsSuccessStatusCode)
                    Debug.WriteLine("Response Success!");
            }
        }

        public static async Task RemoveReviewAsync(int id)
        {
            var response = await client.DeleteAsync($"api/Review/{id}");
            if (!response.IsSuccessStatusCode)
                Debug.WriteLine("Response Success!");
        }

        public static async Task<IEnumerable<Review>> GetReviewAsync(int restId)
        {
            try
            {
                var json = await client.GetStringAsync($"api/Review/List/{restId}");
                Console.WriteLine(json);
                var reviews = JsonConvert.DeserializeObject<IEnumerable<Review>>(json);
                return reviews;
            }
            catch (HttpRequestException ex)
            {
                Console.WriteLine($"HTTP request failed: {ex.Message}");
                return Enumerable.Empty<Review>();
            }
        }
    }
}

RestroomDetailPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:model="clr-namespace:Dook.Shared.Models;assembly=Dook.Shared"
             xmlns:viewmodel="clr-namespace:Dook.ViewModel"
             xmlns:controls="clr-namespace:Dook.Controls"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="Dook.View.RestroomDetailPage"
             x:Name="RestroomPage"
             Title="Pin">

    <ContentPage.BindingContext>
        <viewmodel:InternetRestroomDetailViewModel/>
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <ResourceDictionary>
            <toolkit:SelectedItemEventArgsConverter x:Key="SelectedItemEventArgsConverter"/>
        </ResourceDictionary>
    </ContentPage.Resources>

    <VerticalStackLayout>
        <Frame 
            Margin="10"
            BorderColor="Transparent"
            CornerRadius="50"
            HeightRequest="250"
            WidthRequest="250"
            IsClippedToBounds="True"
            HorizontalOptions="Center"
            VerticalOptions="Center">
            <Image 
                Source="dook_toilet.png"
                Aspect="AspectFill"
                Margin="0"
                HeightRequest="300"
                WidthRequest="300" />
        </Frame>

        <Label 
            x:DataType="model:Restroom"
            Text="{Binding Name}"
            FontSize="23"
            HorizontalOptions="Center"/>
        <Label 
            x:DataType="model:Restroom"
            Text="{Binding Address}"
            FontSize="18"
            HorizontalOptions="Center"/>
        <Label 
            x:DataType="model:Restroom"
            Text="{Binding Username, StringFormat='Registered by: {0}'}"
            HorizontalOptions="Center"
            FontSize="18"/>

        <ListView
            x:DataType="viewmodel:InternetRestroomDetailViewModel"
            BindingContext="{Binding Source={viewmodel:InternetRestroomDetailViewModel}}"
            BackgroundColor="LightGray"
            CachingStrategy="RecycleElement"
            HasUnevenRows="True"
            IsPullToRefreshEnabled="True"
            IsRefreshing="{Binding IsBusy, Mode=OneWay}"
            ItemsSource="{Binding Review}"
            RefreshControlColor="#37A7FC"
            SelectionMode="None"
            SeparatorVisibility="None"
            WidthRequest="340"
            HeightRequest="350"
            x:Name="ReviewList"
            Margin="10">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="model:Review">
                    <ViewCell>
                        <ViewCell.ContextActions>
                            <ToolbarItem
                                x:DataType="viewmodel:InternetRestroomDetailViewModel"
                                Command="{Binding RemoveCommand}"
                                CommandParameter="{Binding .}"
                                IsDestructive="True"
                                Text="Delete" />
                        </ViewCell.ContextActions>
                        <Grid Padding="10">
                            <Frame CornerRadius="20" HasShadow="True">
                                <StackLayout Orientation="Horizontal">
                                    <StackLayout VerticalOptions="Center">
                                        <Label
                                            FontSize="Large"
                                            Text="{Binding Username}"
                                            VerticalOptions="Center"/>
                                        <controls:RatingControl 
                                            Amount="5"
                                            CurrentValue="{Binding Stars}"
                                            AccentColor="Yellow"
                                            StarSize="36" 
                                            VerticalOptions="End" />
                                        <Label
                                            FontSize="Small"
                                            Text="{Binding Text}"
                                            VerticalOptions="Center"/>
                                    </StackLayout>
                                </StackLayout>
                            </Frame>
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </VerticalStackLayout>
</ContentPage>

RestroomDetailPage.xaml.cs

using Dook.Services;
using Dook.Shared.Models;
using Dook.ViewModel;
using Microsoft.Maui.Controls.Maps;
using System.Windows.Input;

namespace Dook.View;

[QueryProperty(nameof(RestroomId), nameof(RestroomId))]
public partial class RestroomDetailPage : ContentPage
{
    public string RestroomId { get; set; }
    public Restroom restroom { get; set; }
    public RestroomDetailPage()
    {
        InitializeComponent();

        var vm = (InternetRestroomDetailViewModel)this.BindingContext;
        ToolbarItem item = new ToolbarItem
        {
            Text = "Add"
        };
        item.Clicked += async (s, args) =>
        {
            if (vm.AddCommand.CanExecute(restroom.Id.ToString()))
                await vm.AddCommand.ExecuteAsync(restroom.Id.ToString());
        };
        this.ToolbarItems.Add(item);

        ICommand refreshCommand = new Command(async () =>
        {
            if (vm.RefreshCommand.CanExecute(restroom.Id.ToString()))
                vm.RefreshCommand.ExecuteAsync(restroom.Id.ToString());
        });
        ReviewList.RefreshCommand = refreshCommand;
    }

    protected override async void OnAppearing()
    {
        base.OnAppearing();
        int.TryParse(RestroomId, out var result);

        restroom = await InternetRestroomService.GetSingularPinAsync(result);
        BindingContext = restroom;
    }
}

InternetRestroomDetailViewModel.vs

using Dook.Shared.Models;
using MvvmHelpers.Commands;
using MvvmHelpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dook.Services;

namespace Dook.ViewModel
{
    public class InternetRestroomDetailViewModel : BaseViewModel
    {
        public ObservableRangeCollection<Review> Review { get; set; }
        public String rID { get; set; }
        public AsyncCommand<String> RefreshCommand { get; }
        public AsyncCommand<String> AddCommand { get; }
        public AsyncCommand<Review> RemoveCommand { get; }

        public InternetRestroomDetailViewModel()
        {
            Title = "My Review";

            Review = new ObservableRangeCollection<Review>();

            RefreshCommand = new AsyncCommand<String>(RefreshAsync);
            AddCommand = new AsyncCommand<String>(AddAsync);
            RemoveCommand = new AsyncCommand<Review>(RemoveAsync);
        }

        async Task AddAsync(String restId)
        {
            var username = await App.Current.MainPage.DisplayPromptAsync("Username", "Username for review");
            var stars = await App.Current.MainPage.DisplayPromptAsync("Stars", "Stars for review", maxLength: 1, keyboard: Keyboard.Numeric);
            var text = await App.Current.MainPage.DisplayPromptAsync("Text", "Add text", maxLength: 50);
            rID = restId;
            if (username == null || stars == null || text == null) { return; }
            await InternetReviewService.AddReviewAsync(username, Double.Parse(stars), text, Int32.Parse(restId));
            await RefreshAsync(restId);
        }
        async Task RemoveAsync(Review review)
        {
            await InternetReviewService.RemoveReviewAsync(review.Id);
            await RefreshAsync(rID);
        }

        async Task RefreshAsync(String restId)
        {
            IsBusy = true;
            await Task.Delay(2000);
            Review.Clear();
            var reviews = await InternetReviewService.GetReviewAsync(Int32.Parse(restId));
            try
            {
                Review.AddRange(reviews);
            }
            catch(Exception ex) 
            {
                Debug.WriteLine(ex.Message);
            }
            IsBusy = false;
        }
    }
}

BaseViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Dook.ViewModel
{
    public partial class BaseViewModel : ObservableObject
    {
        public BaseViewModel()
        {

        }

        [ObservableProperty]
        [NotifyPropertyChangedFor(nameof(IsNotBusy))]
        bool isBusy;

        [ObservableProperty]
        string title;

        public bool IsNotBusy => !IsBusy;
    }
}
c# .net xamarin xamarin.ios maui
1个回答
0
投票

我通过删除后面代码中的第一个绑定上下文解决了这个问题 -

RestroomDetailPage.xaml.cs
。看起来这个绑定上下文干扰了列表视图的绑定,因为列表视图使用了不同的绑定上下文。

© www.soinside.com 2019 - 2024. All rights reserved.