読者です 読者をやめる 読者になる 読者になる

らこらこブログ

唐揚げとアニメとプログラミングが大好きです

Livetを使って変更通知機能付きBackgroundWorkerを作ってみた

新しく作ってるWPFアプリケーションでLivetを使わせていただいてます。ひぃこら言いながら勉強してます。
その中でProgressBarを使うことになったのでせっかくなのでMVVMっぽく(ダメなパターン)実装できるかなと思い、出来上がったのがProgressBarViewModelとNotifiableBackgroundWorkerを使った実装です。
関係ない部分はバッサリカットしてるんでご了承ください。しかし長いです。
まずはProgressBarを配置するWindowのVMを

public class SampleWindowViewModel : ViewModel
{
    private ProgressBarViewModel _ProgressbarVM;

    public ProgressBarViewModel ProgressBarVM
    {
        get
        {
            if (_ProgressbarVM == null)
            {
                _ProgressbarVM = new ProgressBarViewModel();
            }
            return _ProgressbarVM;
        }
    }
}

WindowのVMでProgreessBarViewModelのインスタンスを持っています。寿命はWindowと一緒(のはず)なのでさして問題ではないと思ってこうしてます。

次にこのProgressBarViewModelを。

    public class ProgressBarViewModel : ViewModel
    {
        public NotifiableBackgroundWorker Worker;

        public ProgressBarViewModel()
        {
            Worker = new NotifiableBackgroundWorker(); #継承させるなら任意の子クラスに。
            var listener = new PropertyChangedEventListener(Worker);
            listener.RegisterHandler((sender, e) => WorkerUpdateHandler(sender, e));
            this.CompositeDisposable.Add(listener);
        }

        #region CurrentValue変更通知プロパティ
        private double _CurrentValue;

        public double CurrentValue
        {
            get
            { return _CurrentValue; }
            set
            {
                if (_CurrentValue == value)
                    return;
                _CurrentValue = value;
                RaisePropertyChanged("CurrentValue");
            }
        }
        #endregion

        #region MaxValue変更通知プロパティ
        private double _MaxValue = 100;

        public double MaxValue
        {
            get
            { return _MaxValue; }
            set
            {
                if (_MaxValue == value)
                    return;
                _MaxValue = value;
                RaisePropertyChanged("MaxValue");
            }
        }
        #endregion

        #region ExtraValue変更通知プロパティ
        private object _ExtraValue;

        public object ExtraValue
        {
            get
            { return _ExtraValue; }
            set
            { 
                if (_ExtraValue == value)
                    return;
                _ExtraValue = value;
                RaisePropertyChanged("ExtraValue");
            }
        }
        #endregion

        private void WorkerUpdateHandler(object sender, PropertyChangedEventArgs e)
        {
            var worker = sender as NotifiableBackgroundWorker;
            switch (e.PropertyName)
            {
                case "Value":
                {
                    CurrentValue = worker.Value;
                    break;
                }
                case "ExtraValue":
                {
                    ExtraValue  = worker.ExtraValue;
                    break;
                }
            }
        }

    }

初期化の部分で肝心要のクラスNotifialBackgroundWorkerのインスタンスを作ってPropertyChangedEventListenerにハンドラを追加しています。
NotificationObjectであるNotifialBackgroundWorkerの方でプロパティの変更通知が発火されるとハンドラが呼び出され、値を更新します。
Worker経由で非同期処理のStart、Cancelを行います。

NotifiableBackgroundWorkerの実装はこのようになってます。Workメソッドに関しては中身なんでもいいです。時間かかる処理で。

    public class NotifiableBackgroundWorker : NotificationObject
    {

        public BackgroundWorker Worker;

        public NotifiableBackgroundWorker()
        {
            Worker = new BackgroundWorker();
            Worker.DoWork += new DoWorkEventHandler(Work);
            Worker.ProgressChanged += new ProgressChangedEventHandler(ProgressChanged);
            Worker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(OnCompleted);
            Worker.WorkerReportsProgress = true;
            Worker.WorkerSupportsCancellation = true;
        }

        #region Value変更通知プロパティ
        private double _Value = 0;

        public double Value
        {
            get
            { return _Value; }
            set
            { 
                if (_Value == value)
                    return;
                _Value = value;
                RaisePropertyChanged("Value");
            }
        }
        #endregion        
        
        
        #region ExtraValue変更通知プロパティ
        private object _ExtraValue;

        public object ExtraValue
        {
            get
            { return _ExtraValue; }
            set
            { 
                if (_ExtraValue == value)
                    return;
                _ExtraValue = value;
                RaisePropertyChanged("ExtraValue");
            }
        }
        #endregion


        public void Start()
        {
            Value = 0;
            Worker.RunWorkerAsync();
        }

        public void Cancel()
        {
            Worker.CancelAsync();
        }

        private void ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Value = e.ProgressPercentage;
            ExtraValue = e.UserState;
        }

        public virtual void Work(object sender, DoWorkEventArgs e)
        {
            #お好きな処理を。
            for (int i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(100);
                this.Worker.ReportProgress(i + 1);
            }
        }

        public virtual void OnCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            
        }
    }

最後にV。これは単純に

<ProgressBar DataContext="{Binding Path=ProgressBarVM, Mode=OneWay}" 
    Maximum="{Binding Path=MaxValue}" 
    Value="{Binding Path=CurrentValue}"/>

使い方としてはまずNotifiableBackgroundWorkerを継承するなりしてWorkメソッドと(必要ならOnCompleteを)定義します。
そしてProgressBarViewModelのコンストラクタでそのクラスのインスタンスを生成してあげればいいです。
別に設置したボタンのコマンドなりでProgressBarViewModel.Worker.Start()をしてあげれば動き始めます。
結果として、BackgroundWorkerのプロパティの変更をViewで受け取れるようになりました。
初心者がこうじゃないそうじゃないと試行錯誤して作った実装なので突っ込みどころ満載だと思うんでコメントしてくれたら嬉しいです。