简体   繁体   中英

Xamarin ProgressBar not updating real time

All,

I'm fairly new to Xamarin and more notably, C# so please, be kind ...!

I have a Xamarin.Forms application that drops down to .iOS using a DependencyService which then traverses all of the songs on my device and returns using a custom "Song" model.

There's no doubt a better way to do it but in order to return the album artwork back to the PCL, I've taken the iOS UIImage and turned it into a System.IO.Stream object and am returning that through the Song model.

Adding this artwork functionality resulted in a much larger overhead when processing each song. To try and give the user an appreciation for what is going on, I've put a progress bar on the page and want it to update each time I process a single song.

My problem is, I've been unable to get the progress bar to update in real time. It only updates once the process is complete.

I'm not using MVVM at this stage, so this is the code behind ...

using Xamarin.Forms;
using TestApplication.Interfaces;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System;

namespace TestApplication
{
    public partial class TestApplicationPage : ContentPage, INotifyPropertyChanged
    {
        private double _progress;
        public double Progress
        {
            get { return _progress; }
            set
            {
                _progress = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public TestApplication()
        {
            InitializeComponent();
            BindingContext = this;
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        async void GetNoOfSongs(object sender, System.EventArgs e)
        {
            Action<double> progressUpdate = UpdateProgress;

            var songs = await DependencyService.Get<IMedia>().GetSongs(progressUpdate);

            await DisplayAlert("No. of Songs", string.Format("You have { 0} songs on your device!", songs.Count), "Ok");
        }

        void UpdateProgress(double obj)
        {
            Progress = (double)obj;
        }
    }
}

... this is the XAML page ...

<?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:local="clr-namespace:TestApplication" x:Class="TestApplication.TestApplicationPage">
    <StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
        <Button Text="No. of Songs" Clicked="GetNoOfSongs"></Button>
        <ProgressBar Margin="20" x:Name="progressBar" Progress="{Binding Progress}" WidthRequest="400"></ProgressBar>
    </StackLayout>
</ContentPage>

This is the Song model ...

using System;
using System.IO;

namespace TestApplication.Models
{
    public class Song
    {
        public string Artist { get; set; }
        public string Album { get; set; }
        public string Title { get; set; }
        public Stream Artwork { get; set; }
        public TimeSpan Duration { get; set; }
    }
}

... this is the IMedia interface ...

using System.Threading.Tasks;
using System.Collections.Generic;
using TestApplication.Models;
using System;

namespace TestApplication.Interfaces
{
    public interface IMedia
    {
        Task<bool> IsAuthorised();
        Task<List<Song>> GetSongs(Action<double> callback);
    }
}

... and this is the DependencyService implementation within the .iOS project ...

using TestApplication.Interfaces;
using TestApplication.Models;
using MediaPlayer;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.IO;

[assembly: Xamarin.Forms.Dependency(typeof(TestApplication.iOS.Media))]
namespace TestApplication.iOS
{
    public class Media : IMedia
    {
        public List<Song> Songs { get; private set; }

        public async Task<bool> IsAuthorised()
        {
            await MPMediaLibrary.RequestAuthorizationAsync();

            if (MPMediaLibrary.AuthorizationStatus == MPMediaLibraryAuthorizationStatus.Authorized)
                return true;
            else
                return false;
        }

        public async Task<List<Song>> GetSongs(Action<double> callback)
        {
            Songs = new List<Song> { };

            if (await IsAuthorised())
            {
                var songs = MPMediaQuery.SongsQuery.Items;
                double index = 0;

                foreach (var song in songs)
                {
                    index++;

                    callback.Invoke(index / songs.Length);

                    Stream artwork = null;

                    if (song.Artwork != null)
                        artwork = song.Artwork.ImageWithSize(song.Artwork.Bounds.Size).AsPNG().AsStream();

                    Songs.Add(new Song
                    {
                        Artist = song.AlbumArtist,
                        Album = song.AlbumTitle,
                        Title = song.Title,
                        Artwork = artwork,
                        Duration = TimeSpan.FromSeconds(song.PlaybackDuration),
                    });
                }
            }

            return Songs;
        }
    }
}

... you'll see I've bound the progress value to a property. Maybe for the purpose of this functionality I could just update it through the ProgressBar object when it runs but I know the binding works.

I just can't put my finger on why it's not updating on the fly. If I debug, it's going into the callback and updating the property and firing the OnPropertyChanged event but the UI isn't updating until the end.

I'm thinking it has something to do with the whole async/await thing but can't be sure.

I'm sure someone out there has the answer for me and I'm appreciative of any forthcoming help.

Thanks

It looks like because you do all your time consuming computations in the ui thread. This thread should be dedicated to update the ui. If you do large computation in this thread and that you want to update the ui, it will not work because the ui thread is busy with your computation. What you must do is to launch your computations in another thread (with something like Task.Factory.StartNew or Task.Run depending on you requirement). Now that you are running your long process in another thread, you must update the ui in the ui thread by calling Device.BeginInvokeOnMainThread .

In the end, this is what you can get :

var songs = await Task.Run(async () => await DependencyService.Get<IMedia>().GetSongs(progressUpdate));

And

void UpdateProgress(double obj)
        {
            Device.BeginInvokeOnMainThread(() => Progress = (double)obj);
        }

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