2017-12-01 27 views
1

すべて、Xamarin ProgressBarがリアルタイムで更新されない

私はXamarinにかなり新しく、C#ですので、親切にしてください!

DependencyServiceを使用して.iOSにドロップするXamarin.Formsアプリケーションがあります。このアプリケーションは、デバイス上のすべての曲をトラバースし、カスタム "Song"モデルを使用して戻ります。

これはもっと良い方法ですが、アルバムアートワークをPCLに戻すには、iOS UIImageを使ってSystem.IO.Streamオブジェクトに変換し、それをソングモデル。

このアートワーク機能を追加すると、各曲を処理する際のオーバーヘッドが大幅に増加しました。ユーザーに何が起こっているのかを感謝するために、ページに進行状況バーを置き、1つの曲を処理するたびに更新するようにしました。

私の問題は、進行状況バーをリアルタイムで更新することができなかったことです。プロセスが完了すると更新されます。

...これは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: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> 

これがあると、私はこの段階でMVVMを使用していないので、これは背後にあるコードである...

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; 
     } 
    } 
} 

ソングモデル...

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; } 
    } 
} 

が...これは...

012 IMEDIAインタフェースであります
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); 
    } 
} 

...と、これは.iOSプロジェクト内DependencyService実装です...

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; 
     } 
    } 
} 

...私はプロパティに進捗値をバインドしました表示されます。たぶん、この機能の目的のために、ProgressBarオブジェクトを実行してもそれを更新することはできますが、バインディングが機能していることがわかります。

私はそれがオンザフライで更新されていない理由について私の指を置くことはできません。デバッグすると、コールバックに入り、プロパティを更新してOnPropertyChangedイベントを発生させますが、UIは最後まで更新されません。

私はそれが非同期/待望のものと何か関係があるとは思っていますが、確信が持てません。

誰かが私のための答えを持っていると確信しており、今後の助けに感謝しています。

おかげ

+0

頭の上に私はあなたがあなたのストリームを大幅に削減するbase64文字列に変換することをお勧めオーバーヘッド –

+0

ありがとう、それもチェックします。このスレッドの主な目的は、プログレスバーが正しく更新されるようにすることでした。オーバヘッドでは、動作時にははるかに明確ですが、パフォーマンスにはBase64をチェックします。 –

答えて

4

それはあなたがUIスレッドにすべての時間のかかる計算を行うためのように見えます。このスレッドは、UIを更新するために専用にする必要があります。このスレッドで大きな計算を行い、uiを更新したい場合は、uiスレッドがあなたの計算に忙しいので、うまくいきません。あなたが行う必要があるのは、(要求に応じてTask.Factory.StartNewTask.Runなどの)別のスレッドで計算を開始することです。 長いプロセスを別のスレッドで実行しているので、Device.BeginInvokeOnMainThreadを呼び出して、uiスレッドのuiを更新する必要があります。最後に

、これはあなたが得ることができるものである:あなたのストリームの場合

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

そして

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

なぜ2回「Task.Run'?最初に待っていた結果を変数に導入することは、読みやすくする方が良いでしょう。それはコンパイルされませんでしたが、これは働いていたので、私はあなたがほんの少し今までに提供されたコードを変更する必要がありました – VMAtm

+0

... はTask.Runを(非同期()=> { VAR曲=がDependencyService.Get を(待つ)待っています。 GetSongs(progressUpdate); }); ...私はそれが非同期/待機全体と関係があることを知っていました。私はそれについてもう少し誤解しています。バックグラウンド課題の全体です。答えをありがとう! Device.BeginInvokeOnMainThreadに関しては、私はそれを以前は持っていましたが、私は方程式のTask.Run側を起動しなかったので、明らかにそれは働きませんでした。 –

+0

@ VMAtm、あなたは正しいです。なぜ私は2つの 'Task.Run'を置くのかわかりません。私は編集しました – Daniel

関連する問題