简体   繁体   中英

Xamarin UWP seems to bind to the wrong view model

I have a Xamarin project for Android and UWP. This issue seems to only happen on UWP.

In my Xamarin project I have ContentPage with a view model bound as context. In this ViewModel there's an ObservableCollection with another kind of view model. When I create a new instance of this underlying ViewModel and add to my ObservableCollection, sometimes the ContentPage works as expected, showing an item in my ListView. But sometimes there's an empty element added, that I can see when hovering over the list. When this happens I get a bunch of warnings in the Output tab.

My DownloadsPage :

<ContentPage 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"
             xmlns:local="clr-namespace:Downloader.Converters"
             mc:Ignorable="d"
             x:Class="Downloader.Views.DownloadsPage">

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:DownloadStatusToColorConverter x:Key="downloadStatusToColor" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
        <ListView x:Name="DownloadsListView" SelectionMode="None" ItemsSource="{Binding Downloads}" RowHeight="70">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Padding="10" BackgroundColor="{Binding DownloadStatus, Converter={StaticResource downloadStatusToColor}}">
                            <Label Text="{Binding Name}"
                                   d:Text="{Binding .}"
                                   LineBreakMode="NoWrap"
                                   Style="{DynamicResource ListItemTextStyle}"
                                   FontSize="16" />
                            <Grid Grid.Row="0" Grid.Column="0" Padding="10,0,10,0">
                                <ProgressBar BackgroundColor="Transparent"  Progress="{Binding PercentDownloaded}" HorizontalOptions="FillAndExpand" HeightRequest="20">
                                </ProgressBar>
                                <Label Text="{Binding PercentString}" HorizontalTextAlignment="Center"></Label>
                            </Grid>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </RefreshView>

</ContentPage>

DownloadsViewModel is set as context in the code-behind like this:

public partial class DownloadsPage : ContentPage
{
    private readonly DownloadsViewModel _viewModel;

    public DownloadsPage()
    {
        InitializeComponent();

        BindingContext = _viewModel = new DownloadsViewModel();

        Device.StartTimer(TimeSpan.FromSeconds(1), () =>
        {
            Device.BeginInvokeOnMainThread(() => _viewModel.RefreshDownloads());
            return true;
        });
    }
}

The bound DownloadsViewModel :

public class DownloadsViewModel : BaseViewModel
{
    public ObservableCollection<DownloadViewModel> Downloads { get; set; } = new ObservableCollection<DownloadViewModel>();
 
    public Command LoadItemsCommand { get; set; }

    public DownloadsViewModel()
    {
        Title = "Downloads";
        LoadItemsCommand = new Command(() => {
            IsBusy = true;
            Downloads.Clear();
            RefreshDownloads();
            IsBusy = false;
        });
    }

    public void RefreshDownloads()
    {
        foreach (var download in DownloadManager.GetDownloads())
        {
            var existingDownload = Downloads.FirstOrDefault(d => d.Id == download.Id);

            if (existingDownload != null)
            {
                existingDownload.UpdateValues(download);
            }
            else
            {
                Downloads.Add(new DownloadViewModel(download));
            }
        }
    }
}

And the ObservableCollection contains DownloadViewModel that looks like this:

public class DownloadViewModel : BaseViewModel
{
    private IDownload _download;
    public DownloadViewModel(IDownload download)
    {
        UpdateValues(download);
    }

    private string _id;
    public string Id
    {
        get { return _id; }
        set { SetProperty(ref _id, value); }
    }

    private string _name;
    public string Name
    {
        get { return _name; }
        set { SetProperty(ref _name, value); }
    }

    private DownloadStatus _status;
    public DownloadStatus DownloadStatus
    {
        get { return _status; }
        set { SetProperty(ref _status, value); }
    }

    public double PercentDownloaded
    {
        get
        {
            return _download.DownloadedBytes == -1
                ? 0f
                : (double)_download.DownloadedBytes / _download.TotalBytes;
        }
    }

    public string PercentString { get => $"{(int)(PercentDownloaded * 100)} %"; }

    public void UpdateValues(IDownload download)
    {
        _download = download;
        Id = _download.Id;
        Name = _download.Name;
        DownloadStatus = _download.Status;
    }
}

The error I sometimes get which causes items in my ListView to be empty:

Binding: 'DownloadStatus' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.StackLayout.BackgroundColor'
Binding: 'Name' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'
Binding: 'PercentDownloaded' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.ProgressBar.Progress'
Binding: 'PercentString' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'

悬停时看到的幽灵物品

When debugging I've confirmed that the item is added to my ObservableCollection as expcted.

How come sometimes it's looking for DownloadStatus, Name, PercentDownloaded and PercentString on DownloadsViewModel instead of DownloadViewModel?

Xamarin UWP seems to bind to the wrong view model

I checked your code sample and it works as expect. But I found the progress value does not update automatically that cause the listview item can't display, I have update the IDownload interface add PercentDownloaded property. For the testing it could works in uwp platform.

The problem was that the ViewModels did not have setters with INotifyPropertyChanged implemented for all properties. The source code is available on Github , and the commit that fixes the issue is this one .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM